/** * after-pack.cjs * * electron-builder afterPack hook for zn-ai * * This hook runs AFTER electron-builder finishes packing. * It handles: * 1. Copying and bundling electron/scripts/ directory * 2. Ensuring required dependencies (playwright, chromium-bidi, bytenode) are included * 3. Cleaning up unnecessary development files to reduce package size */ const fs = require('fs-extra'); const path = require('path'); const esbuild = require('esbuild'); /** * Remove development artifacts from a directory (recursive). * Removes: test directories, TypeScript definitions, source maps, docs, etc. */ function cleanupUnnecessaryFiles(dir) { let removedCount = 0; const REMOVE_DIRS = new Set([ 'test', 'tests', '__tests__', '.github', 'docs', 'examples', 'example', ]); const REMOVE_FILE_EXTS = ['.d.ts', '.d.ts.map', '.js.map', '.mjs.map', '.ts.map', '.markdown']; const REMOVE_FILE_NAMES = new Set([ '.DS_Store', 'README.md', 'CHANGELOG.md', 'LICENSE.md', 'CONTRIBUTING.md', 'tsconfig.json', '.npmignore', '.eslintrc', '.prettierrc', '.editorconfig', ]); function walk(currentDir) { let entries; try { entries = fs.readdirSync(currentDir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { const fullPath = path.join(currentDir, entry.name); if (entry.isDirectory()) { if (REMOVE_DIRS.has(entry.name)) { try { fs.rmSync(fullPath, { recursive: true, force: true }); removedCount++; } catch { /* ignore */ } } else { walk(fullPath); } } else if (entry.isFile()) { const name = entry.name; if (REMOVE_FILE_NAMES.has(name) || REMOVE_FILE_EXTS.some(e => name.endsWith(e))) { try { fs.rmSync(fullPath, { force: true }); removedCount++; } catch { /* ignore */ } } } } } walk(dir); return removedCount; } /** * Clean up platform-specific native packages (optional). * Currently not needed for zn-ai, but kept as a placeholder. */ function cleanupNativePlatformPackages(nodeModulesDir, platform, arch) { // No platform-specific native packages identified for zn-ai yet return 0; } module.exports = async function afterPack(context) { const { appOutDir, electronPlatformName: platform, arch } = context; console.log(`Running afterPack hook for ${platform}-${arch}`); console.log(`App output directory: ${appOutDir}`); // 1. Handle electron/scripts/ directory const scriptsSrc = path.join(__dirname, '..', 'electron/scripts'); const scriptsDest = path.join(appOutDir, 'resources', 'scripts'); if (await fs.pathExists(scriptsSrc)) { await fs.ensureDir(scriptsDest); const files = await fs.readdir(scriptsSrc); for (const file of files) { const srcFile = path.join(scriptsSrc, file); const destFile = path.join(scriptsDest, file); if (file.endsWith('.js')) { // Bundle JavaScript files with esbuild try { await esbuild.build({ entryPoints: [srcFile], outfile: destFile, bundle: true, platform: 'node', target: 'node24', // Adjust based on Electron version external: ['electron'], // Exclude electron from bundling format: 'cjs', }); console.log(`Bundled: ${file}`); } catch (error) { console.error(`Failed to bundle ${file}:`, error); // Fallback to copying the file await fs.copy(srcFile, destFile); } } else { // Copy other files as-is await fs.copy(srcFile, destFile); console.log(`Copied: ${file}`); } } } // 2. Ensure required dependencies are included // electron-builder may exclude some dependencies due to .gitignore rules const ensureDependency = async (depName) => { const src = path.join(__dirname, '..', 'node_modules', depName); const dest = path.join(appOutDir, 'node_modules', depName); if (await fs.pathExists(src)) { await fs.ensureDir(path.dirname(dest)); if (!(await fs.pathExists(dest))) { await fs.copy(src, dest); console.log(`Copied dependency: ${depName}`); } } }; // List of dependencies that need to be explicitly copied const requiredDeps = ['playwright', 'playwright-core', 'chromium-bidi', 'bytenode']; for (const dep of requiredDeps) { await ensureDependency(dep); } // 3. Clean up unnecessary development files from node_modules (skip if SKIP_AFTERPACK_CLEANUP is set) if (!process.env.SKIP_AFTERPACK_CLEANUP) { const nodeModulesDest = path.join(appOutDir, 'node_modules'); if (await fs.pathExists(nodeModulesDest)) { console.log('Cleaning up development files in node_modules...'); const removed = cleanupUnnecessaryFiles(nodeModulesDest); console.log(`Removed ${removed} unnecessary files/directories from node_modules.`); } // 4. Clean up unnecessary files in scripts directory if (await fs.pathExists(scriptsDest)) { console.log('Cleaning up development files in scripts directory...'); const removedScripts = cleanupUnnecessaryFiles(scriptsDest); console.log(`Removed ${removedScripts} unnecessary files/directories from scripts.`); } } else { console.log('Skipping afterPack cleanup (SKIP_AFTERPACK_CLEANUP is set)'); } // 5. Optional: platform-specific native package cleanup // cleanupNativePlatformPackages(nodeModulesDest, platform, arch); console.log('afterPack hook completed successfully'); };