const { Arch } = require('builder-util'); const fs = require('fs'); const path = require('path'); const os = require('os'); const { normalizeArch, rebuildSingleModule, verifyModuleBinary, getModulesToRebuild, } = require('./rebuildNativeModules'); const { verifyBundledAioncoreResources } = require('../packages/shared-scripts/src/verify-bundled-aioncore-resources'); /** * afterPack hook for electron-builder * Rebuilds native modules for cross-architecture builds */ function resolveResourcesDir(electronPlatformName, appOutDir, packager) { if (electronPlatformName !== 'darwin') return path.join(appOutDir, 'resources'); const appName = packager?.appInfo?.productFilename || 'AionUi'; return path.join(appOutDir, `${appName}.app`, 'Contents', 'Resources'); } function verifyBundledResources(resourcesDir, electronPlatformName, targetArch) { const result = verifyBundledAioncoreResources({ resourcesDir, electronPlatformName, targetArch, }); if (result.missing.length > 0) { console.error(` Missing bundled resources: ${result.missing.join(', ')}`); throw new Error(`Packaged app is missing required bundled resource(s): ${result.missing.join(', ')}`); } console.log(` āœ“ Bundled resources verified for ${result.runtimeKey} (${result.checked.length} checks)`); } module.exports = async function afterPack(context) { const { arch, electronPlatformName, appOutDir, packager } = context; const targetArch = normalizeArch(typeof arch === 'string' ? arch : Arch[arch] || process.arch); const buildArch = normalizeArch(os.arch()); console.log(`\nšŸ”§ afterPack hook started`); console.log(` Platform: ${electronPlatformName}, Build arch: ${buildArch}, Target arch: ${targetArch}`); const isCrossCompile = buildArch !== targetArch; const forceRebuild = process.env.FORCE_NATIVE_REBUILD === 'true'; const needsSameArchRebuild = electronPlatformName === 'win32'; // åŖęœ‰ Windows éœ€č¦åŒęž¶ęž„é‡å»ŗä»„åŒ¹é… Electron ABI | Only Windows needs same-arch rebuild to match Electron ABI // Linux ä½æē”Øé¢„ē¼–čÆ‘äŗŒčæ›åˆ¶ļ¼Œéæå… GLIBC ē‰ˆęœ¬ä¾čµ– | Linux uses prebuilt binaries which are GLIBC-independent const resourcesDir = resolveResourcesDir(electronPlatformName, appOutDir, packager); console.log(` Checking resources directory: ${resourcesDir}`); if (fs.existsSync(resourcesDir)) { const resourcesContents = fs.readdirSync(resourcesDir); console.log(` Contents: ${resourcesContents.join(', ')}`); const unpackedDir = path.join(resourcesDir, 'app.asar.unpacked'); if (fs.existsSync(unpackedDir)) { const unpackedContents = fs.readdirSync(unpackedDir); console.log(` app.asar.unpacked contents: ${unpackedContents.join(', ')}`); const nodeModulesDir = path.join(unpackedDir, 'node_modules'); if (fs.existsSync(nodeModulesDir)) { const modulesContents = fs.readdirSync(nodeModulesDir); console.log(` node_modules contents: ${modulesContents.slice(0, 10).join(', ')}...`); } else { console.warn(` āš ļø node_modules not found in app.asar.unpacked`); } } else { console.warn(` āš ļø app.asar.unpacked not found`); } verifyBundledResources(resourcesDir, electronPlatformName, targetArch); } else { throw new Error(`resources directory not found: ${resourcesDir}`); } if (!isCrossCompile && !needsSameArchRebuild && !forceRebuild) { console.log(` āœ“ Same architecture, rebuild skipped (set FORCE_NATIVE_REBUILD=true to override)\n`); return; } // Note: Previously there was an optimization to skip macOS cross-compilation, // but this caused incorrect architecture binaries (arm64) to be included in x64 builds. // Now we always rebuild native modules for cross-compilation to ensure correctness. // The rebuild process uses prebuild-install first (fast), falling back to source compilation only when needed. if (isCrossCompile) { console.log(` āš ļø Cross-compilation detected (${buildArch} → ${targetArch}), will rebuild native modules`); if (electronPlatformName === 'darwin') { console.log(` šŸ’” Using prebuild-install for faster cross-architecture build`); } } else if (needsSameArchRebuild || forceRebuild) { console.log(` ā„¹ļø Rebuilding native modules for platform requirements (force=${forceRebuild})`); } console.log(`\nšŸ”§ Checking native modules (${electronPlatformName}-${targetArch})...`); console.log(` appOutDir: ${appOutDir}`); const electronVersion = packager?.info?.electronVersion ?? packager?.config?.electronVersion ?? require('../package.json').devDependencies?.electron?.replace(/^\D*/, ''); const nodeModulesDir = path.join(resourcesDir, 'app.asar.unpacked', 'node_modules'); // Modules that need to be rebuilt for cross-compilation // Use platform-specific module list (Windows skips node-pty due to cross-compilation issues) const modulesToRebuild = getModulesToRebuild(electronPlatformName); console.log(` Modules to rebuild: ${modulesToRebuild.join(', ')}`); // For cross-compilation, clean up build artifacts from the wrong architecture // This prevents node-gyp-build from loading incorrect binaries if (isCrossCompile) { console.log(`\n🧹 Cleaning up wrong-architecture build artifacts...`); for (const moduleName of modulesToRebuild) { const moduleRoot = path.join(nodeModulesDir, moduleName); if (!fs.existsSync(moduleRoot)) continue; // Remove build/ directory (contains wrong-arch compiled binaries) const buildDir = path.join(moduleRoot, 'build'); if (fs.existsSync(buildDir)) { fs.rmSync(buildDir, { recursive: true, force: true }); console.log(` āœ“ Removed ${moduleName}/build/`); } // Remove bin/ directory (might contain wrong-arch binaries) const binDir = path.join(moduleRoot, 'bin'); if (fs.existsSync(binDir)) { fs.rmSync(binDir, { recursive: true, force: true }); console.log(` āœ“ Removed ${moduleName}/bin/`); } } // Also clean up architecture-specific packages that shouldn't be included // Remove packages for the opposite architecture of the target const wrongArchSuffix = targetArch === 'arm64' ? 'x64' : 'arm64'; console.log(`\n🧹 Removing ${wrongArchSuffix}-specific optional dependencies (target: ${targetArch})...`); if (fs.existsSync(nodeModulesDir)) { const allModules = fs.readdirSync(nodeModulesDir); for (const module of allModules) { const modulePath = path.join(nodeModulesDir, module); // Handle scoped packages (e.g., @lydell, @napi-rs) if (module.startsWith('@') && fs.existsSync(modulePath) && fs.statSync(modulePath).isDirectory()) { const scopedPackages = fs.readdirSync(modulePath); for (const pkg of scopedPackages) { if (pkg.includes(`-${wrongArchSuffix}`) || pkg.includes(`-${electronPlatformName}-${wrongArchSuffix}`)) { const pkgPath = path.join(modulePath, pkg); if (fs.existsSync(pkgPath) && fs.statSync(pkgPath).isDirectory()) { fs.rmSync(pkgPath, { recursive: true, force: true }); console.log(` āœ“ Removed ${module}/${pkg}`); } } } } // Handle regular packages else if ( module.includes(`-${wrongArchSuffix}`) || module.includes(`-${electronPlatformName}-${wrongArchSuffix}`) ) { if (fs.existsSync(modulePath) && fs.statSync(modulePath).isDirectory()) { fs.rmSync(modulePath, { recursive: true, force: true }); console.log(` āœ“ Removed ${module}`); } } } } } const failedModules = []; for (const moduleName of modulesToRebuild) { const moduleRoot = path.join(nodeModulesDir, moduleName); if (!fs.existsSync(moduleRoot)) { console.warn(` āš ļø ${moduleName} not found, skipping`); continue; } console.log(` āœ“ Found ${moduleName}, rebuilding for ${targetArch}...`); // For Windows, prefer prebuild-install first (faster and more reliable in CI) // electron-rebuild can hang on "Searching dependency tree" in some CI environments // prebuild-install will fall back to electron-rebuild internally if no prebuilt binary exists const forceRebuildFromSource = false; // Always try prebuild-install first const success = rebuildSingleModule({ moduleName, moduleRoot, platform: electronPlatformName, arch: targetArch, electronVersion, projectRoot: path.resolve(__dirname, '..'), buildArch: buildArch, // Pass build architecture for cross-compile detection forceRebuild: forceRebuildFromSource, // Always try prebuild-install first, fallback to rebuild }); if (success) { console.log(` āœ“ Rebuild completed`); } else { console.error(` āœ— Rebuild failed`); failedModules.push(moduleName); continue; } const verified = verifyModuleBinary(moduleRoot, moduleName); if (verified) { console.log(` āœ“ Binary verification passed`); } else { console.error(` āœ— Binary verification failed`); failedModules.push(moduleName); } console.log(''); // Empty line between modules } if (failedModules.length > 0) { throw new Error(`Failed to rebuild modules for ${electronPlatformName}-${targetArch}: ${failedModules.join(', ')}`); } console.log(`āœ… All native modules rebuilt successfully for ${targetArch}\n`); };