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..679521a9f 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)