diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21fbce026f5..0f160582310 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,101 +1,126 @@ name: CI on: - pull_request: - push: - branches: - - main + workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string + +permissions: {} jobs: - check: - name: Check - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 + disabled: + name: Disabled + runs-on: ubuntu-24.04 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: Ensure Electron runtime is installed - run: vp run --filter @t3tools/desktop ensure:electron - - - name: Check - run: vp check - - - name: Typecheck - run: vpr typecheck - - - name: Build desktop pipeline - run: vp run build:desktop - - - name: Verify preload bundle output + - name: Explain disabled state run: | - test -f apps/desktop/dist-electron/preload.cjs - grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.cjs - grep -n "__clerk_internal_electron_passkeys" apps/desktop/dist-electron/preload.cjs - - test: - name: Test - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - 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: Ensure Electron runtime is installed - run: vp run --filter @t3tools/desktop ensure:electron - - - name: Test - run: vp run test - - mobile_native_static_analysis: - name: Mobile Native Static Analysis - runs-on: blacksmith-12vcpu-macos-26 - timeout-minutes: 10 - 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 mobile native static analysis tools - run: brew bundle install --file apps/mobile/Brewfile - - - name: Lint mobile native sources - run: vp run lint:mobile - - release_smoke: - name: Release Smoke - runs-on: blacksmith-8vcpu-ubuntu-2404 - timeout-minutes: 10 - 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: Exercise release-only workflow steps - run: node scripts/release-smoke.ts + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." + +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# name: CI +# +# on: +# pull_request: +# push: +# branches: +# - main +# +# jobs: +# check: +# name: Check +# runs-on: blacksmith-8vcpu-ubuntu-2404 +# timeout-minutes: 10 +# 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: Ensure Electron runtime is installed +# run: vp run --filter @t3tools/desktop ensure:electron +# +# - name: Check +# run: vp check +# +# - name: Typecheck +# run: vpr typecheck +# +# - name: Build desktop pipeline +# run: vp run build:desktop +# +# - name: Verify preload bundle output +# run: | +# test -f apps/desktop/dist-electron/preload.cjs +# grep -nE "desktopBridge|getLocalEnvironmentBootstrap|PICK_FOLDER_CHANNEL|wsUrl" apps/desktop/dist-electron/preload.cjs +# grep -n "__clerk_internal_electron_passkeys" apps/desktop/dist-electron/preload.cjs +# +# test: +# name: Test +# runs-on: blacksmith-8vcpu-ubuntu-2404 +# timeout-minutes: 10 +# 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: Ensure Electron runtime is installed +# run: vp run --filter @t3tools/desktop ensure:electron +# +# - name: Test +# run: vp run test +# +# mobile_native_static_analysis: +# name: Mobile Native Static Analysis +# runs-on: blacksmith-12vcpu-macos-26 +# timeout-minutes: 10 +# 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 mobile native static analysis tools +# run: brew bundle install --file apps/mobile/Brewfile +# +# - name: Lint mobile native sources +# run: vp run lint:mobile +# +# release_smoke: +# name: Release Smoke +# runs-on: blacksmith-8vcpu-ubuntu-2404 +# timeout-minutes: 10 +# 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: Exercise release-only workflow steps +# run: node scripts/release-smoke.ts diff --git a/.github/workflows/deploy-relay.yml b/.github/workflows/deploy-relay.yml index 94d4af17e41..3c90253a805 100644 --- a/.github/workflows/deploy-relay.yml +++ b/.github/workflows/deploy-relay.yml @@ -1,80 +1,105 @@ name: Deploy T3 Connect relay on: - push: - branches: - - main + workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string -permissions: - contents: read - id-token: none - statuses: write - -concurrency: - group: relay-production - cancel-in-progress: false +permissions: {} 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" + disabled: + name: Disabled + runs-on: ubuntu-24.04 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}.`; + - name: Explain disabled state + run: | + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." - 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}`, - }); +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# 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/issue-labels.yml b/.github/workflows/issue-labels.yml index d6571d65d45..ad81feb73c8 100644 --- a/.github/workflows/issue-labels.yml +++ b/.github/workflows/issue-labels.yml @@ -1,75 +1,100 @@ name: Issue Labels on: - push: - branches: - - main - paths: - - .github/ISSUE_TEMPLATE/** - - .github/workflows/issue-labels.yml workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string -permissions: - issues: write +permissions: {} jobs: - sync: - name: Sync issue labels + disabled: + name: Disabled runs-on: ubuntu-24.04 steps: - - name: Ensure managed issue labels exist - uses: actions/github-script@v7 - with: - script: | - const managedLabels = [ - { - name: "bug", - color: "d73a4a", - description: "Something is broken or behaving incorrectly.", - }, - { - name: "enhancement", - color: "a2eeef", - description: "Requested improvement or new capability.", - }, - { - name: "needs-triage", - color: "fbca04", - description: "Issue needs maintainer review and initial categorization.", - }, - ]; + - name: Explain disabled state + run: | + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." - 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; - } - - await github.rest.issues.createLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - name: label.name, - color: label.color, - description: label.description, - }); - } - } +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# name: Issue Labels +# +# on: +# push: +# branches: +# - main +# paths: +# - .github/ISSUE_TEMPLATE/** +# - .github/workflows/issue-labels.yml +# workflow_dispatch: +# +# permissions: +# issues: write +# +# jobs: +# sync: +# name: Sync issue labels +# runs-on: ubuntu-24.04 +# steps: +# - name: Ensure managed issue labels exist +# uses: actions/github-script@v7 +# with: +# script: | +# const managedLabels = [ +# { +# name: "bug", +# color: "d73a4a", +# description: "Something is broken or behaving incorrectly.", +# }, +# { +# name: "enhancement", +# color: "a2eeef", +# description: "Requested improvement or new capability.", +# }, +# { +# name: "needs-triage", +# color: "fbca04", +# description: "Issue needs maintainer review and initial categorization.", +# }, +# ]; +# +# 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; +# } +# +# await github.rest.issues.createLabel({ +# owner: context.repo.owner, +# repo: context.repo.repo, +# name: label.name, +# color: label.color, +# description: label.description, +# }); +# } +# } diff --git a/.github/workflows/mobile-eas-preview.yml b/.github/workflows/mobile-eas-preview.yml index a16763cb141..6126e2a01b1 100644 --- a/.github/workflows/mobile-eas-preview.yml +++ b/.github/workflows/mobile-eas-preview.yml @@ -1,80 +1,105 @@ name: Mobile EAS Preview on: - pull_request: - types: [opened, reopened, synchronize, labeled, unlabeled] + workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string + +permissions: {} 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 + disabled: + name: Disabled + runs-on: ubuntu-24.04 steps: - - id: expo-token - name: Check for EXPO_TOKEN - env: - EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + - name: Explain disabled state 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 }} - packager: pnpm - - - 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 + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." - - 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 }} +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# 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 }} +# packager: pnpm +# +# - 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/pr-size.yml b/.github/workflows/pr-size.yml index af557dff62d..51812f6ca82 100644 --- a/.github/workflows/pr-size.yml +++ b/.github/workflows/pr-size.yml @@ -1,295 +1,320 @@ name: PR Size on: - pull_request_target: - types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] + workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string -permissions: - contents: read +permissions: {} jobs: - prepare-config: - name: Prepare PR size config + disabled: + name: Disabled 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).", - }, - ]; + - name: Explain disabled state + run: | + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." - 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})`, - ); +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# 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 index c4abb08b727..2392a54ab8c 100644 --- a/.github/workflows/pr-vouch.yml +++ b/.github/workflows/pr-vouch.yml @@ -1,199 +1,224 @@ 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 + workflow_dispatch: + inputs: + reason: + description: Optional reason for manually running this disabled workflow. + required: false + type: string -permissions: - contents: read - issues: write - pull-requests: write +permissions: {} jobs: - collect-targets: - name: Collect PR targets + disabled: + name: Disabled 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}`); + - name: Explain disabled state + run: | + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." + +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# 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 index 168c000c38b..30851fa36c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,957 +1,982 @@ 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)" + reason: + description: Optional reason for manually running this disabled workflow. required: false type: string -permissions: - contents: read - id-token: none +permissions: {} 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" - - build: - name: Build ${{ matrix.label }} - needs: [preflight, relay_public_config] - if: ${{ !failure() && !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: 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 - 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 + disabled: + name: Disabled + runs-on: ubuntu-24.04 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_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_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 }} + - name: Explain disabled state 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 }}" + echo "This workflow is intentionally manual-only." + echo "Automatic triggers were removed to prevent unwanted GitHub Actions usage." + +# Previous workflow body is kept below for reference. +# Restore the relevant triggers and jobs only when this workflow should run again. +# +# 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" +# +# build: +# name: Build ${{ matrix.label }} +# needs: [preflight, relay_public_config] +# if: ${{ !failure() && !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: 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 +# 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_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_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 }}"