Release #502
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| - "!v*-nightly.*" | |
| - "!v*-preview.*" | |
| schedule: | |
| # Avoid minute zero, when GitHub scheduled jobs are busiest. | |
| - cron: "8,38 * * * *" | |
| workflow_dispatch: | |
| inputs: | |
| channel: | |
| description: "Release channel" | |
| required: false | |
| default: stable | |
| type: choice | |
| options: | |
| - stable | |
| - nightly | |
| - preview | |
| version: | |
| description: "Stable version override (for example 1.2.3). Defaults to the version the latest nightly previewed." | |
| required: false | |
| type: string | |
| # Serialize nightlies (scheduled and manual) so overlapping runs cannot build | |
| # the same commit twice or publish out of order. Stable tag releases get their | |
| # own group so a nightly never blocks them. Running publishers are never | |
| # canceled, and queue: max keeps every pending run instead of the default | |
| # newest-wins single slot, so a queued stable tag can never be silently | |
| # dropped. Automatic nightlies recheck the release gap after leaving the queue. | |
| concurrency: | |
| group: release-${{ (github.event_name == 'schedule' || inputs.channel == 'nightly' || inputs.channel == 'preview') && 'nightly' || 'stable' }} | |
| cancel-in-progress: false | |
| queue: max | |
| permissions: | |
| contents: read | |
| id-token: none | |
| jobs: | |
| # Picks the commit every later job builds. Nightlies and tag pushes build the | |
| # triggering commit. Manual stable releases build the commit of the latest | |
| # published nightly, so stable only ever ships a build that nightly users | |
| # have already run. Scheduled runs also decide here whether a nightly is due. | |
| resolve_commit: | |
| name: Resolve release commit | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 5 | |
| outputs: | |
| ref: ${{ steps.resolve.outputs.ref }} | |
| nightly_version: ${{ steps.resolve.outputs.nightly_version }} | |
| has_changes: ${{ steps.resolve.outputs.has_changes }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| sparse-checkout: .github/scripts | |
| - id: resolve | |
| name: Resolve release commit | |
| uses: actions/github-script@v8 | |
| env: | |
| DISPATCH_CHANNEL: ${{ inputs.channel }} | |
| with: | |
| script: | | |
| const { | |
| shouldReleaseNightly, | |
| resolveLatestNightlyCommit, | |
| } = require('./.github/scripts/check-nightly-release.cjs'); | |
| if (context.eventName === 'schedule') { | |
| core.setOutput('has_changes', await shouldReleaseNightly({ github, context, core })); | |
| core.setOutput('ref', context.sha); | |
| } else if (context.eventName === 'workflow_dispatch' && process.env.DISPATCH_CHANNEL !== 'nightly' && process.env.DISPATCH_CHANNEL !== 'preview') { | |
| const { tag, sha, version } = await resolveLatestNightlyCommit({ github, context, core }); | |
| core.notice(`Stable release builds ${sha}, the commit shipped by ${tag}.`); | |
| core.setOutput('ref', sha); | |
| core.setOutput('nightly_version', version); | |
| } else { | |
| core.setOutput('ref', context.sha); | |
| } | |
| preflight: | |
| name: Preflight | |
| needs: [resolve_commit] | |
| if: | | |
| needs.resolve_commit.result == 'success' && | |
| (github.event_name != 'schedule' || needs.resolve_commit.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: ${{ needs.resolve_commit.outputs.ref }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.resolve_commit.outputs.ref }} | |
| fetch-depth: 0 | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Vite+ | |
| uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: true | |
| env: | |
| pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata | |
| - id: release_meta | |
| name: Resolve release version | |
| shell: bash | |
| env: | |
| DISPATCH_CHANNEL: ${{ github.event.inputs.channel }} | |
| DISPATCH_VERSION: ${{ github.event.inputs.version }} | |
| NIGHTLY_VERSION: ${{ needs.resolve_commit.outputs.nightly_version }} | |
| NIGHTLY_DATE: ${{ github.run_started_at }} | |
| NIGHTLY_SHA: ${{ needs.resolve_commit.outputs.ref }} | |
| 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" | |
| elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "preview" ]]; then | |
| # Manual-only test train: exercises the whole release flow for a | |
| # commit end users must never receive. Never scheduled. | |
| # Same versioning as nightly under its own prerelease identifier. | |
| # A preview release is reachable only by asking for it: npm gets it | |
| # under the `preview` dist-tag, which nothing resolves by default, | |
| # its desktop builds carry no update feed, and no updater manifest | |
| # is attached to the release, so neither stable nor nightly | |
| # installs can ever be offered one. | |
| nightly_date="$(date -u -d "$NIGHTLY_DATE" +%Y%m%d)" | |
| node scripts/resolve-nightly-release.ts \ | |
| --channel preview \ | |
| --date "$nightly_date" \ | |
| --run-number "$NIGHTLY_RUN_NUMBER" \ | |
| --sha "$NIGHTLY_SHA" \ | |
| --github-output | |
| echo "release_channel=preview" >> "$GITHUB_OUTPUT" | |
| echo "cli_dist_tag=preview" >> "$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:-$NIGHTLY_VERSION}" | |
| if [[ -z "$raw" ]]; then | |
| echo "workflow_dispatch stable releases need a version input or a published nightly." >&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 | |
| - 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 | |
| # Share only the verification results, not the large registry metadata cache. | |
| - name: Upload dependency verification | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-dependency-verification | |
| path: ${{ runner.temp }}/pnpm-metadata/lockfile-verified.jsonl | |
| quality: | |
| name: Release quality checks | |
| needs: [preflight] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }} | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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: vp run typecheck | |
| - uses: ./.github/actions/setup-apt-mirrors | |
| - name: Install browser secret helper build libraries | |
| run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config | |
| - name: Test | |
| run: vp run test | |
| relay_public_config: | |
| name: Resolve T3 Connect public config | |
| # Consumes only the release commit, not preflight's resolved version, so it | |
| # runs alongside preflight instead of after it. The condition mirrors preflight's. | |
| needs: [resolve_commit] | |
| if: | | |
| needs.resolve_commit.result == 'success' && | |
| (github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true') | |
| 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.resolve_commit.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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" | |
| # The platform-independent JS (server bundle, web client, Electron main) is | |
| # built exactly once here and handed to every platform job as `js-bundle`. | |
| # The relay/Clerk values are baked into the bundle, so they belong to this | |
| # job rather than to the packaging jobs. | |
| build_bundle: | |
| name: Build JS bundle | |
| # Same gating as relay_public_config: only the release commit is needed, so | |
| # this runs alongside preflight. See the condition comment there. | |
| needs: [preflight, relay_public_config] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' }} | |
| runs-on: blacksmith-32vcpu-ubuntu-2404 | |
| 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 }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Vite+ | |
| uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: false | |
| # pnpm checks the lockfile and policy before reusing this result. A missing | |
| # artifact leaves the cache empty, so installation runs the checks again. | |
| - name: Download dependency verification | |
| continue-on-error: true | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: release-dependency-verification | |
| path: ${{ runner.temp }}/pnpm-metadata | |
| - name: Install bundle dependencies | |
| env: | |
| pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata | |
| run: vp install --filter=t3... --filter=@t3tools/web... --filter=@t3tools/desktop... --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 }}" | |
| - uses: ./.github/actions/setup-apt-mirrors | |
| # @t3tools/desktop#build compiles the Linux browser secret helper on a | |
| # Linux host before packing, and that needs libsecret headers. | |
| - name: Install browser secret helper build libraries | |
| run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config | |
| # Runs t3#build (which depends on @t3tools/web#build) and | |
| # @t3tools/desktop#build, so apps/server/dist holds the server bundle | |
| # plus the web client and apps/desktop/dist-electron the Electron main. | |
| - name: Build JS bundle | |
| run: vp run build:desktop | |
| # Two paths under apps/ so the artifact root is apps/; consumers download | |
| # into `apps` to restore both at their original locations. | |
| - name: Upload JS bundle | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: js-bundle | |
| path: | | |
| apps/server/dist | |
| apps/desktop/dist-electron | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # One job per platform and architecture (see release-desktop.yml), each on | |
| # hardware of its own architecture, and each gated only on what it consumes: | |
| # every platform needs the JS bundle, and the Windows jobs also need the | |
| # same-arch Linux job, whose CLI archive they embed as the WSL runtime. Every | |
| # job builds the desktop app; all but macOS x64 also build the CLI archive | |
| # for their platform, so a target either ships fully or not at all. | |
| desktop_mac_arm64: | |
| name: Desktop macOS arm64 | |
| needs: [preflight, relay_public_config, build_bundle] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: macOS arm64 | |
| runner: blacksmith-12vcpu-macos-26 | |
| platform: mac | |
| target: dmg | |
| arch: arm64 | |
| rust_target: aarch64-apple-darwin | |
| resource_key: darwin-arm64 | |
| cli_archive: true | |
| desktop_mac_x64: | |
| name: Desktop macOS x64 | |
| needs: [preflight, relay_public_config, build_bundle] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: macOS x64 | |
| runner: blacksmith-12vcpu-macos-26 | |
| platform: mac | |
| target: dmg | |
| arch: x64 | |
| rust_target: x86_64-apple-darwin | |
| resource_key: darwin-x64 | |
| # No CLI archive: Node single-executables are unsupported on x64 macOS | |
| # (the SEA docs list macOS as arm64 only) and the built binary segfaults | |
| # on start. The x64 desktop app is Electron and unaffected. | |
| cli_archive: false | |
| desktop_linux_x64: | |
| name: Desktop Linux x64 | |
| needs: [preflight, relay_public_config, build_bundle] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: Linux x64 | |
| runner: blacksmith-32vcpu-ubuntu-2404 | |
| platform: linux | |
| target: AppImage | |
| arch: x64 | |
| rust_target: x86_64-unknown-linux-gnu | |
| resource_key: linux-x64 | |
| cli_archive: true | |
| # node-pty has no Linux prebuild and compiles from source, so the arm64 app | |
| # and archive are built on arm64 hardware rather than cross-built. | |
| desktop_linux_arm64: | |
| name: Desktop Linux arm64 | |
| needs: [preflight, relay_public_config, build_bundle] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: Linux arm64 | |
| runner: ubuntu-24.04-arm | |
| platform: linux | |
| target: AppImage | |
| arch: arm64 | |
| rust_target: aarch64-unknown-linux-gnu | |
| resource_key: linux-arm64 | |
| cli_archive: true | |
| # The Windows jobs embed the same-arch Linux CLI archive as the WSL runtime. | |
| # `!cancelled()` (not `!failure()`) still lets them start when that Linux job | |
| # failed; the download step inside then fails this single platform if the | |
| # archive is missing. | |
| desktop_win_x64: | |
| name: Desktop Windows x64 | |
| needs: [preflight, relay_public_config, build_bundle, desktop_linux_x64] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: Windows x64 | |
| runner: blacksmith-32vcpu-windows-2025 | |
| platform: win | |
| target: nsis | |
| arch: x64 | |
| rust_target: x86_64-pc-windows-msvc | |
| resource_key: win32-x64 | |
| cli_archive: true | |
| desktop_win_arm64: | |
| name: Desktop Windows arm64 | |
| needs: [preflight, relay_public_config, build_bundle, desktop_linux_arm64] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build_bundle.result == 'success' }} | |
| uses: ./.github/workflows/release-desktop.yml | |
| secrets: inherit | |
| with: | |
| version: ${{ needs.preflight.outputs.version }} | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| release_channel: ${{ needs.preflight.outputs.release_channel }} | |
| clerk_publishable_key: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| clerk_jwt_template: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| relay_url: ${{ needs.relay_public_config.outputs.relay_url }} | |
| label: Windows arm64 | |
| runner: windows-11-arm | |
| platform: win | |
| target: nsis | |
| arch: arm64 | |
| rust_target: aarch64-pc-windows-msvc | |
| resource_key: win32-arm64 | |
| cli_archive: true | |
| # npm gets the same bytes as the GitHub Release: the launcher plus one | |
| # package per CLI archive. Preview publishes too, under the `preview` | |
| # dist-tag, which nothing resolves unless asked for by name. | |
| publish_cli: | |
| name: Publish CLI to npm | |
| needs: | |
| [ | |
| preflight, | |
| relay_public_config, | |
| quality, | |
| desktop_mac_arm64, | |
| desktop_linux_x64, | |
| desktop_linux_arm64, | |
| desktop_win_x64, | |
| desktop_win_arm64, | |
| ] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' }} | |
| runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Vite+ | |
| uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: | | |
| args: | |
| - --filter=t3... | |
| - --filter=@t3tools/scripts... | |
| - name: Download all CLI archives | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: cli-* | |
| merge-multiple: true | |
| path: release-cli | |
| - name: Build npm packages from CLI archives | |
| run: node scripts/build-npm-platform-packages.ts --archives-dir release-cli --version "${{ needs.preflight.outputs.version }}" --output-dir npm-packages | |
| # A dry run of every package first: an auth or scope error here (the | |
| # @t3code org missing, a package without a trusted publisher) fails | |
| # before anything is live, instead of after some platforms already are. | |
| - name: Check npm publish access (dry run) | |
| run: | | |
| if ! node apps/server/scripts/cli.ts publish --packages-dir npm-packages --tag "${{ needs.preflight.outputs.cli_dist_tag }}" --provenance --dry-run --verbose; then | |
| echo "::error::npm publish --dry-run failed. Make sure the @t3code npm org exists and that t3 and every @t3code/t3-<platform> package has a trusted publisher registered for .github/workflows/release.yml (see docs/operations/release.md)." >&2 | |
| exit 1 | |
| fi | |
| - name: Publish CLI packages | |
| run: node apps/server/scripts/cli.ts publish --packages-dir npm-packages --tag "${{ needs.preflight.outputs.cli_dist_tag }}" --provenance --verbose | |
| release: | |
| name: Publish GitHub Release | |
| needs: | |
| [ | |
| preflight, | |
| desktop_mac_arm64, | |
| desktop_mac_x64, | |
| desktop_linux_x64, | |
| desktop_linux_arm64, | |
| desktop_win_x64, | |
| desktop_win_arm64, | |
| publish_cli, | |
| ] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_mac_x64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' && needs.publish_cli.result == 'success' }} | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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: Download all CLI archives | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: cli-* | |
| merge-multiple: true | |
| path: release-assets | |
| # Installers verify archives against this file, so it is written from the | |
| # signed bytes that get uploaded, never from an earlier stage. | |
| - name: Write CLI archive checksums | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cd release-assets | |
| shopt -s nullglob | |
| archives=(t3-*.tar.gz t3-*.zip) | |
| if [[ ${#archives[@]} -eq 0 ]]; then | |
| echo "No CLI archives were produced." >&2 | |
| exit 1 | |
| fi | |
| sha256sum "${archives[@]}" > SHA256SUMS | |
| cat SHA256SUMS | |
| # The desktop build omits the publish config for preview versions, so | |
| # electron-builder emits no updater manifests for them. Refuse to publish | |
| # if one shows up anyway: a `latest*.yml` or `nightly*.yml` on a preview | |
| # release is what would let a stable or nightly install update onto it. | |
| - name: Refuse updater metadata on preview releases | |
| if: needs.preflight.outputs.release_channel == 'preview' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob extglob | |
| # builder-debug.yml is electron-builder's config dump, not a feed. | |
| updater_files=(release-assets/!(builder-debug).yml release-assets/*.blockmap) | |
| if [[ ${#updater_files[@]} -ne 0 ]]; then | |
| printf 'Preview releases must not carry updater metadata, found: %s\n' "${updater_files[*]}" >&2 | |
| exit 1 | |
| fi | |
| # electron-updater reads one manifest per platform and channel and picks | |
| # the file entry whose name carries the running arch, so the per-arch | |
| # manifests the build jobs wrote are merged back into that one file. | |
| - name: Merge macOS updater manifests | |
| if: needs.preflight.outputs.release_channel != 'preview' | |
| 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 | |
| if: needs.preflight.outputs.release_channel != 'preview' | |
| run: | | |
| shopt -s nullglob | |
| for x64_manifest in release-assets/*-win-x64.yml; do | |
| arm64_manifest="${x64_manifest%-x64.yml}-arm64.yml" | |
| merged_manifest="${x64_manifest%-win-x64.yml}.yml" | |
| if [[ -f "$arm64_manifest" ]]; then | |
| node scripts/merge-update-manifests.ts --platform win "$x64_manifest" "$arm64_manifest" "$merged_manifest" | |
| rm -f "$x64_manifest" "$arm64_manifest" | |
| else | |
| mv "$x64_manifest" "$merged_manifest" | |
| fi | |
| done | |
| # Updater manifests and blockmaps are what electron-updater consumes. | |
| # They are only listed for channels an updater is meant to follow. | |
| - id: release_files | |
| name: Resolve release asset list | |
| shell: bash | |
| run: | | |
| { | |
| echo 'files<<EOF' | |
| echo 'release-assets/*.dmg' | |
| echo 'release-assets/*.zip' | |
| echo 'release-assets/*.AppImage' | |
| echo 'release-assets/*.exe' | |
| if [[ "${{ needs.preflight.outputs.release_channel }}" != "preview" ]]; then | |
| echo 'release-assets/*.blockmap' | |
| echo 'release-assets/*.yml' | |
| fi | |
| echo 'release-assets/*.tar.gz' | |
| echo 'release-assets/SHA256SUMS' | |
| echo 'EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| # A preview release gets a warning instead of generated notes. Generated | |
| # notes would list every commit since the previous preview, which is | |
| # unmerged branch history no one should read as a changelog, and would | |
| # make the release look like any other build to someone browsing the | |
| # releases page. | |
| - name: Write preview release notes | |
| if: needs.preflight.outputs.release_channel == 'preview' | |
| shell: bash | |
| run: | | |
| cat > release-notes.md <<'EOF' | |
| > [!WARNING] | |
| > **This is a preview build. Do not install it unless you know exactly why you are here.** | |
| > | |
| > Preview builds are cut by maintainers from unreleased branches to exercise the release pipeline. They can be broken, receive no fixes, are never offered as updates, and are not supported. If you want T3 Code, install the [latest release](https://github.com/pingdotgg/t3code/releases/latest) or a nightly instead. | |
| Built from `${{ needs.preflight.outputs.ref }}`. | |
| EOF | |
| - name: Publish release | |
| if: needs.preflight.outputs.previous_tag != '' | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.preflight.outputs.tag }} | |
| target_commitish: ${{ needs.preflight.outputs.ref }} | |
| name: ${{ needs.preflight.outputs.release_name }} | |
| generate_release_notes: ${{ needs.preflight.outputs.release_channel != 'preview' }} | |
| body_path: ${{ needs.preflight.outputs.release_channel == 'preview' && 'release-notes.md' || '' }} | |
| previous_tag: ${{ needs.preflight.outputs.previous_tag }} | |
| prerelease: ${{ needs.preflight.outputs.is_prerelease }} | |
| make_latest: ${{ needs.preflight.outputs.make_latest }} | |
| files: ${{ steps.release_files.outputs.files }} | |
| fail_on_unmatched_files: true | |
| token: ${{ github.token }} | |
| - name: Publish first release | |
| if: needs.preflight.outputs.previous_tag == '' | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| tag_name: ${{ needs.preflight.outputs.tag }} | |
| target_commitish: ${{ needs.preflight.outputs.ref }} | |
| name: ${{ needs.preflight.outputs.release_name }} | |
| generate_release_notes: ${{ needs.preflight.outputs.release_channel != 'preview' }} | |
| body_path: ${{ needs.preflight.outputs.release_channel == 'preview' && 'release-notes.md' || '' }} | |
| prerelease: ${{ needs.preflight.outputs.is_prerelease }} | |
| make_latest: ${{ needs.preflight.outputs.make_latest }} | |
| files: ${{ steps.release_files.outputs.files }} | |
| fail_on_unmatched_files: true | |
| token: ${{ github.token }} | |
| publish_aur: | |
| name: Publish AUR package | |
| needs: [preflight, release] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel != 'preview' }} | |
| uses: ./.github/workflows/publish-aur.yml | |
| with: | |
| release_tag: ${{ needs.preflight.outputs.tag }} | |
| secrets: | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| 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' && needs.preflight.outputs.release_channel != 'preview' }} | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 10 | |
| env: | |
| T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} | |
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | |
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | |
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | |
| T3CODE_WEB_ROUTER_URL: ${{ vars.T3CODE_WEB_ROUTER_URL }} | |
| T3CODE_WEB_LATEST_DOMAIN: ${{ vars.T3CODE_WEB_LATEST_DOMAIN }} | |
| T3CODE_WEB_NIGHTLY_DOMAIN: ${{ vars.T3CODE_WEB_NIGHTLY_DOMAIN }} | |
| VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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 \ | |
| --archive=tgz \ | |
| --prod \ | |
| --skip-domain \ | |
| --yes \ | |
| --token "$VERCEL_TOKEN" \ | |
| "${vercel_scope_args[@]}" \ | |
| --build-env "APP_VERSION=${{ needs.preflight.outputs.version }}" \ | |
| --build-env "T3CODE_CLERK_PUBLISHABLE_KEY=${T3CODE_CLERK_PUBLISHABLE_KEY:-}" \ | |
| --build-env "T3CODE_CLERK_JWT_TEMPLATE=${T3CODE_CLERK_JWT_TEMPLATE:-}" \ | |
| --build-env "T3CODE_CLERK_CLI_OAUTH_CLIENT_ID=${T3CODE_CLERK_CLI_OAUTH_CLIENT_ID:-}" \ | |
| --build-env "T3CODE_RELAY_URL=${T3CODE_RELAY_URL:-}" \ | |
| --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=${T3CODE_RELAY_CLIENT_OTLP_TRACES_URL:-}" \ | |
| --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=${T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET:-}" \ | |
| --build-env "T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=${T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN:-}" \ | |
| --build-env "VITE_HOSTED_APP_URL=$router_url" \ | |
| --build-env "VITE_HOSTED_APP_CHANNEL=$channel_name" | |
| )" | |
| echo "Aliasing $deployment_url to $channel_domain." | |
| vp dlx vercel@53.1.1 alias set "$deployment_url" "$channel_domain" \ | |
| --token "$VERCEL_TOKEN" \ | |
| "${vercel_scope_args[@]}" | |
| if [[ "$channel_name" == "latest" && -n "$router_domain" && "$router_domain" != "$channel_domain" ]]; then | |
| echo "Aliasing $deployment_url to router domain $router_domain." | |
| vp dlx vercel@53.1.1 alias set "$deployment_url" "$router_domain" \ | |
| --token "$VERCEL_TOKEN" \ | |
| "${vercel_scope_args[@]}" | |
| fi | |
| deploy_marketing: | |
| name: Deploy marketing site | |
| needs: [preflight, release] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'nightly' }} | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| timeout-minutes: 10 | |
| env: | |
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | |
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | |
| VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.preflight.outputs.ref }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Vite+ | |
| uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: true | |
| run-install: | | |
| args: | |
| - --filter=@t3tools/marketing... | |
| - name: Deploy marketing site to Vercel | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${VERCEL_TOKEN:-}" || -z "${VERCEL_ORG_ID:-}" ]]; then | |
| echo "Missing one or more required Vercel secrets: VERCEL_TOKEN, VERCEL_ORG_ID." >&2 | |
| exit 1 | |
| fi | |
| VERCEL_PROJECT_ID="$( | |
| curl --fail --silent --show-error \ | |
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v9/projects/t3code-marketing?teamId=$VERCEL_ORG_ID" \ | |
| | jq --exit-status --raw-output '.id' | |
| )" | |
| export VERCEL_PROJECT_ID | |
| vp dlx vercel@53.1.1 deploy \ | |
| --archive=tgz \ | |
| --prod \ | |
| --yes \ | |
| --token "$VERCEL_TOKEN" \ | |
| --scope "${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}" | |
| 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 | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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.preflight.outputs.release_channel != 'preview' && | |
| 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 }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - 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 }}" |