mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-22 09:34:53 +02:00
[eric] electron: fix the electron 42 windows build - electronLanguages en to en-US so the renderer gets a non-empty --lang (electron 42 renamed en.pak to en-US.pak; empty locale crashed blink LCIDFromLocaleInternal with 0xC0000005), bump electron-builder 25.1.8 to 26.8.1 + migrate win.sign to signtoolOptions, after-pack hook restores the 9router node_modules eb26 drops from extraResources so the subscription service stops hanging, and verify-locale-paks + verify-router-deps gate both in verify-all
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
// electron-builder 26 special-excludes node_modules from extraResources (25 did
|
||||
// not), so the bundled 9Router - a Next.js standalone whose server.js does
|
||||
// require('next') - ships WITHOUT its deps. The result: 9Router dies with
|
||||
// "Cannot find module 'next'", never binds :20128, and the Models tab spins on
|
||||
// "Starting subscription service..." forever. We copy router/node_modules into
|
||||
// the packed app HERE rather than after electron-builder finishes, because
|
||||
// afterPack runs BEFORE code-signing: on macOS the whole .app is sealed by the
|
||||
// signature, so injecting files post-sign would invalidate it. The .next dotdir
|
||||
// is handled by the package.json extraResources filter; only node_modules needs
|
||||
// this rescue.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
exports.default = async function afterPack(context) {
|
||||
const { appOutDir, electronPlatformName, packager } = context;
|
||||
const src = path.join(__dirname, '..', 'build-staging', 'router', 'node_modules');
|
||||
if (!fs.existsSync(src)) return; // dev/no-router build; nothing to do
|
||||
|
||||
let routerDir;
|
||||
if (electronPlatformName === 'darwin') {
|
||||
const appName = packager.appInfo.productFilename; // "OpenSwarm"
|
||||
routerDir = path.join(appOutDir, `${appName}.app`, 'Contents', 'Resources', 'router');
|
||||
} else {
|
||||
routerDir = path.join(appOutDir, 'resources', 'router');
|
||||
}
|
||||
if (!fs.existsSync(routerDir)) return; // router not staged into this target
|
||||
|
||||
const dest = path.join(routerDir, 'node_modules');
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.cpSync(src, dest, { recursive: true });
|
||||
}
|
||||
if (!fs.existsSync(path.join(dest, 'next'))) {
|
||||
throw new Error(`afterPack: 9Router node_modules/next missing in ${routerDir} after copy`);
|
||||
}
|
||||
console.log(`[afterPack] staged 9Router node_modules into ${routerDir}`);
|
||||
};
|
||||
Generated
+1009
-1885
File diff suppressed because it is too large
Load Diff
+18
-8
@@ -23,13 +23,16 @@
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "3.1.1",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "castlabs/electron-releases#v42.0.0+wvcus",
|
||||
"electron-builder": "25.1.8"
|
||||
"electron": "github:castlabs/electron-releases#v42.0.0+wvcus",
|
||||
"electron-builder": "^26.8.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.clusterlabs.openswarm",
|
||||
"productName": "OpenSwarm",
|
||||
"electronLanguages": ["en"],
|
||||
"afterPack": "./build/after-pack.js",
|
||||
"electronLanguages": [
|
||||
"en-US"
|
||||
],
|
||||
"electronDownload": {
|
||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||
},
|
||||
@@ -69,13 +72,18 @@
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": ["x64"]
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"artifactName": "OpenSwarm-Setup-${arch}.${ext}",
|
||||
"sign": "./build/sign-windows.js",
|
||||
"signingHashAlgorithms": ["sha256"],
|
||||
"signDlls": false
|
||||
"signtoolOptions": {
|
||||
"sign": "./build/sign-windows.js",
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
]
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
@@ -123,7 +131,9 @@
|
||||
"from": "build-staging/router",
|
||||
"to": "router",
|
||||
"filter": [
|
||||
"**/*"
|
||||
"**/*",
|
||||
"**/.*",
|
||||
"**/.*/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -465,6 +465,11 @@ try {
|
||||
if ($LASTEXITCODE -ne 0) { throw "electron-builder failed" }
|
||||
} finally { Pop-Location }
|
||||
|
||||
# NOTE: the bundled 9Router's node_modules (which electron-builder 26 drops from
|
||||
# extraResources) is restored by the build/after-pack.js afterPack hook, which
|
||||
# runs inside electron-builder BEFORE code-signing so the copied files are sealed
|
||||
# by the signature. See that file for the why.
|
||||
|
||||
Remove-Item -Recurse -Force $Staging -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host ""
|
||||
|
||||
@@ -35,6 +35,8 @@ function main() {
|
||||
['packaging parity (Win+Mac stage the same dirs)', 'verify-packaging-parity.js', []],
|
||||
['deps fully pinned (reproducible backend builds)', 'verify-deps-pinned.js', []],
|
||||
['no build-host paths leaked into the artifact', 'verify-host-leakage.js', appArg],
|
||||
['locale paks shipped (empty --lang -> Blink null-deref crash)', 'verify-locale-paks.js', appArg],
|
||||
['9router deps shipped (else subscription service hangs)', 'verify-router-deps.js', appArg],
|
||||
['bundled python runs (--version + import smoke)', 'verify-python-health.js', appArg],
|
||||
['MCP bundles answer initialize over stdio', 'verify-mcp-bundles.js', appArg],
|
||||
['update feed sha512 matches files on disk', 'verify-update-feed.js', []],
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env node
|
||||
// Guards the empty-locale renderer crash. A packaged build MUST ship Chromium's
|
||||
// locale .pak files (locales/en-US.pak + the full ~50). If they are missing,
|
||||
// Electron launches the renderer with an EMPTY --lang, and Blink's
|
||||
// LCIDFromLocaleInternal (third_party/blink/.../text/locale_win.cc) null-derefs
|
||||
// (STATUS_ACCESS_VIOLATION 0xC0000005, read of 0x8) the instant a text/agent/
|
||||
// webview surface mounts - a hard crash on the first real interaction, with no
|
||||
// JS error to localize it. electron-builder has been observed to drop these on a
|
||||
// --dir repack, so we assert them explicitly rather than trust the packager.
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const h = require('./lib/app-harness');
|
||||
|
||||
// The build intentionally trims locales to en-US via electronLanguages, so the
|
||||
// count is small by design - the only thing that matters is that the locale the
|
||||
// renderer resolves to (en-US.pak) is actually present. Zero paks / a missing
|
||||
// en-US.pak is the crash condition.
|
||||
const REQUIRED = 'en-US.pak';
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { app: null };
|
||||
for (let i = 0; i < argv.length; i++) if (argv[i] === '--app') out.app = argv[++i];
|
||||
return out;
|
||||
}
|
||||
|
||||
function pakCount(dir) {
|
||||
try { return fs.readdirSync(dir).filter((f) => f.toLowerCase().endsWith('.pak')); }
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
function checkWinLinux(exe) {
|
||||
const dir = path.join(path.dirname(exe), 'locales');
|
||||
const paks = pakCount(dir);
|
||||
if (paks === null) return { ok: false, msg: `locales/ dir missing at ${dir}` };
|
||||
const hasReq = paks.some((p) => p.toLowerCase() === REQUIRED.toLowerCase());
|
||||
process.stdout.write(` ${dir}: ${paks.length} paks, en-US.pak=${hasReq}\n`);
|
||||
if (!hasReq) return { ok: false, msg: `${REQUIRED} missing from ${dir} (${paks.length} paks present)` };
|
||||
return { ok: true, msg: `${paks.length} paks incl ${REQUIRED}` };
|
||||
}
|
||||
|
||||
function checkMac(exe) {
|
||||
// mac stores locale paks inside the Electron Framework; layout varies by version,
|
||||
// and this crash is Windows-specific, so be informational rather than blocking.
|
||||
const appRoot = exe.slice(0, exe.indexOf('.app') + 4);
|
||||
const found = [];
|
||||
(function walk(d, depth) {
|
||||
if (depth > 6) return;
|
||||
let ents = [];
|
||||
try { ents = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
||||
for (const e of ents) {
|
||||
const full = path.join(d, e.name);
|
||||
if (e.isDirectory()) walk(full, depth + 1);
|
||||
else if (e.isFile() && e.name.toLowerCase().endsWith('.pak')) found.push(full);
|
||||
}
|
||||
})(appRoot, 0);
|
||||
process.stdout.write(` mac: found ${found.length} .pak file(s) under the app bundle\n`);
|
||||
return { ok: found.length > 0, msg: `${found.length} paks (mac is informational)` , soft: true };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const exe = h.packagedAppPath(args.app);
|
||||
const res = process.platform === 'darwin' ? checkMac(exe) : checkWinLinux(exe);
|
||||
if (res.ok) { process.stdout.write(`PASS locale paks present (${res.msg})\n`); process.exit(0); }
|
||||
if (res.soft) { process.stdout.write(`WARN ${res.msg} - could not confirm mac paks; not blocking\n`); process.exit(0); }
|
||||
process.stderr.write(
|
||||
`FAIL packaged build is MISSING Chromium locale paks: ${res.msg}\n` +
|
||||
` The renderer would launch with an empty --lang and crash in Blink's\n` +
|
||||
` LCIDFromLocaleInternal (0xC0000005) on the first text/agent/webview mount.\n` +
|
||||
` Fix: ensure electron-builder copies node_modules/electron/dist/locales/*.pak\n` +
|
||||
` into the packaged output (this regressed on --dir repacks of the v42 build).\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env node
|
||||
// Guards the "subscription service hangs forever" failure. The bundled 9Router is
|
||||
// a Next.js standalone server whose server.js does require('next'), so it needs
|
||||
// router/node_modules (and router/.next) present in the packaged app. electron-
|
||||
// builder 26 special-excludes node_modules from extraResources (25 did not), and
|
||||
// drops the .next dotdir unless the filter opts dotfiles in - either way the
|
||||
// router dies with "Cannot find module 'next'", 9Router never binds :20128, and
|
||||
// the Models tab spins on "Starting subscription service..." indefinitely. Assert
|
||||
// the deps are actually in the package.
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const h = require('./lib/app-harness');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { app: null };
|
||||
for (let i = 0; i < argv.length; i++) if (argv[i] === '--app') out.app = argv[++i];
|
||||
return out;
|
||||
}
|
||||
|
||||
function routerDir(exe) {
|
||||
if (process.platform === 'darwin') {
|
||||
const appRoot = exe.slice(0, exe.indexOf('.app') + 4);
|
||||
return path.join(appRoot, 'Contents', 'Resources', 'router');
|
||||
}
|
||||
return path.join(path.dirname(exe), 'resources', 'router');
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const exe = h.packagedAppPath(args.app);
|
||||
const dir = routerDir(exe);
|
||||
const checks = {
|
||||
'server.js': path.join(dir, 'server.js'),
|
||||
'node_modules/next': path.join(dir, 'node_modules', 'next'),
|
||||
'.next': path.join(dir, '.next'),
|
||||
};
|
||||
const missing = [];
|
||||
for (const [label, p] of Object.entries(checks)) {
|
||||
const ok = fs.existsSync(p);
|
||||
process.stdout.write(` ${ok ? 'ok ' : 'MISSING'} ${label}\n`);
|
||||
if (!ok) missing.push(label);
|
||||
}
|
||||
if (missing.length === 0) {
|
||||
process.stdout.write(`PASS 9Router deps present in ${dir}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
process.stderr.write(
|
||||
`FAIL bundled 9Router is missing ${missing.join(', ')} in ${dir}\n` +
|
||||
` server.js does require('next'); without node_modules/.next the 9Router\n` +
|
||||
` never starts, port 20128 stays dead, and the subscription service hangs.\n` +
|
||||
` Fix: ensure the build copies router/node_modules + .next into the package\n` +
|
||||
` (electron-builder 26 drops node_modules from extraResources; build-app-win.ps1\n` +
|
||||
` copies it back post-build, and the package.json filter opts the .next dotdir in).\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user