Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,8 @@ jobs:
with:
node-version-file: package.json

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

- name: Exercise release-only workflow steps
run: node scripts/release-smoke.ts
105 changes: 76 additions & 29 deletions .github/workflows/release.yml
Comment thread
juliusmarminge marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
name: Release Desktop
name: Release

on:
push:
tags:
- "v*.*.*"
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
inputs:
channel:
description: "Release channel"
required: false
default: stable
type: choice
options:
- stable
- nightly
version:
description: "Release version (for example 1.2.3 or v1.2.3)"
required: true
required: false
type: string

permissions:
Expand All @@ -21,8 +31,11 @@ jobs:
runs-on: ubuntu-24.04
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 }}
cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }}
is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }}
make_latest: ${{ steps.release_meta.outputs.make_latest }}
ref: ${{ github.sha }}
Expand All @@ -33,27 +46,55 @@ jobs:
- id: release_meta
name: Resolve release version
shell: bash
env:
DISPATCH_CHANNEL: ${{ github.event.inputs.channel }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
NIGHTLY_DATE: ${{ github.run_started_at }}
NIGHTLY_SHA: ${{ github.sha }}
NIGHTLY_RUN_NUMBER: ${{ github.run_number }}
run: |
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
raw="${{ github.event.inputs.version }}"
else
raw="${GITHUB_REF_NAME}"
fi
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)"

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
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.

echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$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 "release_channel=nightly" >> "$GITHUB_OUTPUT"
echo "cli_dist_tag=nightly" >> "$GITHUB_OUTPUT"
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "make_latest=false" >> "$GITHUB_OUTPUT"
else
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
raw="${DISPATCH_VERSION}"
if [[ -z "$raw" ]]; then
echo "workflow_dispatch stable releases require the version input." >&2
exit 1
fi
else
raw="${GITHUB_REF_NAME}"
fi

version="${raw#v}"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
echo "Invalid release version: $raw" >&2
exit 1
fi

echo "release_channel=stable" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "name=T3 Code v$version" >> "$GITHUB_OUTPUT"
echo "cli_dist_tag=latest" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
echo "make_latest=true" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
echo "make_latest=false" >> "$GITHUB_OUTPUT"
fi
fi

- name: Setup Bun
Expand Down Expand Up @@ -206,16 +247,17 @@ jobs:
"release/*.AppImage" \
"release/*.exe" \
"release/*.blockmap" \
"release/latest*.yml"; do
"release/*.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
shopt -s nullglob
for manifest in release-publish/*-mac.yml; do
mv "$manifest" "${manifest%.yml}-${{ matrix.arch }}.yml"
done
fi

- name: Upload build artifacts
Expand Down Expand Up @@ -257,7 +299,7 @@ jobs:
run: bun run build --filter=@t3tools/web --filter=t3

- name: Publish CLI package
run: node apps/server/scripts/cli.ts publish --tag latest --app-version "${{ needs.preflight.outputs.version }}" --verbose
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
Expand All @@ -284,17 +326,21 @@ jobs:

- 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
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-mac-update-manifests.ts "$arm64_manifest" "$x64_manifest"
rm -f "$x64_manifest"
fi
done

- name: Publish release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.preflight.outputs.tag }}
target_commitish: ${{ needs.preflight.outputs.ref }}
name: T3 Code v${{ needs.preflight.outputs.version }}
name: ${{ needs.preflight.outputs.release_name }}
generate_release_notes: true
prerelease: ${{ needs.preflight.outputs.is_prerelease }}
make_latest: ${{ needs.preflight.outputs.make_latest }}
Expand All @@ -304,11 +350,12 @@ jobs:
release-assets/*.AppImage
release-assets/*.exe
release-assets/*.blockmap
release-assets/latest*.yml
release-assets/*.yml
fail_on_unmatched_files: true

finalize:
name: Finalize release
if: needs.preflight.outputs.release_channel == 'stable'
needs: [preflight, release]
runs-on: ubuntu-24.04
timeout-minutes: 10
Expand Down
20 changes: 20 additions & 0 deletions apps/desktop/src/desktopSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DEFAULT_DESKTOP_SETTINGS,
readDesktopSettings,
setDesktopServerExposurePreference,
setDesktopUpdateChannelPreference,
writeDesktopSettings,
} from "./desktopSettings";

Expand Down Expand Up @@ -35,10 +36,12 @@ describe("desktopSettings", () => {

writeDesktopSettings(settingsPath, {
serverExposureMode: "network-accessible",
updateChannel: "latest",
});

expect(readDesktopSettings(settingsPath)).toEqual({
serverExposureMode: "network-accessible",
updateChannel: "latest",
});
});

Expand All @@ -47,11 +50,28 @@ describe("desktopSettings", () => {
setDesktopServerExposurePreference(
{
serverExposureMode: "local-only",
updateChannel: "latest",
},
"network-accessible",
),
).toEqual({
serverExposureMode: "network-accessible",
updateChannel: "latest",
});
});

it("persists the requested nightly update channel", () => {
expect(
setDesktopUpdateChannelPreference(
{
serverExposureMode: "local-only",
updateChannel: "latest",
},
"nightly",
),
).toEqual({
serverExposureMode: "local-only",
updateChannel: "nightly",
});
});

Expand Down
18 changes: 17 additions & 1 deletion apps/desktop/src/desktopSettings.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as FS from "node:fs";
import * as Path from "node:path";
import type { DesktopServerExposureMode } from "@t3tools/contracts";
import type { DesktopServerExposureMode, DesktopUpdateChannel } from "@t3tools/contracts";

export interface DesktopSettings {
readonly serverExposureMode: DesktopServerExposureMode;
readonly updateChannel: DesktopUpdateChannel;
}

export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
serverExposureMode: "local-only",
updateChannel: "latest",
};

export function setDesktopServerExposurePreference(
Expand All @@ -22,6 +24,18 @@ export function setDesktopServerExposurePreference(
};
}

export function setDesktopUpdateChannelPreference(
settings: DesktopSettings,
requestedChannel: DesktopUpdateChannel,
): DesktopSettings {
return settings.updateChannel === requestedChannel
? settings
: {
...settings,
updateChannel: requestedChannel,
};
}

export function readDesktopSettings(settingsPath: string): DesktopSettings {
try {
if (!FS.existsSync(settingsPath)) {
Expand All @@ -31,11 +45,13 @@ export function readDesktopSettings(settingsPath: string): DesktopSettings {
const raw = FS.readFileSync(settingsPath, "utf8");
const parsed = JSON.parse(raw) as {
readonly serverExposureMode?: unknown;
readonly updateChannel?: unknown;
};

return {
serverExposureMode:
parsed.serverExposureMode === "network-accessible" ? "network-accessible" : "local-only",
updateChannel: parsed.updateChannel === "nightly" ? "nightly" : "latest",
};
} catch {
return DEFAULT_DESKTOP_SETTINGS;
Expand Down
Loading
Loading