Skip to content

Main Artifact Release #75

Main Artifact Release

Main Artifact Release #75

name: Main Artifact Release
on:
# Manual, plus a nightly build at ~23:17 UTC. Landing a commit on main no longer
# auto-publishes a release. (Cron is UTC; adjust for local time / DST.)
# Minute offset (17, not 0) avoids GitHub's top-of-hour scheduler congestion,
# where minute-zero runs are frequently delayed or dropped.
workflow_dispatch:
schedule:
- cron: "17 23 * * *"
permissions:
contents: write
jobs:
metadata:
name: Resolve release metadata
runs-on: ubuntu-24.04
# Releases are built only from main, even though the trigger is manual:
# workflow_dispatch can target any ref, so gate every job on the main branch.
if: ${{ github.ref == 'refs/heads/main' }}
outputs:
release_tag: ${{ steps.nightly.outputs.tag }}
release_name: ${{ steps.nightly.outputs.name }}
release_version: ${{ steps.nightly.outputs.version }}
stable_baseline_tag: ${{ steps.stable_baseline.outputs.latest_tag }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 0
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: true
- id: nightly
name: Resolve nightly prerelease metadata
shell: bash
run: |
set -euo pipefail
nightly_date="$(date -u +%Y%m%d)"
node scripts/resolve-nightly-release.ts \
--date "$nightly_date" \
--run-number "${{ github.run_number }}" \
--sha "$GITHUB_SHA" \
--github-output
- id: stable_baseline
name: Snapshot latest stable tag
shell: bash
run: |
set -euo pipefail
git tag --list 'v*' > "$RUNNER_TEMP/stable-tags.txt"
node scripts/resolve-stable-promotion.ts \
--tags-file "$RUNNER_TEMP/stable-tags.txt" \
--github-output
public_config:
name: Resolve artifact public config
runs-on: ubuntu-24.04
if: ${{ github.ref == 'refs/heads/main' }}
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:
T3CODE_RELAY_URL: ${{ vars.T3CODE_RELAY_URL }}
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:
- id: public_config
name: Resolve build-time cloud config
shell: bash
run: |
set -euo pipefail
relay_url="${T3CODE_RELAY_URL:-}"
if [[ -z "$relay_url" ]]; then
relay_domain="${RELAY_DOMAIN:-}"
if [[ -z "$relay_domain" && -n "${RELAY_API_ZONE_NAME:-}" ]]; then
relay_domain="relay.$RELAY_API_ZONE_NAME"
fi
if [[ -n "$relay_domain" ]]; then
relay_url="https://$relay_domain"
fi
fi
# This fork ships local-only artifacts, so cloud sign-in config is
# optional. Embed it only when the full set is present and the relay
# URL is HTTPS; otherwise warn and emit empty values so desktop
# builds still succeed with cloud features disabled.
clerk_publishable_key=""
clerk_jwt_template=""
clerk_cli_oauth_client_id=""
resolved_relay_url=""
required=(
relay_url
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
echo "::warning::Cloud public config incomplete (${missing[*]}); building local-only artifacts without cloud sign-in."
elif [[ ! "$relay_url" =~ ^https:// ]]; then
echo "::warning::Ignoring non-HTTPS relay_url ($relay_url); building local-only artifacts."
else
clerk_publishable_key="$CLERK_PUBLISHABLE_KEY"
clerk_jwt_template="$CLERK_JWT_TEMPLATE"
clerk_cli_oauth_client_id="$CLERK_CLI_OAUTH_CLIENT_ID"
resolved_relay_url="$relay_url"
fi
{
echo "clerk_publishable_key=$clerk_publishable_key"
echo "clerk_jwt_template=$clerk_jwt_template"
echo "clerk_cli_oauth_client_id=$clerk_cli_oauth_client_id"
echo "relay_url=$resolved_relay_url"
} >> "$GITHUB_OUTPUT"
publish_artifacts:
name: Build and publish artifacts
if: ${{ github.ref == 'refs/heads/main' }}
needs: [metadata, public_config]
uses: ./.github/workflows/reusable-build-release-artifacts.yml
with:
ref: ${{ github.sha }}
release_tag: ${{ needs.metadata.outputs.release_tag }}
release_name: ${{ needs.metadata.outputs.release_name }}
release_version: ${{ needs.metadata.outputs.release_version }}
clerk_publishable_key: ${{ needs.public_config.outputs.clerk_publishable_key }}
clerk_jwt_template: ${{ needs.public_config.outputs.clerk_jwt_template }}
clerk_cli_oauth_client_id: ${{ needs.public_config.outputs.clerk_cli_oauth_client_id }}
relay_url: ${{ needs.public_config.outputs.relay_url }}
# Main artifacts use nightly-channel app versions, so every release must
# be a GitHub prerelease for the nightly updater query to discover it.
prerelease: true
windows_signing: true
secrets: inherit
record_nightly_source:
name: Record verified-nightly source
needs: [metadata, publish_artifacts]
if: ${{ github.ref == 'refs/heads/main' && needs.publish_artifacts.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Write immutable source metadata
env:
NIGHTLY_TAG: ${{ needs.metadata.outputs.release_tag }}
NIGHTLY_VERSION: ${{ needs.metadata.outputs.release_version }}
STABLE_BASELINE_TAG: ${{ needs.metadata.outputs.stable_baseline_tag }}
shell: bash
run: |
set -euo pipefail
jq -n \
--arg commit "$GITHUB_SHA" \
--arg nightlyTag "$NIGHTLY_TAG" \
--arg nightlyVersion "$NIGHTLY_VERSION" \
--arg stableBaselineTag "$STABLE_BASELINE_TAG" \
--argjson sourceRunId "$GITHUB_RUN_ID" \
--arg sourceRunUrl "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
'{
schemaVersion: 1,
commit: $commit,
nightlyTag: $nightlyTag,
nightlyVersion: $nightlyVersion,
stableBaselineTag: $stableBaselineTag,
sourceRunId: $sourceRunId,
sourceRunUrl: $sourceRunUrl
}' > verified-nightly-source.json
- name: Upload verified-nightly source metadata
uses: actions/upload-artifact@v7
with:
name: verified-nightly-source
path: verified-nightly-source.json
if-no-files-found: error
retention-days: 14
prune_nightly:
name: Prune old nightly releases
needs: publish_artifacts
# Only after a successful publish on main; keeps the Releases page from
# accumulating a daily nightly prerelease forever. Stable v* tags without
# nightly prerelease metadata are untouched.
if: ${{ github.ref == 'refs/heads/main' && needs.publish_artifacts.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Keep only the 14 most recent nightly prereleases
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
shell: bash
run: |
set -euo pipefail
pointer_json="$(
gh api "repos/$GH_REPO/contents/client-verified-latest.json?ref=client-verified-latest" \
--jq '.content' | base64 -d
)" || {
echo "::warning::Unable to read client-verified-latest pointer; skipping nightly pruning."
exit 0
}
pinned_release_tag="$(
jq -r '(.desktop.artifacts.release // "") | rtrimstr("/") | split("/")[-1]' \
<<< "$pointer_json"
)" || {
echo "::warning::Unable to parse client-verified-latest pointer; skipping nightly pruning."
exit 0
}
if [[ -z "$pinned_release_tag" || "$pinned_release_tag" == "null" ]]; then
echo "::warning::No verified client release is pinned; skipping nightly pruning."
exit 0
fi
echo "Keeping verified client pointer release $pinned_release_tag"
stale="$(
gh release list --limit 300 --json tagName,isPrerelease,createdAt \
| jq -r --arg keep "$pinned_release_tag" \
'[.[] | select(.isPrerelease and ((.tagName | startswith("main-")) or (.tagName | test("^(?:nightly-)?v[0-9]+\\.[0-9]+\\.[0-9]+-nightly\\.[0-9]{8}\\.[0-9]+$"))) and .tagName != $keep)]
| sort_by(.createdAt) | reverse | .[14:] | .[].tagName'
)"
if [[ -z "$stale" ]]; then
echo "No stale nightly releases to prune."
exit 0
fi
while IFS= read -r tag; do
[[ -n "$tag" ]] || continue
echo "Deleting old nightly release $tag"
gh release delete "$tag" --cleanup-tag --yes || true
done <<< "$stale"