mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
build(electron): exclude uuidv7 and guard against future asar regressions
uuidv7 is imported only from src/app/util/uuid-v7.ts; the Angular
bundler inlines it (verified via chunk-65AEYVPY.js.map source
attribution). Adding it to the three electron-builder configs drops
another ~80 KB from the asar.
Extend tools/verify-electron-requires.js to also flag bare require()
targets in electron/ that match a `\!**/<pkg>/**` exclusion in
electron-builder.yaml. Without this, adding `require('@noble/ciphers')`
or similar in electron/main.ts would pass dev (which resolves against
on-disk node_modules) and only crash with MODULE_NOT_FOUND on packaged
releases. The script is already invoked from .github/workflows/
electron-smoke.yml, so the new check runs in CI automatically.
Manual verification covers 14 cases (8 excluded, 6 allowed) including
@noble/ciphers, @noble/hashes, scoped-vs-bare resolution, and Node
built-ins.
This commit is contained in:
parent
95b0d8e2f7
commit
ddc01ab6cf
4 changed files with 87 additions and 20 deletions
|
|
@ -29,6 +29,8 @@ files:
|
|||
# copies are dead weight at runtime.
|
||||
- '!**/@noble/**'
|
||||
- '!**/hash-wasm/**'
|
||||
# uuidv7 is imported by src/ (Angular bundle inlines it); not used by electron/
|
||||
- '!**/uuidv7/**'
|
||||
directories:
|
||||
output: './.tmp/app-builds'
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ files:
|
|||
# copies are dead weight at runtime.
|
||||
- '!**/@noble/**'
|
||||
- '!**/hash-wasm/**'
|
||||
# uuidv7 is imported by src/ (Angular bundle inlines it); not used by electron/
|
||||
- '!**/uuidv7/**'
|
||||
directories:
|
||||
output: './.tmp/app-builds'
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ files:
|
|||
# copies are dead weight at runtime.
|
||||
- '!**/@noble/**'
|
||||
- '!**/hash-wasm/**'
|
||||
# uuidv7 is imported by src/ (Angular bundle inlines it); not used by electron/
|
||||
- '!**/uuidv7/**'
|
||||
#afterSign: ./tools/notarizeMacApp.js
|
||||
|
||||
directories:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
#!/usr/bin/env node
|
||||
/*
|
||||
* Walks the contents of a packaged app.asar and verifies that every relative
|
||||
* require() call inside electron/ .js/.cjs/.mjs files resolves to a file that
|
||||
* was actually packaged. Catches #7320-class bugs where tsc happily compiles
|
||||
* an import reaching out of the electron tree (e.g. '../src/app/util/foo')
|
||||
* but the compiled dependency lives outside the files glob in
|
||||
* electron-builder.yaml and is therefore missing from app.asar at runtime.
|
||||
* Walks the contents of a packaged app.asar and verifies, for every .js/.cjs/.mjs
|
||||
* file under electron/, that:
|
||||
*
|
||||
* 1. Every relative require() target resolves to a file that was actually
|
||||
* packaged. Catches #7320-class bugs where tsc happily compiles an import
|
||||
* reaching out of the electron tree (e.g. '../src/app/util/foo') but the
|
||||
* compiled dependency lives outside the files glob in electron-builder.yaml
|
||||
* and is therefore missing from app.asar at runtime.
|
||||
*
|
||||
* 2. No bare require() targets a package that electron-builder.yaml lists as
|
||||
* excluded from the asar (e.g. require('@noble/ciphers')). This catches
|
||||
* the regression class where dev (running against on-disk node_modules)
|
||||
* succeeds, but the packaged release crashes with MODULE_NOT_FOUND because
|
||||
* the package was pruned out of app.asar. The exclusion list is parsed
|
||||
* from electron-builder.yaml; keep that file as the single source of truth.
|
||||
*
|
||||
* Usage:
|
||||
* node tools/verify-electron-requires.js <path/to/app.asar>
|
||||
*
|
||||
* Exits 0 on success, 1 when unresolvable requires are found, 2 on usage
|
||||
* errors.
|
||||
* Exits 0 on success, 1 when violations are found, 2 on usage errors.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
|
@ -68,6 +76,44 @@ const collectRelativeRequires = (src) => {
|
|||
return targets;
|
||||
};
|
||||
|
||||
// Bare specifiers: anything that doesn't start with '.' or '/'. Built-ins
|
||||
// (fs, path, electron, etc.) and runtime deps both pass through here; the
|
||||
// exclusion check below is what flags forbidden ones.
|
||||
const collectBareRequires = (src) => {
|
||||
const targets = [];
|
||||
const re = /\brequire\(\s*(['"])([^./'"][^'"\n]*)\1\s*\)/g;
|
||||
let m;
|
||||
while ((m = re.exec(src)) !== null) targets.push(m[2]);
|
||||
return targets;
|
||||
};
|
||||
|
||||
// Parse `'!**/<name>/**'` entries from electron-builder.yaml. Multi-segment
|
||||
// patterns like '!**/@nx/nx-darwin-*/**' don't match this shape and are
|
||||
// intentionally skipped — only full-package exclusions count as "you can't
|
||||
// import this from electron/".
|
||||
const parseExcludedPackages = (yamlPath) => {
|
||||
const text = fs.readFileSync(yamlPath, 'utf8');
|
||||
const re = /^\s*-\s*['"]!\*\*\/([^/'"*]+)\/\*\*['"]\s*$/gm;
|
||||
const set = new Set();
|
||||
let m;
|
||||
while ((m = re.exec(text)) !== null) set.add(m[1]);
|
||||
return set;
|
||||
};
|
||||
|
||||
// '@scope/pkg/sub' -> '@scope/pkg'; 'pkg/sub' -> 'pkg'. Used to test against
|
||||
// the exclusion set, which can hold either a scope ('@noble' covers all of
|
||||
// @noble/*) or a full package name ('hash-wasm').
|
||||
const isExcludedBareRequire = (target, excluded) => {
|
||||
if (target.startsWith('@')) {
|
||||
const [scope, pkg] = target.split('/');
|
||||
if (excluded.has(scope)) return scope;
|
||||
if (pkg != null && excluded.has(`${scope}/${pkg}`)) return `${scope}/${pkg}`;
|
||||
return null;
|
||||
}
|
||||
const [pkg] = target.split('/');
|
||||
return excluded.has(pkg) ? pkg : null;
|
||||
};
|
||||
|
||||
// Guard: a resolved path must stay inside the extracted asar tree.
|
||||
// Without this, a relative require with enough `..` climbs above `tmp` and
|
||||
// Node's resolver happily hits the host filesystem (or the host's
|
||||
|
|
@ -75,6 +121,17 @@ const collectRelativeRequires = (src) => {
|
|||
const tmpPrefix = tmp.endsWith(path.sep) ? tmp : tmp + path.sep;
|
||||
const isInsideTmp = (p) => p === tmp || p.startsWith(tmpPrefix);
|
||||
|
||||
// Resolve electron-builder.yaml relative to this script's location so the
|
||||
// check works regardless of where node is invoked from.
|
||||
const builderYaml = path.join(__dirname, '..', 'electron-builder.yaml');
|
||||
let excludedPackages;
|
||||
try {
|
||||
excludedPackages = parseExcludedPackages(builderYaml);
|
||||
} catch (err) {
|
||||
console.error(`Could not read exclusion list from ${builderYaml}: ${err.message}`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
let exitCode = 0;
|
||||
try {
|
||||
asar.extractAll(asarPath, tmp);
|
||||
|
|
@ -104,8 +161,14 @@ try {
|
|||
);
|
||||
}
|
||||
} catch {
|
||||
errors.push(`${path.relative(tmp, file)}: cannot resolve require('${target}')`);
|
||||
}
|
||||
}
|
||||
for (const target of collectBareRequires(src)) {
|
||||
const hit = isExcludedBareRequire(target, excludedPackages);
|
||||
if (hit != null) {
|
||||
errors.push(
|
||||
`${path.relative(tmp, file)}: cannot resolve require('${target}')`,
|
||||
`${path.relative(tmp, file)}: require('${target}') targets '${hit}', which electron-builder.yaml excludes from app.asar — the packaged release will crash with MODULE_NOT_FOUND`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -118,22 +181,20 @@ try {
|
|||
if (exitCode !== 0) process.exit(exitCode);
|
||||
|
||||
if (errors.length) {
|
||||
console.error(
|
||||
`Found ${errors.length} unresolvable require() target(s) inside ${asarPath}:\n`,
|
||||
);
|
||||
console.error(`Found ${errors.length} require() problem(s) inside ${asarPath}:\n`);
|
||||
for (const err of errors) console.error(' ' + err);
|
||||
console.error('\nFix for relative-require failures: move the imported module under');
|
||||
console.error('electron/shared-with-frontend/ (or another path covered by files: in');
|
||||
console.error('electron-builder.yaml) and update importers.');
|
||||
console.error(
|
||||
'\nThis means a .ts file under electron/ imported a path that was not packaged.',
|
||||
);
|
||||
console.error(
|
||||
'Fix: move the imported module under electron/shared-with-frontend/ (or another',
|
||||
);
|
||||
console.error(
|
||||
'path covered by files: in electron-builder.yaml) and update importers.',
|
||||
'\nFix for excluded-package requires: either drop the require in electron/',
|
||||
);
|
||||
console.error('or remove the matching `!**/<pkg>/**` line from electron-builder.yaml');
|
||||
console.error('(and the two build/electron-builder.mas*.yaml siblings).');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`OK: all require() targets under electron/ resolve cleanly in ${asarPath}`);
|
||||
console.log(
|
||||
`OK: all relative require() targets under electron/ resolve cleanly in ${asarPath}`,
|
||||
` (${excludedPackages.size} package(s) checked against asar exclusion list)`,
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue