diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000000..d9c32428b77 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,37 @@ +changelog: + exclude: + labels: + - no-release-note + - documentation + categories: + - title: Breaking changes + labels: + - semver:major + - breaking-change + - title: Features + labels: + - enhancement + - feature + - semver:minor + - title: Fixes + labels: + - bug + - fix + - semver:patch + - title: Runtime updates + labels: + - area:codex + - area:claude + - area:grok + - area:opencode + - title: Upstream updates + labels: + - upstream + - upstream-sync + - title: Dependencies and maintenance + labels: + - dependencies + - maintenance + - title: Other changes + labels: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21fbce026f5..cd2823699a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: jobs: check: name: Check - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout @@ -42,7 +42,7 @@ jobs: test: name: Test - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout @@ -63,7 +63,7 @@ jobs: mobile_native_static_analysis: name: Mobile Native Static Analysis - runs-on: blacksmith-12vcpu-macos-26 + runs-on: macos-15 timeout-minutes: 10 steps: - name: Checkout @@ -84,7 +84,7 @@ jobs: release_smoke: name: Release Smoke - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout diff --git a/.github/workflows/mobile-showcase-screenshots.yml b/.github/workflows/mobile-showcase-screenshots.yml index 36dfb61f73f..dfc3db4484c 100644 --- a/.github/workflows/mobile-showcase-screenshots.yml +++ b/.github/workflows/mobile-showcase-screenshots.yml @@ -32,7 +32,7 @@ jobs: ios: name: iPhone 6.9, iPhone 6.5, and iPad 13 if: inputs.platform == 'all' || inputs.platform == 'ios' - runs-on: blacksmith-12vcpu-macos-26 + runs-on: macos-15 timeout-minutes: 60 steps: - name: Checkout @@ -70,7 +70,7 @@ jobs: android: name: Android phone, 7-inch tablet, and 10-inch tablet if: inputs.platform == 'all' || inputs.platform == 'android' - runs-on: blacksmith-16vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 timeout-minutes: 60 env: T3_SHOWCASE_ANDROID_ABI: x86_64 diff --git a/AGENTS.md b/AGENTS.md index b7786ba84b5..ba52dd91ca9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,3 +137,17 @@ Full glossary with file links: `docs/reference/encyclopedia.md` - Don't verify with browsers or computer use unless the user explicitly agrees or requests it. - Security is important, but should not be over-indexed on, especially for dev mode/maintainer-only features. + +## Agent skills + +### Issue tracker + +Product specs and implementation issues are tracked in this fork's GitHub Issues. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Engineering skills use the repository's existing triage vocabulary plus explicit agent and human readiness labels. See `docs/agents/triage-labels.md`. + +### Domain docs + +This monorepo uses a multi-context domain layout. The root glossary and encyclopedia remain authoritative until context-specific maps are introduced. See `docs/agents/domain.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..a674f75d1ad --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +This file records user-visible changes made by this product fork. Upstream T3 Code changes are recorded when they are integrated, not when they first appear upstream. + +The project follows semantic versioning after its first public release. + +## Unreleased + +### Added + +### Changed + +### Fixed + +### Removed + +### Security diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000000..162f0de3326 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,23 @@ +# Roadmap + +The roadmap describes product direction, not delivery promises. Detailed work lives in GitHub Issues. + +## Now + +- Establish the standalone product identity and repository boundaries. +- Revamp the T3 Code web GUI while preserving its typed runtime contracts. +- Keep one real provider path working throughout the redesign. +- Establish upstream synchronization, release notes, and repository governance. + +## Next + +- Qualify Codex, Claude Code, Grok Build, and OpenCode across the redesigned interface. +- Add runtime-aware skills, MCP, plugins, commands, and settings surfaces. +- Make OpenCode the default terminal experience while retaining native provider CLIs. +- Harden local data isolation, recovery, permissions, and runtime observability. + +## Later + +- Package the web-first product for desktop distribution. +- Add multi-pane and multi-agent orchestration after workspace isolation is proven. +- Add beta-user access controls and support workflows. diff --git a/UPSTREAM.md b/UPSTREAM.md new file mode 100644 index 00000000000..5eb4885c62f --- /dev/null +++ b/UPSTREAM.md @@ -0,0 +1,30 @@ +# Upstream relationship + +This repository is a product fork of [pingdotgg/t3code](https://github.com/pingdotgg/t3code). + +## Remotes + +- `origin` is the product fork. +- `upstream` is the official T3 Code repository. + +## Policy + +Product `main` is never updated from upstream automatically. Each upstream update is evaluated on a dedicated integration branch and merged through a pull request after focused tests and the provider qualification matrix pass. + +Prefer a released upstream tag or a recorded commit over an unpinned nightly. Selective cherry-picks are reserved for isolated fixes whose dependencies are understood. + +## Local changes + +Keep product identity, design tokens, product configuration, documentation, and feature policy as separate as practical from provider and orchestration internals. Avoid moving inherited directories or copying upstream components into parallel implementations. + +## Updating + +1. Fetch the official upstream remote. +2. Create an `integration/upstream-YYYY-MM-DD` branch from product `main`. +3. Merge the selected upstream tag or commit. +4. Resolve conflicts without discarding product behavior. +5. Run focused checks, client integration validation, and the provider matrix. +6. Record the new baseline and any retained local patches. +7. Merge the integration branch through a reviewed pull request. + +See `docs/upstream/` for the current baseline and operating details. diff --git a/apps/web/src/lib/stashImageCompression.test.ts b/apps/web/src/lib/stashImageCompression.test.ts index aaffa284cbb..7bba0d983eb 100644 --- a/apps/web/src/lib/stashImageCompression.test.ts +++ b/apps/web/src/lib/stashImageCompression.test.ts @@ -118,9 +118,12 @@ describe("compressImageForStash", () => { }); it("reports too-large when even the smallest encoding overflows the budget", async () => { - const { close } = stubCanvasPipeline(() => 8_000_000); + // Keep the fixture small: the encoder runs once per quality and scale + // step, so multi-megabyte blobs make this assertion CPU-bound in CI. + const budgetChars = 1_300; + const { close } = stubCanvasPipeline(() => 8_000); - const result = await compressImageForStash(makeFile(9_000_000)); + const result = await compressImageForStash(makeFile(9_000), budgetChars); expect(result).toEqual({ ok: false, reason: "too-large" }); // The bitmap must still be released on the give-up path. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 00000000000..c96315728eb --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,32 @@ +# Domain documentation + +This repository is a multi-context monorepo. + +## Current sources of truth + +Before exploring a product area: + +1. Read the glossary and operating model in the root `AGENTS.md`. +2. Read the matching entries in `docs/reference/encyclopedia.md`. +3. Read relevant architecture and provider documentation. +4. Read applicable ADRs when they exist. + +The established terms include provider, client, environment, project, thread, turn, and T3 home. Use those terms consistently in specs, issues, tests, and product copy. + +## Future context maps + +When domain-modeling work creates context documents, the root `CONTEXT-MAP.md` will point to the relevant context. Expected contexts include: + +- Web client +- Desktop client +- Mobile client +- Server and orchestration +- Provider runtime boundary +- Shared contracts +- Shared client runtime + +Context documents and ADRs are created only when a real terminology or durable-decision need appears. Their absence does not block repository work. + +## ADR conflicts + +If proposed work conflicts with an existing ADR, name the conflict explicitly rather than silently replacing the prior decision. diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 00000000000..1c3f71e16d8 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,27 @@ +# Issue tracker: GitHub + +Issues, product specs, and implementation tickets for this repository live in GitHub Issues. Use the `gh` CLI from this clone so the fork is inferred from `origin`. + +## Conventions + +- Create a product spec or ticket with `gh issue create`. +- Read the full issue, labels, and comments before changing its state. +- Use native GitHub issue dependencies for blocking relationships when available. +- Apply the repository triage label that matches the engineering workflow role. +- Close an implementation ticket only after its acceptance behavior and validation notes are recorded. + +## Pull requests as a triage surface + +PRs as a request surface: no. + +Pull requests implement already accepted work. Raw feature requests and bug reports enter through Issues. + +## Publishing from an engineering skill + +When an engineering skill says to publish to the issue tracker, create a GitHub issue in this fork. + +When a skill says to fetch the relevant ticket, read the issue body, labels, dependencies, assignees, and comments. + +## Wayfinding + +A wayfinding map is one GitHub issue whose child issues hold individual decisions. Prefer native sub-issues and issue dependencies. If those features are unavailable, use an ordered task list in the map and a `Blocked by:` line in each child. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 00000000000..a45f3882a19 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,13 @@ +# Triage labels + +The engineering skills speak in canonical triage roles. This table maps those roles to this fork's GitHub labels. + +| Engineering role | Repository label | Meaning | +| ----------------- | ----------------- | ---------------------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate the issue | +| `needs-info` | `needs more info` | Waiting for the reporter to provide information | +| `ready-for-agent` | `ready-for-agent` | Fully specified and safe for an implementation agent | +| `ready-for-human` | `ready-for-human` | Requires a human decision or implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +Use the repository label in the middle column when a skill refers to an engineering role. diff --git a/docs/operations/release.md b/docs/operations/release.md index 9e908550054..5cd0551ca01 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -1,330 +1,23 @@ -# Release Checklist +# Release operations -This document covers the unified release workflow for stable and nightly desktop releases. +The fork does not currently publish desktop, CLI, mobile, hosted web, or relay releases. -## What the workflow does +## Current automation -- Workflow: `.github/workflows/release.yml` -- Triggers: - - push tag matching `v*.*.*` for stable releases - - scheduled nightly check every three hours - - manual `workflow_dispatch` for either channel -- Runs quality gates first: lint, typecheck, test. -- Reads the shared production T3 Connect relay URL and Clerk client configuration before packaging clients. -- Builds four artifacts in parallel for both channels: - - macOS `arm64` DMG - - macOS `x64` DMG - - Linux `x64` AppImage - - Windows `x64` NSIS installer -- Publishes one GitHub Release with all produced files. - - Stable tags with a suffix after `X.Y.Z` (for example `1.2.3-alpha.1`) are published as GitHub prereleases. - - Only plain stable `X.Y.Z` releases are marked as the repository's latest release. - - Nightly runs are always GitHub prereleases and never marked latest. - - Automatically generated release notes are pinned to the previous tag in the same channel, so stable compares to the previous stable tag and nightly compares to the previous nightly tag. -- Includes Electron auto-update metadata (for example `latest*.yml`, `nightly*.yml`, and `*.blockmap`) in release assets. -- Publishes the CLI package (`apps/server`, npm package `t3`) with OIDC trusted publishing from the same workflow file: - - stable releases publish npm dist-tag `latest` - - nightly releases publish npm dist-tag `nightly` -- Deploys the hosted web app to Vercel only after a release is published: - - stable releases are aliased to the `latest` hosted app channel - - nightly releases are aliased to the `nightly` hosted app channel -- Signing is optional and auto-detected per platform from secrets. +- Pull requests and pushes to `main` run CI on standard GitHub-hosted runners. +- Mobile showcase screenshots can be started manually and do not publish an application. +- GitHub can generate draft release notes from merged pull-request labels. -## T3 Connect relay deployment +## Disabled upstream automation -The relay is a shared control plane versioned separately from client releases. Stable and nightly -client builds must point at the same relay so users see the same linked environments when switching -release channels. +The inherited T3 Code release, relay deployment, and Expo delivery workflows are archived under `docs/upstream/workflows/`. Files outside `.github/workflows/` cannot execute as GitHub Actions. -`.github/workflows/deploy-relay.yml` deploys Alchemy stage `prod` on every push to `main`. The -release workflow reads the relay URL and Clerk client configuration from the existing `production` -GitHub Actions environment before building desktop, CLI, or hosted web artifacts. +Do not push a version tag expecting publication. Before restoring any publication workflow: -Required repository variables shared by relay deployments: +1. Complete every applicable item in `docs/releases/prerequisites.md`. +2. Replace inherited service identifiers with fork-owned configuration. +3. Review the workflow permissions and rollback path. +4. Prove the workflow in a non-production environment. +5. Add an explicit release approval gate. -- `CLOUDFLARE_ACCOUNT_ID` -- `PLANETSCALE_ORGANIZATION` -- `AXIOM_ORG_ID` - -Required repository secrets shared by relay deployments: - -- `CLOUDFLARE_API_TOKEN` -- `PLANETSCALE_API_TOKEN_ID` -- `PLANETSCALE_API_TOKEN` -- `AXIOM_TOKEN` - -Required `production` environment variables: - -- `RELAY_API_ZONE_NAME` -- `RELAY_TUNNEL_ZONE_NAME` -- `CLERK_PUBLISHABLE_KEY` -- `CLERK_JWT_AUDIENCE` -- `CLERK_JWT_TEMPLATE` -- `CLERK_CLI_OAUTH_CLIENT_ID` -- `APNS_ENVIRONMENT` -- `APNS_TEAM_ID` -- `APNS_KEY_ID` -- `APNS_BUNDLE_ID` - -Optional `production` environment variables: - -- `RELAY_DOMAIN` when overriding the derived `relay.` domain - -Required `production` environment secrets: - -- `CLERK_SECRET_KEY` -- `APNS_PRIVATE_KEY` - -The account-scoped repository credentials are consumed by Alchemy while provisioning relay stages; they -are not bound into the relay Worker. The production deployment uses an Axiom personal access token, -so `AXIOM_ORG_ID` must accompany `AXIOM_TOKEN`. The `prod` stage owns the retained PlanetScale -database. Local personal stages provision isolated branches from it and are never deployed by CI. -Production adopts the configured relay API and tunnel DNS zones as retained Cloudflare resources. -Personal stages reference the production-owned zones. - -Developers deploy personal stages locally rather than through pull-request automation: - -```sh -vp run --filter t3code-relay deploy -- --stage "$USER" --env-file .env.local -``` - -## Hosted web app release deployment - -The hosted app is intentionally not deployed by Vercel's Git integration. The -web project disables automatic Git deployments in `apps/web/vercel.ts` via -`git.deploymentEnabled: false`, and `.github/workflows/release.yml` deploys the -web app with Vercel CLI after the GitHub Release succeeds. - -Required GitHub Actions secrets: - -- `VERCEL_TOKEN` -- `VERCEL_ORG_ID` -- `VERCEL_PROJECT_ID` - -Optional GitHub Actions variables: - -- `VERCEL_TEAM_SLUG`: overrides the Vercel CLI scope when the team slug is preferred over the `VERCEL_ORG_ID` secret. -- `T3CODE_WEB_ROUTER_URL`: defaults to `https://app.t3.codes`. -- `T3CODE_WEB_LATEST_DOMAIN`: defaults to `latest.app.t3.codes`. -- `T3CODE_WEB_NIGHTLY_DOMAIN`: defaults to `nightly.app.t3.codes`. - -Required Vercel domains: - -- `app.t3.codes`: the router domain users open, updated by stable releases. -- `latest.app.t3.codes`: channel alias updated by stable releases. -- `nightly.app.t3.codes`: channel alias updated by nightly releases. - -The router domain uses `apps/web/vercel.ts` routes. Users opt into a channel by -visiting `/__t3code/channel?channel=latest` or -`/__t3code/channel?channel=nightly`; the router stores the -`t3code_web_channel` cookie and rewrites future requests on `app.t3.codes` to -the matching channel alias. - -The release deploy job rewrites release package versions before upload so the -hosted app's About panel renders the release version. Stable deploys alias the -same deployment to both the `latest` channel and the router domain so the router -rules stay current. Nightly deploys only alias the `nightly` channel. The job -also passes `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted -update track selector in the About panel. Changing the selector navigates -through `/__t3code/channel` on the router domain so the user's channel cookie is -updated before redirecting to the hosted app root. - -One-time Vercel dashboard setup: - -1. Confirm the web project root directory remains `apps/web`. -2. Add the three domains above to the web project. -3. Disable automatic Git deployments in the dashboard if desired; the committed - `vercel.ts` setting is the source-of-truth, but disconnecting Git in the - dashboard is also safe. -4. Run one stable release deployment, or manually alias the current stable - deployment, so `app.t3.codes` points at a deployment containing the router - rules in `apps/web/vercel.ts`. Future stable releases keep this alias current. - -## Nightly builds - -- Workflow: `.github/workflows/release.yml` -- Triggers: - - scheduled check every three hours - - manual `workflow_dispatch` with `channel=nightly` -- Runs the same desktop quality gates and artifact matrix as the tagged release flow. -- Publishes a GitHub prerelease only: - - tag format: `nightly-vX.Y.Z-nightly.YYYYMMDD.` - - release name includes the short commit SHA - - `make_latest` is always `false` -- Uses the next stable patch version as the nightly base. For example, `0.0.17` produces nightlies on `0.0.18-nightly.*`. -- Publishes Electron auto-update metadata to the dedicated `nightly` updater channel, so desktop users can opt into that track independently from stable. -- Publishes the CLI package (`apps/server`, npm package `t3`) to the `nightly` npm dist-tag using the same nightly version. -- Does not commit version bumps back to `main`. - -## Server self-update release invariant - -Connected servers update to the client's exact version, not to an npm dist-tag. Every released -desktop or hosted client version must therefore have a matching `t3@` package available on -npm before users can receive that client. - -The workflow enforces this ordering: - -1. `publish_cli` publishes the exact stable or nightly version to npm. -2. `release` depends on `publish_cli` before exposing desktop artifacts in GitHub Releases. -3. `deploy_web` depends on `release` before moving the hosted channel to the new client. - -Preserve these dependencies when changing the release graph. Publishing a client first would leave -the **Update server** action targeting a package version that does not exist yet. - -For a release smoke test, confirm `npm view t3@ version` returns the expected version, then -connect the new client to a server on the previous version and verify that the update action -reconnects to the matching server. Test one automatic path and the manual or desktop-managed -guidance when those environments are available. - -## Desktop auto-update notes - -- Runtime updater: `electron-updater` in `apps/desktop/src/main.ts`. -- Update UX: - - Background checks run on startup delay + interval. - - No automatic download or install. - - The desktop UI shows a rocket update button when an update is available; click once to download, click again after download to restart/install. -- Provider: GitHub Releases (`provider: github`) configured at build time. -- Repository slug source: - - `T3CODE_DESKTOP_UPDATE_REPOSITORY` (format `owner/repo`), if set. - - otherwise `GITHUB_REPOSITORY` from GitHub Actions. -- Temporary private-repo auth workaround: - - set `T3CODE_DESKTOP_UPDATE_GITHUB_TOKEN` (or `GH_TOKEN`) in the desktop app runtime environment. - - the app forwards it as an `Authorization: Bearer ` request header for updater HTTP calls. -- Required release assets for updater: - - platform installers (`.exe`, `.dmg`, `.AppImage`, plus macOS `.zip` for Squirrel.Mac update payloads) - - channel metadata: `latest*.yml` for stable releases, `nightly*.yml` for nightly releases - - `*.blockmap` files (used for differential downloads) -- macOS metadata note: - - `electron-updater` reads `latest-mac.yml` on stable and `nightly-mac.yml` on nightly, for both Intel and Apple Silicon. - - The workflow merges the per-arch mac manifests into one channel-specific mac manifest before publishing the GitHub Release. - -## 0) npm OIDC trusted publishing setup (CLI) - -The workflow publishes the CLI with `npm publish` from `apps/server` after bumping -the package version to the release tag version. - -Checklist: - -1. Confirm npm org/user owns package `t3` (or rename package first if needed). -2. In npm package settings, configure Trusted Publisher: - - Provider: GitHub Actions - - Repository: this repo - - Workflow file: `.github/workflows/release.yml` - - Environment (if used): match your npm trusted publishing config -3. Ensure npm account and org policies allow trusted publishing for the package. -4. Create release tag `vX.Y.Z` and push; workflow will: - - set `apps/server/package.json` version to `X.Y.Z` - - build web + server - - run `npm publish --access public --tag latest` -5. Nightly runs from the same workflow file publish with `npm publish --access public --tag nightly`. - -## 1) Dry-run release without signing - -Use this first to validate the release pipeline. - -1. Confirm no signing secrets are required for this test. -2. Create a test tag: - - `git tag v0.0.0-test.1` - - `git push origin v0.0.0-test.1` -3. Wait for `.github/workflows/release.yml` to finish. -4. Verify the GitHub Release contains all platform artifacts. -5. Download each artifact and sanity-check installation on each OS. - -## 2) Apple signing + notarization setup (macOS) - -Required secrets used by the workflow: - -- `CSC_LINK` -- `CSC_KEY_PASSWORD` -- `APPLE_API_KEY` -- `APPLE_API_KEY_ID` -- `APPLE_API_ISSUER` -- `MACOS_PROVISIONING_PROFILE` (base64-encoded provisioning profile with Associated Domains) - -Required repository variables: - -- `APPLE_TEAM_ID` - -Optional repository variables: - -- `CLERK_PASSKEY_RP_DOMAINS`: comma-separated RP-domain override. By default, the build derives the - domain from the production Clerk publishable key. - -Checklist: - -1. Apple Developer account access: - - Team has rights to create Developer ID certificates. -2. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains. -3. Create a `Developer ID Application` certificate and a compatible provisioning profile for that - App ID with Associated Domains enabled. -4. Export the certificate + private key as `.p12` from Keychain. -5. Base64-encode the `.p12` and store as `CSC_LINK`. -6. Base64-encode the provisioning profile and store it as `MACOS_PROVISIONING_PROFILE`. -7. Store the `.p12` export password as `CSC_KEY_PASSWORD`, and set `APPLE_TEAM_ID` to the - 10-character Apple Developer Team ID. -8. In App Store Connect, create an API key (Team key). -9. Add API key values: - - `APPLE_API_KEY`: contents of the downloaded `.p8` - - `APPLE_API_KEY_ID`: Key ID - - `APPLE_API_ISSUER`: Issuer ID -10. Complete the Clerk Native API and AASA setup in [T3 Connect Clerk Setup](../cloud/t3-connect-clerk.md#desktop-passkeys). -11. Re-run a tag release and confirm macOS artifacts are signed/notarized and contain the expected - `com.apple.developer.associated-domains` entitlement. - -Notes: - -- `APPLE_API_KEY` is stored as raw key text in secrets. -- The workflow writes it to a temporary `AuthKey_.p8` file at runtime. -- The workflow decodes `MACOS_PROVISIONING_PROFILE`, validates it with `security cms`, and passes it - to the desktop packager. - -## 3) Azure Trusted Signing setup (Windows) - -Required secrets used by the workflow: - -- `AZURE_TENANT_ID` -- `AZURE_CLIENT_ID` -- `AZURE_CLIENT_SECRET` -- `AZURE_TRUSTED_SIGNING_ENDPOINT` -- `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME` -- `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME` -- `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME` - -Checklist: - -1. Create Azure Trusted Signing account and certificate profile. -2. Record ATS values: - - Endpoint - - Account name - - Certificate profile name - - Publisher name -3. Create/choose an Entra app registration (service principal). -4. Grant service principal permissions required by Trusted Signing. -5. Create a client secret for the service principal. -6. Add Azure secrets listed above in GitHub Actions secrets. -7. Re-run a tag release and confirm Windows installer is signed. - -## 4) Ongoing release checklist - -1. Ensure `main` is green in CI. -2. Bump app version as needed. -3. Create release tag: `vX.Y.Z`. -4. Push tag. -5. Verify workflow steps: - - preflight passes - - all matrix builds pass - - `publish_cli` publishes the exact release version before the release job - - release job uploads expected files -6. Smoke test downloaded artifacts. - -## 5) Troubleshooting - -- macOS build unsigned when expected signed: - - Check all Apple secrets plus `APPLE_TEAM_ID` are populated and non-empty. - - Confirm the provisioning profile belongs to `APPLE_TEAM_ID.com.t3tools.t3code` and includes - Associated Domains. -- Windows build unsigned when expected signed: - - Check all Azure ATS and auth secrets are populated and non-empty. -- Build fails with signing error: - - Retry with secrets removed to confirm unsigned path still works. - - Re-check certificate/profile names and tenant/client credentials. +The archived T3 Code release process remains available at `docs/upstream/operations/release.md` for upstream comparison only. diff --git a/docs/releases/README.md b/docs/releases/README.md new file mode 100644 index 00000000000..ec4860a0dfa --- /dev/null +++ b/docs/releases/README.md @@ -0,0 +1,21 @@ +# Release documentation + +Use each release surface for one purpose: + +- `README.md` describes the product as it exists now. +- `CHANGELOG.md` records user-visible changes by version. +- GitHub release notes explain why a release matters and link the included pull requests. +- Pull requests record implementation scope and validation. +- Commits preserve focused engineering history. + +## Release checklist + +1. Select the release version and included pull requests. +2. Confirm each included pull request has a release category or an explicit exclusion. +3. Update `CHANGELOG.md`. +4. Update the README only when current installation, support, or product behavior changed. +5. Run the release and provider qualification checks. +6. Generate GitHub release notes and review them before publication. +7. Record breaking changes, migrations, and known limitations explicitly. + +The inherited T3 release, relay deployment, and Expo delivery workflows are archived under `docs/upstream/workflows/`. They are documentation, not active GitHub Actions workflows. Do not restore them until every external account in `prerequisites.md` has a named fork owner, a least-privilege credential, a reviewed release path, and a tested rollback path. diff --git a/docs/releases/prerequisites.md b/docs/releases/prerequisites.md new file mode 100644 index 00000000000..c013211ffb1 --- /dev/null +++ b/docs/releases/prerequisites.md @@ -0,0 +1,35 @@ +# Release prerequisites + +The fork does not inherit T3 Code production accounts. A release workflow may be restored only after each required service has a named fork owner, a least-privilege credential, and a tested rollback path. + +## Required before desktop or CLI publication + +| Prerequisite | Owner | Status | +| --------------------------------------------------------- | ------------------ | -------------- | +| Fork-owned npm organization or package scope | `@prathyaksh24-wq` | Not configured | +| Windows signing identity and credentials | `@prathyaksh24-wq` | Not configured | +| Apple signing, notarization, and provisioning credentials | `@prathyaksh24-wq` | Not configured | +| GitHub release identity with repository-only permissions | `@prathyaksh24-wq` | Not configured | +| Stable and prerelease versioning rules | `@prathyaksh24-wq` | Not decided | + +## Required before hosted web or relay deployment + +| Prerequisite | Owner | Status | +| ----------------------------------------------------------------- | ------------------ | -------------- | +| Fork-owned web hosting project and deployment token | `@prathyaksh24-wq` | Not configured | +| Fork-owned authentication application identifiers and domains | `@prathyaksh24-wq` | Not configured | +| Fork-owned relay infrastructure, database, observability, and DNS | `@prathyaksh24-wq` | Not configured | +| Privacy and retention policy for hosted telemetry | `@prathyaksh24-wq` | Not decided | + +## Optional surfaces + +| Prerequisite | Owner | Status | +| ------------------------------------------------ | ------------------ | -------------- | +| Fork-owned Expo project and delivery credentials | `@prathyaksh24-wq` | Not configured | +| Optional announcement integration | `@prathyaksh24-wq` | Not configured | + +## Current state + +- Pull-request CI uses standard GitHub-hosted runners. +- Mobile showcase screenshots are manual and use standard GitHub-hosted runners. +- T3 Code release, relay deployment, and Expo delivery workflows are archived as upstream reference material and cannot execute. diff --git a/docs/upstream/baseline.md b/docs/upstream/baseline.md new file mode 100644 index 00000000000..1fda3a07670 --- /dev/null +++ b/docs/upstream/baseline.md @@ -0,0 +1,23 @@ +# Upstream baseline + +## Current baseline + +- Fork: `prathyaksh24-wq/t3code` +- Upstream: `pingdotgg/t3code` +- Baseline commit: `694f8d1c6eaaabafbf5c2861ae524174919ef625` +- Upstream version at baseline: `0.0.30` +- Verified: 2026-07-29 + +The fork's `main` and upstream `main` resolved to the same commit when this baseline was recorded. + +## Inherited provider surface + +The baseline advertises support for Codex, Claude Code, Cursor, Grok Build, and OpenCode. Provider-specific runtimes remain inherited; the product fork does not reimplement them. + +## Qualification status + +The source baseline has been confirmed. Build, browser, authentication, provider, and runtime qualification remain pending and must be recorded before the first product release. + +## Updating this file + +Update the commit, version, date, qualification results, and retained patch list in the same pull request that integrates a new upstream baseline. diff --git a/docs/upstream/operations/release.md b/docs/upstream/operations/release.md new file mode 100644 index 00000000000..f0ad7f276fd --- /dev/null +++ b/docs/upstream/operations/release.md @@ -0,0 +1,332 @@ + + +# Release Checklist + +This document covers the unified release workflow for stable and nightly desktop releases. + +## What the workflow does + +- Workflow: `.github/workflows/release.yml` +- Triggers: + - push tag matching `v*.*.*` for stable releases + - scheduled nightly check every three hours + - manual `workflow_dispatch` for either channel +- Runs quality gates first: lint, typecheck, test. +- Reads the shared production T3 Connect relay URL and Clerk client configuration before packaging clients. +- Builds four artifacts in parallel for both channels: + - macOS `arm64` DMG + - macOS `x64` DMG + - Linux `x64` AppImage + - Windows `x64` NSIS installer +- Publishes one GitHub Release with all produced files. + - Stable tags with a suffix after `X.Y.Z` (for example `1.2.3-alpha.1`) are published as GitHub prereleases. + - Only plain stable `X.Y.Z` releases are marked as the repository's latest release. + - Nightly runs are always GitHub prereleases and never marked latest. + - Automatically generated release notes are pinned to the previous tag in the same channel, so stable compares to the previous stable tag and nightly compares to the previous nightly tag. +- Includes Electron auto-update metadata (for example `latest*.yml`, `nightly*.yml`, and `*.blockmap`) in release assets. +- Publishes the CLI package (`apps/server`, npm package `t3`) with OIDC trusted publishing from the same workflow file: + - stable releases publish npm dist-tag `latest` + - nightly releases publish npm dist-tag `nightly` +- Deploys the hosted web app to Vercel only after a release is published: + - stable releases are aliased to the `latest` hosted app channel + - nightly releases are aliased to the `nightly` hosted app channel +- Signing is optional and auto-detected per platform from secrets. + +## T3 Connect relay deployment + +The relay is a shared control plane versioned separately from client releases. Stable and nightly +client builds must point at the same relay so users see the same linked environments when switching +release channels. + +`.github/workflows/deploy-relay.yml` deploys Alchemy stage `prod` on every push to `main`. The +release workflow reads the relay URL and Clerk client configuration from the existing `production` +GitHub Actions environment before building desktop, CLI, or hosted web artifacts. + +Required repository variables shared by relay deployments: + +- `CLOUDFLARE_ACCOUNT_ID` +- `PLANETSCALE_ORGANIZATION` +- `AXIOM_ORG_ID` + +Required repository secrets shared by relay deployments: + +- `CLOUDFLARE_API_TOKEN` +- `PLANETSCALE_API_TOKEN_ID` +- `PLANETSCALE_API_TOKEN` +- `AXIOM_TOKEN` + +Required `production` environment variables: + +- `RELAY_API_ZONE_NAME` +- `RELAY_TUNNEL_ZONE_NAME` +- `CLERK_PUBLISHABLE_KEY` +- `CLERK_JWT_AUDIENCE` +- `CLERK_JWT_TEMPLATE` +- `CLERK_CLI_OAUTH_CLIENT_ID` +- `APNS_ENVIRONMENT` +- `APNS_TEAM_ID` +- `APNS_KEY_ID` +- `APNS_BUNDLE_ID` + +Optional `production` environment variables: + +- `RELAY_DOMAIN` when overriding the derived `relay.` domain + +Required `production` environment secrets: + +- `CLERK_SECRET_KEY` +- `APNS_PRIVATE_KEY` + +The account-scoped repository credentials are consumed by Alchemy while provisioning relay stages; they +are not bound into the relay Worker. The production deployment uses an Axiom personal access token, +so `AXIOM_ORG_ID` must accompany `AXIOM_TOKEN`. The `prod` stage owns the retained PlanetScale +database. Local personal stages provision isolated branches from it and are never deployed by CI. +Production adopts the configured relay API and tunnel DNS zones as retained Cloudflare resources. +Personal stages reference the production-owned zones. + +Developers deploy personal stages locally rather than through pull-request automation: + +```sh +vp run --filter t3code-relay deploy -- --stage "$USER" --env-file .env.local +``` + +## Hosted web app release deployment + +The hosted app is intentionally not deployed by Vercel's Git integration. The +web project disables automatic Git deployments in `apps/web/vercel.ts` via +`git.deploymentEnabled: false`, and `.github/workflows/release.yml` deploys the +web app with Vercel CLI after the GitHub Release succeeds. + +Required GitHub Actions secrets: + +- `VERCEL_TOKEN` +- `VERCEL_ORG_ID` +- `VERCEL_PROJECT_ID` + +Optional GitHub Actions variables: + +- `VERCEL_TEAM_SLUG`: overrides the Vercel CLI scope when the team slug is preferred over the `VERCEL_ORG_ID` secret. +- `T3CODE_WEB_ROUTER_URL`: defaults to `https://app.t3.codes`. +- `T3CODE_WEB_LATEST_DOMAIN`: defaults to `latest.app.t3.codes`. +- `T3CODE_WEB_NIGHTLY_DOMAIN`: defaults to `nightly.app.t3.codes`. + +Required Vercel domains: + +- `app.t3.codes`: the router domain users open, updated by stable releases. +- `latest.app.t3.codes`: channel alias updated by stable releases. +- `nightly.app.t3.codes`: channel alias updated by nightly releases. + +The router domain uses `apps/web/vercel.ts` routes. Users opt into a channel by +visiting `/__t3code/channel?channel=latest` or +`/__t3code/channel?channel=nightly`; the router stores the +`t3code_web_channel` cookie and rewrites future requests on `app.t3.codes` to +the matching channel alias. + +The release deploy job rewrites release package versions before upload so the +hosted app's About panel renders the release version. Stable deploys alias the +same deployment to both the `latest` channel and the router domain so the router +rules stay current. Nightly deploys only alias the `nightly` channel. The job +also passes `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted +update track selector in the About panel. Changing the selector navigates +through `/__t3code/channel` on the router domain so the user's channel cookie is +updated before redirecting to the hosted app root. + +One-time Vercel dashboard setup: + +1. Confirm the web project root directory remains `apps/web`. +2. Add the three domains above to the web project. +3. Disable automatic Git deployments in the dashboard if desired; the committed + `vercel.ts` setting is the source-of-truth, but disconnecting Git in the + dashboard is also safe. +4. Run one stable release deployment, or manually alias the current stable + deployment, so `app.t3.codes` points at a deployment containing the router + rules in `apps/web/vercel.ts`. Future stable releases keep this alias current. + +## Nightly builds + +- Workflow: `.github/workflows/release.yml` +- Triggers: + - scheduled check every three hours + - manual `workflow_dispatch` with `channel=nightly` +- Runs the same desktop quality gates and artifact matrix as the tagged release flow. +- Publishes a GitHub prerelease only: + - tag format: `nightly-vX.Y.Z-nightly.YYYYMMDD.` + - release name includes the short commit SHA + - `make_latest` is always `false` +- Uses the next stable patch version as the nightly base. For example, `0.0.17` produces nightlies on `0.0.18-nightly.*`. +- Publishes Electron auto-update metadata to the dedicated `nightly` updater channel, so desktop users can opt into that track independently from stable. +- Publishes the CLI package (`apps/server`, npm package `t3`) to the `nightly` npm dist-tag using the same nightly version. +- Does not commit version bumps back to `main`. + +## Server self-update release invariant + +Connected servers update to the client's exact version, not to an npm dist-tag. Every released +desktop or hosted client version must therefore have a matching `t3@` package available on +npm before users can receive that client. + +The workflow enforces this ordering: + +1. `publish_cli` publishes the exact stable or nightly version to npm. +2. `release` depends on `publish_cli` before exposing desktop artifacts in GitHub Releases. +3. `deploy_web` depends on `release` before moving the hosted channel to the new client. + +Preserve these dependencies when changing the release graph. Publishing a client first would leave +the **Update server** action targeting a package version that does not exist yet. + +For a release smoke test, confirm `npm view t3@ version` returns the expected version, then +connect the new client to a server on the previous version and verify that the update action +reconnects to the matching server. Test one automatic path and the manual or desktop-managed +guidance when those environments are available. + +## Desktop auto-update notes + +- Runtime updater: `electron-updater` in `apps/desktop/src/main.ts`. +- Update UX: + - Background checks run on startup delay + interval. + - No automatic download or install. + - The desktop UI shows a rocket update button when an update is available; click once to download, click again after download to restart/install. +- Provider: GitHub Releases (`provider: github`) configured at build time. +- Repository slug source: + - `T3CODE_DESKTOP_UPDATE_REPOSITORY` (format `owner/repo`), if set. + - otherwise `GITHUB_REPOSITORY` from GitHub Actions. +- Temporary private-repo auth workaround: + - set `T3CODE_DESKTOP_UPDATE_GITHUB_TOKEN` (or `GH_TOKEN`) in the desktop app runtime environment. + - the app forwards it as an `Authorization: Bearer ` request header for updater HTTP calls. +- Required release assets for updater: + - platform installers (`.exe`, `.dmg`, `.AppImage`, plus macOS `.zip` for Squirrel.Mac update payloads) + - channel metadata: `latest*.yml` for stable releases, `nightly*.yml` for nightly releases + - `*.blockmap` files (used for differential downloads) +- macOS metadata note: + - `electron-updater` reads `latest-mac.yml` on stable and `nightly-mac.yml` on nightly, for both Intel and Apple Silicon. + - The workflow merges the per-arch mac manifests into one channel-specific mac manifest before publishing the GitHub Release. + +## 0) npm OIDC trusted publishing setup (CLI) + +The workflow publishes the CLI with `npm publish` from `apps/server` after bumping +the package version to the release tag version. + +Checklist: + +1. Confirm npm org/user owns package `t3` (or rename package first if needed). +2. In npm package settings, configure Trusted Publisher: + - Provider: GitHub Actions + - Repository: this repo + - Workflow file: `.github/workflows/release.yml` + - Environment (if used): match your npm trusted publishing config +3. Ensure npm account and org policies allow trusted publishing for the package. +4. Create release tag `vX.Y.Z` and push; workflow will: + - set `apps/server/package.json` version to `X.Y.Z` + - build web + server + - run `npm publish --access public --tag latest` +5. Nightly runs from the same workflow file publish with `npm publish --access public --tag nightly`. + +## 1) Dry-run release without signing + +Use this first to validate the release pipeline. + +1. Confirm no signing secrets are required for this test. +2. Create a test tag: + - `git tag v0.0.0-test.1` + - `git push origin v0.0.0-test.1` +3. Wait for `.github/workflows/release.yml` to finish. +4. Verify the GitHub Release contains all platform artifacts. +5. Download each artifact and sanity-check installation on each OS. + +## 2) Apple signing + notarization setup (macOS) + +Required secrets used by the workflow: + +- `CSC_LINK` +- `CSC_KEY_PASSWORD` +- `APPLE_API_KEY` +- `APPLE_API_KEY_ID` +- `APPLE_API_ISSUER` +- `MACOS_PROVISIONING_PROFILE` (base64-encoded provisioning profile with Associated Domains) + +Required repository variables: + +- `APPLE_TEAM_ID` + +Optional repository variables: + +- `CLERK_PASSKEY_RP_DOMAINS`: comma-separated RP-domain override. By default, the build derives the + domain from the production Clerk publishable key. + +Checklist: + +1. Apple Developer account access: + - Team has rights to create Developer ID certificates. +2. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains. +3. Create a `Developer ID Application` certificate and a compatible provisioning profile for that + App ID with Associated Domains enabled. +4. Export the certificate + private key as `.p12` from Keychain. +5. Base64-encode the `.p12` and store as `CSC_LINK`. +6. Base64-encode the provisioning profile and store it as `MACOS_PROVISIONING_PROFILE`. +7. Store the `.p12` export password as `CSC_KEY_PASSWORD`, and set `APPLE_TEAM_ID` to the + 10-character Apple Developer Team ID. +8. In App Store Connect, create an API key (Team key). +9. Add API key values: + - `APPLE_API_KEY`: contents of the downloaded `.p8` + - `APPLE_API_KEY_ID`: Key ID + - `APPLE_API_ISSUER`: Issuer ID +10. Complete the Clerk Native API and AASA setup in [T3 Connect Clerk Setup](../cloud/t3-connect-clerk.md#desktop-passkeys). +11. Re-run a tag release and confirm macOS artifacts are signed/notarized and contain the expected + `com.apple.developer.associated-domains` entitlement. + +Notes: + +- `APPLE_API_KEY` is stored as raw key text in secrets. +- The workflow writes it to a temporary `AuthKey_.p8` file at runtime. +- The workflow decodes `MACOS_PROVISIONING_PROFILE`, validates it with `security cms`, and passes it + to the desktop packager. + +## 3) Azure Trusted Signing setup (Windows) + +Required secrets used by the workflow: + +- `AZURE_TENANT_ID` +- `AZURE_CLIENT_ID` +- `AZURE_CLIENT_SECRET` +- `AZURE_TRUSTED_SIGNING_ENDPOINT` +- `AZURE_TRUSTED_SIGNING_ACCOUNT_NAME` +- `AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME` +- `AZURE_TRUSTED_SIGNING_PUBLISHER_NAME` + +Checklist: + +1. Create Azure Trusted Signing account and certificate profile. +2. Record ATS values: + - Endpoint + - Account name + - Certificate profile name + - Publisher name +3. Create/choose an Entra app registration (service principal). +4. Grant service principal permissions required by Trusted Signing. +5. Create a client secret for the service principal. +6. Add Azure secrets listed above in GitHub Actions secrets. +7. Re-run a tag release and confirm Windows installer is signed. + +## 4) Ongoing release checklist + +1. Ensure `main` is green in CI. +2. Bump app version as needed. +3. Create release tag: `vX.Y.Z`. +4. Push tag. +5. Verify workflow steps: + - preflight passes + - all matrix builds pass + - `publish_cli` publishes the exact release version before the release job + - release job uploads expected files +6. Smoke test downloaded artifacts. + +## 5) Troubleshooting + +- macOS build unsigned when expected signed: + - Check all Apple secrets plus `APPLE_TEAM_ID` are populated and non-empty. + - Confirm the provisioning profile belongs to `APPLE_TEAM_ID.com.t3tools.t3code` and includes + Associated Domains. +- Windows build unsigned when expected signed: + - Check all Azure ATS and auth secrets are populated and non-empty. +- Build fails with signing error: + - Retry with secrets removed to confirm unsigned path still works. + - Re-check certificate/profile names and tenant/client credentials. diff --git a/docs/upstream/patches.md b/docs/upstream/patches.md new file mode 100644 index 00000000000..b028ef1eeba --- /dev/null +++ b/docs/upstream/patches.md @@ -0,0 +1,10 @@ +# Upstream patch ledger + +Record selective upstream cherry-picks and durable local divergences here. + +| Upstream change | Product integration | Reason | Local modification | +| ------------------------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| T3 production release, relay, and Expo workflows | Archived under `docs/upstream/workflows/` | Prevent the fork from publishing to or waiting on upstream-owned services | Restore only after the named owner provides least-privilege credentials, a reviewed release path, and a tested rollback | +| Blacksmith runner labels | Standard GitHub-hosted runner labels | Let CI and manually triggered mobile workflows run in the public fork | Recheck runner capacity and timeouts after each upstream sync | + +A normal full-baseline merge does not need one row per upstream commit. Record only changes that would otherwise be difficult to understand during a later upstream synchronization. diff --git a/docs/upstream/sync-process.md b/docs/upstream/sync-process.md new file mode 100644 index 00000000000..be9fbcbb188 --- /dev/null +++ b/docs/upstream/sync-process.md @@ -0,0 +1,37 @@ +# Upstream synchronization + +## Cadence + +- Review upstream releases weekly. +- Evaluate a full integration monthly. +- Review security and provider-compatibility fixes promptly. +- Check for a compatible upstream baseline before each product release. + +## Integration flow + +1. Fetch upstream branches and tags. +2. Choose a released tag or recorded commit. +3. Create an integration branch from product `main`. +4. Merge the selected upstream baseline without rewriting public product history. +5. Resolve conflicts by preserving both inherited runtime behavior and intentional product policy. +6. Run focused checks for changed areas. +7. Run the web client integration pass and applicable provider matrix entries. +8. Update the baseline and patch ledger. +9. Open a pull request that explains upstream changes, retained divergences, and validation. + +## Selective updates + +Prefer a complete compatible upstream release. Cherry-pick only when: + +- The change is isolated. +- Its dependencies are understood. +- Waiting for the complete release carries more risk. +- The cherry-pick is recorded in the patch ledger. + +## Automation + +A scheduled workflow may detect upstream movement and open an issue or draft synchronization pull request. It must never merge upstream changes automatically. + +## Release infrastructure warning + +The inherited release workflow references T3-owned signing, package publishing, hosted application, relay, and notification infrastructure. Fork-specific release work must replace or disable those integrations before publishing product artifacts. diff --git a/.github/workflows/deploy-relay.yml b/docs/upstream/workflows/deploy-relay.yml similarity index 96% rename from .github/workflows/deploy-relay.yml rename to docs/upstream/workflows/deploy-relay.yml index 94d4af17e41..0f5e87e6b2b 100644 --- a/.github/workflows/deploy-relay.yml +++ b/docs/upstream/workflows/deploy-relay.yml @@ -1,3 +1,4 @@ +# Archived upstream workflow. This file is documentation and is not executed by GitHub Actions. name: Deploy T3 Connect relay on: diff --git a/.github/workflows/mobile-eas-preview.yml b/docs/upstream/workflows/mobile-eas-preview.yml similarity index 95% rename from .github/workflows/mobile-eas-preview.yml rename to docs/upstream/workflows/mobile-eas-preview.yml index 32e45fef54e..0ee45a9dfb5 100644 --- a/.github/workflows/mobile-eas-preview.yml +++ b/docs/upstream/workflows/mobile-eas-preview.yml @@ -1,3 +1,4 @@ +# Archived upstream workflow. Restore only after the fork owns its Expo project configuration. name: Mobile EAS Preview on: @@ -8,7 +9,7 @@ jobs: preview: name: EAS Preview if: contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 permissions: contents: read pull-requests: write diff --git a/.github/workflows/mobile-eas-production.yml b/docs/upstream/workflows/mobile-eas-production.yml similarity index 96% rename from .github/workflows/mobile-eas-production.yml rename to docs/upstream/workflows/mobile-eas-production.yml index 685df85e57c..a241391907f 100644 --- a/.github/workflows/mobile-eas-production.yml +++ b/docs/upstream/workflows/mobile-eas-production.yml @@ -1,3 +1,4 @@ +# Archived upstream workflow. Restore only after the fork owns its Expo project configuration. name: Mobile EAS Production # Production builds and OTA updates run from CI (Linux) — never from a laptop. @@ -33,7 +34,7 @@ on: jobs: production: name: EAS Production ${{ inputs.mode }} - runs-on: blacksmith-8vcpu-ubuntu-2404 + runs-on: ubuntu-24.04 permissions: contents: read env: diff --git a/.github/workflows/release.yml b/docs/upstream/workflows/release.yml similarity index 99% rename from .github/workflows/release.yml rename to docs/upstream/workflows/release.yml index f6e876bde5c..e738a4ff65b 100644 --- a/.github/workflows/release.yml +++ b/docs/upstream/workflows/release.yml @@ -1,3 +1,4 @@ +# Archived upstream workflow. This file is documentation and is not executed by GitHub Actions. name: Release on: diff --git a/infra/relay/scripts/deploy.test.ts b/infra/relay/scripts/deploy.test.ts index 87098b25daf..cc90f0c02cd 100644 --- a/infra/relay/scripts/deploy.test.ts +++ b/infra/relay/scripts/deploy.test.ts @@ -205,7 +205,7 @@ describe("release workflow tracing config propagation", () => { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const workflowPath = yield* path.fromFileUrl( - new URL("../../../.github/workflows/release.yml", import.meta.url), + new URL("../../../docs/upstream/workflows/release.yml", import.meta.url), ); const workflow = yield* fileSystem.readFileString(workflowPath); diff --git a/scripts/fork-workflow-policy.test.ts b/scripts/fork-workflow-policy.test.ts new file mode 100644 index 00000000000..47902245b28 --- /dev/null +++ b/scripts/fork-workflow-policy.test.ts @@ -0,0 +1,40 @@ +import { assert, it } from "@effect/vitest"; +import * as NodeURL from "node:url"; + +import { + findActiveWorkflowViolations, + findForkWorkflowPolicyViolations, +} from "./fork-workflow-policy.ts"; + +const repositoryRoot = NodeURL.fileURLToPath(new URL("../", import.meta.url)); + +it("keeps fork workflows on available runners and outside upstream production accounts", () => { + assert.deepEqual(findForkWorkflowPolicyViolations(repositoryRoot), []); +}); + +it("allows a standard hosted runner and the built-in GitHub token", () => { + const workflow = [ + "jobs:", + " check:", + " runs-on: ubuntu-24.04", + " token: secrets.GITHUB_TOKEN", + ].join("\n"); + + assert.deepEqual(findActiveWorkflowViolations("ci.yml", workflow), []); +}); + +it("rejects unapproved runners, secrets, and repository variables", () => { + const workflow = [ + "jobs:", + " publish:", + " runs-on: blacksmith-8vcpu-ubuntu-2404", + " token: secrets.EXPO_TOKEN", + " project: vars.EXPO_PROJECT_ID", + ].join("\n"); + + assert.deepEqual(findActiveWorkflowViolations("publish.yml", workflow), [ + "publish.yml: runner 'blacksmith-8vcpu-ubuntu-2404' is not approved for this fork", + "publish.yml: credential 'secrets.EXPO_TOKEN' has no approved fork owner", + "publish.yml: credential 'vars.EXPO_PROJECT_ID' has no approved fork owner", + ]); +}); diff --git a/scripts/fork-workflow-policy.ts b/scripts/fork-workflow-policy.ts new file mode 100644 index 00000000000..657543b4fb8 --- /dev/null +++ b/scripts/fork-workflow-policy.ts @@ -0,0 +1,41 @@ +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; + +const approvedRunnerLabels = new Set(["macos-15", "ubuntu-24.04"]); +const approvedCredentialReferences = new Set(["secrets.GITHUB_TOKEN"]); +const credentialReferencePattern = /\b(?:secrets|vars)\.[A-Za-z0-9_]+\b/g; +const runnerLabelPattern = /^\s*runs-on:\s*([^\s#]+)\s*(?:#.*)?$/gm; + +export function findActiveWorkflowViolations( + workflowName: string, + workflow: string, +): ReadonlyArray { + const runnerViolations = Array.from(workflow.matchAll(runnerLabelPattern)).flatMap((match) => { + const runnerLabel = match[1]; + return runnerLabel !== undefined && !approvedRunnerLabels.has(runnerLabel) + ? [`${workflowName}: runner '${runnerLabel}' is not approved for this fork`] + : []; + }); + const credentialViolations = Array.from( + new Set(workflow.match(credentialReferencePattern) ?? []), + ).flatMap((reference) => + approvedCredentialReferences.has(reference) + ? [] + : [`${workflowName}: credential '${reference}' has no approved fork owner`], + ); + + return [...runnerViolations, ...credentialViolations]; +} + +export function findForkWorkflowPolicyViolations(repositoryRoot: string): ReadonlyArray { + const workflowsDirectory = NodePath.join(repositoryRoot, ".github", "workflows"); + const workflowNames = NodeFS.readdirSync(workflowsDirectory).filter( + (name) => name.endsWith(".yml") || name.endsWith(".yaml"), + ); + + return workflowNames.flatMap((name) => { + const workflow = NodeFS.readFileSync(NodePath.join(workflowsDirectory, name), "utf8"); + return findActiveWorkflowViolations(name, workflow); + }); +}