Release #176
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.*" | |
| 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 | |
| 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') && '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') { | |
| 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" | |
| 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" | |
| # node-pty publishes no Linux prebuilt and the WSL backend runs under the | |
| # distro's own (Linux) Node, which can't load the Windows/Electron binary. We | |
| # build the Linux pty.node here, on Linux, and hand it to the Windows packaging | |
| # job — the Windows artifact then ships a ready WSL backend binary with no | |
| # cross-compiling and no first-launch compiler/node-gyp/network on the user's | |
| # machine. node-pty is N-API, so one binary works across all WSL Node versions. | |
| build_wsl_node_pty: | |
| name: Build WSL node-pty (linux-x64) | |
| # Same gating as relay_public_config: only the release commit is needed, so | |
| # this runs alongside preflight. See the condition comment there. | |
| 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: 15 | |
| 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=t3... | |
| - name: Build node-pty linux-x64 prebuild | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Resolve node-pty from apps/server (where it's a dependency) and build | |
| # its native binary from source for Linux. node-addon-api resolves from | |
| # node-pty's own dependency tree, so node-gyp has everything it needs. | |
| pty_pkg="$(node -e "console.log(require.resolve('node-pty/package.json', { paths: ['$GITHUB_WORKSPACE/apps/server'] }))")" | |
| pty_dir="$(dirname "$pty_pkg")" | |
| ( cd "$pty_dir" && npx --yes node-gyp rebuild ) | |
| mkdir -p wsl-prebuild | |
| cp "$pty_dir/build/Release/pty.node" wsl-prebuild/pty.node | |
| file wsl-prebuild/pty.node | |
| - name: Upload node-pty linux-x64 prebuild | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wsl-node-pty-x64 | |
| path: wsl-prebuild/pty.node | |
| if-no-files-found: error | |
| build: | |
| name: Build ${{ matrix.label }} | |
| # build_wsl_node_pty stays in `needs` so it runs first and its artifact is | |
| # available to download, but only the Windows matrix entry consumes it. We | |
| # therefore gate the job on preflight + relay (must succeed) WITHOUT requiring | |
| # build_wsl_node_pty, so a failed Linux prebuild doesn't skip the macOS/Linux | |
| # builds. `!cancelled()` (not `!failure()`) lets the job run even when | |
| # build_wsl_node_pty failed; the Windows-only download step below then fails | |
| # that single platform if the prebuild is missing. | |
| needs: [preflight, relay_public_config, build_wsl_node_pty] | |
| if: ${{ !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 30 | |
| env: | |
| T3CODE_CLERK_PUBLISHABLE_KEY: ${{ needs.relay_public_config.outputs.clerk_publishable_key }} | |
| T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} | |
| T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} | |
| T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - label: macOS arm64 | |
| runner: blacksmith-12vcpu-macos-26 | |
| platform: mac | |
| target: dmg | |
| arch: arm64 | |
| rust_target: aarch64-apple-darwin | |
| resource_key: darwin-arm64 | |
| - label: macOS x64 | |
| runner: blacksmith-12vcpu-macos-26 | |
| platform: mac | |
| target: dmg | |
| arch: x64 | |
| rust_target: x86_64-apple-darwin | |
| resource_key: darwin-x64 | |
| - 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 | |
| - 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 | |
| # - 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 }} | |
| sparse-checkout: | | |
| /* | |
| !/.repos/ | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Vite+ | |
| uses: voidzero-dev/setup-vp@v1 | |
| with: | |
| node-version-file: package.json | |
| cache: ${{ matrix.platform != 'win' }} | |
| run-install: false | |
| - name: Resolve Windows package cache path | |
| if: matrix.platform == 'win' | |
| id: package_cache_path | |
| shell: pwsh | |
| run: '"path=$(vp pm cache dir)" >> $env:GITHUB_OUTPUT' | |
| - name: Cache Windows packages | |
| if: matrix.platform == 'win' | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ steps.package_cache_path.outputs.path }} | |
| key: windows-release-packages-v1-${{ matrix.arch }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| # 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 desktop dependencies | |
| env: | |
| pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata | |
| run: vp install --filter=@t3tools/desktop... --filter=t3... --filter=@t3tools/scripts... | |
| - name: Cache resource monitor | |
| id: resource_monitor_cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: native/resource-monitor/target/${{ matrix.rust_target }}/release/t3-resource-monitor${{ matrix.platform == 'win' && '.exe' || '' }} | |
| key: resource-monitor-${{ matrix.rust_target }}-${{ hashFiles('native/resource-monitor/Cargo.lock', 'native/resource-monitor/Cargo.toml', 'native/resource-monitor/src/**') }} | |
| - name: Setup Rust | |
| if: steps.resource_monitor_cache.outputs.cache-hit != 'true' | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.rust_target }} | |
| - name: Download relay client tracing config | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: relay-client-tracing-config | |
| path: ${{ runner.temp }}/relay-client-tracing | |
| - name: Load relay client tracing config | |
| shell: bash | |
| run: | | |
| config_path="$RUNNER_TEMP/relay-client-tracing/relay-client-tracing.env" | |
| tracing_token="$(sed -n 's/^T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=//p' "$config_path")" | |
| echo "::add-mask::$tracing_token" | |
| cat "$config_path" >> "$GITHUB_ENV" | |
| - name: Align package versions to release version | |
| run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}" | |
| - name: Download WSL node-pty prebuild | |
| if: matrix.platform == 'win' | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: wsl-node-pty-x64 | |
| path: wsl-prebuild | |
| - name: Install Spectre-mitigated MSVC libs | |
| if: matrix.platform == 'win' | |
| shell: pwsh | |
| run: | | |
| $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" | |
| $installPath = & $vswhere -products * -latest -property installationPath | |
| $setupExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe" | |
| $proc = Start-Process -FilePath $setupExe ` | |
| -ArgumentList "modify", "--installPath", "`"$installPath`"", "--add", ` | |
| "Microsoft.VisualStudio.Component.VC.Runtimes.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 | |
| } | |
| - uses: ./.github/actions/setup-apt-mirrors | |
| if: matrix.platform == 'linux' | |
| - name: Install Linux desktop build libraries | |
| if: matrix.platform == 'linux' | |
| shell: bash | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libsecret-1-dev pkg-config | |
| if ! command -v magick >/dev/null 2>&1 && ! command -v convert >/dev/null 2>&1; then | |
| 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: | |
| pnpm_config_cache_dir: ${{ runner.temp }}/pnpm-metadata | |
| T3CODE_DESKTOP_REUSE_RESOURCE_MONITOR: ${{ steps.resource_monitor_cache.outputs.cache-hit == 'true' }} | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} | |
| MACOS_PROVISIONING_PROFILE: ${{ secrets.MACOS_PROVISIONING_PROFILE }} | |
| T3CODE_CLERK_PASSKEY_RP_DOMAINS: ${{ vars.CLERK_PASSKEY_RP_DOMAINS }} | |
| AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} | |
| AZURE_TRUSTED_SIGNING_ENDPOINT: ${{ secrets.AZURE_TRUSTED_SIGNING_ENDPOINT }} | |
| AZURE_TRUSTED_SIGNING_ACCOUNT_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} | |
| AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| AZURE_TRUSTED_SIGNING_PUBLISHER_NAME: ${{ secrets.AZURE_TRUSTED_SIGNING_PUBLISHER_NAME }} | |
| run: | | |
| args=( | |
| --platform "${{ matrix.platform }}" | |
| --target "${{ matrix.target }}" | |
| --arch "${{ matrix.arch }}" | |
| --build-version "${{ needs.preflight.outputs.version }}" | |
| --verbose | |
| ) | |
| has_all() { | |
| for value in "$@"; do | |
| if [[ -z "$value" ]]; then | |
| return 1 | |
| fi | |
| done | |
| return 0 | |
| } | |
| if [[ "${{ matrix.platform }}" == "mac" ]]; then | |
| if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then | |
| if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then | |
| echo "macOS signing is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2 | |
| exit 1 | |
| fi | |
| key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" | |
| printf '%s' "$APPLE_API_KEY" > "$key_path" | |
| export APPLE_API_KEY="$key_path" | |
| profile_path="$RUNNER_TEMP/t3code.provisionprofile" | |
| printf '%s' "$MACOS_PROVISIONING_PROFILE" | base64 -D > "$profile_path" | |
| security cms -D -i "$profile_path" >/dev/null | |
| export T3CODE_APPLE_TEAM_ID="$APPLE_TEAM_ID" | |
| export T3CODE_MACOS_PROVISIONING_PROFILE="$profile_path" | |
| echo "macOS signing enabled." | |
| args+=(--signed) | |
| else | |
| echo "macOS signing disabled (missing one or more Apple signing secrets)." | |
| fi | |
| elif [[ "${{ matrix.platform }}" == "win" ]]; then | |
| # Bundle the Linux node-pty binary built by the build_wsl_node_pty job | |
| # so the packaged WSL backend ships a ready binary (no first-launch | |
| # compile). Required for a working WSL backend on Windows. | |
| args+=(--wsl-prebuild "$GITHUB_WORKSPACE/wsl-prebuild/pty.node") | |
| if has_all \ | |
| "$AZURE_TENANT_ID" \ | |
| "$AZURE_CLIENT_ID" \ | |
| "$AZURE_CLIENT_SECRET" \ | |
| "$AZURE_TRUSTED_SIGNING_ENDPOINT" \ | |
| "$AZURE_TRUSTED_SIGNING_ACCOUNT_NAME" \ | |
| "$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \ | |
| "$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then | |
| echo "Windows signing enabled (Azure Trusted Signing)." | |
| args+=(--signed) | |
| else | |
| echo "Windows signing disabled (missing one or more Azure Trusted Signing secrets)." | |
| fi | |
| else | |
| echo "Signing disabled for ${{ matrix.platform }}." | |
| fi | |
| vp run dist:desktop:artifact "${args[@]}" | |
| - name: Collect release assets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p release-publish | |
| shopt -s nullglob | |
| for pattern in \ | |
| "release/*.dmg" \ | |
| "release/*.zip" \ | |
| "release/*.AppImage" \ | |
| "release/*.exe" \ | |
| "release/*.blockmap" \ | |
| "release/*.yml"; do | |
| for file in $pattern; do | |
| cp "$file" release-publish/ | |
| done | |
| done | |
| if [[ "${{ matrix.platform }}" == "mac" && "${{ matrix.arch }}" != "arm64" ]]; then | |
| shopt -s nullglob | |
| for manifest in release-publish/*-mac.yml; do | |
| mv "$manifest" "${manifest%.yml}-${{ matrix.arch }}.yml" | |
| done | |
| fi | |
| # Enable if Windows arm64 builds are enabled. | |
| # Windows updater metadata is channel-specific (for example | |
| # "latest.yml" or "nightly.yml"). Suffix each per-arch copy so the | |
| # release job can merge matching arm64/x64 manifests back into one | |
| # canonical manifest per channel. | |
| # if [[ "${{ matrix.platform }}" == "win" ]]; then | |
| # shopt -s nullglob | |
| # for manifest in release-publish/*.yml; do | |
| # mv "$manifest" "${manifest%.yml}-win-${{ matrix.arch }}.yml" | |
| # done | |
| # fi | |
| - name: Collect resource monitor | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| binary_name="t3-resource-monitor" | |
| if [[ "${{ matrix.platform }}" == "win" ]]; then | |
| binary_name="${binary_name}.exe" | |
| fi | |
| source_path="native/resource-monitor/target/${{ matrix.rust_target }}/release/${binary_name}" | |
| target_dir="resource-monitor-publish/${{ matrix.resource_key }}" | |
| mkdir -p "$target_dir" | |
| cp "$source_path" "$target_dir/$binary_name" | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: desktop-${{ matrix.platform }}-${{ matrix.arch }} | |
| path: release-publish/* | |
| if-no-files-found: error | |
| - name: Upload resource monitor | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: resource-monitor-${{ matrix.resource_key }} | |
| path: resource-monitor-publish/${{ matrix.resource_key }}/* | |
| if-no-files-found: error | |
| publish_cli: | |
| name: Publish CLI to npm | |
| needs: [preflight, relay_public_config, quality, build] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.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 }} | |
| 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/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 }}" | |
| # The t3 build task depends on @t3tools/web#build, so the web client is | |
| # built (once) as part of this step. | |
| - name: Build CLI package | |
| run: vp run --filter t3 build | |
| - name: Download resource monitors | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: resource-monitor-* | |
| path: ${{ runner.temp }}/resource-monitors | |
| - name: Bundle resource monitors into CLI package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for artifact_dir in "$RUNNER_TEMP"/resource-monitors/resource-monitor-*; do | |
| resource_key="${artifact_dir##*/resource-monitor-}" | |
| target_dir="apps/server/dist/resource-monitor/${resource_key}" | |
| mkdir -p "$target_dir" | |
| cp "$artifact_dir"/t3-resource-monitor* "$target_dir/" | |
| chmod +x "$target_dir"/t3-resource-monitor 2>/dev/null || true | |
| done | |
| - 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: 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: 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: 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: 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: ${{ 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: 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: ${{ github.token }} | |
| publish_aur: | |
| name: Publish AUR package | |
| needs: [preflight, release] | |
| if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' }} | |
| 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' }} | |
| 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.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 }}" |