feat: enhance after-pack script to copy OpenClaw runtime dependencies
- Added a new script `bundle-openclaw.mjs` to bundle OpenClaw runtime dependencies. - Updated `after-pack.cjs` to copy bundled OpenClaw runtime and its node_modules. - Improved cleanup of unnecessary development files in node_modules. - Adjusted paths for resources in the packaging process. style: update loading indicator styles in ChatHistoryPanel - Changed the border radius and padding for the loading indicator in ChatHistoryPanel. fix: improve ProvidersSection to handle provider account syncing - Added logic to sync model configuration to provider accounts. - Introduced error handling and loading states during the sync process. - Enhanced vendor resolution and account management logic. fix: fallback session handling in chat store - Implemented fallback session logic in loadSessions to ensure a valid session is always available on error.
This commit is contained in:
304
scripts/bundle-openclaw.mjs
Normal file
304
scripts/bundle-openclaw.mjs
Normal file
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const OUTPUT = path.join(ROOT, 'build', 'openclaw');
|
||||
const NODE_MODULES = path.join(ROOT, 'node_modules');
|
||||
|
||||
function log(message) {
|
||||
console.log(`[bundle-openclaw] ${message}`);
|
||||
}
|
||||
|
||||
function normWin(filePath) {
|
||||
if (process.platform !== 'win32') return filePath;
|
||||
if (filePath.startsWith('\\\\?\\')) return filePath;
|
||||
return `\\\\?\\${filePath.replace(/\//g, '\\')}`;
|
||||
}
|
||||
|
||||
function getVirtualStoreNodeModules(realPkgPath) {
|
||||
let current = realPkgPath;
|
||||
|
||||
while (current !== path.dirname(current)) {
|
||||
if (path.basename(current) === 'node_modules') {
|
||||
return current;
|
||||
}
|
||||
|
||||
current = path.dirname(current);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function listPackages(nodeModulesDir) {
|
||||
const results = [];
|
||||
const targetDir = normWin(nodeModulesDir);
|
||||
if (!fs.existsSync(targetDir)) return results;
|
||||
|
||||
for (const entry of fs.readdirSync(targetDir)) {
|
||||
if (entry === '.bin') continue;
|
||||
const entryPath = path.join(nodeModulesDir, entry);
|
||||
|
||||
if (entry.startsWith('@')) {
|
||||
let scopedEntries = [];
|
||||
|
||||
try {
|
||||
scopedEntries = fs.readdirSync(normWin(entryPath));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const scoped of scopedEntries) {
|
||||
results.push({
|
||||
name: `${entry}/${scoped}`,
|
||||
fullPath: path.join(entryPath, scoped),
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push({ name: entry, fullPath: entryPath });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
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 cleanupNodeModules(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)) {
|
||||
if (rmSafe(fullPath)) removedCount++;
|
||||
continue;
|
||||
}
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
REMOVE_FILE_NAMES.has(entry.name)
|
||||
|| REMOVE_FILE_EXTS.some((ext) => entry.name.endsWith(ext))
|
||||
) {
|
||||
if (rmSafe(fullPath)) removedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
walk(dir);
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
function cleanupBundle(outputDir) {
|
||||
let removedCount = 0;
|
||||
|
||||
for (const name of ['CHANGELOG.md', 'README.md']) {
|
||||
if (rmSafe(path.join(outputDir, name))) removedCount++;
|
||||
}
|
||||
|
||||
removedCount += cleanupNodeModules(path.join(outputDir, 'node_modules'));
|
||||
|
||||
const extensionsDir = path.join(outputDir, 'dist', 'extensions');
|
||||
if (fs.existsSync(extensionsDir)) {
|
||||
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
removedCount += cleanupNodeModules(path.join(extensionsDir, entry.name, 'node_modules'));
|
||||
}
|
||||
}
|
||||
|
||||
return removedCount;
|
||||
}
|
||||
|
||||
const openclawLink = path.join(NODE_MODULES, 'openclaw');
|
||||
if (!fs.existsSync(openclawLink)) {
|
||||
console.error('[bundle-openclaw] node_modules/openclaw not found. Run pnpm install first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const openclawReal = fs.realpathSync(openclawLink);
|
||||
log(`resolved openclaw package: ${openclawReal}`);
|
||||
|
||||
if (fs.existsSync(OUTPUT)) {
|
||||
fs.rmSync(OUTPUT, { recursive: true, force: true });
|
||||
}
|
||||
fs.mkdirSync(OUTPUT, { recursive: true });
|
||||
|
||||
log('copying openclaw package root...');
|
||||
fs.cpSync(normWin(openclawReal), normWin(OUTPUT), { recursive: true, dereference: true });
|
||||
|
||||
const openclawVirtualNodeModules = getVirtualStoreNodeModules(openclawReal);
|
||||
if (!openclawVirtualNodeModules) {
|
||||
console.error('[bundle-openclaw] could not determine pnpm virtual store for openclaw.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const collected = new Map();
|
||||
const queue = [{ nodeModulesDir: openclawVirtualNodeModules, skipPkg: 'openclaw' }];
|
||||
|
||||
const SKIP_PACKAGES = new Set([
|
||||
'typescript',
|
||||
'@playwright/test',
|
||||
'@discordjs/opus',
|
||||
]);
|
||||
const SKIP_SCOPES = ['@cloudflare/', '@types/'];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (!current) break;
|
||||
|
||||
for (const { name, fullPath } of listPackages(current.nodeModulesDir)) {
|
||||
if (name === current.skipPkg) continue;
|
||||
if (SKIP_PACKAGES.has(name) || SKIP_SCOPES.some((scope) => name.startsWith(scope))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let realPath;
|
||||
try {
|
||||
realPath = fs.realpathSync(fullPath);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collected.has(realPath)) continue;
|
||||
collected.set(realPath, name);
|
||||
|
||||
const depVirtualNodeModules = getVirtualStoreNodeModules(realPath);
|
||||
if (depVirtualNodeModules && depVirtualNodeModules !== current.nodeModulesDir) {
|
||||
queue.push({ nodeModulesDir: depVirtualNodeModules, skipPkg: name });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log(`resolved ${collected.size} transitive packages.`);
|
||||
|
||||
const outputNodeModules = path.join(OUTPUT, 'node_modules');
|
||||
fs.mkdirSync(outputNodeModules, { recursive: true });
|
||||
|
||||
const copiedNames = new Set();
|
||||
let copiedCount = 0;
|
||||
let skippedDuplicates = 0;
|
||||
|
||||
for (const [realPath, pkgName] of collected) {
|
||||
if (copiedNames.has(pkgName)) {
|
||||
skippedDuplicates++;
|
||||
continue;
|
||||
}
|
||||
|
||||
copiedNames.add(pkgName);
|
||||
const dest = path.join(outputNodeModules, pkgName);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(normWin(path.dirname(dest)), { recursive: true });
|
||||
fs.cpSync(normWin(realPath), normWin(dest), { recursive: true, dereference: true });
|
||||
copiedCount++;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log(`skipped ${pkgName}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
log(`copied ${copiedCount} packages to bundled node_modules (${skippedDuplicates} duplicates skipped).`);
|
||||
|
||||
const extensionsDir = path.join(OUTPUT, 'dist', 'extensions');
|
||||
let mergedExtensionPackages = 0;
|
||||
|
||||
if (fs.existsSync(extensionsDir)) {
|
||||
for (const extEntry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
||||
if (!extEntry.isDirectory()) continue;
|
||||
|
||||
const extNodeModules = path.join(extensionsDir, extEntry.name, 'node_modules');
|
||||
if (!fs.existsSync(extNodeModules)) continue;
|
||||
|
||||
for (const pkgEntry of fs.readdirSync(extNodeModules, { withFileTypes: true })) {
|
||||
if (!pkgEntry.isDirectory() || pkgEntry.name === '.bin') continue;
|
||||
const srcPkg = path.join(extNodeModules, pkgEntry.name);
|
||||
|
||||
if (pkgEntry.name.startsWith('@')) {
|
||||
let scopeEntries = [];
|
||||
try {
|
||||
scopeEntries = fs.readdirSync(srcPkg, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const scopeEntry of scopeEntries) {
|
||||
if (!scopeEntry.isDirectory()) continue;
|
||||
const scopedName = `${pkgEntry.name}/${scopeEntry.name}`;
|
||||
if (copiedNames.has(scopedName)) continue;
|
||||
|
||||
const srcScoped = path.join(srcPkg, scopeEntry.name);
|
||||
const destScoped = path.join(outputNodeModules, pkgEntry.name, scopeEntry.name);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(normWin(path.dirname(destScoped)), { recursive: true });
|
||||
fs.cpSync(normWin(srcScoped), normWin(destScoped), { recursive: true, dereference: true });
|
||||
copiedNames.add(scopedName);
|
||||
mergedExtensionPackages++;
|
||||
} catch {
|
||||
// Ignore extension copy failures and continue building the rest.
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (copiedNames.has(pkgEntry.name)) continue;
|
||||
|
||||
const destPkg = path.join(outputNodeModules, pkgEntry.name);
|
||||
try {
|
||||
fs.cpSync(normWin(srcPkg), normWin(destPkg), { recursive: true, dereference: true });
|
||||
copiedNames.add(pkgEntry.name);
|
||||
mergedExtensionPackages++;
|
||||
} catch {
|
||||
// Ignore extension copy failures and continue building the rest.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mergedExtensionPackages > 0) {
|
||||
log(`merged ${mergedExtensionPackages} built-in extension packages into top-level node_modules.`);
|
||||
}
|
||||
|
||||
const removedCount = cleanupBundle(OUTPUT);
|
||||
log(`removed ${removedCount} development-only files from bundled runtime.`);
|
||||
log(`OpenClaw bundle ready at ${OUTPUT}`);
|
||||
Reference in New Issue
Block a user