upgrade openclaw to 3.23 (#652)
Co-authored-by: Felix <24791380+vcfgv@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
b786b773f1
commit
ba5947e2cb
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
|
||||
const { cpSync, existsSync, readdirSync, rmSync, statSync, mkdirSync, realpathSync } = require('fs');
|
||||
const { join, dirname, basename } = require('path');
|
||||
const { join, dirname, basename, relative } = require('path');
|
||||
|
||||
// On Windows, paths in pnpm's virtual store can exceed the default MAX_PATH
|
||||
// limit (260 chars). Node.js 18.17+ respects the system LongPathsEnabled
|
||||
@@ -287,6 +287,84 @@ function patchBrokenModules(nodeModulesDir) {
|
||||
}
|
||||
}
|
||||
|
||||
// lru-cache CJS/ESM interop fix (recursive):
|
||||
// Multiple versions of lru-cache may exist in the output tree — not just
|
||||
// at node_modules/lru-cache/ but also nested inside other packages.
|
||||
// Older CJS versions (v5, v6) export the class via `module.exports = LRUCache`
|
||||
// without a named `LRUCache` property, so `import { LRUCache } from 'lru-cache'`
|
||||
// fails in Node.js 22+ ESM interop (used by Electron 40+).
|
||||
// We recursively scan the entire output for ALL lru-cache installations and
|
||||
// patch each CJS entry to ensure `exports.LRUCache` always exists.
|
||||
function patchAllLruCacheInstances(rootDir) {
|
||||
let lruCount = 0;
|
||||
const stack = [rootDir];
|
||||
while (stack.length > 0) {
|
||||
const dir = stack.pop();
|
||||
let entries;
|
||||
try { entries = readdirSync(normWin(dir), { withFileTypes: true }); } catch { continue; }
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
let isDirectory = entry.isDirectory();
|
||||
if (!isDirectory) {
|
||||
// pnpm layout may contain symlink/junction directories on Windows.
|
||||
try { isDirectory = statSync(normWin(fullPath)).isDirectory(); } catch { isDirectory = false; }
|
||||
}
|
||||
if (!isDirectory) continue;
|
||||
if (entry.name === 'lru-cache') {
|
||||
const pkgPath = join(fullPath, 'package.json');
|
||||
if (!existsSync(normWin(pkgPath))) { stack.push(fullPath); continue; }
|
||||
try {
|
||||
const pkg = JSON.parse(readFileSync(normWin(pkgPath), 'utf8'));
|
||||
if (pkg.type === 'module') continue; // ESM version — already has named exports
|
||||
const mainFile = pkg.main || 'index.js';
|
||||
const entryFile = join(fullPath, mainFile);
|
||||
if (!existsSync(normWin(entryFile))) continue;
|
||||
const original = readFileSync(normWin(entryFile), 'utf8');
|
||||
if (!original.includes('exports.LRUCache')) {
|
||||
const patched = [
|
||||
original,
|
||||
'',
|
||||
'// ClawX patch: add LRUCache named export for Node.js 22+ ESM interop',
|
||||
'if (typeof module.exports === "function" && !module.exports.LRUCache) {',
|
||||
' module.exports.LRUCache = module.exports;',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
writeFileSync(normWin(entryFile), patched, 'utf8');
|
||||
lruCount++;
|
||||
console.log(`[after-pack] 🩹 Patched lru-cache CJS (v${pkg.version}) at ${relative(rootDir, fullPath)}`);
|
||||
}
|
||||
|
||||
// lru-cache v7 ESM entry exports default only; add named export.
|
||||
const moduleFile = typeof pkg.module === 'string' ? pkg.module : null;
|
||||
if (moduleFile) {
|
||||
const esmEntry = join(fullPath, moduleFile);
|
||||
if (existsSync(normWin(esmEntry))) {
|
||||
const esmOriginal = readFileSync(normWin(esmEntry), 'utf8');
|
||||
if (
|
||||
esmOriginal.includes('export default LRUCache') &&
|
||||
!esmOriginal.includes('export { LRUCache')
|
||||
) {
|
||||
const esmPatched = [esmOriginal, '', 'export { LRUCache }', ''].join('\n');
|
||||
writeFileSync(normWin(esmEntry), esmPatched, 'utf8');
|
||||
lruCount++;
|
||||
console.log(`[after-pack] 🩹 Patched lru-cache ESM (v${pkg.version}) at ${relative(rootDir, fullPath)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[after-pack] ⚠️ Failed to patch lru-cache at ${fullPath}:`, err.message);
|
||||
}
|
||||
} else {
|
||||
stack.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lruCount;
|
||||
}
|
||||
const lruPatched = patchAllLruCacheInstances(nodeModulesDir);
|
||||
count += lruPatched;
|
||||
|
||||
if (count > 0) {
|
||||
console.log(`[after-pack] 🩹 Patched ${count} broken module(s) in ${nodeModulesDir}`);
|
||||
}
|
||||
@@ -497,7 +575,7 @@ exports.default = async function afterPack(context) {
|
||||
const BUNDLED_PLUGINS = [
|
||||
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
||||
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
||||
{ npmName: '@sliverp/qqbot', pluginId: 'qqbot' },
|
||||
{ npmName: '@tencent-connect/openclaw-qqbot', pluginId: 'qqbot' },
|
||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||
];
|
||||
@@ -535,4 +613,86 @@ exports.default = async function afterPack(context) {
|
||||
if (nativeRemoved > 0) {
|
||||
console.log(`[after-pack] ✅ Removed ${nativeRemoved} non-target native platform packages.`);
|
||||
}
|
||||
|
||||
// 5. Patch lru-cache in app.asar.unpacked
|
||||
//
|
||||
// Production dependencies (electron-updater → semver → lru-cache@6,
|
||||
// posthog-node → proxy agents → lru-cache@7, etc.) end up inside app.asar.
|
||||
// Older CJS versions lack the `LRUCache` named export, breaking
|
||||
// `import { LRUCache }` in Electron 40+ (Node.js 22+ ESM interop).
|
||||
//
|
||||
// electron-builder.yml lists `**/node_modules/lru-cache/**` in asarUnpack,
|
||||
// which extracts those files to app.asar.unpacked/. We patch them here so
|
||||
// Electron's transparent asar fs layer serves the fixed version at runtime.
|
||||
const asarUnpackedDir = join(resourcesDir, 'app.asar.unpacked');
|
||||
if (existsSync(asarUnpackedDir)) {
|
||||
const { readFileSync: readFS, writeFileSync: writeFS } = require('fs');
|
||||
let asarLruCount = 0;
|
||||
const lruStack = [asarUnpackedDir];
|
||||
while (lruStack.length > 0) {
|
||||
const dir = lruStack.pop();
|
||||
let entries;
|
||||
try { entries = readdirSync(normWin(dir), { withFileTypes: true }); } catch { continue; }
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
let isDirectory = entry.isDirectory();
|
||||
if (!isDirectory) {
|
||||
// pnpm layout may contain symlink/junction directories on Windows.
|
||||
try { isDirectory = statSync(normWin(fullPath)).isDirectory(); } catch { isDirectory = false; }
|
||||
}
|
||||
if (!isDirectory) continue;
|
||||
if (entry.name === 'lru-cache') {
|
||||
const pkgPath = join(fullPath, 'package.json');
|
||||
if (!existsSync(normWin(pkgPath))) { lruStack.push(fullPath); continue; }
|
||||
try {
|
||||
const pkg = JSON.parse(readFS(normWin(pkgPath), 'utf8'));
|
||||
if (pkg.type === 'module') continue; // ESM — already exports LRUCache
|
||||
const mainFile = pkg.main || 'index.js';
|
||||
const entryFile = join(fullPath, mainFile);
|
||||
if (!existsSync(normWin(entryFile))) continue;
|
||||
const original = readFS(normWin(entryFile), 'utf8');
|
||||
if (!original.includes('exports.LRUCache')) {
|
||||
const patched = [
|
||||
original,
|
||||
'',
|
||||
'// ClawX patch: add LRUCache named export for Node.js 22+ ESM interop',
|
||||
'if (typeof module.exports === "function" && !module.exports.LRUCache) {',
|
||||
' module.exports.LRUCache = module.exports;',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
writeFS(normWin(entryFile), patched, 'utf8');
|
||||
asarLruCount++;
|
||||
console.log(`[after-pack] 🩹 Patched lru-cache CJS (v${pkg.version}) in app.asar.unpacked at ${relative(asarUnpackedDir, fullPath)}`);
|
||||
}
|
||||
|
||||
// lru-cache v7 ESM entry exports default only; add named export.
|
||||
const moduleFile = typeof pkg.module === 'string' ? pkg.module : null;
|
||||
if (moduleFile) {
|
||||
const esmEntry = join(fullPath, moduleFile);
|
||||
if (existsSync(normWin(esmEntry))) {
|
||||
const esmOriginal = readFS(normWin(esmEntry), 'utf8');
|
||||
if (
|
||||
esmOriginal.includes('export default LRUCache') &&
|
||||
!esmOriginal.includes('export { LRUCache')
|
||||
) {
|
||||
const esmPatched = [esmOriginal, '', 'export { LRUCache }', ''].join('\n');
|
||||
writeFS(normWin(esmEntry), esmPatched, 'utf8');
|
||||
asarLruCount++;
|
||||
console.log(`[after-pack] 🩹 Patched lru-cache ESM (v${pkg.version}) in app.asar.unpacked at ${relative(asarUnpackedDir, fullPath)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[after-pack] ⚠️ Failed to patch lru-cache in asar.unpacked at ${fullPath}:`, err.message);
|
||||
}
|
||||
} else {
|
||||
lruStack.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (asarLruCount > 0) {
|
||||
console.log(`[after-pack] 🩹 Patched ${asarLruCount} lru-cache instance(s) in app.asar.unpacked`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ function normWin(p) {
|
||||
const PLUGINS = [
|
||||
{ npmName: '@soimy/dingtalk', pluginId: 'dingtalk' },
|
||||
{ npmName: '@wecom/wecom-openclaw-plugin', pluginId: 'wecom' },
|
||||
{ npmName: '@sliverp/qqbot', pluginId: 'qqbot' },
|
||||
{ npmName: '@tencent-connect/openclaw-qqbot', pluginId: 'qqbot' },
|
||||
{ npmName: '@larksuite/openclaw-lark', pluginId: 'feishu-openclaw-plugin' },
|
||||
{ npmName: '@tencent-weixin/openclaw-weixin', pluginId: 'openclaw-weixin' },
|
||||
];
|
||||
|
||||
@@ -179,6 +179,61 @@ while (queue.length > 0) {
|
||||
echo` Found ${collected.size} total packages (direct + transitive)`;
|
||||
echo` Skipped ${skippedDevCount} dev-only package references`;
|
||||
|
||||
// 4b. Collect extra packages required by ClawX's Electron main process that are
|
||||
// NOT deps of openclaw. These are resolved from openclaw's context at runtime
|
||||
// (via createRequire from the openclaw directory) so they must live in the
|
||||
// bundled openclaw/node_modules/.
|
||||
//
|
||||
// For each package we resolve it from the workspace's own node_modules,
|
||||
// then BFS its transitive deps exactly like we did for openclaw above.
|
||||
const EXTRA_BUNDLED_PACKAGES = [
|
||||
'@whiskeysockets/baileys', // WhatsApp channel (was a dep of old clawdbot, not openclaw)
|
||||
];
|
||||
|
||||
let extraCount = 0;
|
||||
for (const pkgName of EXTRA_BUNDLED_PACKAGES) {
|
||||
const pkgLink = path.join(NODE_MODULES, ...pkgName.split('/'));
|
||||
if (!fs.existsSync(pkgLink)) {
|
||||
echo` ⚠️ Extra package ${pkgName} not found in workspace node_modules, skipping.`;
|
||||
continue;
|
||||
}
|
||||
|
||||
let pkgReal;
|
||||
try { pkgReal = fs.realpathSync(pkgLink); } catch { continue; }
|
||||
|
||||
if (!collected.has(pkgReal)) {
|
||||
collected.set(pkgReal, pkgName);
|
||||
extraCount++;
|
||||
|
||||
// BFS this package's own transitive deps
|
||||
const depVirtualNM = getVirtualStoreNodeModules(pkgReal);
|
||||
if (depVirtualNM) {
|
||||
const extraQueue = [{ nodeModulesDir: depVirtualNM, skipPkg: pkgName }];
|
||||
while (extraQueue.length > 0) {
|
||||
const { nodeModulesDir, skipPkg } = extraQueue.shift();
|
||||
const packages = listPackages(nodeModulesDir);
|
||||
for (const { name, fullPath } of packages) {
|
||||
if (name === skipPkg) continue;
|
||||
if (SKIP_PACKAGES.has(name) || SKIP_SCOPES.some(s => name.startsWith(s))) continue;
|
||||
let realPath;
|
||||
try { realPath = fs.realpathSync(fullPath); } catch { continue; }
|
||||
if (collected.has(realPath)) continue;
|
||||
collected.set(realPath, name);
|
||||
extraCount++;
|
||||
const innerVirtualNM = getVirtualStoreNodeModules(realPath);
|
||||
if (innerVirtualNM && innerVirtualNM !== nodeModulesDir) {
|
||||
extraQueue.push({ nodeModulesDir: innerVirtualNM, skipPkg: name });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extraCount > 0) {
|
||||
echo` Added ${extraCount} extra packages (+ transitive deps) for Electron main process`;
|
||||
}
|
||||
|
||||
// 5. Copy all collected packages into OUTPUT/node_modules/ (flat structure)
|
||||
//
|
||||
// IMPORTANT: BFS guarantees direct deps are encountered before transitive deps.
|
||||
@@ -457,6 +512,84 @@ function patchBrokenModules(nodeModulesDir) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
// lru-cache CJS/ESM interop fix (recursive):
|
||||
// Multiple versions of lru-cache may exist in the output tree — not just
|
||||
// at node_modules/lru-cache/ but also nested inside other packages.
|
||||
// Older CJS versions (v5, v6) export the class via `module.exports = LRUCache`
|
||||
// without a named `LRUCache` property, so `import { LRUCache } from 'lru-cache'`
|
||||
// fails in Node.js 22+ ESM interop (used by Electron 40+).
|
||||
// We recursively scan the entire output for ALL lru-cache installations and
|
||||
// patch each CJS entry to ensure `exports.LRUCache` always exists.
|
||||
function patchAllLruCacheInstances(rootDir) {
|
||||
let lruCount = 0;
|
||||
const stack = [rootDir];
|
||||
while (stack.length > 0) {
|
||||
const dir = stack.pop();
|
||||
let entries;
|
||||
try { entries = fs.readdirSync(normWin(dir), { withFileTypes: true }); } catch { continue; }
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
let isDirectory = entry.isDirectory();
|
||||
if (!isDirectory) {
|
||||
// pnpm layout may contain symlink/junction directories on Windows.
|
||||
try { isDirectory = fs.statSync(normWin(fullPath)).isDirectory(); } catch { isDirectory = false; }
|
||||
}
|
||||
if (!isDirectory) continue;
|
||||
if (entry.name === 'lru-cache') {
|
||||
const pkgPath = path.join(fullPath, 'package.json');
|
||||
if (!fs.existsSync(normWin(pkgPath))) { stack.push(fullPath); continue; }
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(normWin(pkgPath), 'utf8'));
|
||||
if (pkg.type === 'module') continue; // ESM version — already has named exports
|
||||
const mainFile = pkg.main || 'index.js';
|
||||
const entryFile = path.join(fullPath, mainFile);
|
||||
if (!fs.existsSync(normWin(entryFile))) continue;
|
||||
const original = fs.readFileSync(normWin(entryFile), 'utf8');
|
||||
if (!original.includes('exports.LRUCache')) {
|
||||
const patched = [
|
||||
original,
|
||||
'',
|
||||
'// ClawX patch: add LRUCache named export for Node.js 22+ ESM interop',
|
||||
'if (typeof module.exports === "function" && !module.exports.LRUCache) {',
|
||||
' module.exports.LRUCache = module.exports;',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
fs.writeFileSync(normWin(entryFile), patched, 'utf8');
|
||||
lruCount++;
|
||||
echo` 🩹 Patched lru-cache CJS (v${pkg.version}) at ${path.relative(rootDir, fullPath)}`;
|
||||
}
|
||||
|
||||
// lru-cache v7 ESM entry exports default only; add named export.
|
||||
const moduleFile = typeof pkg.module === 'string' ? pkg.module : null;
|
||||
if (moduleFile) {
|
||||
const esmEntry = path.join(fullPath, moduleFile);
|
||||
if (fs.existsSync(normWin(esmEntry))) {
|
||||
const esmOriginal = fs.readFileSync(normWin(esmEntry), 'utf8');
|
||||
if (
|
||||
esmOriginal.includes('export default LRUCache') &&
|
||||
!esmOriginal.includes('export { LRUCache')
|
||||
) {
|
||||
const esmPatched = [esmOriginal, '', 'export { LRUCache }', ''].join('\n');
|
||||
fs.writeFileSync(normWin(esmEntry), esmPatched, 'utf8');
|
||||
lruCount++;
|
||||
echo` 🩹 Patched lru-cache ESM (v${pkg.version}) at ${path.relative(rootDir, fullPath)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
echo` ⚠️ Failed to patch lru-cache at ${fullPath}: ${err.message}`;
|
||||
}
|
||||
} else {
|
||||
stack.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return lruCount;
|
||||
}
|
||||
const lruPatched = patchAllLruCacheInstances(nodeModulesDir);
|
||||
count += lruPatched;
|
||||
|
||||
if (count > 0) {
|
||||
echo` 🩹 Patched ${count} broken module(s) in node_modules`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user