Compare commits

...
16 Commits
Author SHA1 Message Date
paisley 464fd1f08a v0.4.0-alpha.8 2026-05-03 15:06:14 +08:00
paisley 9b130880c3 fix package 2026-05-03 15:05:45 +08:00
paisley 899ea52175 v0.4.0-alpha.7 2026-05-03 14:03:45 +08:00
paisley 38a7a4cebd fix package 2026-05-03 13:16:25 +08:00
paisley 153fb5dd86 fix package 2026-05-03 13:03:20 +08:00
paisley 38f6566fda v0.4.0-alpha.4 2026-05-03 12:54:02 +08:00
paisley 788e77da79 fix package 2026-05-03 12:53:19 +08:00
paisley c024ef9392 fix package 2026-05-03 12:32:07 +08:00
paisley ccdb4e739d fix mac package 2026-05-03 12:29:52 +08:00
paisley 972aca6e2c Merge branch 'main' into release/v0.4.0_tag_commit 2026-05-03 12:23:45 +08:00
paisley 299d4c89f9 0.4.0-alpha.1 2026-05-01 12:16:04 +08:00
paisley ea8799435f Merge branch 'main' into release/v0.4.0_tag_commit 2026-05-01 12:15:17 +08:00
Haze 213d34bfcf fix(package): update premajor-alpha script to preminor-alpha in package.json 2026-04-30 19:02:41 +08:00
Haze 420fa92d6e 0.4.0-alpha.0 2026-04-30 19:00:56 +08:00
Haze f88be09846 1.0.0-alpha.0 2026-04-30 18:59:07 +08:00
Haze b0daa6139f feat(package): add premajor and prerelease version scripts for alpha releases 2026-04-30 18:59:02 +08:00
4 changed files with 244 additions and 42 deletions
+10 -8
View File
@@ -1,6 +1,6 @@
{
"name": "clawx",
"version": "0.3.11",
"version": "0.4.0-alpha.8",
"pnpm": {
"onlyBuiltDependencies": [
"@discordjs/opus",
@@ -36,7 +36,7 @@
"predev": "node scripts/generate-ext-bridge.mjs && zx scripts/prepare-preinstalled-skills-dev.mjs",
"dev": "vite",
"ext:bridge": "node scripts/generate-ext-bridge.mjs",
"build": "node scripts/generate-ext-bridge.mjs && pnpm run build:vite && zx scripts/bundle-openclaw.mjs && zx scripts/bundle-openclaw-plugins.mjs && zx scripts/bundle-preinstalled-skills.mjs && electron-builder",
"build": "node scripts/generate-ext-bridge.mjs && pnpm run build:vite && zx scripts/bundle-openclaw.mjs && zx scripts/bundle-openclaw-plugins.mjs && zx scripts/bundle-preinstalled-skills.mjs && node scripts/run-electron-builder.mjs",
"build:vite": "node --max-old-space-size=6144 ./node_modules/vite/bin/vite.js build",
"bundle:openclaw-plugins": "zx scripts/bundle-openclaw-plugins.mjs",
"bundle:preinstalled-skills": "zx scripts/bundle-preinstalled-skills.mjs",
@@ -60,15 +60,17 @@
"prep:win-binaries": "pnpm run uv:download:win && pnpm run node:download:win",
"icons": "zx scripts/generate-icons.mjs",
"package": "node scripts/generate-ext-bridge.mjs && pnpm run build:vite && zx scripts/bundle-openclaw.mjs && zx scripts/bundle-openclaw-plugins.mjs && zx scripts/bundle-preinstalled-skills.mjs",
"package:mac": "pnpm run package && electron-builder --mac --publish never",
"package:mac:local": "SKIP_PREINSTALLED_SKILLS=1 pnpm run package && electron-builder --mac --publish never",
"package:win": "pnpm run prep:win-binaries && pnpm run package && electron-builder --win --publish never",
"package:linux": "pnpm run package && electron-builder --linux --publish never",
"release": "pnpm run uv:download && pnpm run package && electron-builder --publish always",
"package:mac": "pnpm run package && node scripts/run-electron-builder.mjs --mac --publish never",
"package:mac:local": "SKIP_PREINSTALLED_SKILLS=1 pnpm run package && node scripts/run-electron-builder.mjs --mac --publish never",
"package:win": "pnpm run prep:win-binaries && pnpm run package && node scripts/run-electron-builder.mjs --win --publish never",
"package:linux": "pnpm run package && node scripts/run-electron-builder.mjs --linux --publish never",
"release": "pnpm run uv:download && pnpm run package && node scripts/run-electron-builder.mjs --publish always",
"version": "node scripts/assert-release-version.mjs",
"version:patch": "pnpm version patch",
"version:minor": "pnpm version minor",
"version:major": "pnpm version major",
"version:preminor-alpha": "pnpm version preminor --preid=alpha",
"version:prerelease-alpha": "pnpm version prerelease --preid=alpha",
"version:prerelease-beta": "pnpm version prerelease --preid=beta",
"release:validate": "node scripts/assert-tag-matches-package.mjs",
"postversion": "git push && git push --tags"
@@ -177,4 +179,4 @@
"zx": "^8.8.5"
},
"packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268"
}
}
+128 -27
View File
@@ -19,7 +19,7 @@
* @mariozechner/clipboard).
*/
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync } = require('fs');
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync, readFileSync, writeFileSync } = require('fs');
const { join, dirname, basename, relative } = require('path');
// On Windows, paths in pnpm's virtual store can exceed the default MAX_PATH
@@ -40,6 +40,26 @@ function resolveArch(archEnum) {
return ARCH_MAP[archEnum] || 'x64';
}
function readJsonSafe(filePath) {
try {
return JSON.parse(readFileSync(normWin(filePath), 'utf8'));
} catch {
return null;
}
}
function listPackageDeps(pkgJson) {
return Object.keys({
...(pkgJson?.dependencies && typeof pkgJson.dependencies === 'object' ? pkgJson.dependencies : {}),
...(pkgJson?.optionalDependencies && typeof pkgJson.optionalDependencies === 'object' ? pkgJson.optionalDependencies : {}),
}).sort((a, b) => a.localeCompare(b));
}
function readInstalledPackageVersion(packageDir) {
const pkg = readJsonSafe(join(packageDir, 'package.json'));
return typeof pkg?.version === 'string' ? pkg.version.trim() : null;
}
// ── General cleanup ──────────────────────────────────────────────────────────
function cleanupUnnecessaryFiles(dir) {
@@ -48,7 +68,17 @@ function cleanupUnnecessaryFiles(dir) {
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'];
// .d.mts / .d.cts are TypeScript declaration files for ESM/CJS dual-package
// builds. They are useless at runtime but show up in huge volumes from
// typed packages (e.g. typebox), and inflate the per-process file count
// that codesign opens during macOS signing → EMFILE.
const REMOVE_FILE_EXTS = [
'.d.ts', '.d.ts.map',
'.d.mts', '.d.mts.map',
'.d.cts', '.d.cts.map',
'.js.map', '.mjs.map', '.cjs.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',
@@ -607,50 +637,121 @@ exports.default = async function afterPack(context) {
// the top-level node_modules/ as well.
const buildExtDir = join(__dirname, '..', 'build', 'openclaw', 'dist', 'extensions');
const packExtDir = join(openclawRoot, 'dist', 'extensions');
// ClawX always uses the official @larksuite/openclaw-lark plugin for Feishu.
// The built-in openclaw dist/extensions/feishu tree is redundant, and on macOS
// its mirrored runtime deps significantly increase codesign file pressure.
rmSync(join(packExtDir, 'feishu'), { recursive: true, force: true });
if (existsSync(buildExtDir)) {
let extNMCount = 0;
let mergedPkgCount = 0;
let prunedSharedDepCount = 0;
for (const extEntry of readdirSync(buildExtDir, { withFileTypes: true })) {
if (!extEntry.isDirectory()) continue;
if (extEntry.name === 'feishu') continue;
const srcNM = join(buildExtDir, extEntry.name, 'node_modules');
if (!existsSync(srcNM)) continue;
// Copy to extension's own node_modules (for direct requires from extension code).
// electron-builder may leave behind an empty destination directory when it
// skips node_modules content via .gitignore filtering, so always replace it.
const destExtNM = join(packExtDir, extEntry.name, 'node_modules');
const destExtRoot = join(packExtDir, extEntry.name);
const destExtNM = join(destExtRoot, 'node_modules');
rmSync(destExtNM, { recursive: true, force: true });
cpSync(srcNM, destExtNM, { recursive: true });
mkdirSync(destExtNM, { recursive: true });
extNMCount++;
// Merge into top-level openclaw/node_modules/ (for shared chunks in dist/)
for (const pkgEntry of readdirSync(srcNM, { withFileTypes: true })) {
if (!pkgEntry.isDirectory() || pkgEntry.name === '.bin') continue;
const srcPkg = join(srcNM, pkgEntry.name);
const destPkg = join(dest, pkgEntry.name);
if (platform === 'darwin') {
const buildPkgPath = join(buildExtDir, extEntry.name, 'package.json');
const packPkgPath = join(destExtRoot, 'package.json');
const buildPkgJson = readJsonSafe(buildPkgPath) || {};
// Deep copy the fallback so mutations to packPkgJson don't bleed back
// into buildPkgJson (which we still iterate via listPackageDeps below).
const packPkgJson = readJsonSafe(packPkgPath) || JSON.parse(JSON.stringify(buildPkgJson));
if (pkgEntry.name.startsWith('@')) {
// Scoped package — iterate sub-entries
for (const scopeEntry of readdirSync(srcPkg, { withFileTypes: true })) {
if (!scopeEntry.isDirectory()) continue;
const srcScoped = join(srcPkg, scopeEntry.name);
const destScoped = join(destPkg, scopeEntry.name);
if (!existsSync(destScoped)) {
mkdirSync(dirname(destScoped), { recursive: true });
cpSync(srcScoped, destScoped, { recursive: true });
for (const depName of listPackageDeps(buildPkgJson)) {
const srcDepPkg = join(srcNM, ...depName.split('/'));
const destDepPkg = join(dest, ...depName.split('/'));
if (!existsSync(destDepPkg) && existsSync(srcDepPkg)) {
mkdirSync(dirname(destDepPkg), { recursive: true });
cpSync(srcDepPkg, destDepPkg, { recursive: true });
mergedPkgCount++;
}
// Reuse the top-level openclaw/node_modules copy whenever possible:
// - if src is missing, we have no reference version → trust top-level
// (Node's module resolution will walk up from
// dist/extensions/<ext>/<file>.js to openclaw/node_modules/ anyway).
// - if src exists and matches top-level version, we can safely share.
// Only force a per-extension local copy when we actually have a
// version conflict between the extension's pinned dep and the
// top-level shared dep.
const srcVersion = existsSync(srcDepPkg) ? readInstalledPackageVersion(srcDepPkg) : null;
const destVersion = existsSync(destDepPkg) ? readInstalledPackageVersion(destDepPkg) : null;
const canReuseTopLevel = existsSync(destDepPkg) && (
!srcVersion || (destVersion && srcVersion === destVersion)
);
if (canReuseTopLevel) {
if (packPkgJson.dependencies && depName in packPkgJson.dependencies) {
delete packPkgJson.dependencies[depName];
prunedSharedDepCount++;
}
if (packPkgJson.optionalDependencies && depName in packPkgJson.optionalDependencies) {
delete packPkgJson.optionalDependencies[depName];
prunedSharedDepCount++;
}
continue;
}
const extDepPkg = join(destExtNM, ...depName.split('/'));
mkdirSync(dirname(extDepPkg), { recursive: true });
if (existsSync(srcDepPkg)) {
cpSync(srcDepPkg, extDepPkg, { recursive: true });
} else if (existsSync(destDepPkg)) {
cpSync(destDepPkg, extDepPkg, { recursive: true });
}
}
if (packPkgJson.dependencies && Object.keys(packPkgJson.dependencies).length === 0) {
delete packPkgJson.dependencies;
}
if (packPkgJson.optionalDependencies && Object.keys(packPkgJson.optionalDependencies).length === 0) {
delete packPkgJson.optionalDependencies;
}
writeFileSync(packPkgPath, JSON.stringify(packPkgJson, null, 2) + '\n', 'utf8');
} else {
for (const pkgEntry of readdirSync(srcNM, { withFileTypes: true })) {
if (!pkgEntry.isDirectory() || pkgEntry.name === '.bin') continue;
const srcPkg = join(srcNM, pkgEntry.name);
const destPkg = join(dest, pkgEntry.name);
if (pkgEntry.name.startsWith('@')) {
// Scoped package — iterate sub-entries
for (const scopeEntry of readdirSync(srcPkg, { withFileTypes: true })) {
if (!scopeEntry.isDirectory()) continue;
const srcScoped = join(srcPkg, scopeEntry.name);
const destScoped = join(destPkg, scopeEntry.name);
if (!existsSync(destScoped)) {
mkdirSync(dirname(destScoped), { recursive: true });
cpSync(srcScoped, destScoped, { recursive: true });
mergedPkgCount++;
}
const extScoped = join(destExtNM, pkgEntry.name, scopeEntry.name);
mkdirSync(dirname(extScoped), { recursive: true });
cpSync(srcScoped, extScoped, { recursive: true });
}
} else {
if (!existsSync(destPkg)) {
cpSync(srcPkg, destPkg, { recursive: true });
mergedPkgCount++;
}
}
} else {
if (!existsSync(destPkg)) {
cpSync(srcPkg, destPkg, { recursive: true });
mergedPkgCount++;
const extPkg = join(destExtNM, pkgEntry.name);
cpSync(srcPkg, extPkg, { recursive: true });
}
}
}
}
if (extNMCount > 0) {
console.log(`[after-pack] ✅ Copied node_modules for ${extNMCount} built-in extension(s), merged ${mergedPkgCount} packages into top-level.`);
console.log(`[after-pack] ✅ Prepared node_modules for ${extNMCount} built-in extension(s), merged ${mergedPkgCount} packages into top-level${prunedSharedDepCount > 0 ? `, pruned ${prunedSharedDepCount} redundant direct deps on macOS` : ''}.`);
}
}
+55 -7
View File
@@ -56,10 +56,36 @@ function shouldCopyOpenClawPackageEntry(src) {
return true;
}
// 2. Clean and create output directory
if (fs.existsSync(OUTPUT)) {
fs.rmSync(OUTPUT, { recursive: true });
function removeDirRobust(targetDir) {
if (!fs.existsSync(targetDir)) return;
try {
fs.rmSync(targetDir, {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 100,
});
} catch (error) {
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
if (fs.existsSync(targetDir) && (code === 'EACCES' || code === 'ENOTEMPTY' || code === 'EPERM')) {
try {
fs.removeSync(targetDir);
} catch {
// fall through to final existence check below
}
} else {
throw error;
}
}
if (fs.existsSync(targetDir)) {
throw new Error(`Failed to remove directory: ${targetDir}`);
}
}
// 2. Clean and create output directory
removeDirRobust(OUTPUT);
fs.mkdirSync(OUTPUT, { recursive: true });
// 3. Copy openclaw package itself to OUTPUT root
@@ -458,7 +484,13 @@ function rmSafe(target) {
function cleanupBundle(outputDir) {
let removedCount = 0;
const nm = path.join(outputDir, 'node_modules');
const ext = path.join(outputDir, 'extensions');
// OpenClaw 3.x ships built-in extensions under dist/extensions/<ext>/, not
// extensions/. The previous `path.join(outputDir, 'extensions')` silently
// resolved to a non-existent directory so the entire walkExt() pass below
// (which is what cleans .d.ts / .d.mts / source maps inside per-extension
// node_modules) was a no-op. That left ~28k .d.mts files in the bundle and
// contributed to the macOS codesign EMFILE blow-up.
const ext = path.join(outputDir, 'dist', 'extensions');
// --- openclaw root junk ---
for (const name of ['CHANGELOG.md', 'README.md']) {
@@ -475,7 +507,17 @@ function cleanupBundle(outputDir) {
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'];
// .d.mts / .d.cts are TypeScript declaration files for ESM/CJS dual-package
// builds. They are useless at runtime but show up in huge volumes from
// typed packages (e.g. typebox), and inflate the per-process file count
// that codesign opens during macOS signing → EMFILE.
const NM_REMOVE_FILE_EXTS = [
'.d.ts', '.d.ts.map',
'.d.mts', '.d.mts.map',
'.d.cts', '.d.cts.map',
'.js.map', '.mjs.map', '.cjs.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',
@@ -529,7 +571,13 @@ function cleanupBundle(outputDir) {
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_EXTS = [
'.d.ts', '.d.ts.map',
'.d.mts', '.d.mts.map',
'.d.cts', '.d.cts.map',
'.js.map', '.mjs.map', '.cjs.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',
@@ -565,7 +613,7 @@ function cleanupBundle(outputDir) {
'node_modules/koffi/src',
'node_modules/koffi/vendor',
'node_modules/koffi/doc',
'extensions/feishu', // Removed in favor of official @larksuite/openclaw-lark plugin
'dist/extensions/feishu', // Removed in favor of official @larksuite/openclaw-lark plugin
];
for (const rel of LARGE_REMOVALS) {
if (rmSafe(path.join(outputDir, rel))) removedCount++;
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const ELECTRON_BUILDER_BIN = process.platform === 'win32'
? path.join(ROOT, 'node_modules', '.bin', 'electron-builder.cmd')
: path.join(ROOT, 'node_modules', '.bin', 'electron-builder');
const args = process.argv.slice(2);
function shellQuote(value) {
return `'${String(value).replace(/'/g, `'\\''`)}'`;
}
function spawnElectronBuilder() {
if (process.platform === 'darwin') {
const command = [
'ulimit -n 65536 >/dev/null 2>&1 || ulimit -n 32768 >/dev/null 2>&1 || ulimit -n 16384 >/dev/null 2>&1 || true',
`exec ${shellQuote(ELECTRON_BUILDER_BIN)}${args.length > 0 ? ` ${args.map(shellQuote).join(' ')}` : ''}`,
].join('; ');
return spawn('/bin/bash', ['-lc', command], {
cwd: ROOT,
stdio: 'inherit',
env: process.env,
});
}
return spawn(ELECTRON_BUILDER_BIN, args, {
cwd: ROOT,
stdio: 'inherit',
env: process.env,
shell: process.platform === 'win32',
});
}
const child = spawnElectronBuilder();
child.on('exit', (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 1);
});
child.on('error', (error) => {
console.error(error);
process.exit(1);
});