diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td deleted file mode 100644 index 73376110d9a..00000000000 --- a/.github/VOUCHED.td +++ /dev/null @@ -1,35 +0,0 @@ -# Trust list for this repository. -# -# External contributors listed here are treated as trusted by the vouch -# workflow. Collaborators with write access are automatically trusted and -# do not need to be duplicated in this file. -# -# Syntax: -# github:username -# -github:username reason for denouncement -# -# Keep entries sorted alphabetically. -github:adityavardhansharma -github:binbandit -github:chuks-qua -github:cursoragent -github:gbarros-dev -github:github-actions[bot] -github:hwanseoc -github:jamesx0416 -github:jasonLaster -github:JoeEverest -github:maria-rcks -github:nmggithub -github:Noojuno -github:notkainoa -github:PatrickBauer -github:realAhmedRoach -github:shiroyasha9 -github:Yash-Singh1 -github:eggfriedrice24 -github:Ymit24 -github:shivamhwp -github:jappyjan -github:justsomelegs -github:UtkarshUsername diff --git a/.github/workflows/deploy-relay.yml b/.github/workflows/deploy-relay.yml deleted file mode 100644 index 94d4af17e41..00000000000 --- a/.github/workflows/deploy-relay.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Deploy T3 Connect relay - -on: - push: - branches: - - main - -permissions: - contents: read - id-token: none - statuses: write - -concurrency: - group: relay-production - cancel-in-progress: false - -jobs: - deploy_relay: - name: Deploy production relay - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 15 - environment: - name: production - env: - CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} - PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }} - AXIOM_ORG_ID: ${{ vars.AXIOM_ORG_ID }} - RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }} - RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }} - RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }} - CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} - CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }} - APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }} - APNS_TEAM_ID: ${{ vars.APNS_TEAM_ID }} - APNS_KEY_ID: ${{ vars.APNS_KEY_ID }} - APNS_BUNDLE_ID: ${{ vars.APNS_BUNDLE_ID }} - ALCHEMY_TELEMETRY_DISABLED: "1" - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Deploy production relay stage - id: deploy - run: vp run --filter t3code-relay deploy --stage prod --yes --github-output - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PLANETSCALE_API_TOKEN_ID: ${{ secrets.PLANETSCALE_API_TOKEN_ID }} - PLANETSCALE_API_TOKEN: ${{ secrets.PLANETSCALE_API_TOKEN }} - AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }} - CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} - APNS_PRIVATE_KEY: ${{ secrets.APNS_PRIVATE_KEY }} - - - name: Publish relay deploy commit status - uses: actions/github-script@v8 - with: - script: | - const result = "${{ steps.deploy.outputs.result }}"; - const changed = "${{ steps.deploy.outputs.changed }}" === "true"; - const description = changed - ? "Relay production deploy applied infrastructure changes." - : result === "noop" - ? "Relay production deploy was a no-op." - : `Relay production deploy completed with result: ${result}.`; - - await github.rest.repos.createCommitStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: context.sha, - state: "success", - context: "Relay deploy / production", - description, - target_url: `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`, - }); diff --git a/.github/workflows/desktop-artifacts.yml b/.github/workflows/desktop-artifacts.yml new file mode 100644 index 00000000000..1bcf9c87fa0 --- /dev/null +++ b/.github/workflows/desktop-artifacts.yml @@ -0,0 +1,207 @@ +# Fork workflow: build the desktop binaries for every platform this repo already +# supports, on every push to `main`. +# +# Upstream produces these from `release.yml`, which also reads the production +# T3 Connect relay config, signs, publishes to npm, and cuts a GitHub Release — +# all of which need Cloudflare / Apple / Azure / npm / GitHub App credentials +# this fork does not have. That workflow was removed; this one keeps the part a +# fork can actually run. +# +# Constraints this file deliberately respects: +# - no secrets beyond the automatic GITHUB_TOKEN (which it never uses) +# - standard GitHub-hosted runners only (upstream's `blacksmith-*` labels do +# not resolve here, so those jobs sat queued for 24h and were cancelled) +# - builds are unsigned: passing `--signed` is what pulls in signing +# credentials, so it is never passed. `build-desktop-artifact.ts` then sets +# CSC_IDENTITY_AUTO_DISCOVERY=false and skips notarization. +name: Desktop Artifacts + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: desktop-artifacts-${{ github.ref }} + cancel-in-progress: true + +jobs: + wsl_node_pty: + name: Build WSL node-pty (linux-x64) + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@v1 + with: + node-version-file: package.json + cache: true + run-install: true + + - name: Build node-pty linux-x64 prebuild + shell: bash + run: | + set -euo pipefail + # Resolve node-pty from apps/server (where it's a dependency) and build + # its native binary from source for Linux. node-addon-api resolves from + # node-pty's own dependency tree, so node-gyp has everything it needs. + pty_pkg="$(node -e "console.log(require.resolve('node-pty/package.json', { paths: ['$GITHUB_WORKSPACE/apps/server'] }))")" + pty_dir="$(dirname "$pty_pkg")" + ( cd "$pty_dir" && npx --yes node-gyp rebuild ) + mkdir -p wsl-prebuild + cp "$pty_dir/build/Release/pty.node" wsl-prebuild/pty.node + file wsl-prebuild/pty.node + + - name: Upload node-pty linux-x64 prebuild + uses: actions/upload-artifact@v7 + with: + name: wsl-node-pty-x64 + path: wsl-prebuild/pty.node + if-no-files-found: error + retention-days: 7 + + build: + name: ${{ matrix.label }} + # Only the Windows entry consumes the prebuild, and it degrades to a warning + # when the file is absent, so `!cancelled()` (not `!failure()`) keeps every + # platform building even when the prebuild job fails. + needs: [wsl_node_pty] + if: ${{ !cancelled() }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + # macOS x64 cross-builds from the arm64 runner the same way upstream + # does: the staged workspace pins pnpm `supportedArchitectures`, so the + # native deps are fetched for the target arch, not the host's. + - label: macOS arm64 + runner: macos-latest + platform: mac + target: dmg + arch: arm64 + - label: macOS x64 + runner: macos-latest + platform: mac + target: dmg + arch: x64 + - label: Linux x64 + runner: ubuntu-24.04 + platform: linux + target: AppImage + arch: x64 + - label: Windows x64 + runner: windows-2025 + platform: win + target: nsis + arch: x64 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@v1 + with: + node-version-file: package.json + cache: true + run-install: true + + - name: Install ImageMagick + if: matrix.platform == 'linux' + shell: bash + run: | + if ! command -v magick >/dev/null 2>&1 && ! command -v convert >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y imagemagick + fi + + if command -v magick >/dev/null 2>&1; then + magick -version + else + convert -version + fi + + - name: Install Spectre-mitigated MSVC libs + if: matrix.platform == 'win' + shell: pwsh + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $installPath = & $vswhere -products * -latest -property installationPath + $setupExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe" + $proc = Start-Process -FilePath $setupExe ` + -ArgumentList "modify", "--installPath", "`"$installPath`"", "--add", ` + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64.Spectre", "--quiet", "--norestart" ` + -Wait -PassThru -NoNewWindow + if ($null -eq $proc -or $proc.ExitCode -ne 0) { + $code = if ($null -ne $proc) { $proc.ExitCode } else { 1 } + Write-Error "Visual Studio Installer failed with exit code $code" + exit $code + } + + # Bundles the Linux node-pty binary so the packaged WSL backend ships a + # ready binary. Non-fatal: without it the Windows artifact still builds, + # and its WSL backend compiles pty.node on first launch instead. + - name: Download WSL node-pty prebuild + id: wsl_prebuild + if: matrix.platform == 'win' + continue-on-error: true + uses: actions/download-artifact@v8 + with: + name: wsl-node-pty-x64 + path: wsl-prebuild + + - name: Build desktop artifact + shell: bash + run: | + set -euo pipefail + + args=( + --platform "${{ matrix.platform }}" + --target "${{ matrix.target }}" + --arch "${{ matrix.arch }}" + --verbose + ) + + if [[ "${{ matrix.platform }}" == "win" ]]; then + if [[ -f "$GITHUB_WORKSPACE/wsl-prebuild/pty.node" ]]; then + args+=(--wsl-prebuild "$GITHUB_WORKSPACE/wsl-prebuild/pty.node") + else + echo "::warning::WSL node-pty prebuild unavailable; the packaged WSL backend will compile pty.node on first launch." + fi + fi + + vp run dist:desktop:artifact "${args[@]}" + + # build-desktop-artifact.ts copies only files (never the unpacked app + # directories) into release/, so the whole directory is safe to upload. + - name: Summarize artifacts + if: always() + shell: bash + run: | + { + echo "### ${{ matrix.label }} (unsigned)" + echo + if [[ -d release ]]; then + echo '```' + ls -lh release + echo '```' + else + echo "No artifacts were produced." + fi + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload desktop artifact + uses: actions/upload-artifact@v7 + with: + name: desktop-${{ matrix.platform }}-${{ matrix.arch }} + path: release/* + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/mobile-eas-preview.yml b/.github/workflows/mobile-eas-preview.yml deleted file mode 100644 index 32e45fef54e..00000000000 --- a/.github/workflows/mobile-eas-preview.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Mobile EAS Preview - -on: - pull_request: - types: [opened, reopened, synchronize, labeled, unlabeled] - -jobs: - preview: - name: EAS Preview - if: contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') - runs-on: blacksmith-8vcpu-ubuntu-2404 - permissions: - contents: read - pull-requests: write - env: - APP_VARIANT: preview - NODE_OPTIONS: --max-old-space-size=8192 - MOBILE_VERSION_POLICY: fingerprint - steps: - - id: expo-token - name: Check for EXPO_TOKEN - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: | - if [ -n "$EXPO_TOKEN" ]; then - echo "present=true" >> "$GITHUB_OUTPUT" - else - echo "present=false" >> "$GITHUB_OUTPUT" - echo "EXPO_TOKEN is not available; skipping EAS preview." - fi - - - name: Checkout - if: steps.expo-token.outputs.present == 'true' - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup Vite+ - if: steps.expo-token.outputs.present == 'true' - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Expose pnpm - if: steps.expo-token.outputs.present == 'true' - run: | - pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")" - vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin" - echo "$vp_pnpm_bin" >> "$GITHUB_PATH" - "$vp_pnpm_bin/pnpm" --version - - - name: Setup EAS - if: steps.expo-token.outputs.present == 'true' - uses: expo/expo-github-action@v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - # npm, not pnpm: this only installs eas-cli into the action's own - # tool dir, and pnpm 11 hard-fails that install on dtrace-provider's - # ignored build script (no allowBuilds config outside the repo). - packager: npm - - - name: Pull preview environment variables - if: steps.expo-token.outputs.present == 'true' - working-directory: apps/mobile - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: eas env:pull preview --non-interactive - - - name: Deploy with fingerprint check - if: steps.expo-token.outputs.present == 'true' - uses: expo/expo-github-action/continuous-deploy-fingerprint@main - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - with: - profile: preview:dev - branch: pr-${{ github.event.pull_request.number }} - platform: all - environment: preview - working-directory: apps/mobile - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/mobile-eas-production.yml b/.github/workflows/mobile-eas-production.yml deleted file mode 100644 index 685df85e57c..00000000000 --- a/.github/workflows/mobile-eas-production.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: Mobile EAS Production - -# Production builds and OTA updates run from CI (Linux) — never from a laptop. -# Under the fingerprint runtime-version policy the fingerprint must be computed -# in the same OS/pnpm as the EAS build; a macOS `eas build` computes a different -# fingerprint (platform-specific deps + pnpm version) and errors. On this Linux -# runner, with corepack pinning pnpm 10.24 in eas.json, local == build. -on: - workflow_dispatch: - inputs: - mode: - description: "build (+ auto-submit to TestFlight) or update (OTA)" - required: true - type: choice - default: build - options: - - build - - update - platform: - description: "Target platform" - required: true - type: choice - default: ios - options: - - ios - - android - - all - message: - description: "OTA update message (mode=update only)" - required: false - type: string - -jobs: - production: - name: EAS Production ${{ inputs.mode }} - runs-on: blacksmith-8vcpu-ubuntu-2404 - permissions: - contents: read - env: - APP_VARIANT: production - NODE_OPTIONS: --max-old-space-size=8192 - steps: - - id: expo-token - name: Check for EXPO_TOKEN - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: | - if [ -n "$EXPO_TOKEN" ]; then - echo "present=true" >> "$GITHUB_OUTPUT" - else - echo "present=false" >> "$GITHUB_OUTPUT" - echo "EXPO_TOKEN is not available; skipping EAS production job." - fi - - - name: Checkout - if: steps.expo-token.outputs.present == 'true' - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup Vite+ - if: steps.expo-token.outputs.present == 'true' - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Expose pnpm - if: steps.expo-token.outputs.present == 'true' - run: | - pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")" - vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin" - echo "$vp_pnpm_bin" >> "$GITHUB_PATH" - "$vp_pnpm_bin/pnpm" --version - - - name: Setup EAS - if: steps.expo-token.outputs.present == 'true' - uses: expo/expo-github-action@v8 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - # npm, not pnpm: this only installs eas-cli into the action's own - # tool dir, and pnpm 11 hard-fails that install on dtrace-provider's - # ignored build script (no allowBuilds config outside the repo). - packager: npm - - - name: Pull production environment variables - if: steps.expo-token.outputs.present == 'true' - working-directory: apps/mobile - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: eas env:pull production --non-interactive - - - name: Build and submit - if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'build' - working-directory: apps/mobile - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: eas build --platform ${{ inputs.platform }} --profile production --auto-submit --non-interactive --no-wait - - - name: Publish OTA update - if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'update' - working-directory: apps/mobile - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} - run: | - eas update \ - --channel production \ - --environment production \ - --platform ${{ inputs.platform }} \ - --message "${{ inputs.message || format('Production OTA ({0})', github.sha) }}" \ - --non-interactive diff --git a/.github/workflows/mobile-showcase-screenshots.yml b/.github/workflows/mobile-showcase-screenshots.yml deleted file mode 100644 index 36dfb61f73f..00000000000 --- a/.github/workflows/mobile-showcase-screenshots.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Mobile Showcase Screenshots - -on: - workflow_dispatch: - inputs: - platform: - description: Device platforms to capture - required: true - default: all - type: choice - options: - - all - - ios - - android - appearance: - description: System appearances to capture - required: true - default: both - type: choice - options: - - both - - dark - - light - -permissions: - contents: read - -env: - NODE_OPTIONS: --max-old-space-size=8192 - -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 - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Expose pnpm - run: | - pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")" - vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin" - echo "$vp_pnpm_bin" >> "$GITHUB_PATH" - "$vp_pnpm_bin/pnpm" --version - - - name: Capture iOS showcase - run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" - - - name: Validate App Store Connect assets - run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --validate-only - - - name: Upload iOS screenshots - if: always() - uses: actions/upload-artifact@v7 - with: - name: app-store-connect-screenshots - path: artifacts/app-store/screenshots/apple/ - if-no-files-found: warn - retention-days: 14 - - android: - name: Android phone, 7-inch tablet, and 10-inch tablet - if: inputs.platform == 'all' || inputs.platform == 'android' - runs-on: blacksmith-16vcpu-ubuntu-2404 - timeout-minutes: 60 - env: - T3_SHOWCASE_ANDROID_ABI: x86_64 - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Expose pnpm - run: | - pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")" - vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin" - echo "$vp_pnpm_bin" >> "$GITHUB_PATH" - "$vp_pnpm_bin/pnpm" --version - - - name: Setup Java - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 17 - - - name: Setup Gradle cache - uses: gradle/actions/setup-gradle@v5 - - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS="static_node=kvm"' \ - | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: Capture Android showcase - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 36 - target: google_apis - arch: x86_64 - profile: pixel_7_pro - avd-name: Pixel_10_Pro - cores: 8 - ram-size: 4096M - disable-animations: false - script: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}" - - - name: Validate Google Play assets - run: pnpm screenshots:mobile --platform android --appearance "${{ inputs.appearance }}" --validate-only - - - name: Upload Android screenshots - if: always() - uses: actions/upload-artifact@v7 - with: - name: google-play-screenshots - path: artifacts/app-store/screenshots/google-play/ - if-no-files-found: warn - retention-days: 14 diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml deleted file mode 100644 index af557dff62d..00000000000 --- a/.github/workflows/pr-size.yml +++ /dev/null @@ -1,295 +0,0 @@ -name: PR Size - -on: - pull_request_target: - types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] - -permissions: - contents: read - -jobs: - prepare-config: - name: Prepare PR size config - runs-on: ubuntu-24.04 - outputs: - labels_json: ${{ steps.config.outputs.labels_json }} - steps: - - id: config - name: Build PR size label config - uses: actions/github-script@v8 - with: - result-encoding: string - script: | - const managedLabels = [ - { - name: "size:XS", - color: "0e8a16", - description: "0-9 effective changed lines (test files excluded in mixed PRs).", - }, - { - name: "size:S", - color: "5ebd3e", - description: "10-29 effective changed lines (test files excluded in mixed PRs).", - }, - { - name: "size:M", - color: "fbca04", - description: "30-99 effective changed lines (test files excluded in mixed PRs).", - }, - { - name: "size:L", - color: "fe7d37", - description: "100-499 effective changed lines (test files excluded in mixed PRs).", - }, - { - name: "size:XL", - color: "d93f0b", - description: "500-999 effective changed lines (test files excluded in mixed PRs).", - }, - { - name: "size:XXL", - color: "b60205", - description: "1,000+ effective changed lines (test files excluded in mixed PRs).", - }, - ]; - - core.setOutput("labels_json", JSON.stringify(managedLabels)); - sync-label-definitions: - name: Sync PR size label definitions - needs: prepare-config - if: github.event_name != 'pull_request_target' - runs-on: ubuntu-24.04 - permissions: - contents: read - issues: write - steps: - - name: Ensure PR size labels exist - uses: actions/github-script@v8 - env: - PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} - with: - script: | - const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); - - for (const label of managedLabels) { - try { - const { data: existing } = await github.rest.issues.getLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - }); - - if ( - existing.color !== label.color || - (existing.description ?? "") !== label.description - ) { - await github.rest.issues.updateLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description, - }); - } - } catch (error) { - if (error.status !== 404) { - throw error; - } - - try { - await github.rest.issues.createLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description, - }); - } catch (createError) { - if (createError.status !== 422) { - throw createError; - } - } - } - } - label: - name: Label PR size - needs: prepare-config - if: github.event_name == 'pull_request_target' - runs-on: ubuntu-24.04 - permissions: - contents: read - issues: read - pull-requests: write - concurrency: - group: pr-size-${{ github.event.pull_request.number }} - cancel-in-progress: true - steps: - # This pull_request_target job may fetch untrusted PR commits only as passive - # git data. Do not add dependency installs, build/test scripts, or cache - # actions here; use pull_request plus workflow_run for that pattern instead. - - name: Checkout base repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Sync PR size label - uses: actions/github-script@v8 - env: - PR_SIZE_LABELS_JSON: ${{ needs.prepare-config.outputs.labels_json }} - with: - script: | - const { execFileSync } = require("node:child_process"); - - const issueNumber = context.payload.pull_request.number; - const baseSha = context.payload.pull_request.base.sha; - const headSha = context.payload.pull_request.head.sha; - const headTrackingRef = `refs/remotes/pr-size/${issueNumber}`; - const managedLabels = JSON.parse(process.env.PR_SIZE_LABELS_JSON ?? "[]"); - const managedLabelNames = new Set(managedLabels.map((label) => label.name)); - // Keep this aligned with the repo's test entrypoints and test-only support files. - const testExcludePathspecs = [ - ":(glob,exclude)**/__tests__/**", - ":(glob,exclude)**/test/**", - ":(glob,exclude)**/tests/**", - ":(glob,exclude)apps/server/integration/**", - ":(glob,exclude)**/*.test.*", - ":(glob,exclude)**/*.spec.*", - ":(glob,exclude)**/*.browser.*", - ":(glob,exclude)**/*.integration.*", - ]; - - const sumNumstat = (text) => - text - .split("\n") - .filter(Boolean) - .reduce((total, line) => { - const [insertionsRaw = "0", deletionsRaw = "0"] = line.split("\t"); - const additions = - insertionsRaw === "-" ? 0 : Number.parseInt(insertionsRaw, 10) || 0; - const deletions = - deletionsRaw === "-" ? 0 : Number.parseInt(deletionsRaw, 10) || 0; - - return total + additions + deletions; - }, 0); - - const resolveSizeLabel = (totalChangedLines) => { - if (totalChangedLines < 10) { - return "size:XS"; - } - - if (totalChangedLines < 30) { - return "size:S"; - } - - if (totalChangedLines < 100) { - return "size:M"; - } - - if (totalChangedLines < 500) { - return "size:L"; - } - - if (totalChangedLines < 1000) { - return "size:XL"; - } - - return "size:XXL"; - }; - - execFileSync("git", ["fetch", "--no-tags", "origin", baseSha], { - stdio: "inherit", - }); - - execFileSync( - "git", - ["fetch", "--no-tags", "origin", `+refs/pull/${issueNumber}/head:${headTrackingRef}`], - { - stdio: "inherit", - }, - ); - - const resolvedHeadSha = execFileSync("git", ["rev-parse", headTrackingRef], { - encoding: "utf8", - }).trim(); - - if (resolvedHeadSha !== headSha) { - core.warning( - `Fetched head SHA ${resolvedHeadSha} does not match pull request head SHA ${headSha}; using fetched ref for sizing.`, - ); - } - - execFileSync("git", ["cat-file", "-e", `${baseSha}^{commit}`], { - stdio: "inherit", - }); - - const diffArgs = [ - "diff", - "--numstat", - "--ignore-all-space", - "--ignore-blank-lines", - `${baseSha}...${resolvedHeadSha}`, - ]; - - const totalChangedLines = sumNumstat( - execFileSync( - "git", - diffArgs, - { encoding: "utf8" }, - ), - ); - const nonTestChangedLines = sumNumstat( - execFileSync("git", [...diffArgs, "--", ".", ...testExcludePathspecs], { - encoding: "utf8", - }), - ); - const testChangedLines = Math.max(0, totalChangedLines - nonTestChangedLines); - - const changedLines = nonTestChangedLines === 0 ? testChangedLines : nonTestChangedLines; - const nextLabelName = resolveSizeLabel(changedLines); - - const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - per_page: 100, - }); - - for (const label of currentLabels) { - if (!managedLabelNames.has(label.name) || label.name === nextLabelName) { - continue; - } - - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - name: label.name, - }); - } catch (removeError) { - if (removeError.status !== 404) { - throw removeError; - } - } - } - - if (!currentLabels.some((label) => label.name === nextLabelName)) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: [nextLabelName], - }); - } - - const classification = - nonTestChangedLines === 0 - ? testChangedLines > 0 - ? "test-only PR" - : "no line changes" - : testChangedLines > 0 - ? "test lines excluded" - : "all non-test changes"; - - core.info( - `PR #${issueNumber}: ${nonTestChangedLines} non-test lines, ${testChangedLines} test lines, ${changedLines} effective lines -> ${nextLabelName} (${classification})`, - ); diff --git a/.github/workflows/pr-vouch.yml b/.github/workflows/pr-vouch.yml deleted file mode 100644 index c4abb08b727..00000000000 --- a/.github/workflows/pr-vouch.yml +++ /dev/null @@ -1,199 +0,0 @@ -name: PR Vouch - -on: - pull_request_target: - types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] - issue_comment: - types: [created] - push: - branches: - - main - paths: - - .github/VOUCHED.td - - .github/workflows/pr-vouch.yml - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - collect-targets: - name: Collect PR targets - runs-on: ubuntu-24.04 - outputs: - targets: ${{ steps.collect.outputs.targets }} - steps: - - id: collect - uses: actions/github-script@v8 - with: - script: | - if (context.eventName === "pull_request_target") { - const pr = context.payload.pull_request; - core.setOutput("targets", JSON.stringify([{ number: pr.number, user: pr.user.login }])); - return; - } - - if (context.eventName === "issue_comment") { - const issue = context.payload.issue; - const body = context.payload.comment?.body ?? ""; - if (!issue?.pull_request || !body.includes("/recheck-vouch")) { - core.setOutput("targets", "[]"); - return; - } - - core.setOutput( - "targets", - JSON.stringify([{ number: issue.number, user: issue.user.login }]), - ); - return; - } - - const pulls = await github.paginate(github.rest.pulls.list, { - owner: context.repo.owner, - repo: context.repo.repo, - state: "open", - per_page: 100, - }); - - const targets = pulls.map((pull) => ({ - number: pull.number, - user: pull.user.login, - })); - core.setOutput("targets", JSON.stringify(targets)); - - label: - name: Label PR ${{ matrix.target.number }} - needs: collect-targets - if: ${{ needs.collect-targets.outputs.targets != '[]' }} - runs-on: ubuntu-24.04 - concurrency: - group: pr-vouch-${{ matrix.target.number }} - cancel-in-progress: true - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.collect-targets.outputs.targets) }} - steps: - - id: vouch - name: Check PR author trust - uses: mitchellh/vouch/action/check-user@v1 - with: - user: ${{ matrix.target.user }} - allow-fail: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Sync PR labels - uses: actions/github-script@v8 - env: - PR_NUMBER: ${{ matrix.target.number }} - VOUCH_STATUS: ${{ steps.vouch.outputs.status }} - with: - script: | - const issueNumber = Number(process.env.PR_NUMBER); - const status = process.env.VOUCH_STATUS; - const managedLabels = [ - { - name: "vouch:trusted", - color: "1f883d", - description: "PR author is trusted by repo permissions or the VOUCHED list.", - }, - { - name: "vouch:unvouched", - color: "fbca04", - description: "PR author is not yet trusted in the VOUCHED list.", - }, - { - name: "vouch:denounced", - color: "d1242f", - description: "PR author is explicitly blocked by the VOUCHED list.", - }, - ]; - - const managedLabelNames = new Set(managedLabels.map((label) => label.name)); - - for (const label of managedLabels) { - try { - const { data: existing } = await github.rest.issues.getLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - }); - - if ( - existing.color !== label.color || - (existing.description ?? "") !== label.description - ) { - await github.rest.issues.updateLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description, - }); - } - } catch (error) { - if (error.status !== 404) { - throw error; - } - - try { - await github.rest.issues.createLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description, - }); - } catch (createError) { - if (createError.status !== 422) { - throw createError; - } - } - } - } - - const nextLabelName = - status === "denounced" - ? "vouch:denounced" - : ["bot", "collaborator", "vouched"].includes(status) - ? "vouch:trusted" - : "vouch:unvouched"; - - const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - per_page: 100, - }); - - for (const label of currentLabels) { - if (!managedLabelNames.has(label.name) || label.name === nextLabelName) { - continue; - } - - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - name: label.name, - }); - } catch (removeError) { - if (removeError.status !== 404) { - throw removeError; - } - } - } - - if (!currentLabels.some((label) => label.name === nextLabelName)) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: [nextLabelName], - }); - } - - core.info(`PR #${issueNumber}: ${status} -> ${nextLabelName}`); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 668d1fcb59d..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,1024 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*.*.*" - - "!v*-nightly.*" - schedule: - - cron: "0 */3 * * *" - workflow_dispatch: - inputs: - channel: - description: "Release channel" - required: false - default: stable - type: choice - options: - - stable - - nightly - version: - description: "Release version (for example 1.2.3 or v1.2.3)" - required: false - type: string - -permissions: - contents: read - id-token: none - -jobs: - check_changes: - name: Check for changes since last nightly - if: github.event_name == 'schedule' - runs-on: blacksmith-8vcpu-ubuntu-2404 - outputs: - has_changes: ${{ steps.check.outputs.has_changes }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - id: check - name: Compare HEAD to last nightly tag - run: | - last_nightly_tag=$(git tag --list 'v*-nightly.*' 'nightly-v*' --sort=-creatordate | head -n 1) - if [[ -z "$last_nightly_tag" ]]; then - echo "No previous nightly tag found. Proceeding with release." - echo "has_changes=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - last_nightly_sha=$(git rev-parse "$last_nightly_tag^{commit}") - head_sha=$(git rev-parse HEAD) - - if [[ "$last_nightly_sha" == "$head_sha" ]]; then - echo "No changes on main since last nightly release ($last_nightly_tag). Skipping." - echo "has_changes=false" >> "$GITHUB_OUTPUT" - else - echo "Changes detected on main since $last_nightly_tag ($last_nightly_sha → $head_sha). Proceeding." - echo "has_changes=true" >> "$GITHUB_OUTPUT" - fi - - preflight: - name: Preflight - needs: [check_changes] - if: | - !failure() && !cancelled() && - (github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true') - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - outputs: - release_channel: ${{ steps.release_meta.outputs.release_channel }} - version: ${{ steps.release_meta.outputs.version }} - tag: ${{ steps.release_meta.outputs.tag }} - release_name: ${{ steps.release_meta.outputs.name }} - short_sha: ${{ steps.release_meta.outputs.short_sha }} - previous_tag: ${{ steps.previous_tag.outputs.previous_tag }} - cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }} - is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }} - make_latest: ${{ steps.release_meta.outputs.make_latest }} - ref: ${{ github.sha }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Ensure Electron runtime is installed - run: vp run --filter @t3tools/desktop ensure:electron - - - id: release_meta - name: Resolve release version - shell: bash - env: - DISPATCH_CHANNEL: ${{ github.event.inputs.channel }} - DISPATCH_VERSION: ${{ github.event.inputs.version }} - NIGHTLY_DATE: ${{ github.run_started_at }} - NIGHTLY_SHA: ${{ github.sha }} - NIGHTLY_RUN_NUMBER: ${{ github.run_number }} - run: | - if [[ "${GITHUB_EVENT_NAME}" == "schedule" || ( "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "nightly" ) ]]; then - nightly_date="$(date -u -d "$NIGHTLY_DATE" +%Y%m%d)" - - node scripts/resolve-nightly-release.ts \ - --date "$nightly_date" \ - --run-number "$NIGHTLY_RUN_NUMBER" \ - --sha "$NIGHTLY_SHA" \ - --github-output - - echo "release_channel=nightly" >> "$GITHUB_OUTPUT" - echo "cli_dist_tag=nightly" >> "$GITHUB_OUTPUT" - echo "is_prerelease=true" >> "$GITHUB_OUTPUT" - echo "make_latest=false" >> "$GITHUB_OUTPUT" - else - if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then - raw="${DISPATCH_VERSION}" - if [[ -z "$raw" ]]; then - echo "workflow_dispatch stable releases require the version input." >&2 - exit 1 - fi - else - raw="${GITHUB_REF_NAME}" - fi - - version="${raw#v}" - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then - echo "Invalid release version: $raw" >&2 - exit 1 - fi - - echo "release_channel=stable" >> "$GITHUB_OUTPUT" - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "tag=v$version" >> "$GITHUB_OUTPUT" - echo "name=T3 Code v$version" >> "$GITHUB_OUTPUT" - echo "cli_dist_tag=latest" >> "$GITHUB_OUTPUT" - if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "is_prerelease=false" >> "$GITHUB_OUTPUT" - echo "make_latest=true" >> "$GITHUB_OUTPUT" - else - echo "is_prerelease=true" >> "$GITHUB_OUTPUT" - echo "make_latest=false" >> "$GITHUB_OUTPUT" - fi - fi - - - name: Check - run: vp check - - - name: Typecheck - run: vp run typecheck - - - name: Test - run: vp run test - - - id: previous_tag - name: Resolve previous release tag - run: | - node scripts/resolve-previous-release-tag.ts \ - --channel "${{ steps.release_meta.outputs.release_channel }}" \ - --current-tag "${{ steps.release_meta.outputs.tag }}" \ - --github-output - - relay_public_config: - name: Resolve T3 Connect public config - needs: preflight - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }} - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 5 - environment: - name: production - outputs: - clerk_publishable_key: ${{ steps.public_config.outputs.clerk_publishable_key }} - clerk_jwt_template: ${{ steps.public_config.outputs.clerk_jwt_template }} - clerk_cli_oauth_client_id: ${{ steps.public_config.outputs.clerk_cli_oauth_client_id }} - relay_url: ${{ steps.public_config.outputs.relay_url }} - env: - CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }} - RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }} - CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} - CLERK_JWT_TEMPLATE: ${{ vars.CLERK_JWT_TEMPLATE }} - CLERK_CLI_OAUTH_CLIENT_ID: ${{ vars.CLERK_CLI_OAUTH_CLIENT_ID }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=t3code-relay... - - - id: relay_state - name: Read production relay tracing config - shell: bash - run: | - vp run --filter t3code-relay deploy \ - --stage prod \ - --read-state \ - --github-output \ - --github-env-file "$RUNNER_TEMP/relay-client-tracing.env" - - - name: Upload relay client tracing config - uses: actions/upload-artifact@v7 - with: - name: relay-client-tracing-config - path: ${{ runner.temp }}/relay-client-tracing.env - if-no-files-found: error - retention-days: 1 - - - id: public_config - name: Resolve production relay public config - shell: bash - run: | - set -euo pipefail - - relay_domain="${RELAY_DOMAIN:-}" - if [[ -z "$relay_domain" && -n "${RELAY_API_ZONE_NAME:-}" ]]; then - relay_domain="relay.$RELAY_API_ZONE_NAME" - fi - required=( - relay_domain - CLERK_PUBLISHABLE_KEY - CLERK_JWT_TEMPLATE - CLERK_CLI_OAUTH_CLIENT_ID - ) - missing=() - for name in "${required[@]}"; do - if [[ -z "${!name:-}" ]]; then - missing+=("$name") - fi - done - if (( ${#missing[@]} > 0 )); then - printf 'Missing required relay deployment configuration: %s\n' "${missing[*]}" >&2 - exit 1 - fi - - echo "clerk_publishable_key=$CLERK_PUBLISHABLE_KEY" >> "$GITHUB_OUTPUT" - echo "clerk_jwt_template=$CLERK_JWT_TEMPLATE" >> "$GITHUB_OUTPUT" - echo "clerk_cli_oauth_client_id=$CLERK_CLI_OAUTH_CLIENT_ID" >> "$GITHUB_OUTPUT" - echo "relay_url=https://$relay_domain" >> "$GITHUB_OUTPUT" - - # node-pty publishes no Linux prebuilt and the WSL backend runs under the - # distro's own (Linux) Node, which can't load the Windows/Electron binary. We - # build the Linux pty.node here, on Linux, and hand it to the Windows packaging - # job — the Windows artifact then ships a ready WSL backend binary with no - # cross-compiling and no first-launch compiler/node-gyp/network on the user's - # machine. node-pty is N-API, so one binary works across all WSL Node versions. - build_wsl_node_pty: - name: Build WSL node-pty (linux-x64) - needs: [preflight] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }} - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - fetch-depth: 0 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Build node-pty linux-x64 prebuild - shell: bash - run: | - set -euo pipefail - # Resolve node-pty from apps/server (where it's a dependency) and build - # its native binary from source for Linux. node-addon-api resolves from - # node-pty's own dependency tree, so node-gyp has everything it needs. - pty_pkg="$(node -e "console.log(require.resolve('node-pty/package.json', { paths: ['$GITHUB_WORKSPACE/apps/server'] }))")" - pty_dir="$(dirname "$pty_pkg")" - ( cd "$pty_dir" && npx --yes node-gyp rebuild ) - mkdir -p wsl-prebuild - cp "$pty_dir/build/Release/pty.node" wsl-prebuild/pty.node - file wsl-prebuild/pty.node - - - name: Upload node-pty linux-x64 prebuild - uses: actions/upload-artifact@v7 - with: - name: wsl-node-pty-x64 - path: wsl-prebuild/pty.node - if-no-files-found: error - - build: - name: Build ${{ matrix.label }} - # build_wsl_node_pty stays in `needs` so it runs first and its artifact is - # available to download, but only the Windows matrix entry consumes it. We - # therefore gate the job on preflight + relay (must succeed) WITHOUT requiring - # build_wsl_node_pty, so a failed Linux prebuild doesn't skip the macOS/Linux - # builds. `!cancelled()` (not `!failure()`) lets the job run even when - # build_wsl_node_pty failed; the Windows-only download step below then fails - # that single platform if the prebuild is missing. - needs: [preflight, relay_public_config, build_wsl_node_pty] - if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - env: - T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} - T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} - T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} - T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} - strategy: - fail-fast: false - matrix: - include: - - label: macOS arm64 - runner: blacksmith-12vcpu-macos-26 - platform: mac - target: dmg - arch: arm64 - - label: macOS x64 - runner: blacksmith-12vcpu-macos-26 - platform: mac - target: dmg - arch: x64 - - label: Linux x64 - runner: blacksmith-32vcpu-ubuntu-2404 - platform: linux - target: AppImage - arch: x64 - - label: Windows x64 - runner: blacksmith-32vcpu-windows-2025 - platform: win - target: nsis - arch: x64 - # - label: Windows arm64 - # runner: windows-11-arm - # platform: win - # target: nsis - # arch: arm64 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - fetch-depth: 0 - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: true - - - name: Download relay client tracing config - uses: actions/download-artifact@v8 - with: - name: relay-client-tracing-config - path: ${{ runner.temp }}/relay-client-tracing - - - name: Load relay client tracing config - shell: bash - run: | - config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env" - tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")" - echo "::add-mask::$tracing_token" - cat "$config_path" >> "$GITHUB_ENV" - - - name: Align package versions to release version - run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" - - - name: Download WSL node-pty prebuild - if: matrix.platform == 'win' - uses: actions/download-artifact@v7 - with: - name: wsl-node-pty-x64 - path: wsl-prebuild - - - name: Install Spectre-mitigated MSVC libs - if: matrix.platform == 'win' - shell: pwsh - run: | - $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - $installPath = & $vswhere -products * -latest -property installationPath - $setupExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe" - $proc = Start-Process -FilePath $setupExe ` - -ArgumentList "modify", "--installPath", "`"$installPath`"", "--add", ` - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64.Spectre", "--quiet", "--norestart" ` - -Wait -PassThru -NoNewWindow - if ($null -eq $proc -or $proc.ExitCode -ne 0) { - $code = if ($null -ne $proc) { $proc.ExitCode } else { 1 } - Write-Error "Visual Studio Installer failed with exit code $code" - exit $code - } - - - name: Install ImageMagick - if: matrix.platform == 'linux' - shell: bash - run: | - if ! command -v magick >/dev/null 2>&1 && ! command -v convert >/dev/null 2>&1; then - sudo apt-get update - sudo apt-get install -y imagemagick - fi - - if command -v magick >/dev/null 2>&1; then - magick -version - else - convert -version - fi - - - name: Prepare Azure Trusted Signing - if: matrix.platform == 'win' - shell: pwsh - env: - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} - AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} - AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} - AZURE_TRUSTED_SIGNING_PUBLISHER_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_PUBLISHER_NAME }} - run: | - $ErrorActionPreference = "Stop" - - $requiredSecrets = @( - $env:AZURE_TENANT_ID, - $env:AZURE_CLIENT_ID, - $env:AZURE_CLIENT_SECRET, - $env:AZURE_TRUSTED_SIGNING_ENDPOINT, - $env:AZURE_TRUSTED_SIGNING_ACCOUNT_NAME, - $env:AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME, - $env:AZURE_TRUSTED_SIGNING_PUBLISHER_NAME - ) - if ($requiredSecrets | Where-Object { [string]::IsNullOrWhiteSpace($_) }) { - Write-Host "Azure Trusted Signing disabled; skipping TrustedSigning module preparation." - exit 0 - } - - try { - Install-PackageProvider ` - -Name NuGet ` - -MinimumVersion 2.8.5.201 ` - -Force ` - -Scope CurrentUser ` - -ErrorAction Stop - } catch { - Write-Warning "Could not bootstrap NuGet package provider. Continuing because the runner may already have a usable provider. $($_.Exception.Message)" - } - - Install-Module ` - -Name TrustedSigning ` - -MinimumVersion 0.5.0 ` - -Force ` - -AllowClobber ` - -Repository PSGallery ` - -Scope CurrentUser ` - -ErrorAction Stop - - Import-Module TrustedSigning -MinimumVersion 0.5.0 -Force - Get-Command Invoke-TrustedSigning -ErrorAction Stop - - $moduleRoots = @( - [System.IO.Path]::Combine([Environment]::GetFolderPath("MyDocuments"), "PowerShell", "Modules"), - [System.IO.Path]::Combine([Environment]::GetFolderPath("MyDocuments"), "WindowsPowerShell", "Modules"), - [System.IO.Path]::Combine($env:ProgramFiles, "PowerShell", "Modules"), - [System.IO.Path]::Combine($env:ProgramFiles, "WindowsPowerShell", "Modules") - ) - $modulePathEntries = @($moduleRoots + ($env:PSModulePath -split ";")) | - Where-Object { $_ -and (Test-Path $_) } | - Select-Object -Unique - "PSModulePath=$($modulePathEntries -join ';')" >> $env:GITHUB_ENV - - - name: Build desktop artifact - shell: bash - env: - CSC_LINK: ${{ secrets.CSC_LINK }} - CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} - MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_PROVISIONING_PROFILE }} - T3CODE_CLERK_PASSKEY_RP_DOMAINS: ${{ vars.CLERK_PASSKEY_RP_DOMAINS }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} - AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} - AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} - AZURE_TRUSTED_SIGNING_PUBLISHER_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_PUBLISHER_NAME }} - run: | - args=( - --platform "${{ matrix.platform }}" - --target "${{ matrix.target }}" - --arch "${{ matrix.arch }}" - --build-version "${{ needs.preflight.outputs.version }}" - --verbose - ) - - has_all() { - for value in "$@"; do - if [[ -z "$value" ]]; then - return 1 - fi - done - return 0 - } - - if [[ "${{ matrix.platform }}" == "mac" ]]; then - if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then - if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then - echo "macOS signing is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2 - exit 1 - fi - - key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" - printf '%s' "$APPLE_API_KEY" > "$key_path" - export APPLE_API_KEY="$key_path" - - profile_path="$RUNNER_TEMP/t3code.provisionprofile" - printf '%s' "$MACOS_PROVISIONING_PROFILE" | base64 -D > "$profile_path" - security cms -D -i "$profile_path" >/dev/null - export T3CODE_APPLE_TEAM_ID="$APPLE_TEAM_ID" - export T3CODE_MACOS_PROVISIONING_PROFILE="$profile_path" - - echo "macOS signing enabled." - args+=(--signed) - else - echo "macOS signing disabled (missing one or more Apple signing secrets)." - fi - elif [[ "${{ matrix.platform }}" == "win" ]]; then - # Bundle the Linux node-pty binary built by the build_wsl_node_pty job - # so the packaged WSL backend ships a ready binary (no first-launch - # compile). Required for a working WSL backend on Windows. - args+=(--wsl-prebuild "$GITHUB_WORKSPACE/wsl-prebuild/pty.node") - if has_all \ - "$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"; then - echo "Windows signing enabled (Azure Trusted Signing)." - args+=(--signed) - else - echo "Windows signing disabled (missing one or more Azure Trusted Signing secrets)." - fi - else - echo "Signing disabled for ${{ matrix.platform }}." - fi - - vp run dist:desktop:artifact "${args[@]}" - - - name: Collect release assets - shell: bash - run: | - set -euo pipefail - mkdir -p release-publish - - shopt -s nullglob - for pattern in \ - "release/*.dmg" \ - "release/*.zip" \ - "release/*.AppImage" \ - "release/*.exe" \ - "release/*.blockmap" \ - "release/*.yml"; do - for file in $pattern; do - cp "$file" release-publish/ - done - done - - if [[ "${{ matrix.platform }}" == "mac" && "${{ matrix.arch }}" != "arm64" ]]; then - shopt -s nullglob - for manifest in release-publish/*-mac.yml; do - mv "$manifest" "${manifest%.yml}-${{ matrix.arch }}.yml" - done - fi - - # Enable if Windows arm64 builds are enabled. - # Windows updater metadata is channel-specific (for example - # "latest.yml" or "nightly.yml"). Suffix each per-arch copy so the - # release job can merge matching arm64/x64 manifests back into one - # canonical manifest per channel. - # if [[ "${{ matrix.platform }}" == "win" ]]; then - # shopt -s nullglob - # for manifest in release-publish/*.yml; do - # mv "$manifest" "${manifest%.yml}-win-${{ matrix.arch }}.yml" - # done - # fi - - - name: Upload build artifacts - uses: actions/upload-artifact@v7 - with: - name: desktop-${{ matrix.platform }}-${{ matrix.arch }} - path: release-publish/* - if-no-files-found: error - - publish_cli: - name: Publish CLI to npm - needs: [preflight, relay_public_config, build] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }} - runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - permissions: - contents: read - id-token: write - env: - T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} - T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} - T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} - T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=t3... - - --filter=@t3tools/web... - - --filter=@t3tools/scripts... - - - name: Download relay client tracing config - uses: actions/download-artifact@v8 - with: - name: relay-client-tracing-config - path: ${{ runner.temp }}/relay-client-tracing - - - name: Load relay client tracing config - shell: bash - run: | - config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env" - tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")" - echo "::add-mask::$tracing_token" - cat "$config_path" >> "$GITHUB_ENV" - - - name: Align package versions to release version - run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" - - - name: Build web package - run: vp run --filter @t3tools/web build - - - name: Build CLI package - run: vp run --filter t3 build - - - name: Publish CLI package - run: node apps/server/scripts/cli.ts publish --tag "${{ needs.preflight.outputs.cli_dist_tag }}" --app-version "${{ needs.preflight.outputs.version }}" --verbose - - release: - name: Publish GitHub Release - needs: [preflight, build, publish_cli] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }} - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - steps: - - id: app_token - name: Mint release app token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.RELEASE_APP_ID }} - private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=@t3tools/scripts... - - - name: Download all desktop artifacts - uses: actions/download-artifact@v8 - with: - pattern: desktop-* - merge-multiple: true - path: release-assets - - - name: Merge macOS updater manifests - run: | - shopt -s nullglob - for x64_manifest in release-assets/*-mac-x64.yml; do - arm64_manifest="${x64_manifest%-x64.yml}.yml" - if [[ -f "$arm64_manifest" ]]; then - node scripts/merge-update-manifests.ts --platform mac "$arm64_manifest" "$x64_manifest" - rm -f "$x64_manifest" - fi - done - - # - name: Merge Windows updater manifests - # run: | - # shopt -s nullglob - # found_windows_manifest=false - # for x64_manifest in release-assets/*-win-x64.yml; do - # if [[ "$(basename "$x64_manifest")" == builder-debug-* ]]; then - # continue - # fi - - # arm64_manifest="${x64_manifest/-x64.yml/-arm64.yml}" - # output_manifest="${x64_manifest/-win-x64.yml/.yml}" - # if [[ ! -f "$arm64_manifest" ]]; then - # echo "Missing matching arm64 Windows manifest for $x64_manifest" >&2 - # exit 1 - # fi - - # found_windows_manifest=true - # node scripts/merge-update-manifests.ts --platform win \ - # "$arm64_manifest" \ - # "$x64_manifest" \ - # "$output_manifest" - # rm -f "$arm64_manifest" "$x64_manifest" - # done - - # if [[ "$found_windows_manifest" != true ]]; then - # echo "No Windows updater manifests found to merge." >&2 - # exit 1 - # fi - - - name: Publish release - if: needs.preflight.outputs.previous_tag != '' - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.preflight.outputs.tag }} - target_commitish: ${{ needs.preflight.outputs.ref }} - name: ${{ needs.preflight.outputs.release_name }} - generate_release_notes: true - previous_tag: ${{ needs.preflight.outputs.previous_tag }} - prerelease: ${{ needs.preflight.outputs.is_prerelease }} - make_latest: ${{ needs.preflight.outputs.make_latest }} - files: | - release-assets/*.dmg - release-assets/*.zip - release-assets/*.AppImage - release-assets/*.exe - release-assets/*.blockmap - release-assets/*.yml - fail_on_unmatched_files: true - token: ${{ steps.app_token.outputs.token }} - - - name: Publish first release - if: needs.preflight.outputs.previous_tag == '' - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ needs.preflight.outputs.tag }} - target_commitish: ${{ needs.preflight.outputs.ref }} - name: ${{ needs.preflight.outputs.release_name }} - generate_release_notes: true - prerelease: ${{ needs.preflight.outputs.is_prerelease }} - make_latest: ${{ needs.preflight.outputs.make_latest }} - files: | - release-assets/*.dmg - release-assets/*.zip - release-assets/*.AppImage - release-assets/*.exe - release-assets/*.blockmap - release-assets/*.yml - fail_on_unmatched_files: true - token: ${{ steps.app_token.outputs.token }} - - deploy_web: - name: Deploy hosted web app - needs: [preflight, relay_public_config, release] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }} - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - env: - T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} - T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} - T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} - T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - T3CODE_WEB_ROUTER_URL: ${{ vars.T3CODE_WEB_ROUTER_URL }} - T3CODE_WEB_LATEST_DOMAIN: ${{ vars.T3CODE_WEB_LATEST_DOMAIN }} - T3CODE_WEB_NIGHTLY_DOMAIN: ${{ vars.T3CODE_WEB_NIGHTLY_DOMAIN }} - VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=@t3tools/scripts... - - --filter=@t3tools/web... - - - name: Download relay client tracing config - uses: actions/download-artifact@v8 - with: - name: relay-client-tracing-config - path: ${{ runner.temp }}/relay-client-tracing - - - name: Load relay client tracing config - shell: bash - run: | - config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env" - tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")" - echo "::add-mask::$tracing_token" - cat "$config_path" >> "$GITHUB_ENV" - - - name: Align package versions to release version - run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" - - - name: Refresh release lockfile - run: vp install --lockfile-only --ignore-scripts - - - name: Deploy and alias channel - shell: bash - run: | - set -euo pipefail - - if [[ -z "${VERCEL_TOKEN:-}" || -z "${VERCEL_ORG_ID:-}" || -z "${VERCEL_PROJECT_ID:-}" ]]; then - echo "Missing one or more required Vercel secrets: VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID." >&2 - exit 1 - fi - - router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}" - latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}" - nightly_domain="${T3CODE_WEB_NIGHTLY_DOMAIN:-nightly.app.t3.codes}" - router_domain="${router_url#http://}" - router_domain="${router_domain#https://}" - router_domain="${router_domain%%/*}" - - if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then - channel_domain="$latest_domain" - channel_name="latest" - else - channel_domain="$nightly_domain" - channel_name="nightly" - fi - - vercel_scope="${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}" - vercel_scope_args=(--scope "$vercel_scope") - - echo "Deploying hosted web app for $channel_name channel." - deployment_url="$( - vp dlx vercel@53.1.1 deploy \ - --prod \ - --skip-domain \ - --yes \ - --token "$VERCEL_TOKEN" \ - "${vercel_scope_args[@]}" \ - --build-env "APP_VERSION=${{ needs.preflight.outputs.version }}" \ - --build-env "T3CODE_CLERK_PUBLISHABLE_KEY=${T3CODE_CLERK_PUBLISHABLE_KEY:-}" \ - --build-env "T3CODE_CLERK_JWT_TEMPLATE=${T3CODE_CLERK_JWT_TEMPLATE:-}" \ - --build-env "T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=${T3CODE_CLERK_CLI_OAUTH_CLIENT_ID:-}" \ - --build-env "T3CODE_RELAY_URL=${T3CODE_RELAY_URL:-}" \ - --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=${T3CODE_RELAY_CLIENT_OTLP_TRACES_URL:-}" \ - --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=${T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET:-}" \ - --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=${T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN:-}" \ - --build-env "VITE_HOSTED_APP_URL=$router_url" \ - --build-env "VITE_HOSTED_APP_CHANNEL=$channel_name" - )" - - echo "Aliasing $deployment_url to $channel_domain." - vp dlx vercel@53.1.1 alias set "$deployment_url" "$channel_domain" \ - --token "$VERCEL_TOKEN" \ - "${vercel_scope_args[@]}" - - if [[ "$channel_name" == "latest" && -n "$router_domain" && "$router_domain" != "$channel_domain" ]]; then - echo "Aliasing $deployment_url to router domain $router_domain." - vp dlx vercel@53.1.1 alias set "$deployment_url" "$router_domain" \ - --token "$VERCEL_TOKEN" \ - "${vercel_scope_args[@]}" - fi - - finalize: - name: Finalize release - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }} - needs: [preflight, release] - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - steps: - - id: app_token - name: Mint release app token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.RELEASE_APP_ID }} - private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - - - name: Checkout - uses: actions/checkout@v6 - with: - ref: main - fetch-depth: 0 - token: ${{ steps.app_token.outputs.token }} - persist-credentials: true - - - id: app_bot - name: Resolve GitHub App bot identity - env: - GH_TOKEN: ${{ steps.app_token.outputs.token }} - APP_SLUG: ${{ steps.app_token.outputs.app-slug }} - run: | - user_id="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" - echo "name=${APP_SLUG}[bot]" >> "$GITHUB_OUTPUT" - echo "email=${user_id}+${APP_SLUG}[bot]@users.noreply.github.com" >> "$GITHUB_OUTPUT" - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=@t3tools/scripts... - - --filter=@t3tools/oxlint-plugin-t3code... - - - id: update_versions - name: Update version strings - env: - RELEASE_VERSION: ${{ needs.preflight.outputs.version }} - run: node scripts/update-release-package-versions.ts "$RELEASE_VERSION" --github-output - - - name: Format package.json files - if: steps.update_versions.outputs.changed == 'true' - run: vp fmt apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json - - - name: Refresh lockfile - if: steps.update_versions.outputs.changed == 'true' - run: vp install --lockfile-only --ignore-scripts - - - name: Commit and push version bump - if: steps.update_versions.outputs.changed == 'true' - shell: bash - env: - RELEASE_TAG: ${{ needs.preflight.outputs.tag }} - run: | - if git diff --quiet -- apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json pnpm-lock.yaml; then - echo "No version changes to commit." - exit 0 - fi - - git config user.name "${{ steps.app_bot.outputs.name }}" - git config user.email "${{ steps.app_bot.outputs.email }}" - - git add apps/server/package.json apps/desktop/package.json apps/web/package.json packages/contracts/package.json pnpm-lock.yaml - git commit -m "chore(release): prepare $RELEASE_TAG" - git push origin HEAD:main - - announce_discord: - name: Announce release on Discord - if: | - always() && !cancelled() && - needs.preflight.result == 'success' && - needs.relay_public_config.result == 'success' && - needs.release.result == 'success' && - needs.deploy_web.result == 'success' && - (needs.finalize.result == 'success' || needs.finalize.result == 'skipped') - needs: [preflight, relay_public_config, release, deploy_web, finalize] - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ needs.preflight.outputs.ref }} - - - name: Setup Vite+ - uses: voidzero-dev/setup-vp@v1 - with: - node-version-file: package.json - cache: true - run-install: | - args: - - --filter=@t3tools/scripts... - - - name: Announce prerelease on Discord - if: needs.preflight.outputs.is_prerelease == 'true' - continue-on-error: true - env: - DISCORD_MENTION_ROLE_ID: ${{ secrets.DISCORD_RELEASE_NIGHTLY_ROLE_ID }} - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }} - run: | - node scripts/notify-discord-release.ts prerelease \ - --role-id "$DISCORD_MENTION_ROLE_ID" \ - --release-name "${{ needs.preflight.outputs.release_name }}" \ - --release-version "${{ needs.preflight.outputs.version }}" \ - --tag "${{ needs.preflight.outputs.tag }}" \ - --release-url "https://github.com/${{ github.repository }}/releases/tag/${{ needs.preflight.outputs.tag }}" - - - name: Announce latest release on Discord - if: needs.preflight.outputs.make_latest == 'true' - continue-on-error: true - env: - DISCORD_MENTION_ROLE_ID: ${{ secrets.DISCORD_RELEASE_LATEST_ROLE_ID }} - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }} - run: | - node scripts/notify-discord-release.ts latest \ - --role-id "$DISCORD_MENTION_ROLE_ID" \ - --release-name "${{ needs.preflight.outputs.release_name }}" \ - --release-version "${{ needs.preflight.outputs.version }}" \ - --tag "${{ needs.preflight.outputs.tag }}" \ - --release-url "https://github.com/${{ github.repository }}/releases/tag/${{ needs.preflight.outputs.tag }}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b734a99bbb..fb1992fe739 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,10 +8,6 @@ You can still open an issue or PR, but please do so knowing there is a high chan If that sounds annoying, that is because it is. This project is still early and we are trying to keep scope, quality, and direction under control. -PRs are automatically labeled with a `vouch:*` trust status and a `size:*` diff size based on changed lines. - -If you are an external contributor, expect `vouch:unvouched` until we explicitly add you to [.github/VOUCHED.td](.github/VOUCHED.td). - ## What We Are Most Likely To Accept Small, focused bug fixes. diff --git a/FORK.md b/FORK.md index 49d19bdff5f..15a9cee9a3f 100644 --- a/FORK.md +++ b/FORK.md @@ -126,6 +126,8 @@ old pingdotgg + aadit merges) and the fork's changes are being re-applied as **d (`scripts/install.sh`) is hardcoded to aadit's repo. Nothing additive for a dev fork. 7. **CI fork-runnable** (PR #8) — `ci.yml` Blacksmith runners → `ubuntu-24.04`, mobile job dropped. `release.yml` left as-is (release-only; needs secrets a fork lacks). + **Superseded 2026-07-27:** every credentialed / Blacksmith-only workflow (including + `release.yml`) was deleted and `desktop-artifacts.yml` added — see section F. > **Verification note:** several classes of test failure are **environmental, not > regressions** — always diff against clean upstream before chasing one. @@ -505,18 +507,56 @@ rebasing directly onto `pingdotgg/main`. ### F. CI / workflows adapted for the fork -- **Files:** `.github/workflows/ci.yml`, `.github/workflows/pr-size.yml`, - `.github/workflows/release.yml` -- **What:** `ci.yml`: upstream's Blacksmith runners (`blacksmith-8vcpu-ubuntu-2404` - on `check` / `test` / `release_smoke`) → `ubuntu-24.04`, and the macOS-only - `mobile_native_static_analysis` job dropped (this fork does not target mobile, - and `blacksmith-12vcpu-macos-26` is unavailable to it). `pr-size.yml` and - `release.yml` were part of the pre-reset fork; as of the 2026-07-23 reset only - `ci.yml` is modified — `release.yml` is left as upstream ships it, since it is - release-only and needs secrets a fork lacks. +- **Files:** `.github/workflows/*`, `.github/VOUCHED.td`, `docs/operations/ci.md`, + `docs/operations/release.md`, `infra/relay/README.md`, + `infra/relay/scripts/deploy.test.ts`, `CONTRIBUTING.md` +- **Standing rule:** a workflow stays in this fork only if it can actually run + here — **standard GitHub-hosted runners** (upstream's `blacksmith-*` labels do + not resolve; jobs sit queued for 24h and are auto-cancelled) and **no + credentials beyond the automatic `GITHUB_TOKEN`**. Everything else is deleted, + not disabled. Verified against the fork's run history: 30+ consecutive + scheduled `Release` runs were cancelled after the 24h queue timeout, and the + only push-triggered `CI` run ever recorded went the same way. +- **Kept (2 workflows):** + - `ci.yml` — upstream's Blacksmith runners (`blacksmith-8vcpu-ubuntu-2404` on + `check` / `test` / `release_smoke`) → `ubuntu-24.04`, and the macOS-only + `mobile_native_static_analysis` job dropped (this fork does not target + mobile, and `blacksmith-12vcpu-macos-26` is unavailable to it). + - `issue-labels.yml` — unmodified; `GITHUB_TOKEN` only, and it bootstraps the + labels `.github/ISSUE_TEMPLATE/*.yml` apply. +- **Added:** `desktop-artifacts.yml` — builds the four platforms upstream's + `release.yml` matrix covers (macOS `arm64`/`x64` DMG, Linux `x64` AppImage, + Windows `x64` NSIS) on every push to `main` and on dispatch, **unsigned**, and + uploads them as workflow artifacts. Carries over the two secret-free steps that + matter from upstream's build job: the Linux `node-pty` prebuild bundled into + the Windows artifact (non-fatal when missing) and the Spectre-mitigated MSVC + libs install. It never passes `--signed`, which is what pulls signing + credentials into `scripts/build-desktop-artifact.ts`. +- **Deleted (needs credentials and/or Blacksmith runners):** `release.yml` + (Cloudflare + Clerk + Apple + Azure + npm OIDC + release GitHub App; its + 3-hourly nightly cron was pure noise here), `deploy-relay.yml` (Cloudflare, + PlanetScale, Axiom, Clerk, APNs; ran on every push to `main`), + `mobile-eas-preview.yml` / `mobile-eas-production.yml` (`EXPO_TOKEN`), + `mobile-showcase-screenshots.yml` (`blacksmith-12vcpu-macos-26` / + `blacksmith-16vcpu-ubuntu-2404`). +- **Deleted (upstream community governance, no value in this fork):** + `pr-vouch.yml` + `.github/VOUCHED.td` (trust-gates external contributors + against upstream's contributor list) and `pr-size.yml` (size labels on PRs that + are all authored by the fork owner and its agents; its `sync-label-definitions` + job was also dead code — `if: github.event_name != 'pull_request_target'` on a + workflow whose only trigger is `pull_request_target`). Both used + `pull_request_target`. +- **Fallout fixed with the deletions:** the release-workflow tracing-config guard + in `infra/relay/scripts/deploy.test.ts` read `release.yml` off disk and would + fail `vp run test` once it was gone — dropped, with a restore note in place. + `CONTRIBUTING.md` lost its `vouch:*` / `size:*` paragraph; + `docs/operations/ci.md`, `docs/operations/release.md`, and + `infra/relay/README.md` gained fork notes. - **Re-apply notes:** Highest-churn area. Re-derive from upstream's **new** - workflow files and re-apply these transformations (runner swap, drop - fork-unavailable jobs/secrets), rather than force-keeping stale fork copies. + workflow files and re-apply the standing rule above (runner swap, drop + credentialed/unavailable jobs) rather than force-keeping stale fork copies. A + new upstream workflow arriving in a rebase is opt-**in**: it ships only if it + passes the standing rule. - **Redundancy check (as of `89c5a19`): keep.** Upstream has not touched `.github/workflows/ci.yml` since `b41e89e`, so the runner swap replayed cleanly. Re-check on every rebase — a new upstream job would silently be diff --git a/README.md b/README.md index b8227f3bf81..0d5b0854cbd 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ > they need (Gemini CLI `.cmd` shim resolution, Copilot detection), terminal Ctrl-key > forwarding, and CI adjusted to run on standard GitHub-hosted runners. > +> **Fork builds:** every push to `main` here runs the +> [Desktop Artifacts](https://github.com/mclaren-data-systems/t3code/actions/workflows/desktop-artifacts.yml) +> workflow, which builds **unsigned** macOS (arm64/x64), Linux (x64), and Windows (x64) desktop +> binaries and attaches them to the run (14-day retention). This fork publishes no GitHub Releases +> and no npm package — the install instructions below point at upstream. +> > A few further UX refinements — thread-scoped commit selection, a completed dot that persists > until the thread is read, per-thread composer message history, and an always-visible > new-thread button — are maintained on separate branches and not yet on `main`. diff --git a/docs/operations/ci.md b/docs/operations/ci.md index 7a0447ec070..ee17e3625c4 100644 --- a/docs/operations/ci.md +++ b/docs/operations/ci.md @@ -1,6 +1,10 @@ # CI quality gates -- `.github/workflows/ci.yml` runs `vp check` (lint + typecheck), `vpr typecheck`, and `vp run test` on pull requests and pushes to `main`. -- `.github/workflows/release.yml` builds macOS (`arm64` and `x64`), Linux (`x64`), and Windows (`x64`) desktop artifacts from a single `v*.*.*` tag and publishes one GitHub release. -- The release workflow auto-enables signing only when platform credentials are present. macOS passkey builds additionally require `APPLE_TEAM_ID` and the `MACOS_PROVISIONING_PROFILE` secret; Windows uses Azure Trusted Signing. Without the core signing credentials, it still releases unsigned artifacts. -- See [Release Checklist](./release.md) for the full release/signing setup checklist. +This fork runs a reduced workflow set: everything here uses standard GitHub-hosted +runners and no credentials beyond the automatic `GITHUB_TOKEN`. See +[FORK.md](../../FORK.md) section F for what was removed from upstream and why. + +- `.github/workflows/ci.yml` runs `vp check` (lint + typecheck), `vpr typecheck`, the desktop build pipeline, and `vp run test` on pull requests and pushes to `main`. +- `.github/workflows/desktop-artifacts.yml` builds macOS (`arm64` and `x64`), Linux (`x64`), and Windows (`x64`) desktop artifacts on every push to `main`, plus on manual dispatch. Artifacts are **unsigned** and uploaded as workflow artifacts (14-day retention); nothing is published to a GitHub Release. +- `.github/workflows/issue-labels.yml` keeps the labels the issue forms apply (`bug`, `enhancement`, `needs-triage`) in sync. +- Upstream's `release.yml` (tag-driven signed release + npm publish) and `deploy-relay.yml` are not present in this fork — see [Release Checklist](./release.md) for what they do upstream. diff --git a/docs/operations/mobile-app-store-screenshots.md b/docs/operations/mobile-app-store-screenshots.md index 2c36ab9d008..05fabd3cd37 100644 --- a/docs/operations/mobile-app-store-screenshots.md +++ b/docs/operations/mobile-app-store-screenshots.md @@ -79,6 +79,10 @@ names, light/dark appearance, scenes, output directory, capture delay, Android A ## Capture in GitHub Actions +> **Fork note:** the `Mobile Showcase Screenshots` workflow is not present in this fork — it runs +> only on Blacksmith macOS/Linux runners this fork cannot schedule. Capture locally with +> `pnpm screenshots:mobile` (above); the rest of this section is upstream reference. + Run the `Mobile Showcase Screenshots` workflow from GitHub's Actions tab, choose `all`, `ios`, or `android`, and select `light`, `dark`, or `both`. The default dispatch captures both appearances and runs iOS and Android concurrently: iPhone and iPad capture on a diff --git a/docs/operations/release.md b/docs/operations/release.md index 9e908550054..8cc61b560be 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -1,5 +1,12 @@ # Release Checklist +> **Fork note:** the workflows this document describes (`.github/workflows/release.yml`, +> `.github/workflows/deploy-relay.yml`) are **not present in this fork** — they require +> Cloudflare, PlanetScale, Axiom, Clerk, Apple, Azure, npm, and GitHub App credentials a +> fork does not have. Kept as upstream reference for rebases. This fork builds unsigned +> desktop binaries from `.github/workflows/desktop-artifacts.yml` on every push to `main` +> and does not cut releases; see [CI quality gates](./ci.md). + This document covers the unified release workflow for stable and nightly desktop releases. ## What the workflow does diff --git a/infra/relay/README.md b/infra/relay/README.md index 114d5e9b07f..40f5266ec69 100644 --- a/infra/relay/README.md +++ b/infra/relay/README.md @@ -114,6 +114,11 @@ the URL manually. ### Deployment CI +> **Fork note:** `.github/workflows/deploy-relay.yml` is not present in this fork — it needs +> Cloudflare, PlanetScale, Axiom, Clerk, and APNs credentials a fork does not have, so it +> failed on every push to `main`. The rest of this section is upstream reference; local +> `deploy` runs against a personal stage still work with your own credentials. + The relay is versioned separately from client releases. `.github/workflows/deploy-relay.yml` deploys the shared Alchemy `prod` stage on every push to `main`. Stable and nightly release builds both resolve their static public config from the same diff --git a/infra/relay/scripts/deploy.test.ts b/infra/relay/scripts/deploy.test.ts index 87098b25daf..1e19673f2a3 100644 --- a/infra/relay/scripts/deploy.test.ts +++ b/infra/relay/scripts/deploy.test.ts @@ -1,8 +1,4 @@ -import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; -import * as Effect from "effect/Effect"; -import * as FileSystem from "effect/FileSystem"; -import * as Path from "effect/Path"; import { hasDeployChanges, @@ -199,24 +195,11 @@ describe("serializeRelayClientTracingEnvironment", () => { }); }); -describe("release workflow tracing config propagation", () => { - it.effect("uses an artifact instead of a masked cross-job token output", () => - Effect.gen(function* () { - const fileSystem = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const workflowPath = yield* path.fromFileUrl( - new URL("../../../.github/workflows/release.yml", import.meta.url), - ); - const workflow = yield* fileSystem.readFileString(workflowPath); - - expect(workflow).not.toContain("client_tracing_token:"); - expect(workflow).not.toContain("needs.relay_public_config.outputs.client_tracing_token"); - expect(workflow).toContain('--github-env-file "$RUNNER_TEMP/relay-client-tracing.env"'); - expect(workflow).toContain("name: relay-client-tracing-config"); - expect(workflow).toContain('cat "$config_path" >> "$GITHUB_ENV"'); - }).pipe(Effect.provide(NodeServices.layer)), - ); -}); +// Fork note: upstream also asserts here that `.github/workflows/release.yml` +// propagates the relay tracing config through an artifact rather than a masked +// cross-job output. This fork has no release/relay-deploy workflow (both need +// credentials a fork lacks), so the guard has nothing to read and was dropped. +// Restore it together with those workflows on any rebase that brings them back. describe("publicConfigFromOutput", () => { it("reads the complete public tracing config from persisted Alchemy output", () => {