Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
33f5299
Add nightly desktop release flow
juliusmarminge Apr 14, 2026
71211b6
Add nightly update channel and release asset routing
juliusmarminge Apr 14, 2026
eb78ad3
Reorganize nightly and dev brand assets
juliusmarminge Apr 14, 2026
e31527d
Remove obsolete blueprint icon asset
juliusmarminge Apr 14, 2026
b3b32bb
Extract mock update server URL resolution logic
juliusmarminge Apr 14, 2026
aa4c156
Run bun install without lockfile-only
juliusmarminge Apr 14, 2026
97f90dd
fix: only allow downgrade when on nightly channel
cursoragent Apr 14, 2026
65753f7
Install deps before release smoke test
juliusmarminge Apr 14, 2026
97efd80
fix: only allow downgrade when on nightly channel
cursor[bot] Apr 14, 2026
31dce76
Publish nightly CLI to npm
juliusmarminge Apr 14, 2026
e4e0c90
Unify stable and nightly release workflows
juliusmarminge Apr 14, 2026
3b8c3a8
Add development macOS icon generation for desktop launcher
juliusmarminge Apr 14, 2026
87dd61f
fixxx
juliusmarminge Apr 14, 2026
9d5eac0
Tighten nightly release and desktop update flow
juliusmarminge Apr 14, 2026
3fafa19
Extract nightly release metadata builder
juliusmarminge Apr 14, 2026
662b86c
Format nightly release resolver invocation
juliusmarminge Apr 14, 2026
79c032a
Normalize icon asset formatting
juliusmarminge Apr 14, 2026
1666b94
Validate mock update server port as an integer
juliusmarminge Apr 14, 2026
5424cfb
Resolve nightly release metadata from desktop package
juliusmarminge Apr 14, 2026
5042785
Include short SHA in nightly release metadata
juliusmarminge Apr 14, 2026
2c825c0
Pin release notes to prior channel tag
juliusmarminge Apr 15, 2026
b623a30
Tighten validation for nightly release inputs
juliusmarminge Apr 15, 2026
a1271cb
Restore auto-updater downgrade flag after channel changes
juliusmarminge Apr 15, 2026
2aa826c
Propagate desktop branding to the web app
juliusmarminge Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions .github/workflows/nightly-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
name: Nightly Desktop Release

on:
schedule:
- cron: "0 9 * * *"
workflow_dispatch:

concurrency:
group: nightly-release
cancel-in-progress: false

permissions:
contents: write

