mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
feat(desktop): add FLUXER_DISABLE_MODULE_UPDATES opt-out (#3466)
This commit is contained in:
1 parent
e8dff6a8a1
commit
e403fb6cce
11 files changed
+302
-77
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const {execFile} = require('node:child_process');
|
||||
const {readFileSync} = require('node:fs');
|
||||
const fs = require('node:fs/promises');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
@@ -102,6 +103,7 @@ const isMacBuild = process.argv.includes('--mac');
|
||||
const isWindowsBuild = process.argv.includes('--win');
|
||||
const targetPlatform = isLinuxBuild ? 'linux' : isMacBuild ? 'darwin' : isWindowsBuild ? 'win32' : process.platform;
|
||||
const metadataName = isLinuxBuild ? linuxPackageName : packageName;
|
||||
const shellVersion = process.env.VERSION || readBundledRendererVersion();
|
||||
const supportedTargetArchs = ['x64', 'arm64'];
|
||||
const supportedMacTargetArchs = [...supportedTargetArchs, 'universal'];
|
||||
const electronArch = process.env.ELECTRON_ARCH;
|
||||
@@ -644,6 +646,15 @@ function packagePathParts(packageName) {
|
||||
return [packageName];
|
||||
}
|
||||
|
||||
function readBundledRendererVersion() {
|
||||
try {
|
||||
const {version} = JSON.parse(readFileSync(path.join(__dirname, 'dist', 'renderer', 'version.json'), 'utf8'));
|
||||
return typeof version === 'string' && /^\d+\.\d+\.\d+$/u.test(version) ? version : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isNonEmptyString(value) {
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
}
|
||||
@@ -1623,7 +1634,7 @@ module.exports = {
|
||||
extraMetadata: {
|
||||
main: 'dist/main/index.js',
|
||||
name: metadataName,
|
||||
...(process.env.VERSION ? {version: process.env.VERSION} : {}),
|
||||
...(shellVersion ? {version: shellVersion} : {}),
|
||||
...(targetPlatform === 'linux' ? {desktopName: `${linuxDesktopId}.desktop`} : {}),
|
||||
},
|
||||
extraResources: [
|
||||
|
||||
@@ -39,18 +39,20 @@ const isProduction =
|
||||
process.env.FLUXER_DESKTOP_PRODUCTION === 'true' ||
|
||||
process.env.GITHUB_ACTIONS === 'true';
|
||||
const skipNative = process.env.FLUXER_SKIP_NATIVE === 'true';
|
||||
const embeddedBuildVersion = process.env.PUBLIC_BUILD_VERSION || process.env.BUILD_VERSION || '';
|
||||
let embeddedBuildVersion = process.env.PUBLIC_BUILD_VERSION || process.env.BUILD_VERSION || '';
|
||||
const embeddedReleaseChannel = process.env.PUBLIC_RELEASE_CHANNEL || process.env.RELEASE_CHANNEL || '';
|
||||
const modulesEnabled = process.env.FLUXER_MODULES === '1' || process.env.FLUXER_MODULES === 'true';
|
||||
const offlineBuild = process.env.FLUXER_OFFLINE === '1' || process.env.FLUXER_OFFLINE === 'true';
|
||||
const publicBuildDefines = {
|
||||
'process.env.PUBLIC_BUILD_VERSION': JSON.stringify(embeddedBuildVersion),
|
||||
'process.env.BUILD_VERSION': JSON.stringify(embeddedBuildVersion),
|
||||
'process.env.PUBLIC_RELEASE_CHANNEL': JSON.stringify(embeddedReleaseChannel),
|
||||
'process.env.RELEASE_CHANNEL': JSON.stringify(embeddedReleaseChannel),
|
||||
'process.env.FLUXER_MODULES': JSON.stringify(modulesEnabled ? '1' : ''),
|
||||
'process.env.FLUXER_OFFLINE': JSON.stringify(offlineBuild ? '1' : ''),
|
||||
};
|
||||
function publicBuildDefines() {
|
||||
return {
|
||||
'process.env.PUBLIC_BUILD_VERSION': JSON.stringify(embeddedBuildVersion),
|
||||
'process.env.BUILD_VERSION': JSON.stringify(embeddedBuildVersion),
|
||||
'process.env.PUBLIC_RELEASE_CHANNEL': JSON.stringify(embeddedReleaseChannel),
|
||||
'process.env.RELEASE_CHANNEL': JSON.stringify(embeddedReleaseChannel),
|
||||
'process.env.FLUXER_MODULES': JSON.stringify(modulesEnabled ? '1' : ''),
|
||||
'process.env.FLUXER_OFFLINE': JSON.stringify(offlineBuild ? '1' : ''),
|
||||
};
|
||||
}
|
||||
const electronExternals = [
|
||||
'electron',
|
||||
'electron-log',
|
||||
@@ -116,6 +118,16 @@ function readBundledRendererVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
function fallBackToBundledRendererVersion() {
|
||||
if (embeddedBuildVersion !== '') {
|
||||
return;
|
||||
}
|
||||
const bundledVersion = readBundledRendererVersion();
|
||||
if (BUNDLED_RENDERER_VERSION_PATTERN.test(bundledVersion)) {
|
||||
embeddedBuildVersion = bundledVersion;
|
||||
}
|
||||
}
|
||||
|
||||
class RendererServiceWorkerPresentError extends Error {
|
||||
constructor(present) {
|
||||
super(
|
||||
@@ -674,7 +686,7 @@ async function buildMain() {
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
|
||||
[MAIN_APP_OUTPUT_FILE_DEFINE]: JSON.stringify(MAIN_APP_OUTPUT_FILE_NAME),
|
||||
...publicBuildDefines,
|
||||
...publicBuildDefines(),
|
||||
},
|
||||
banner: {
|
||||
js: `import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);`,
|
||||
@@ -806,7 +818,7 @@ function preloadBuildOptions(entryFileName, outputFileName) {
|
||||
tsconfig: DESKTOP_TSCONFIG,
|
||||
define: {
|
||||
'process.env.NODE_ENV': JSON.stringify(isProduction ? 'production' : 'development'),
|
||||
...publicBuildDefines,
|
||||
...publicBuildDefines(),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -887,6 +899,7 @@ async function build() {
|
||||
if (!options.useSharedRenderer) {
|
||||
copyRendererIntoDist();
|
||||
}
|
||||
fallBackToBundledRendererVersion();
|
||||
if (modulesEnabled) {
|
||||
prunePackedRendererModules();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import {createRequire} from 'node:module';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {after, describe, test} from 'node:test';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const DESKTOP_DIR = path.resolve(import.meta.dirname, '..', '..');
|
||||
const PREFLIGHT_MODULES = path.join('src', 'main', 'NativeModulePreflightModules.json');
|
||||
const temporaryRoots = [];
|
||||
|
||||
after(() => {
|
||||
for (const root of temporaryRoots) {
|
||||
fs.rmSync(root, {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
|
||||
function loadMetadataVersion({version, rendererVersionJson}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'builder-shell-version-'));
|
||||
temporaryRoots.push(root);
|
||||
fs.copyFileSync(
|
||||
path.join(DESKTOP_DIR, 'electron-builder.config.cjs'),
|
||||
path.join(root, 'electron-builder.config.cjs'),
|
||||
);
|
||||
fs.mkdirSync(path.join(root, 'src', 'main'), {recursive: true});
|
||||
fs.copyFileSync(path.join(DESKTOP_DIR, PREFLIGHT_MODULES), path.join(root, PREFLIGHT_MODULES));
|
||||
if (rendererVersionJson != null) {
|
||||
fs.mkdirSync(path.join(root, 'dist', 'renderer'), {recursive: true});
|
||||
fs.writeFileSync(path.join(root, 'dist', 'renderer', 'version.json'), rendererVersionJson);
|
||||
}
|
||||
const previous = process.env.VERSION;
|
||||
if (version == null) {
|
||||
delete process.env.VERSION;
|
||||
} else {
|
||||
process.env.VERSION = version;
|
||||
}
|
||||
try {
|
||||
return require(path.join(root, 'electron-builder.config.cjs')).extraMetadata.version;
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.VERSION;
|
||||
} else {
|
||||
process.env.VERSION = previous;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('the packaged shell version', () => {
|
||||
test('comes from VERSION whenever it is set', () => {
|
||||
assert.equal(
|
||||
loadMetadataVersion({version: '2026.1014.2', rendererVersionJson: '{"version":"2026.1014.9"}'}),
|
||||
'2026.1014.2',
|
||||
);
|
||||
});
|
||||
|
||||
test('falls back to the bundled renderer version when VERSION is unset', () => {
|
||||
assert.equal(loadMetadataVersion({rendererVersionJson: '{"version":"2026.1014.9"}'}), '2026.1014.9');
|
||||
});
|
||||
|
||||
test('keeps the package.json version when the renderer has no numeric version', () => {
|
||||
assert.equal(loadMetadataVersion({rendererVersionJson: '{"version":"dev"}'}), undefined);
|
||||
assert.equal(loadMetadataVersion({rendererVersionJson: 'null'}), undefined);
|
||||
assert.equal(loadMetadataVersion({}), undefined);
|
||||
});
|
||||
});
|
||||
@@ -159,7 +159,7 @@ async function runModuleBootstrap(): Promise<void> {
|
||||
const {configureUserDataPath} = await import('@electron/common/UserDataPath');
|
||||
const {armNativeProbeCache} = await import('@electron/main/NativeProbeCache');
|
||||
const {isStartMinimizedLaunch} = await import('@electron/main/AutostartLaunch');
|
||||
const {isDesktopUpdateRequested} = await import('@electron/main/LaunchOptions');
|
||||
const {areModuleUpdatesDisabled, isDesktopUpdateRequested} = await import('@electron/main/LaunchOptions');
|
||||
const {relaunchStableLaunchPath} = await import('@electron/main/LinuxLaunchPath');
|
||||
const {getDesktopLocalAppProtocol} = await import('@electron/main/LocalAppProtocol');
|
||||
const {
|
||||
@@ -410,6 +410,10 @@ async function runModuleBootstrap(): Promise<void> {
|
||||
logger.warn('Failed to record whether an update is waiting', error);
|
||||
}
|
||||
};
|
||||
const moduleUpdatesDisabled = areModuleUpdatesDisabled();
|
||||
if (moduleUpdatesDisabled) {
|
||||
logger.info('Module updates are disabled by FLUXER_DISABLE_MODULE_UPDATES');
|
||||
}
|
||||
const updater = new ModuleUpdater({
|
||||
store,
|
||||
shellVersion: app.getVersion(),
|
||||
@@ -417,6 +421,7 @@ async function runModuleBootstrap(): Promise<void> {
|
||||
bundledRendererVersion: BUNDLED_RENDERER_VERSION,
|
||||
preferUnversionedBundle: app.isPackaged,
|
||||
forceStartupUpdate: store.shellVersionChanged || updateWasPending || isDesktopUpdateRequested(),
|
||||
moduleUpdatesDisabled,
|
||||
selfUpdateShellFirst:
|
||||
shellUpdatePlan.capability === ShellUpdateCapability.SELF_UPDATE
|
||||
? async (latestVersion) => {
|
||||
@@ -705,35 +710,37 @@ async function runModuleBootstrap(): Promise<void> {
|
||||
},
|
||||
};
|
||||
|
||||
const desktopUpdate = new DesktopUpdateRun({
|
||||
probe: () => updater.checkForUpdate(),
|
||||
canSelfUpdateShell: shellUpdatePlan.capability === ShellUpdateCapability.SELF_UPDATE,
|
||||
runShellSelfUpdate: () => runShellSelfUpdateOnSplash(),
|
||||
installModules: async () => {
|
||||
const launchAttempt = await updater.installPending();
|
||||
if (launchAttempt != null) {
|
||||
await refreshModuleRoots(launchAttempt.committed);
|
||||
}
|
||||
return launchAttempt;
|
||||
},
|
||||
takeover: updateTakeover,
|
||||
publish: (check) => {
|
||||
recordPendingUpdate(check);
|
||||
publishDesktopUpdateCheck(check);
|
||||
},
|
||||
reportFailure: (failure) => {
|
||||
void import('@electron/main/DesktopUpdatePrompt')
|
||||
.then(({reportDesktopUpdateFailure}) => reportDesktopUpdateFailure(failure))
|
||||
.catch((error: unknown) => {
|
||||
logger.error('Failed to report the desktop update failure', error);
|
||||
});
|
||||
},
|
||||
openDownloadsPage: async () => {
|
||||
const {DOWNLOAD_PAGE_URL} = await import('@electron/main/UpdaterDownloads');
|
||||
await shell.openExternal(DOWNLOAD_PAGE_URL);
|
||||
},
|
||||
logger,
|
||||
});
|
||||
const desktopUpdate = moduleUpdatesDisabled
|
||||
? null
|
||||
: new DesktopUpdateRun({
|
||||
probe: () => updater.checkForUpdate(),
|
||||
canSelfUpdateShell: shellUpdatePlan.capability === ShellUpdateCapability.SELF_UPDATE,
|
||||
runShellSelfUpdate: () => runShellSelfUpdateOnSplash(),
|
||||
installModules: async () => {
|
||||
const launchAttempt = await updater.installPending();
|
||||
if (launchAttempt != null) {
|
||||
await refreshModuleRoots(launchAttempt.committed);
|
||||
}
|
||||
return launchAttempt;
|
||||
},
|
||||
takeover: updateTakeover,
|
||||
publish: (check) => {
|
||||
recordPendingUpdate(check);
|
||||
publishDesktopUpdateCheck(check);
|
||||
},
|
||||
reportFailure: (failure) => {
|
||||
void import('@electron/main/DesktopUpdatePrompt')
|
||||
.then(({reportDesktopUpdateFailure}) => reportDesktopUpdateFailure(failure))
|
||||
.catch((error: unknown) => {
|
||||
logger.error('Failed to report the desktop update failure', error);
|
||||
});
|
||||
},
|
||||
openDownloadsPage: async () => {
|
||||
const {DOWNLOAD_PAGE_URL} = await import('@electron/main/UpdaterDownloads');
|
||||
await shell.openExternal(DOWNLOAD_PAGE_URL);
|
||||
},
|
||||
logger,
|
||||
});
|
||||
|
||||
const permit = await runModuleUpdateLoop();
|
||||
localNetworkHint.disarm();
|
||||
@@ -741,25 +748,30 @@ async function runModuleBootstrap(): Promise<void> {
|
||||
recordPendingUpdate({shellNewer: false, modulesChanged: false});
|
||||
}
|
||||
await refreshModuleRoots(permit.launchAttempt.committed);
|
||||
setOnDemandModuleInstaller(
|
||||
createOnDemandModuleInstaller({
|
||||
ensure: (moduleName) => updater.ensureModule(moduleName),
|
||||
refresh: serveOnDemandModules,
|
||||
onFailure: (moduleName, error) => {
|
||||
logger.error('Failed to install a module on demand', {module: moduleName, error});
|
||||
},
|
||||
}),
|
||||
);
|
||||
markSplashLaunching();
|
||||
armDesktopUpdate({
|
||||
check: async (manual) => {
|
||||
const check = await desktopUpdate.check({manual});
|
||||
recordPendingUpdate(check);
|
||||
return check;
|
||||
},
|
||||
start: () => desktopUpdate.start(),
|
||||
});
|
||||
if (desktopUpdate != null) {
|
||||
setOnDemandModuleInstaller(
|
||||
createOnDemandModuleInstaller({
|
||||
ensure: (moduleName) => updater.ensureModule(moduleName),
|
||||
refresh: serveOnDemandModules,
|
||||
onFailure: (moduleName, error) => {
|
||||
logger.error('Failed to install a module on demand', {module: moduleName, error});
|
||||
},
|
||||
}),
|
||||
);
|
||||
armDesktopUpdate({
|
||||
check: async (manual) => {
|
||||
const check = await desktopUpdate.check({manual});
|
||||
recordPendingUpdate(check);
|
||||
return check;
|
||||
},
|
||||
start: () => desktopUpdate.start(),
|
||||
});
|
||||
}
|
||||
armMainWindowHandoff(updater, permit.launchAttempt, () => {
|
||||
if (desktopUpdate == null) {
|
||||
return;
|
||||
}
|
||||
desktopUpdate.markLaunchSettled();
|
||||
preloadUpdateSplash();
|
||||
armDesktopUpdatePoll(
|
||||
|
||||
@@ -275,3 +275,26 @@ describe('Bootstrap entry point', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FLUXER_DISABLE_MODULE_UPDATES', () => {
|
||||
test('reads the environment variable like the other FLUXER_DISABLE switches', async () => {
|
||||
const {areModuleUpdatesDisabled} = await import('./LaunchOptions.ts');
|
||||
const previous = process.env.FLUXER_DISABLE_MODULE_UPDATES;
|
||||
try {
|
||||
delete process.env.FLUXER_DISABLE_MODULE_UPDATES;
|
||||
assert.equal(areModuleUpdatesDisabled(), false);
|
||||
for (const value of ['1', 'true', 'YES', 'on']) {
|
||||
process.env.FLUXER_DISABLE_MODULE_UPDATES = value;
|
||||
assert.equal(areModuleUpdatesDisabled(), true);
|
||||
}
|
||||
process.env.FLUXER_DISABLE_MODULE_UPDATES = '0';
|
||||
assert.equal(areModuleUpdatesDisabled(), false);
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.FLUXER_DISABLE_MODULE_UPDATES;
|
||||
} else {
|
||||
process.env.FLUXER_DISABLE_MODULE_UPDATES = previous;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
} from '@electron/common/Types';
|
||||
import {getDesktopUpdateState, observeDesktopUpdateState} from '@electron/main/DesktopUpdateGate';
|
||||
import {checkForUpdatesFromShell, startDesktopUpdateFromShell} from '@electron/main/DesktopUpdatePrompt';
|
||||
import {areModuleUpdatesDisabled} from '@electron/main/LaunchOptions';
|
||||
import {relaunchStableLaunchPath} from '@electron/main/LinuxLaunchPath';
|
||||
import {onLocaleChange, t} from '@electron/main/MainI18n';
|
||||
import {app, type BrowserWindow, clipboard, Menu, nativeImage, Tray} from 'electron';
|
||||
@@ -399,13 +400,15 @@ function buildTrayMenu(): Menu {
|
||||
click: () => runTrayMenuAction(startDesktopUpdateFromShell),
|
||||
});
|
||||
}
|
||||
menuTemplate.push({
|
||||
label: t('desktop.tray.checkForUpdates'),
|
||||
click: () =>
|
||||
runTrayMenuAction(() => {
|
||||
void checkForUpdatesFromShell();
|
||||
}),
|
||||
});
|
||||
if (!areModuleUpdatesDisabled()) {
|
||||
menuTemplate.push({
|
||||
label: t('desktop.tray.checkForUpdates'),
|
||||
click: () =>
|
||||
runTrayMenuAction(() => {
|
||||
void checkForUpdatesFromShell();
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (trayState.buildInfo) {
|
||||
menuTemplate.push({
|
||||
label: t('desktop.tray.copyBuildInfo'),
|
||||
|
||||
@@ -159,9 +159,14 @@ export function describeLaunchDiagnosticOptions(argv: ReadonlyArray<string> = pr
|
||||
spellcheck: getSpellcheckLaunchMode(argv),
|
||||
v8CodeCacheDisabled: shouldDisableV8CodeCache(argv),
|
||||
nativeGatewayDisableReason: getNativeGatewayDisableReason(argv),
|
||||
moduleUpdatesDisabled: areModuleUpdatesDisabled(),
|
||||
};
|
||||
}
|
||||
|
||||
export function areModuleUpdatesDisabled(): boolean {
|
||||
return truthyEnv('FLUXER_DISABLE_MODULE_UPDATES');
|
||||
}
|
||||
|
||||
export function isDesktopUpdateRequested(argv: ReadonlyArray<string> = process.argv): boolean {
|
||||
return hasFlag(argv, DESKTOP_UPDATE_ARGS);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import {DESKTOP_APP_NAME} from '@electron/common/DesktopIdentity';
|
||||
import {checkForUpdatesFromShell} from '@electron/main/DesktopUpdatePrompt';
|
||||
import {areModuleUpdatesDisabled} from '@electron/main/LaunchOptions';
|
||||
import {onLocaleChange, t} from '@electron/main/MainI18n';
|
||||
import {openExternalDeduped} from '@electron/main/OpenExternal';
|
||||
import {buildTroubleshootingMenuItems} from '@electron/main/Troubleshooting';
|
||||
@@ -31,7 +32,7 @@ function buildTemplate(): Array<MenuItemConstructorOptions> {
|
||||
role: 'about',
|
||||
label: t('desktop.appMenu.about', {appName}),
|
||||
},
|
||||
checkForUpdatesItem(),
|
||||
...(areModuleUpdatesDisabled() ? [] : [checkForUpdatesItem()]),
|
||||
{type: 'separator'},
|
||||
{
|
||||
label: t('desktop.appMenu.preferences'),
|
||||
@@ -209,7 +210,7 @@ function buildTemplate(): Array<MenuItemConstructorOptions> {
|
||||
? `${t('desktop.appMenu.help')}${MACOS_HELP_MENU_TITLE_AUTODETECT_OPT_OUT}`
|
||||
: t('desktop.appMenu.help'),
|
||||
submenu: [
|
||||
...(isMac ? [] : [checkForUpdatesItem(), {type: 'separator' as const}]),
|
||||
...(isMac || areModuleUpdatesDisabled() ? [] : [checkForUpdatesItem(), {type: 'separator' as const}]),
|
||||
{
|
||||
label: t('desktop.appMenu.website'),
|
||||
click: async () => {
|
||||
|
||||
@@ -127,6 +127,7 @@ function createUpdater(store, modules, options = {}) {
|
||||
hasOfflineRenderer: options.bundledRendererVersion != null,
|
||||
bundledRendererVersion: options.bundledRendererVersion ?? null,
|
||||
forceStartupUpdate: options.forceStartupUpdate ?? false,
|
||||
moduleUpdatesDisabled: options.moduleUpdatesDisabled ?? false,
|
||||
selfUpdateShellFirst: options.selfUpdateShellFirst,
|
||||
onState: options.onState,
|
||||
fetch: async (url, init) => {
|
||||
@@ -506,6 +507,75 @@ describe('ModuleUpdater.run', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ModuleUpdater with module updates disabled', () => {
|
||||
test('launches the verified set without fetching even when the manifest demands a shell update', async () => {
|
||||
const userDataPath = createUserData();
|
||||
const store = await openStore(userDataPath);
|
||||
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
|
||||
await store.commit({fluxer_renderer: RENDERER_SHA});
|
||||
assert.equal((await createUpdater(store, {fluxer_renderer: RENDERER_SHA}).updater.run()).status, 'launching');
|
||||
const statuses = [];
|
||||
let shellUpdates = 0;
|
||||
const {updater, requests, sleeps} = createUpdater(
|
||||
store,
|
||||
{fluxer_renderer: NEXT_RENDERER_SHA},
|
||||
{
|
||||
shellLatest: '2026.824.0',
|
||||
shellMinimum: '2026.824.0',
|
||||
forceStartupUpdate: true,
|
||||
moduleUpdatesDisabled: true,
|
||||
onState: (state) => statuses.push(state.status),
|
||||
selfUpdateShellFirst: async () => {
|
||||
shellUpdates += 1;
|
||||
return {reason: 'no-update'};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const outcome = await updater.run();
|
||||
|
||||
assert.equal(outcome.status, 'unreachable-launch');
|
||||
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
|
||||
assert.deepEqual(requests, []);
|
||||
assert.deepEqual(sleeps, []);
|
||||
assert.equal(shellUpdates, 0);
|
||||
assert.deepEqual(statuses, ['unreachable-launch']);
|
||||
assert.equal(outcome.launchAttempt.committed.fluxer_renderer, RENDERER_SHA);
|
||||
});
|
||||
|
||||
test('launches the bundled renderer on a fresh store without fetching', async () => {
|
||||
const store = await openStore(createUserData());
|
||||
const {updater, requests} = createUpdater(
|
||||
store,
|
||||
{fluxer_renderer: NEXT_RENDERER_SHA},
|
||||
{bundledRendererVersion: SHELL_VERSION, moduleUpdatesDisabled: true},
|
||||
);
|
||||
|
||||
const outcome = await updater.run();
|
||||
|
||||
assert.equal(outcome.status, 'unreachable-launch');
|
||||
assert.deepEqual(outcome.committed, {});
|
||||
assert.deepEqual(requests, []);
|
||||
});
|
||||
|
||||
test('blocks at once on the unreachable splash without fetching when nothing is installed', async () => {
|
||||
const store = await openStore(createUserData());
|
||||
const {updater, requests, sleeps} = createUpdater(
|
||||
store,
|
||||
{fluxer_renderer: RENDERER_SHA},
|
||||
{moduleUpdatesDisabled: true},
|
||||
);
|
||||
|
||||
const outcome = await updater.run();
|
||||
|
||||
assert.equal(outcome.status, 'blocked-update-required');
|
||||
assert.equal(outcome.reason, 'nothing-installed');
|
||||
assert.equal(outcome.updateServerUnreachable, true);
|
||||
assert.deepEqual(requests, []);
|
||||
assert.deepEqual(sleeps, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ModuleUpdater checks without downloading and installs only when asked', () => {
|
||||
async function storeOnRenderer() {
|
||||
const userDataPath = createUserData();
|
||||
|
||||
@@ -267,6 +267,7 @@ interface ModuleUpdaterOptions {
|
||||
readonly bundledRendererVersion?: string | null;
|
||||
readonly preferUnversionedBundle?: boolean;
|
||||
readonly forceStartupUpdate?: boolean;
|
||||
readonly moduleUpdatesDisabled?: boolean;
|
||||
readonly selfUpdateShellFirst?: (latestVersion: string) => Promise<{readonly reason: string}>;
|
||||
readonly onState?: (state: ModuleUpdaterSplashState) => void;
|
||||
readonly report?: (report: ModuleUpdaterReport) => void;
|
||||
@@ -286,6 +287,7 @@ export class ModuleUpdater {
|
||||
private readonly shellVersion: ModuleVersion;
|
||||
private readonly hasOfflineRenderer: boolean;
|
||||
private readonly forceStartupUpdate: boolean;
|
||||
private readonly moduleUpdatesDisabled: boolean;
|
||||
private readonly selfUpdateShellFirst: ((latestVersion: string) => Promise<{readonly reason: string}>) | null;
|
||||
private readonly shellFirstAttempts = new Map<string, {readonly reason: string} | null>();
|
||||
private readonly manifestFeed: ModuleManifestFeedIdentity;
|
||||
@@ -314,6 +316,7 @@ export class ModuleUpdater {
|
||||
this.shellVersion = parseModuleVersion(options.shellVersion, 'desktop shell version');
|
||||
this.hasOfflineRenderer = options.hasOfflineRenderer ?? false;
|
||||
this.forceStartupUpdate = options.forceStartupUpdate ?? false;
|
||||
this.moduleUpdatesDisabled = options.moduleUpdatesDisabled ?? false;
|
||||
this.selfUpdateShellFirst = options.selfUpdateShellFirst ?? null;
|
||||
const releaseChannel = options.releaseChannel ?? BUILD_CHANNEL;
|
||||
this.platform = options.platform ?? process.platform;
|
||||
@@ -418,6 +421,18 @@ export class ModuleUpdater {
|
||||
return reverted;
|
||||
}
|
||||
}
|
||||
if (this.moduleUpdatesDisabled) {
|
||||
const decision = await this.planner.evaluateUnreachableLaunch(this.startupUpdateEnforced);
|
||||
if (decision.kind === ModuleUnreachableLaunchDecision.LAUNCH) {
|
||||
return await this.launchUnreachable();
|
||||
}
|
||||
return this.blockUpdateRequired(
|
||||
decision.reason,
|
||||
[],
|
||||
'module updates are disabled by FLUXER_DISABLE_MODULE_UPDATES',
|
||||
{updateServerUnreachable: true},
|
||||
);
|
||||
}
|
||||
if (this.platform === 'linux' && !(await this.startupUpdateRequired())) {
|
||||
return await this.launchIfAtFloor();
|
||||
}
|
||||
@@ -486,13 +501,7 @@ export class ModuleUpdater {
|
||||
if (exhausted || !fetched) {
|
||||
const decision = await this.planner.evaluateUnreachableLaunch(this.startupUpdateEnforced);
|
||||
if (decision.kind === ModuleUnreachableLaunchDecision.LAUNCH) {
|
||||
const launchAttempt = await this.store.recordLaunchAttempt();
|
||||
this.emit(ModuleUpdaterStatus.UNREACHABLE_LAUNCH);
|
||||
return {
|
||||
status: ModuleUpdaterStatus.UNREACHABLE_LAUNCH,
|
||||
committed: this.store.getCommitted(),
|
||||
launchAttempt,
|
||||
};
|
||||
return await this.launchUnreachable();
|
||||
}
|
||||
if (exhausted) {
|
||||
return this.blockUpdateRequired(decision.reason, [], describeErrorChain(error), {
|
||||
@@ -802,6 +811,16 @@ export class ModuleUpdater {
|
||||
};
|
||||
}
|
||||
|
||||
private async launchUnreachable(): Promise<ModuleUpdaterOutcome> {
|
||||
const launchAttempt = await this.store.recordLaunchAttempt();
|
||||
this.emit(ModuleUpdaterStatus.UNREACHABLE_LAUNCH);
|
||||
return {
|
||||
status: ModuleUpdaterStatus.UNREACHABLE_LAUNCH,
|
||||
committed: this.store.getCommitted(),
|
||||
launchAttempt,
|
||||
};
|
||||
}
|
||||
|
||||
private async launchRevertedSetBypassingFloor(
|
||||
committed: Readonly<Record<string, string>>,
|
||||
): Promise<ModuleUpdaterOutcome | null> {
|
||||
|
||||
@@ -73,7 +73,7 @@ import {
|
||||
registerIpcHandlers,
|
||||
} from '@electron/main/IpcHandlers';
|
||||
import {initializeJumpList} from '@electron/main/JumpList';
|
||||
import {describeLaunchDiagnosticOptions} from '@electron/main/LaunchOptions';
|
||||
import {areModuleUpdatesDisabled, describeLaunchDiagnosticOptions} from '@electron/main/LaunchOptions';
|
||||
import {cleanupLegacyHarvestHandlers, registerLegacyHarvestHandlers} from '@electron/main/LegacyHarvestIpc';
|
||||
import {initializeLegacyOriginHarvest} from '@electron/main/LegacyOriginHarvest';
|
||||
import {cleanupVirtmic, registerVirtmicHandlers} from '@electron/main/LinuxAudioCapture';
|
||||
@@ -600,7 +600,7 @@ if (launchConfigurationError) {
|
||||
showWindow,
|
||||
});
|
||||
});
|
||||
if (process.env.FLUXER_OFFLINE !== '1') {
|
||||
if (process.env.FLUXER_OFFLINE !== '1' && !areModuleUpdatesDisabled()) {
|
||||
const {registerUpdater} = await import('@electron/main/Updater');
|
||||
registerUpdater(getMainWindow);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user