Co-authored-by: Cursor Agent <cursor-agent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
184 lines
7.2 KiB
JavaScript
184 lines
7.2 KiB
JavaScript
/**
|
|
* after-pack.cjs
|
|
*
|
|
* electron-builder afterPack hook.
|
|
*
|
|
* Problem: electron-builder respects .gitignore when copying extraResources.
|
|
* Since .gitignore contains "node_modules/", the openclaw bundle's
|
|
* node_modules directory is silently skipped during the extraResources copy.
|
|
*
|
|
* Solution: This hook runs AFTER electron-builder finishes packing. It manually
|
|
* copies build/openclaw/node_modules/ into the output resources directory,
|
|
* bypassing electron-builder's glob filtering entirely.
|
|
*
|
|
* Additionally it performs two rounds of cleanup:
|
|
* 1. General cleanup — removes dev artifacts (type defs, source maps, docs,
|
|
* test dirs) from both the openclaw root and its node_modules.
|
|
* 2. Platform-specific cleanup — strips native binaries for non-target
|
|
* platforms (koffi multi-platform prebuilds, @napi-rs/canvas, @img/sharp,
|
|
* @mariozechner/clipboard).
|
|
*/
|
|
|
|
const { cpSync, existsSync, readdirSync, rmSync, statSync } = require('fs');
|
|
const { join } = require('path');
|
|
|
|
// ── Arch helpers ─────────────────────────────────────────────────────────────
|
|
// electron-builder Arch enum: 0=ia32, 1=x64, 2=armv7l, 3=arm64, 4=universal
|
|
const ARCH_MAP = { 0: 'ia32', 1: 'x64', 2: 'armv7l', 3: 'arm64', 4: 'universal' };
|
|
|
|
function resolveArch(archEnum) {
|
|
return ARCH_MAP[archEnum] || 'x64';
|
|
}
|
|
|
|
// ── General cleanup ──────────────────────────────────────────────────────────
|
|
|
|
function cleanupUnnecessaryFiles(dir) {
|
|
let removedCount = 0;
|
|
|
|
const REMOVE_DIRS = new Set([
|
|
'test', 'tests', '__tests__', '.github', '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 = readdirSync(currentDir, { withFileTypes: true }); } catch { return; }
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = join(currentDir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
if (REMOVE_DIRS.has(entry.name)) {
|
|
try { rmSync(fullPath, { recursive: true, force: true }); removedCount++; } catch { /* */ }
|
|
} 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 { rmSync(fullPath, { force: true }); removedCount++; } catch { /* */ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
walk(dir);
|
|
return removedCount;
|
|
}
|
|
|
|
// ── Platform-specific: koffi ─────────────────────────────────────────────────
|
|
// koffi ships 18 platform pre-builds under koffi/build/koffi/{platform}_{arch}/.
|
|
// We only need the one matching the target.
|
|
|
|
function cleanupKoffi(nodeModulesDir, platform, arch) {
|
|
const koffiDir = join(nodeModulesDir, 'koffi', 'build', 'koffi');
|
|
if (!existsSync(koffiDir)) return 0;
|
|
|
|
const keepTarget = `${platform}_${arch}`;
|
|
let removed = 0;
|
|
for (const entry of readdirSync(koffiDir)) {
|
|
if (entry !== keepTarget) {
|
|
try { rmSync(join(koffiDir, entry), { recursive: true, force: true }); removed++; } catch { /* */ }
|
|
}
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
// ── Platform-specific: scoped native packages ────────────────────────────────
|
|
// Packages like @napi-rs/canvas-darwin-arm64, @img/sharp-linux-x64, etc.
|
|
// Only the variant matching the target platform should survive.
|
|
|
|
const PLATFORM_NATIVE_SCOPES = {
|
|
'@napi-rs': /^canvas-(darwin|linux|win32)-(x64|arm64)/,
|
|
'@img': /^sharp(?:-libvips)?-(darwin|linux|win32)-(x64|arm64)/,
|
|
'@mariozechner': /^clipboard-(darwin|linux|win32)-(x64|arm64|universal)/,
|
|
};
|
|
|
|
function cleanupNativePlatformPackages(nodeModulesDir, platform, arch) {
|
|
let removed = 0;
|
|
|
|
for (const [scope, pattern] of Object.entries(PLATFORM_NATIVE_SCOPES)) {
|
|
const scopeDir = join(nodeModulesDir, scope);
|
|
if (!existsSync(scopeDir)) continue;
|
|
|
|
for (const entry of readdirSync(scopeDir)) {
|
|
const match = entry.match(pattern);
|
|
if (!match) continue; // not a platform-specific package, leave it
|
|
|
|
const pkgPlatform = match[1];
|
|
const pkgArch = match[2];
|
|
|
|
const isMatch =
|
|
pkgPlatform === platform &&
|
|
(pkgArch === arch || pkgArch === 'universal');
|
|
|
|
if (!isMatch) {
|
|
try {
|
|
rmSync(join(scopeDir, entry), { recursive: true, force: true });
|
|
removed++;
|
|
} catch { /* */ }
|
|
}
|
|
}
|
|
}
|
|
|
|
return removed;
|
|
}
|
|
|
|
// ── Main hook ────────────────────────────────────────────────────────────────
|
|
|
|
exports.default = async function afterPack(context) {
|
|
const appOutDir = context.appOutDir;
|
|
const platform = context.electronPlatformName; // 'win32' | 'darwin' | 'linux'
|
|
const arch = resolveArch(context.arch);
|
|
|
|
console.log(`[after-pack] Target: ${platform}/${arch}`);
|
|
|
|
const src = join(__dirname, '..', 'build', 'openclaw', 'node_modules');
|
|
|
|
let resourcesDir;
|
|
if (platform === 'darwin') {
|
|
const appName = context.packager.appInfo.productFilename;
|
|
resourcesDir = join(appOutDir, `${appName}.app`, 'Contents', 'Resources');
|
|
} else {
|
|
resourcesDir = join(appOutDir, 'resources');
|
|
}
|
|
|
|
const openclawRoot = join(resourcesDir, 'openclaw');
|
|
const dest = join(openclawRoot, 'node_modules');
|
|
|
|
if (!existsSync(src)) {
|
|
console.warn('[after-pack] ⚠️ build/openclaw/node_modules not found. Run bundle-openclaw first.');
|
|
return;
|
|
}
|
|
|
|
// 1. Copy node_modules (electron-builder skips it due to .gitignore)
|
|
const depCount = readdirSync(src, { withFileTypes: true })
|
|
.filter(d => d.isDirectory() && d.name !== '.bin')
|
|
.length;
|
|
|
|
console.log(`[after-pack] Copying ${depCount} openclaw dependencies to ${dest} ...`);
|
|
cpSync(src, dest, { recursive: true });
|
|
console.log('[after-pack] ✅ openclaw node_modules copied.');
|
|
|
|
// 2. General cleanup on the full openclaw directory (not just node_modules)
|
|
console.log('[after-pack] 🧹 Cleaning up unnecessary files ...');
|
|
const removedRoot = cleanupUnnecessaryFiles(openclawRoot);
|
|
console.log(`[after-pack] ✅ Removed ${removedRoot} unnecessary files/directories.`);
|
|
|
|
// 3. Platform-specific: strip koffi non-target platform binaries
|
|
const koffiRemoved = cleanupKoffi(dest, platform, arch);
|
|
if (koffiRemoved > 0) {
|
|
console.log(`[after-pack] ✅ koffi: removed ${koffiRemoved} non-target platform binaries (kept ${platform}_${arch}).`);
|
|
}
|
|
|
|
// 4. Platform-specific: strip wrong-platform native packages
|
|
const nativeRemoved = cleanupNativePlatformPackages(dest, platform, arch);
|
|
if (nativeRemoved > 0) {
|
|
console.log(`[after-pack] ✅ Removed ${nativeRemoved} non-target native platform packages.`);
|
|
}
|
|
};
|