jobs:
preflight:
name: Preflight
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
base_version: ${{ steps.release_meta.outputs.base_version }}
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 }}
ref: ${{ github.sha }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: package.json

- id: release_meta
name: Resolve nightly release version
shell: bash
env:
NIGHTLY_DATE: ${{ github.run_started_at }}
NIGHTLY_SHA: ${{ github.sha }}
NIGHTLY_RUN_NUMBER: ${{ github.run_number }}
run: |
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
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

- name: Install dependencies
run: bun install --frozen-lockfile
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Outdated

- name: Lint
run: bun run lint

- name: Typecheck
run: bun run typecheck

- name: Test
run: bun run test

build:
name: Build ${{ matrix.label }}
needs: preflight
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- label: macOS arm64
runner: macos-14
platform: mac
target: dmg
arch: arm64
- label: macOS x64
runner: macos-15-intel
platform: mac
target: dmg
arch: x64
- label: Linux x64
runner: ubuntu-24.04
platform: linux
target: AppImage
arch: x64
- label: Windows x64
runner: windows-2022
platform: win
target: nsis
arch: x64
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
fetch-depth: 0

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: package.json

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: package.json

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Align package versions to nightly version
run: node scripts/update-release-package-versions.ts "${{ needs.preflight.outputs.version }}"

- name: Build desktop artifact
shell: bash
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
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
key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8"
printf '%s' "$APPLE_API_KEY" > "$key_path"
export APPLE_API_KEY="$key_path"
echo "macOS signing enabled."
args+=(--signed)
else
echo "macOS signing disabled (missing one or more Apple signing secrets)."
fi
elif [[ "${{ matrix.platform }}" == "win" ]]; then
if has_all \
"$AZURE_TENANT_ID" \
"$AZURE_CLIENT_ID" \
"$AZURE_CLIENT_SECRET" \
"$AZURE_TRUSTED_SIGNING_ENDPOINT" \
"$AZURE_TRUSTED_SIGNING_ACCOUNT_NAME" \
"$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" \
"$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME"; then
echo "Windows signing enabled (Azure Trusted Signing)."
args+=(--signed)
else
echo "Windows signing disabled (missing one or more Azure Trusted Signing secrets)."
fi
else
echo "Signing disabled for ${{ matrix.platform }}."
fi

bun 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/latest*.yml"; do
for file in $pattern; do
cp "$file" release-publish/
done
done

if [[ "${{ matrix.platform }}" == "mac" && "${{ matrix.arch }}" != "arm64" ]]; then
if [[ -f release-publish/latest-mac.yml ]]; then
mv release-publish/latest-mac.yml "release-publish/latest-mac-${{ matrix.arch }}.yml"
fi
fi

- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: desktop-${{ matrix.platform }}-${{ matrix.arch }}
path: release-publish/*
if-no-files-found: error

release:
name: Publish GitHub Release
needs: [preflight, build]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version-file: package.json

- 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: |
node scripts/merge-mac-update-manifests.ts \
release-assets/latest-mac.yml \
release-assets/latest-mac-x64.yml
rm -f release-assets/latest-mac-x64.yml

- name: Publish nightly prerelease
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.preflight.outputs.tag }}
target_commitish: ${{ needs.preflight.outputs.ref }}
name: ${{ needs.preflight.outputs.release_name }}
generate_release_notes: true
prerelease: true
make_latest: false
files: |
release-assets/*.dmg
release-assets/*.zip
release-assets/*.AppImage
release-assets/*.exe
release-assets/*.blockmap
release-assets/latest*.yml
fail_on_unmatched_files: true
15 changes: 15 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ This document covers how to run desktop releases from one tag, first without sig
- Publishes the CLI package (`apps/server`, npm package `t3`) with OIDC trusted publishing.
- Signing is optional and auto-detected per platform from secrets.

## Nightly builds

- Workflow: `.github/workflows/nightly-release.yml`
- Triggers:
- scheduled every day at `09:00 UTC`
- manual `workflow_dispatch`
- Runs the same desktop quality gates and artifact matrix as the tagged release flow.
- Publishes a GitHub prerelease only:
- tag format: `nightly-vX.Y.Z-nightly.YYYYMMDD.<run_number>`
- release name includes the short commit SHA
- `make_latest` is always `false`
- Uses the current `apps/desktop/package.json` semver core (`X.Y.Z`) as the nightly base, then appends a nightly prerelease suffix.
- Does not publish the CLI package to npm.
- Does not commit version bumps back to `main`.

## Desktop auto-update notes

- Runtime updater: `electron-updater` in `apps/desktop/src/main.ts`.
Expand Down
29 changes: 29 additions & 0 deletions scripts/release-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,35 @@ try {
"Expected bun.lock to contain the smoke version.",
);

const nightlyReleaseMetadata = execFileSync(
process.execPath,
[
resolve(repoRoot, "scripts/resolve-nightly-release.ts"),
"--date",
"20260413",
"--run-number",
"321",
"--sha",
"abcdef1234567890",
"--root",
tempRoot,
],
{
cwd: repoRoot,
encoding: "utf8",
},
);
assertContains(
nightlyReleaseMetadata,
"version=9.9.9-nightly.20260413.321",
"Expected nightly metadata to contain the derived nightly version.",
);
assertContains(
nightlyReleaseMetadata,
"tag=nightly-v9.9.9-nightly.20260413.321",
"Expected nightly metadata to contain the derived nightly tag.",
);
Comment thread
cursor[bot] marked this conversation as resolved.

const { arm64Path, x64Path } = writeMacManifestFixtures(tempRoot);
execFileSync(
process.execPath,
Expand Down
37 changes: 37 additions & 0 deletions scripts/resolve-nightly-release.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { fileURLToPath } from "node:url";

import { describe, expect, it } from "vitest";

import { resolveNightlyReleaseMetadata } from "./resolve-nightly-release";

const repoRoot = fileURLToPath(new URL("..", import.meta.url));

describe("resolveNightlyReleaseMetadata", () => {
it("derives a nightly prerelease version and tag from the desktop package version", () => {
const metadata = resolveNightlyReleaseMetadata({
rootDir: repoRoot,
date: "20260413",
runNumber: "42",
sha: "abcdef1234567890",
});

expect(metadata).toEqual({
baseVersion: "0.0.17",
version: "0.0.17-nightly.20260413.42",
tag: "nightly-v0.0.17-nightly.20260413.42",
name: "T3 Code Nightly 0.0.17-nightly.20260413.42 (abcdef123456)",
shortSha: "abcdef123456",
});
});

it("rejects invalid nightly metadata inputs", () => {
expect(() =>
resolveNightlyReleaseMetadata({
rootDir: repoRoot,
date: "2026-04-13",
runNumber: "0",
sha: "bad-sha",
}),
).toThrow(/Invalid nightly release date/);
});
});
Loading
Loading