History

Respect installed version update channel

- Derive desktop updater channel from the running app version - Keep stable builds on latest and prereleases on their own track - Add tests and release docs for channel matching

Julius Marminge committed 7 months ago

Commit b3291cf1 parent c9b07d64 files · +91 −5Browse files

Unified · Split

4 files
  1. apps/desktop/src/main.ts +22 −5
  2. apps/desktop/src/updateChannel.test.ts +40 −0
  3. apps/desktop/src/updateChannel.ts +26 −0
  4. docs/release.md +3 −0
apps/desktop/src/main.ts+22−5
@@ -54,6 +54,11 @@ import { isBackendReadinessAborted, waitForHttpReady } from "./backendReadiness" · 53 unmodified lines
54import { showDesktopConfirmDialog } from "./confirmDialog";54import { showDesktopConfirmDialog } from "./confirmDialog";
55import { resolveDesktopServerExposure } from "./serverExposure";55import { resolveDesktopServerExposure } from "./serverExposure";
56import { syncShellEnvironment } from "./syncShellEnvironment";56import { syncShellEnvironment } from "./syncShellEnvironment";
57import {
58 resolveDesktopUpdateChannel,
59 shouldAcceptDesktopUpdateForCurrentVersion,
60 shouldAllowDesktopPrereleaseUpdates,
61} from "./updateChannel";
57import { getAutoUpdateDisabledReason, shouldBroadcastDownloadProgress } from "./updateState";62import { getAutoUpdateDisabledReason, shouldBroadcastDownloadProgress } from "./updateState";
58import { ServerListeningDetector } from "./serverListeningDetector";63import { ServerListeningDetector } from "./serverListeningDetector";
59import {64import {
@@ -116,8 +121,6 @@ const APP_RUN_ID = Crypto.randomBytes(6).toString("hex"); · 56 unmodified lines
116const SERVER_SETTINGS_PATH = Path.join(STATE_DIR, "settings.json");121const SERVER_SETTINGS_PATH = Path.join(STATE_DIR, "settings.json");
117const AUTO_UPDATE_STARTUP_DELAY_MS = 15_000;122const AUTO_UPDATE_STARTUP_DELAY_MS = 15_000;
118const AUTO_UPDATE_POLL_INTERVAL_MS = 4 * 60 * 60 * 1000;123const AUTO_UPDATE_POLL_INTERVAL_MS = 4 * 60 * 60 * 1000;
119const DESKTOP_UPDATE_CHANNEL = "latest";
120const DESKTOP_UPDATE_ALLOW_PRERELEASE = false;
121const DESKTOP_LOOPBACK_HOST = "127.0.0.1";124const DESKTOP_LOOPBACK_HOST = "127.0.0.1";
122const DESKTOP_REQUIRED_PORT_PROBE_HOSTS = ["0.0.0.0", "::"] as const;125const DESKTOP_REQUIRED_PORT_PROBE_HOSTS = ["0.0.0.0", "::"] as const;
123const TITLEBAR_HEIGHT = 40;126const TITLEBAR_HEIGHT = 40;
@@ -1168,6 +1171,9 @@ async function installDownloadedUpdate(): Promise<{ accepted: boolean; completed · 1044 unmodified lines
1168}1171}
1169 1172
1170function configureAutoUpdater(): void {1173function configureAutoUpdater(): void {
1174 const currentVersion = app.getVersion();
1175 const updateChannel = resolveDesktopUpdateChannel(currentVersion);
1176 const allowPrerelease = shouldAllowDesktopPrereleaseUpdates(currentVersion);
1171 const githubToken =1177 const githubToken =
1172 process.env.T3CODE_DESKTOP_UPDATE_GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || "";1178 process.env.T3CODE_DESKTOP_UPDATE_GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || "";
1173 if (githubToken) {1179 if (githubToken) {
@@ -1205,12 +1211,14 @@ function configureAutoUpdater(): void { · 31 unmodified lines
1205 1211
1206 autoUpdater.autoDownload = false;1212 autoUpdater.autoDownload = false;
1207 autoUpdater.autoInstallOnAppQuit = false;1213 autoUpdater.autoInstallOnAppQuit = false;
1208 // Keep alpha branding, but force all installs onto the stable update track.1214 autoUpdater.channel = updateChannel;
1209 autoUpdater.channel = DESKTOP_UPDATE_CHANNEL;1215 autoUpdater.allowPrerelease = allowPrerelease;
1210 autoUpdater.allowPrerelease = DESKTOP_UPDATE_ALLOW_PRERELEASE;
1211 autoUpdater.allowDowngrade = false;1216 autoUpdater.allowDowngrade = false;
1212 autoUpdater.disableDifferentialDownload = isArm64HostRunningIntelBuild(desktopRuntimeInfo);1217 autoUpdater.disableDifferentialDownload = isArm64HostRunningIntelBuild(desktopRuntimeInfo);
1213 let lastLoggedDownloadMilestone = -1;1218 let lastLoggedDownloadMilestone = -1;
1219 console.info(
1220 `[desktop-updater] Configured update channel=${updateChannel} currentVersion=${currentVersion} allowPrerelease=${allowPrerelease}.`,
1221 );
1214 1222
1215 if (isArm64HostRunningIntelBuild(desktopRuntimeInfo)) {1223 if (isArm64HostRunningIntelBuild(desktopRuntimeInfo)) {
1216 console.info(1224 console.info(
@@ -1222,6 +1230,15 @@ function configureAutoUpdater(): void { · 5 unmodified lines
1222 console.info("[desktop-updater] Looking for updates...");1230 console.info("[desktop-updater] Looking for updates...");
1223 });1231 });
1224 autoUpdater.on("update-available", (info) => {1232 autoUpdater.on("update-available", (info) => {
1233 if (!shouldAcceptDesktopUpdateForCurrentVersion(currentVersion, info.version)) {
1234 setUpdateState(reduceDesktopUpdateStateOnNoUpdate(updateState, new Date().toISOString()));
1235 lastLoggedDownloadMilestone = -1;
1236 console.info(
1237 `[desktop-updater] Ignoring update ${info.version} on channel=${resolveDesktopUpdateChannel(info.version)} while currentVersion=${currentVersion} is on channel=${updateChannel}.`,
1238 );
1239 return;
1240 }
1241
1225 setUpdateState(1242 setUpdateState(
1226 reduceDesktopUpdateStateOnUpdateAvailable(1243 reduceDesktopUpdateStateOnUpdateAvailable(
1227 updateState,1244 updateState,
apps/desktop/src/updateChannel.test.ts+40−0
@@ -0,0 +1,40 @@
1import { describe, expect, it } from "vitest";
2
3import {
4 resolveDesktopUpdateChannel,
5 shouldAcceptDesktopUpdateForCurrentVersion,
6 shouldAllowDesktopPrereleaseUpdates,
7} from "./updateChannel";
8
9describe("updateChannel", () => {
10 it("uses the stable latest channel for plain versions", () => {
11 expect(resolveDesktopUpdateChannel("0.0.17")).toBe("latest");
12 expect(shouldAllowDesktopPrereleaseUpdates("0.0.17")).toBe(false);
13 });
14
15 it("derives the prerelease channel from the installed version", () => {
16 expect(resolveDesktopUpdateChannel("0.0.18-nightly.20260415")).toBe("nightly");
17 expect(shouldAllowDesktopPrereleaseUpdates("0.0.18-nightly.20260415")).toBe(true);
18 });
19
20 it("accepts updates that stay on the same prerelease track", () => {
21 expect(
22 shouldAcceptDesktopUpdateForCurrentVersion(
23 "0.0.18-nightly.20260415",
24 "0.0.18-nightly.20260416",
25 ),
26 ).toBe(true);
27 });
28
29 it("rejects stable releases while running a nightly build", () => {
30 expect(shouldAcceptDesktopUpdateForCurrentVersion("0.0.18-nightly.20260415", "0.0.17")).toBe(
31 false,
32 );
33 });
34
35 it("rejects updates from a different prerelease track", () => {
36 expect(
37 shouldAcceptDesktopUpdateForCurrentVersion("0.0.18-nightly.20260415", "0.0.18-beta.1"),
38 ).toBe(false);
39 });
40});
apps/desktop/src/updateChannel.ts+26−0
@@ -0,0 +1,26 @@
1const VERSION_PRERELEASE_CHANNEL_PATTERN =
2 /^\d+\.\d+\.\d+(?:-([0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z.-]+)?$/;
3
4function resolvePrereleaseChannel(version: string): string | null {
5 const normalizedVersion = version.trim();
6 const match = VERSION_PRERELEASE_CHANNEL_PATTERN.exec(normalizedVersion);
7 const channel = match?.[1]?.trim().toLowerCase();
8 return channel && channel.length > 0 ? channel : null;
9}
10
11export function resolveDesktopUpdateChannel(version: string): string {
12 return resolvePrereleaseChannel(version) ?? "latest";
13}
14
15export function shouldAllowDesktopPrereleaseUpdates(version: string): boolean {
16 return resolvePrereleaseChannel(version) !== null;
17}
18
19export function shouldAcceptDesktopUpdateForCurrentVersion(
20 currentVersion: string,
21 candidateVersion: string,
22): boolean {
23 return (
24 resolveDesktopUpdateChannel(currentVersion) === resolveDesktopUpdateChannel(candidateVersion)
25 );
26}
docs/release.md+3−0
@@ -25,6 +25,9 @@ This document covers how to run desktop releases from one tag, first without sig · 24 unmodified lines
25 - Background checks run on startup delay + interval.25 - Background checks run on startup delay + interval.
26 - No automatic download or install.26 - No automatic download or install.
27 - The desktop UI shows a rocket update button when an update is available; click once to download, click again after download to restart/install.27 - The desktop UI shows a rocket update button when an update is available; click once to download, click again after download to restart/install.
28- Update channel selection:
29 - Stable builds (`X.Y.Z`) only accept stable updates.
30 - Prerelease builds (for example `X.Y.Z-nightly.N`) stay on that prerelease track and ignore stable or differently-tagged prerelease releases.
28- Provider: GitHub Releases (`provider: github`) configured at build time.31- Provider: GitHub Releases (`provider: github`) configured at build time.
29- Repository slug source:32- Repository slug source:
30 - `T3CODE_DESKTOP_UPDATE_REPOSITORY` (format `owner/repo`), if set.33 - `T3CODE_DESKTOP_UPDATE_REPOSITORY` (format `owner/repo`), if set.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t