diff --git a/packages/desktop/scripts/after-pack.js b/packages/desktop/scripts/after-pack.js index ad92f7f5b..258393187 100644 --- a/packages/desktop/scripts/after-pack.js +++ b/packages/desktop/scripts/after-pack.js @@ -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)) {