feat(package): compress artifact size (#160)
Co-authored-by: Cursor Agent <cursor-agent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
appId: app.clawx.desktop
|
||||
productName: ClawX
|
||||
copyright: Copyright © 2026 ClawX
|
||||
compression: normal
|
||||
compression: maximum
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
|
||||
directories:
|
||||
@@ -21,6 +21,7 @@ extraResources:
|
||||
- "!icons/*.md"
|
||||
- "!icons/*.svg"
|
||||
- "!bin/**"
|
||||
- "!screenshot/**"
|
||||
# OpenClaw package (node_modules copied separately by afterPack hook
|
||||
# because electron-builder respects .gitignore which excludes node_modules/)
|
||||
- from: build/openclaw/
|
||||
|
||||
50
package.json
50
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawx",
|
||||
"version": "0.1.16",
|
||||
"version": "0.1.17-beta.0",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@whiskeysockets/baileys",
|
||||
@@ -42,6 +42,13 @@
|
||||
"postversion": "git push && git push --tags"
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-store": "^11.0.2",
|
||||
"electron-updater": "^6.8.3",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
@@ -54,30 +61,6 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toast": "^1.2.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clawhub": "^0.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"electron-store": "^11.0.2",
|
||||
"electron-updater": "^6.8.3",
|
||||
"framer-motion": "^12.34.2",
|
||||
"i18next": "^25.8.11",
|
||||
"lucide-react": "^0.563.0",
|
||||
"openclaw": "2026.2.23",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-i18next": "^16.5.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"ws": "^8.19.0",
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/node": "^25.3.0",
|
||||
@@ -88,22 +71,39 @@
|
||||
"@typescript-eslint/parser": "^8.56.0",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clawhub": "^0.5.0",
|
||||
"clsx": "^2.1.1",
|
||||
"electron": "^40.6.0",
|
||||
"electron-builder": "^26.8.1",
|
||||
"eslint": "^10.0.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"framer-motion": "^12.34.2",
|
||||
"globals": "^17.3.0",
|
||||
"i18next": "^25.8.11",
|
||||
"jsdom": "^28.1.0",
|
||||
"lucide-react": "^0.563.0",
|
||||
"openclaw": "2026.2.23",
|
||||
"png2icons": "^2.0.1",
|
||||
"postcss": "^8.5.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-i18next": "^16.5.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sharp": "^0.34.5",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-electron": "^0.29.0",
|
||||
"vite-plugin-electron-renderer": "^0.14.6",
|
||||
"vitest": "^4.0.18",
|
||||
"zustand": "^5.0.11",
|
||||
"zx": "^8.8.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8"
|
||||
|
||||
134
pnpm-lock.yaml
generated
134
pnpm-lock.yaml
generated
@@ -8,6 +8,22 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
electron-store:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
electron-updater:
|
||||
specifier: ^6.8.3
|
||||
version: 6.8.3
|
||||
ws:
|
||||
specifier: ^8.19.0
|
||||
version: 8.19.0
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(eslint@10.0.0(jiti@1.21.7))
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.2
|
||||
version: 1.58.2
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -44,73 +60,6 @@ importers:
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.8
|
||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
clawhub:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
electron-store:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
electron-updater:
|
||||
specifier: ^6.8.3
|
||||
version: 6.8.3
|
||||
framer-motion:
|
||||
specifier: ^12.34.2
|
||||
version: 12.34.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
i18next:
|
||||
specifier: ^25.8.11
|
||||
version: 25.8.11(typescript@5.9.3)
|
||||
lucide-react:
|
||||
specifier: ^0.563.0
|
||||
version: 0.563.0(react@19.2.4)
|
||||
openclaw:
|
||||
specifier: 2026.2.23
|
||||
version: 2026.2.23(@napi-rs/canvas@0.1.93)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.15.1(typescript@5.9.3))
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
react-i18next:
|
||||
specifier: ^16.5.4
|
||||
version: 16.5.4(i18next@25.8.11(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
react-markdown:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
|
||||
react-router-dom:
|
||||
specifier: ^7.13.0
|
||||
version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
sonner:
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
tailwind-merge:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.19(yaml@2.8.2))
|
||||
ws:
|
||||
specifier: ^8.19.0
|
||||
version: 8.19.0
|
||||
zustand:
|
||||
specifier: ^5.0.11
|
||||
version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1(eslint@10.0.0(jiti@1.21.7))
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.2
|
||||
version: 1.58.2
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1
|
||||
@@ -141,6 +90,15 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.24
|
||||
version: 10.4.24(postcss@8.5.6)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
clawhub:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
electron:
|
||||
specifier: ^40.6.0
|
||||
version: 40.6.0
|
||||
@@ -156,24 +114,63 @@ importers:
|
||||
eslint-plugin-react-refresh:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0(eslint@10.0.0(jiti@1.21.7))
|
||||
framer-motion:
|
||||
specifier: ^12.34.2
|
||||
version: 12.34.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
globals:
|
||||
specifier: ^17.3.0
|
||||
version: 17.3.0
|
||||
i18next:
|
||||
specifier: ^25.8.11
|
||||
version: 25.8.11(typescript@5.9.3)
|
||||
jsdom:
|
||||
specifier: ^28.1.0
|
||||
version: 28.1.0
|
||||
lucide-react:
|
||||
specifier: ^0.563.0
|
||||
version: 0.563.0(react@19.2.4)
|
||||
openclaw:
|
||||
specifier: 2026.2.23
|
||||
version: 2026.2.23(@napi-rs/canvas@0.1.93)(@types/express@5.0.6)(encoding@0.1.13)(hono@4.11.8)(node-llama-cpp@3.15.1(typescript@5.9.3))
|
||||
png2icons:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1
|
||||
postcss:
|
||||
specifier: ^8.5.6
|
||||
version: 8.5.6
|
||||
react:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4
|
||||
react-dom:
|
||||
specifier: ^19.2.4
|
||||
version: 19.2.4(react@19.2.4)
|
||||
react-i18next:
|
||||
specifier: ^16.5.4
|
||||
version: 16.5.4(i18next@25.8.11(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)
|
||||
react-markdown:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(@types/react@19.2.14)(react@19.2.4)
|
||||
react-router-dom:
|
||||
specifier: ^7.13.0
|
||||
version: 7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
remark-gfm:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
sharp:
|
||||
specifier: ^0.34.5
|
||||
version: 0.34.5
|
||||
sonner:
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
tailwind-merge:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
tailwindcss:
|
||||
specifier: ^3.4.19
|
||||
version: 3.4.19(yaml@2.8.2)
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.19(yaml@2.8.2))
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
@@ -189,6 +186,9 @@ importers:
|
||||
vitest:
|
||||
specifier: ^4.0.18
|
||||
version: 4.0.18(@types/node@25.3.0)(jiti@1.21.7)(jsdom@28.1.0)(yaml@2.8.2)
|
||||
zustand:
|
||||
specifier: ^5.0.11
|
||||
version: 5.0.11(@types/react@19.2.14)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4))
|
||||
zx:
|
||||
specifier: ^8.8.5
|
||||
version: 8.8.5
|
||||
|
||||
@@ -10,73 +10,135 @@
|
||||
* 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 removes unnecessary files (type definitions, source maps, docs)
|
||||
* to reduce the number of files that need to be code-signed on macOS.
|
||||
*
|
||||
* 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');
|
||||
|
||||
/**
|
||||
* Recursively remove unnecessary files to reduce code signing overhead
|
||||
*/
|
||||
// ── 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) {
|
||||
const entries = readdirSync(currentDir, { withFileTypes: true });
|
||||
|
||||
let entries;
|
||||
try { entries = readdirSync(currentDir, { withFileTypes: true }); } catch { return; }
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentDir, entry.name);
|
||||
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Remove entire test directories
|
||||
if (entry.name === 'test' || entry.name === 'tests' ||
|
||||
entry.name === '__tests__' || entry.name === '.github' ||
|
||||
entry.name === 'docs' || entry.name === 'examples') {
|
||||
try {
|
||||
rmSync(fullPath, { recursive: true, force: true });
|
||||
removedCount++;
|
||||
} catch (err) {
|
||||
// Ignore errors
|
||||
}
|
||||
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;
|
||||
// Remove unnecessary file types
|
||||
if (name.endsWith('.d.ts') || name.endsWith('.d.ts.map') ||
|
||||
name.endsWith('.js.map') || name.endsWith('.mjs.map') ||
|
||||
name.endsWith('.ts.map') || name === '.DS_Store' ||
|
||||
name === 'README.md' || name === 'CHANGELOG.md' ||
|
||||
name === 'LICENSE.md' || name === 'CONTRIBUTING.md' ||
|
||||
name.endsWith('.md.txt') || name.endsWith('.markdown') ||
|
||||
name === 'tsconfig.json' || name === '.npmignore' ||
|
||||
name === '.eslintrc' || name === '.prettierrc') {
|
||||
try {
|
||||
rmSync(fullPath, { force: true });
|
||||
removedCount++;
|
||||
} catch (err) {
|
||||
// Ignore errors
|
||||
}
|
||||
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');
|
||||
|
||||
// On macOS, resources live inside the .app bundle
|
||||
let resourcesDir;
|
||||
if (platform === 'darwin') {
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
@@ -85,23 +147,37 @@ exports.default = async function afterPack(context) {
|
||||
resourcesDir = join(appOutDir, 'resources');
|
||||
}
|
||||
|
||||
const dest = join(resourcesDir, 'openclaw', 'node_modules');
|
||||
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 "pnpm run bundle:openclaw" first.');
|
||||
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 successfully.');
|
||||
|
||||
// Clean up unnecessary files to reduce code signing overhead (especially on macOS)
|
||||
console.log('[after-pack] 🧹 Cleaning up unnecessary files (type definitions, source maps, docs)...');
|
||||
const removedCount = cleanupUnnecessaryFiles(dest);
|
||||
console.log(`[after-pack] ✅ Removed ${removedCount} unnecessary files/directories.`);
|
||||
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.`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -127,6 +127,14 @@ if (!openclawVirtualNM) {
|
||||
echo` Virtual store root: ${openclawVirtualNM}`;
|
||||
queue.push({ nodeModulesDir: openclawVirtualNM, skipPkg: 'openclaw' });
|
||||
|
||||
const SKIP_PACKAGES = new Set([
|
||||
'typescript',
|
||||
'playwright-core',
|
||||
'@playwright/test',
|
||||
]);
|
||||
const SKIP_SCOPES = ['@cloudflare/', '@types/'];
|
||||
let skippedDevCount = 0;
|
||||
|
||||
while (queue.length > 0) {
|
||||
const { nodeModulesDir, skipPkg } = queue.shift();
|
||||
const packages = listPackages(nodeModulesDir);
|
||||
@@ -135,6 +143,11 @@ while (queue.length > 0) {
|
||||
// Skip the package that owns this virtual store entry (it's the package itself, not a dep)
|
||||
if (name === skipPkg) continue;
|
||||
|
||||
if (SKIP_PACKAGES.has(name) || SKIP_SCOPES.some(s => name.startsWith(s))) {
|
||||
skippedDevCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
let realPath;
|
||||
try {
|
||||
realPath = fs.realpathSync(fullPath);
|
||||
@@ -156,6 +169,7 @@ while (queue.length > 0) {
|
||||
}
|
||||
|
||||
echo` Found ${collected.size} total packages (direct + transitive)`;
|
||||
echo` Skipped ${skippedDevCount} dev-only package references`;
|
||||
|
||||
// 5. Copy all collected packages into OUTPUT/node_modules/ (flat structure)
|
||||
//
|
||||
@@ -190,13 +204,160 @@ for (const [realPath, pkgName] of collected) {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Verify the bundle
|
||||
// 6. Clean up the bundle to reduce package size
|
||||
//
|
||||
// This removes platform-agnostic waste: dev artifacts, docs, source maps,
|
||||
// type definitions, test directories, and known large unused subdirectories.
|
||||
// Platform-specific cleanup (e.g. koffi binaries) is handled in after-pack.cjs
|
||||
// which has access to the target platform/arch context.
|
||||
|
||||
function getDirSize(dir) {
|
||||
let total = 0;
|
||||
try {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) total += getDirSize(p);
|
||||
else if (entry.isFile()) total += fs.statSync(p).size;
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return total;
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes >= 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024 / 1024).toFixed(1)}G`;
|
||||
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}M`;
|
||||
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
||||
return `${bytes}B`;
|
||||
}
|
||||
|
||||
function rmSafe(target) {
|
||||
try {
|
||||
const stat = fs.lstatSync(target);
|
||||
if (stat.isDirectory()) fs.rmSync(target, { recursive: true, force: true });
|
||||
else fs.rmSync(target, { force: true });
|
||||
return true;
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
function cleanupBundle(outputDir) {
|
||||
let removedCount = 0;
|
||||
const nm = path.join(outputDir, 'node_modules');
|
||||
const ext = path.join(outputDir, 'extensions');
|
||||
|
||||
// --- openclaw root junk ---
|
||||
for (const name of ['CHANGELOG.md', 'README.md']) {
|
||||
if (rmSafe(path.join(outputDir, name))) removedCount++;
|
||||
}
|
||||
|
||||
// docs/ is kept — contains prompt templates and other runtime-used prompts
|
||||
|
||||
// --- extensions: clean junk from source, aggressively clean nested node_modules ---
|
||||
// Extension source (.ts files) are runtime entry points — must be preserved.
|
||||
// Only nested node_modules/ inside extensions get the aggressive cleanup.
|
||||
if (fs.existsSync(ext)) {
|
||||
const JUNK_EXTS = new Set(['.prose', '.ignored_openclaw', '.keep']);
|
||||
const NM_REMOVE_DIRS = new Set([
|
||||
'test', 'tests', '__tests__', '.github', 'docs', 'examples', 'example',
|
||||
]);
|
||||
const NM_REMOVE_FILE_EXTS = ['.d.ts', '.d.ts.map', '.js.map', '.mjs.map', '.ts.map', '.markdown'];
|
||||
const NM_REMOVE_FILE_NAMES = new Set([
|
||||
'.DS_Store', 'README.md', 'CHANGELOG.md', 'LICENSE.md', 'CONTRIBUTING.md',
|
||||
'tsconfig.json', '.npmignore', '.eslintrc', '.prettierrc', '.editorconfig',
|
||||
]);
|
||||
|
||||
function walkExt(dir, insideNodeModules) {
|
||||
let entries;
|
||||
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (insideNodeModules && NM_REMOVE_DIRS.has(entry.name)) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
} else {
|
||||
walkExt(full, insideNodeModules || entry.name === 'node_modules');
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
if (insideNodeModules) {
|
||||
const name = entry.name;
|
||||
if (NM_REMOVE_FILE_NAMES.has(name) || NM_REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
}
|
||||
} else {
|
||||
if (JUNK_EXTS.has(path.extname(entry.name)) || entry.name.endsWith('.md')) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkExt(ext, false);
|
||||
}
|
||||
|
||||
// --- node_modules: remove unnecessary file types and directories ---
|
||||
if (fs.existsSync(nm)) {
|
||||
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 walkClean(dir) {
|
||||
let entries;
|
||||
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
||||
for (const entry of entries) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
if (REMOVE_DIRS.has(entry.name)) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
} else {
|
||||
walkClean(full);
|
||||
}
|
||||
} else if (entry.isFile()) {
|
||||
const name = entry.name;
|
||||
if (REMOVE_FILE_NAMES.has(name) || REMOVE_FILE_EXTS.some(e => name.endsWith(e))) {
|
||||
if (rmSafe(full)) removedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkClean(nm);
|
||||
}
|
||||
|
||||
// --- known large unused subdirectories ---
|
||||
const LARGE_REMOVALS = [
|
||||
'node_modules/pdfjs-dist/legacy',
|
||||
'node_modules/pdfjs-dist/types',
|
||||
'node_modules/node-llama-cpp/llama',
|
||||
'node_modules/koffi/src',
|
||||
'node_modules/koffi/vendor',
|
||||
'node_modules/koffi/doc',
|
||||
];
|
||||
for (const rel of LARGE_REMOVALS) {
|
||||
if (rmSafe(path.join(outputDir, rel))) removedCount++;
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
echo``;
|
||||
echo`🧹 Cleaning up bundle (removing dev artifacts, docs, source maps, type defs)...`;
|
||||
const sizeBefore = getDirSize(OUTPUT);
|
||||
const cleanedCount = cleanupBundle(OUTPUT);
|
||||
const sizeAfter = getDirSize(OUTPUT);
|
||||
echo` Removed ${cleanedCount} files/directories`;
|
||||
echo` Size: ${formatSize(sizeBefore)} → ${formatSize(sizeAfter)} (saved ${formatSize(sizeBefore - sizeAfter)})`;
|
||||
|
||||
// 7. Verify the bundle
|
||||
const entryExists = fs.existsSync(path.join(OUTPUT, 'openclaw.mjs'));
|
||||
const distExists = fs.existsSync(path.join(OUTPUT, 'dist', 'entry.js'));
|
||||
|
||||
echo``;
|
||||
echo`✅ Bundle complete: ${OUTPUT}`;
|
||||
echo` Unique packages copied: ${copiedCount}`;
|
||||
echo` Dev-only packages skipped: ${skippedDevCount}`;
|
||||
echo` Duplicate versions skipped: ${skippedDupes}`;
|
||||
echo` Total discovered: ${collected.size}`;
|
||||
echo` openclaw.mjs: ${entryExists ? '✓' : '✗'}`;
|
||||
|
||||
Reference in New Issue
Block a user