Prune wrong-platform native binaries from Electron builds
Strips onnxruntime-node (linux/win32), claude-agent-sdk ripgrep (non-target platforms), node-pty prebuilds, and sharp libvips from the app.asar.unpacked directory during afterPack. Saves ~80 MB on macOS arm64 builds.
This commit is contained in:
@@ -7,8 +7,125 @@ const WRAPPER_SCRIPT = `#!/bin/bash
|
||||
exec "$(dirname "$(readlink -f "$0")")/${EXECUTABLE_NAME}.bin" --no-sandbox "$@"
|
||||
`;
|
||||
|
||||
// electron-builder arch enum → Node.js arch string
|
||||
const ARCH_MAP = { 0: "ia32", 1: "x64", 2: "armv7l", 3: "arm64", 4: "universal" };
|
||||
|
||||
const RIPGREP_PLATFORM_DIR = {
|
||||
darwin: { arm64: "arm64-darwin", x64: "x64-darwin" },
|
||||
linux: { arm64: "arm64-linux", x64: "x64-linux" },
|
||||
win32: { arm64: "arm64-win32", x64: "x64-win32" },
|
||||
};
|
||||
|
||||
function rmSafe(target) {
|
||||
fs.rmSync(target, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function pruneChildrenExcept(parent, keep) {
|
||||
if (!fs.existsSync(parent)) return;
|
||||
for (const entry of fs.readdirSync(parent)) {
|
||||
if (!keep.has(entry)) {
|
||||
rmSafe(path.join(parent, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneOnnxRuntime(nodeModules, platform, arch) {
|
||||
const onnxBin = path.join(nodeModules, "onnxruntime-node", "bin", "napi-v6");
|
||||
if (!fs.existsSync(onnxBin)) return;
|
||||
|
||||
const otherPlatforms = ["darwin", "linux", "win32"].filter((p) => p !== platform);
|
||||
for (const p of otherPlatforms) {
|
||||
rmSafe(path.join(onnxBin, p));
|
||||
}
|
||||
|
||||
pruneChildrenExcept(path.join(onnxBin, platform), new Set([arch]));
|
||||
|
||||
if (platform === "linux") {
|
||||
const archDir = path.join(onnxBin, "linux", arch);
|
||||
if (fs.existsSync(archDir)) {
|
||||
for (const name of fs.readdirSync(archDir)) {
|
||||
if (name.includes("cuda") || name.includes("tensorrt")) {
|
||||
fs.rmSync(path.join(archDir, name), { force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneClaudeAgentSdk(nodeModules, platform, arch) {
|
||||
const vendorRoot = path.join(nodeModules, "@anthropic-ai", "claude-agent-sdk", "vendor");
|
||||
const keepName = RIPGREP_PLATFORM_DIR[platform]?.[arch];
|
||||
if (!keepName) return;
|
||||
|
||||
pruneChildrenExcept(path.join(vendorRoot, "ripgrep"), new Set(["COPYING", keepName]));
|
||||
pruneChildrenExcept(path.join(vendorRoot, "tree-sitter-bash"), new Set([keepName]));
|
||||
}
|
||||
|
||||
function pruneNodePty(nodeModules, platform, arch) {
|
||||
const prebuilds = path.join(nodeModules, "node-pty", "prebuilds");
|
||||
pruneChildrenExcept(prebuilds, new Set([`${platform}-${arch}`]));
|
||||
|
||||
if (platform !== "win32") {
|
||||
rmSafe(path.join(nodeModules, "node-pty", "third_party"));
|
||||
}
|
||||
}
|
||||
|
||||
function pruneSharpLibvips(nodeModules, platform, arch) {
|
||||
const prefix = `sharp-libvips-${platform}-${arch}`;
|
||||
const imgDir = path.join(nodeModules, "@img");
|
||||
if (!fs.existsSync(imgDir)) return;
|
||||
|
||||
for (const entry of fs.readdirSync(imgDir)) {
|
||||
if (entry.startsWith("sharp-") && entry !== prefix && !entry.startsWith(`sharp-${platform}-${arch}`)) {
|
||||
rmSafe(path.join(imgDir, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pruneNativeModules(appOutDir, platform, arch) {
|
||||
const resourcesDir =
|
||||
platform === "darwin"
|
||||
? path.join(appOutDir, `${EXECUTABLE_NAME}.app`, "Contents", "Resources")
|
||||
: path.join(appOutDir, "resources");
|
||||
|
||||
const nodeModules = path.join(resourcesDir, "app.asar.unpacked", "node_modules");
|
||||
if (!fs.existsSync(nodeModules)) return;
|
||||
|
||||
const before = dirSizeSync(nodeModules);
|
||||
|
||||
pruneOnnxRuntime(nodeModules, platform, arch);
|
||||
pruneClaudeAgentSdk(nodeModules, platform, arch);
|
||||
pruneNodePty(nodeModules, platform, arch);
|
||||
pruneSharpLibvips(nodeModules, platform, arch);
|
||||
|
||||
const after = dirSizeSync(nodeModules);
|
||||
const savedMB = ((before - after) / 1024 / 1024).toFixed(1);
|
||||
console.log(`Pruned native modules: ${savedMB} MB removed (${fmtMB(before)} → ${fmtMB(after)})`);
|
||||
}
|
||||
|
||||
function dirSizeSync(dir) {
|
||||
let total = 0;
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true, recursive: true })) {
|
||||
if (entry.isFile()) {
|
||||
try {
|
||||
total += fs.statSync(path.join(entry.parentPath || entry.path, entry.name)).size;
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function fmtMB(bytes) {
|
||||
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
exports.default = async function afterPack(context) {
|
||||
if (context.electronPlatformName !== "linux") return;
|
||||
const platform = context.electronPlatformName;
|
||||
const arch = ARCH_MAP[context.arch] || process.arch;
|
||||
|
||||
pruneNativeModules(context.appOutDir, platform, arch);
|
||||
|
||||
if (platform !== "linux") return;
|
||||
|
||||
const chromeSandbox = path.join(context.appOutDir, "chrome-sandbox");
|
||||
if (fs.existsSync(chromeSandbox)) {
|
||||
|
||||
Reference in New Issue
Block a user