electron-after-pack.cjs
1 'use strict' 2 3 /* eslint-disable @typescript-eslint/no-require-imports */ 4 const path = require('node:path') 5 const fs = require('node:fs') 6 const { spawnSync } = require('node:child_process') 7 8 const ARCH_NAMES = { 0: 'ia32', 1: 'x64', 2: 'armv7l', 3: 'arm64', 4: 'universal' } 9 10 // Native modules that live in .next/standalone/node_modules and load inside the 11 // Electron child process. Root node_modules are already rebuilt for the target 12 // Electron ABI by electron-builder's @electron/rebuild pass before this hook 13 // fires, so copying those .node files into the packaged standalone is the most 14 // reliable way to keep the two trees in sync per architecture. 15 const NATIVE_MODULES = [ 16 'better-sqlite3', 17 '@mongodb-js/zstd', 18 'node-liblzma', 19 'utf-8-validate', 20 ] 21 22 function copyIfExists(src, dest) { 23 if (!fs.existsSync(src)) return false 24 fs.mkdirSync(path.dirname(dest), { recursive: true }) 25 fs.copyFileSync(src, dest) 26 return true 27 } 28 29 function syncNativeBuildDir(rootPkgDir, standalonePkgDir) { 30 if (!fs.existsSync(rootPkgDir) || !fs.existsSync(standalonePkgDir)) return false 31 const rootBuild = path.join(rootPkgDir, 'build', 'Release') 32 const standaloneBuild = path.join(standalonePkgDir, 'build', 'Release') 33 if (!fs.existsSync(rootBuild)) return false 34 fs.mkdirSync(standaloneBuild, { recursive: true }) 35 let synced = false 36 for (const entry of fs.readdirSync(rootBuild)) { 37 if (!entry.endsWith('.node')) continue 38 copyIfExists(path.join(rootBuild, entry), path.join(standaloneBuild, entry)) 39 synced = true 40 } 41 return synced 42 } 43 44 exports.default = async function afterPack(context) { 45 if (context.electronPlatformName !== 'darwin') return 46 47 const projectDir = context.packager.info.projectDir 48 const appName = context.packager.appInfo.productFilename 49 const appPath = path.join(context.appOutDir, `${appName}.app`) 50 const standaloneNodeModules = path.join(appPath, 'Contents', 'Resources', '.next', 'standalone', 'node_modules') 51 const rootNodeModules = path.join(projectDir, 'node_modules') 52 const archName = ARCH_NAMES[context.arch] 53 if (!archName) throw new Error(`afterPack: unknown arch ${context.arch}`) 54 55 console.log(`[after-pack] syncing native modules into standalone for arch=${archName}`) 56 for (const moduleName of NATIVE_MODULES) { 57 const rootPkg = path.join(rootNodeModules, moduleName) 58 const standalonePkg = path.join(standaloneNodeModules, moduleName) 59 const synced = syncNativeBuildDir(rootPkg, standalonePkg) 60 console.log(`[after-pack] ${moduleName}: ${synced ? 'synced' : 'skipped'}`) 61 } 62 63 console.log(`[after-pack] ad-hoc signing ${appPath}`) 64 const codesign = spawnSync( 65 'codesign', 66 [ 67 '--sign', '-', 68 '--force', 69 '--deep', 70 '--timestamp=none', 71 '--preserve-metadata=entitlements,requirements,flags,runtime', 72 appPath, 73 ], 74 { stdio: 'inherit' }, 75 ) 76 if (codesign.status !== 0) { 77 throw new Error(`afterPack: codesign ad-hoc failed with status ${codesign.status}`) 78 } 79 }