From ec84c8d3afaee11d97353886f22f8d13906a9521 Mon Sep 17 00:00:00 2001 From: elkaix Date: Tue, 25 Aug 2026 17:45:11 -0400 Subject: [PATCH 1/2] fix(desktop): switching back to the stable update channel no longer errors electron-updater rejects assigning null to channel once a channel has been set, so choosing stable after beta or nightly failed with "Channel must be a string, but got: null". Name the default channel explicitly. The updater mock now enforces the same setter rules. --- apps/desktop/src/updater.ts | 8 +++++++- apps/desktop/tests/updater.spec.ts | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/updater.ts b/apps/desktop/src/updater.ts index a71157c8f..eca04807d 100644 --- a/apps/desktop/src/updater.ts +++ b/apps/desktop/src/updater.ts @@ -8,6 +8,12 @@ import { gt, valid } from 'semver' const { autoUpdater } = electronUpdater const UPDATE_SETTINGS_FILE = 'update-settings.json' const UPDATES_UNAVAILABLE_MESSAGE = 'Updates are not available for this build' +/** + * electron-updater's default channel. Its `channel` setter rejects `null` once a + * channel has been set, so switching back to stable must name the channel + * explicitly instead of clearing it. + */ +const STABLE_UPDATER_CHANNEL = 'latest' const INITIAL_CHECK_DELAY_MS = 10_000 const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1_000 const RELEASE_REPOSITORY_PATH = '/PyModel/pythinker-desktop-releases/releases/tag/' @@ -258,7 +264,7 @@ function hasUpdateConfig(): boolean { function configureExplicitConsent(): void { autoUpdater.autoDownload = false autoUpdater.autoInstallOnAppQuit = false - autoUpdater.channel = settings.channel === 'stable' ? null : settings.channel + autoUpdater.channel = settings.channel === 'stable' ? STABLE_UPDATER_CHANNEL : settings.channel autoUpdater.allowPrerelease = settings.channel !== 'stable' autoUpdater.allowDowngrade = false } diff --git a/apps/desktop/tests/updater.spec.ts b/apps/desktop/tests/updater.spec.ts index 146ad0e17..8a0170b45 100644 --- a/apps/desktop/tests/updater.spec.ts +++ b/apps/desktop/tests/updater.spec.ts @@ -15,6 +15,17 @@ vi.mock('electron', () => ({ vi.mock('electron-updater', () => ({ default: { autoUpdater: { + _channel: null as string | null, + get channel(): string | null { + return this._channel + }, + set channel(value: string | null) { + if (this._channel != null) { + if (typeof value !== 'string') throw new Error(`Channel must be a string, but got: ${String(value)}`) + if (value.length === 0) throw new Error('Channel must be not an empty string') + } + this._channel = value + }, on: vi.fn(), checkForUpdates: vi.fn(), downloadUpdate: vi.fn(() => Promise.resolve([])), @@ -58,7 +69,7 @@ afterEach(() => { autoUpdater.autoInstallOnAppQuit = undefined as unknown as boolean autoUpdater.allowPrerelease = undefined as unknown as boolean autoUpdater.allowDowngrade = undefined as unknown as boolean - autoUpdater.channel = null + ;(autoUpdater as unknown as { _channel: string | null })._channel = null }) function temporaryDirectory(): string { @@ -225,7 +236,7 @@ describe('strict update consent', () => { expect(setLocalUpdateChannel('nightly')).toMatchObject({ channel: 'nightly', status: 'idle' }) expect(setLocalNotifyUpdate(false)).toMatchObject({ notifyUpdate: false }) expect(setLocalUpdateChannel('stable')).toMatchObject({ channel: 'stable', status: 'idle' }) - expect(localAutoUpdater.channel).toBeNull() + expect(localAutoUpdater.channel).toBe('latest') expect(localAutoUpdater.allowPrerelease).toBe(false) expect(localAutoUpdater.allowDowngrade).toBe(false) expect(localAutoUpdater.autoDownload).toBe(false) From d70ab646cb1a8e35b0f25713ebded74027ff5655 Mon Sep 17 00:00:00 2001 From: elkaix Date: Tue, 25 Aug 2026 17:50:49 -0400 Subject: [PATCH 2/2] fix(desktop): use strict null check in updater test mock --- apps/desktop/tests/updater.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/tests/updater.spec.ts b/apps/desktop/tests/updater.spec.ts index 8a0170b45..679521a9f 100644 --- a/apps/desktop/tests/updater.spec.ts +++ b/apps/desktop/tests/updater.spec.ts @@ -20,7 +20,7 @@ vi.mock('electron-updater', () => ({ return this._channel }, set channel(value: string | null) { - if (this._channel != null) { + if (this._channel !== null) { if (typeof value !== 'string') throw new Error(`Channel must be a string, but got: ${String(value)}`) if (value.length === 0) throw new Error('Channel must be not an empty string') }