fix(build): restore package.json if electron-builder leaves it mutated
electron-builder's `extraMetadata` feature mutates the source package.json in place during packaging and restores it on clean exit. When the builder is killed mid-flight (parent task timeout, Ctrl-C, OOM) the restore never fires and the source tree is left with a stripped scripts block — every subsequent pnpm command then fails with "Missing script" until someone notices the drift and runs `git checkout`. Observed once in this session when a `build:dir` task was cancelled mid-packaging. Wrap the builder invocation in a bytes-level snapshot + idempotent restore, driven by both normal-exit and SIGINT/SIGTERM/SIGHUP handlers. On a clean run the builder's own restore wrote the original bytes back, our final compare is byte-equal, and the restore is a no-op. On an abnormal termination the snapshot overwrites whatever electron-builder left behind so the working tree is guaranteed clean regardless of exit path. The snapshot approach is intentional over a "reject if tree dirty before build" guard: this is a recovery mechanism, not a precondition, and the repo's own `package.json` is the truth — anything else in there at the moment the builder took its snapshot is the desired state.
This commit is contained in:
1 parent
f60ba5fa29
commit
740d0812ee
1 file changed
+49
-6
@@ -1,4 +1,4 @@
|
|||||||
import { mkdir, rm } from "node:fs/promises";
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { prepareBuildEnv, resolvePublishMode } from "./build/prepare-env.mjs";
|
import { prepareBuildEnv, resolvePublishMode } from "./build/prepare-env.mjs";
|
||||||
@@ -42,9 +42,52 @@ const builderArgs = buildElectronBuilderArgs({
|
|||||||
prepared,
|
prepared,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await runElectronBuilder(builderArgs, prepared.env);
|
// electron-builder with `extraMetadata` mutates the source package.json in
|
||||||
if (result.signal) {
|
// place during packaging and restores it when it finishes. If the process is
|
||||||
process.kill(process.pid, result.signal);
|
// killed mid-flight (CI timeout, Ctrl-C, OOM, parent task cancellation) the
|
||||||
} else {
|
// restore step never fires and the source tree is left with a stripped
|
||||||
process.exit(result.code);
|
// scripts block — every subsequent `pnpm` command then fails until someone
|
||||||
|
// notices the drift and runs `git checkout`. The snapshot + signal-safe
|
||||||
|
// restore below is idempotent: on a clean exit electron-builder's own
|
||||||
|
// restore already wrote the original bytes back and this is a no-op; on
|
||||||
|
// any abnormal termination we guarantee the working tree is clean.
|
||||||
|
const packageJsonSnapshot = await readFile(packageJsonPath);
|
||||||
|
let packageJsonRestored = false;
|
||||||
|
async function restorePackageJsonIfNeeded() {
|
||||||
|
if (packageJsonRestored) return;
|
||||||
|
packageJsonRestored = true;
|
||||||
|
try {
|
||||||
|
const current = await readFile(packageJsonPath);
|
||||||
|
if (current.equals(packageJsonSnapshot)) return;
|
||||||
|
await writeFile(packageJsonPath, packageJsonSnapshot);
|
||||||
|
console.log("[build-desktop] Restored package.json from snapshot after builder left it mutated.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[build-desktop] Failed to restore package.json snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
||||||
|
const signalHandlers = new Map();
|
||||||
|
for (const signal of signals) {
|
||||||
|
const handler = () => {
|
||||||
|
void restorePackageJsonIfNeeded().finally(() => {
|
||||||
|
process.kill(process.pid, signal);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
signalHandlers.set(signal, handler);
|
||||||
|
process.once(signal, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await runElectronBuilder(builderArgs, prepared.env);
|
||||||
|
await restorePackageJsonIfNeeded();
|
||||||
|
for (const [signal, handler] of signalHandlers) process.off(signal, handler);
|
||||||
|
if (result.signal) {
|
||||||
|
process.kill(process.pid, result.signal);
|
||||||
|
} else {
|
||||||
|
process.exit(result.code);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await restorePackageJsonIfNeeded();
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user