Rebuild Electron binary when startup finds it missing
Template tests / tests (push) Failing after 15s

This commit is contained in:
Iisyourdad
2026-06-10 19:08:46 -05:00
parent 3bcac9f673
commit a94f262565
2 changed files with 108 additions and 21 deletions
+74 -21
View File
@@ -35,6 +35,61 @@ function platformBinaryCandidates(platform) {
} }
} }
function electronBinaryCandidates({ packageRoot, distDir, platform }) {
const candidatePaths = [];
const pathHint = packageRoot ? readElectronPathHint(packageRoot) : null;
if (pathHint) {
candidatePaths.push(path.join(distDir, pathHint));
}
for (const relativePath of platformBinaryCandidates(platform)) {
candidatePaths.push(path.join(distDir, relativePath));
}
return candidatePaths;
}
function runNpmRebuild({
packageRoot,
platform = process.platform,
arch = process.arch,
npmExecPath = process.env.npm_execpath || null,
npmNodeExecPath = process.env.npm_node_execpath || process.execPath,
}) {
if (!npmExecPath) {
return false;
}
const result = spawnSync(
npmNodeExecPath,
[npmExecPath, 'rebuild', 'electron', '--force', '--foreground-scripts'],
{
cwd: packageRoot,
env: {
...process.env,
npm_config_platform: platform,
npm_config_arch: arch,
},
stdio: 'inherit',
}
);
if (result.error) {
throw result.error;
}
if (result.signal) {
throw new Error(`Electron repair was interrupted by ${result.signal}`);
}
if (result.status !== 0) {
throw new Error(`Electron rebuild failed with exit code ${result.status ?? 1}`);
}
return true;
}
function repairElectronInstall({ function repairElectronInstall({
packageRoot, packageRoot,
platform = process.platform, platform = process.platform,
@@ -81,6 +136,7 @@ function buildMissingElectronError({ packageRoot, distDir, candidatePaths }) {
'Try reinstalling dependencies from the repo root:', 'Try reinstalling dependencies from the repo root:',
'', '',
' npm install', ' npm install',
' npm rebuild electron --force --foreground-scripts',
'', '',
'If that does not help, delete node_modules/electron and install again.', 'If that does not help, delete node_modules/electron and install again.',
'', '',
@@ -103,32 +159,27 @@ function resolveElectronBinary({
} }
const distDir = overrideDistPath || path.join(packageRoot, 'dist'); const distDir = overrideDistPath || path.join(packageRoot, 'dist');
const candidatePaths = []; const candidatePaths = electronBinaryCandidates({ packageRoot, distDir, platform });
const pathHint = packageRoot ? readElectronPathHint(packageRoot) : null;
if (pathHint) {
candidatePaths.push(path.join(distDir, pathHint));
}
for (const relativePath of platformBinaryCandidates(platform)) {
candidatePaths.push(path.join(distDir, relativePath));
}
const resolved = candidatePaths.find((candidate) => fs.existsSync(candidate)); const resolved = candidatePaths.find((candidate) => fs.existsSync(candidate));
if (!resolved) { if (!resolved) {
if (packageRoot && repairElectronInstall({ packageRoot, platform, arch })) { if (packageRoot) {
const repairedHint = readElectronPathHint(packageRoot); if (runNpmRebuild({ packageRoot, platform, arch })) {
const repairedCandidates = []; const rebuilt = electronBinaryCandidates({ packageRoot, distDir, platform }).find((candidate) =>
if (repairedHint) { fs.existsSync(candidate)
repairedCandidates.push(path.join(distDir, repairedHint)); );
} if (rebuilt) {
for (const relativePath of platformBinaryCandidates(platform)) { return rebuilt;
repairedCandidates.push(path.join(distDir, relativePath)); }
} }
const repaired = repairedCandidates.find((candidate) => fs.existsSync(candidate)); if (repairElectronInstall({ packageRoot, platform, arch })) {
if (repaired) { const repaired = electronBinaryCandidates({ packageRoot, distDir, platform }).find((candidate) =>
return repaired; fs.existsSync(candidate)
);
if (repaired) {
return repaired;
}
} }
} }
@@ -140,8 +191,10 @@ function resolveElectronBinary({
module.exports = { module.exports = {
buildMissingElectronError, buildMissingElectronError,
electronBinaryCandidates,
readElectronPathHint, readElectronPathHint,
repairElectronInstall, repairElectronInstall,
runNpmRebuild,
resolveElectronBinary, resolveElectronBinary,
resolveElectronPackageRoot, resolveElectronPackageRoot,
platformBinaryCandidates, platformBinaryCandidates,
+34
View File
@@ -65,6 +65,40 @@ test('repairs a broken Electron install before resolving the binary', (t) => {
); );
}); });
test('rebuilds Electron through npm when the binary is missing', (t) => {
const root = makeTmpDir('electron-rebuild');
t.after(() => rmrf(root));
fs.mkdirSync(path.join(root, 'dist'), { recursive: true });
const fakeNpmCli = path.join(root, 'fake-npm-cli.js');
fs.writeFileSync(
fakeNpmCli,
[
"const fs = require('node:fs');",
"const path = require('node:path');",
"fs.mkdirSync(path.join(__dirname, 'dist'), { recursive: true });",
"fs.writeFileSync(path.join(__dirname, 'dist', 'electron.exe'), 'binary');",
"fs.writeFileSync(path.join(__dirname, 'path.txt'), 'electron.exe');",
].join('\n')
);
const originalNpmExecPath = process.env.npm_execpath;
const originalNpmNodeExecPath = process.env.npm_node_execpath;
process.env.npm_execpath = fakeNpmCli;
process.env.npm_node_execpath = process.execPath;
t.after(() => {
if (originalNpmExecPath === undefined) delete process.env.npm_execpath;
else process.env.npm_execpath = originalNpmExecPath;
if (originalNpmNodeExecPath === undefined) delete process.env.npm_node_execpath;
else process.env.npm_node_execpath = originalNpmNodeExecPath;
});
assert.equal(
resolveElectronBinary({ packageRoot: root, platform: 'win32' }),
path.join(root, 'dist', 'electron.exe')
);
});
test('reports a helpful error when the runtime is missing', (t) => { test('reports a helpful error when the runtime is missing', (t) => {
const root = makeTmpDir('electron-missing'); const root = makeTmpDir('electron-missing');
t.after(() => rmrf(root)); t.after(() => rmrf(root));