diff --git a/scripts/postinstall-bundled-plugins.mjs b/scripts/postinstall-bundled-plugins.mjs index e3e32e8846d..f49158ef32d 100644 --- a/scripts/postinstall-bundled-plugins.mjs +++ b/scripts/postinstall-bundled-plugins.mjs @@ -104,6 +104,7 @@ const BAILEYS_MEDIA_DISPATCHER_HEADER_REPLACEMENT = [ const BAILEYS_MEDIA_ONCE_IMPORT_RE = /import\s+\{\s*once\s*\}\s+from\s+['"]events['"]/u; const BAILEYS_MEDIA_ASYNC_CONTEXT_RE = /async\s+function\s+encryptedStream|encryptedStream\s*=\s*async/u; +const NODE_COMPILE_CACHE_VERSION_DIR_RE = /^v\d+\.\d+\.\d+-/u; function hasEnvFlag(env, key) { const value = env?.[key]?.trim().toLowerCase(); @@ -865,6 +866,7 @@ function shouldRunBundledPluginPostinstall(params) { export function pruneOpenClawCompileCache(params = {}) { const env = params.env ?? process.env; const pathExists = params.existsSync ?? existsSync; + const readDir = params.readdirSync ?? readdirSync; const remove = params.rmSync ?? rmSync; const log = params.log ?? console; const baseDirs = [ @@ -873,12 +875,25 @@ export function pruneOpenClawCompileCache(params = {}) { ].filter((value, index, values) => value && values.indexOf(value) === index); for (const baseDir of baseDirs) { - const cacheRoot = join(baseDir, "openclaw"); - if (!pathExists(cacheRoot)) { + if (!pathExists(baseDir)) { continue; } try { - remove(cacheRoot, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); + for (const entry of readDir(baseDir, { withFileTypes: true })) { + if (!entry.isDirectory() || !NODE_COMPILE_CACHE_VERSION_DIR_RE.test(entry.name)) { + continue; + } + try { + remove(join(baseDir, entry.name), { + recursive: true, + force: true, + maxRetries: 2, + retryDelay: 100, + }); + } catch (error) { + log.warn?.(`[postinstall] could not prune OpenClaw compile cache: ${String(error)}`); + } + } } catch (error) { log.warn?.(`[postinstall] could not prune OpenClaw compile cache: ${String(error)}`); } diff --git a/test/scripts/postinstall-bundled-plugins.test.ts b/test/scripts/postinstall-bundled-plugins.test.ts index 4ec4414577f..47756f331a7 100644 --- a/test/scripts/postinstall-bundled-plugins.test.ts +++ b/test/scripts/postinstall-bundled-plugins.test.ts @@ -1,4 +1,5 @@ import fs from "node:fs/promises"; +import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { @@ -181,29 +182,79 @@ describe("bundled plugin postinstall", () => { expect(spawnSync).not.toHaveBeenCalled(); }); - it("prunes OpenClaw compile cache during package postinstall", () => { + it("prunes Node versioned compile cache dirs during package postinstall", () => { + const configuredBase = path.join("/tmp", "openclaw-cache"); + const defaultBase = path.join(tmpdir(), "node-compile-cache"); const removed: string[] = []; - const existsSync = vi.fn((value: string) => - value.endsWith(path.join("openclaw-cache", "openclaw")), - ); + const existsSync = vi.fn((value: string) => value === configuredBase || value === defaultBase); + const readdirSync = vi.fn((value: string) => { + if (value === configuredBase) { + return [ + { name: "v22.13.1-x64-efe9a9df-1001", isDirectory: () => true }, + { name: "openclaw", isDirectory: () => true }, + { name: "README", isDirectory: () => false }, + ]; + } + if (value === defaultBase) { + return [{ name: "v24.14.1-x64-efe9a9df-1001", isDirectory: () => true }]; + } + throw new Error(`unexpected readdir: ${value}`); + }); const rmSync = vi.fn((value: string) => { removed.push(value); }); pruneOpenClawCompileCache({ - env: { NODE_COMPILE_CACHE: path.join("/tmp", "openclaw-cache") }, + env: { NODE_COMPILE_CACHE: configuredBase }, existsSync, + readdirSync, rmSync, log: { warn: vi.fn() }, }); - expect(removed).toEqual([path.join("/tmp", "openclaw-cache", "openclaw")]); - expect(rmSync).toHaveBeenCalledWith(path.join("/tmp", "openclaw-cache", "openclaw"), { - recursive: true, - force: true, - maxRetries: 2, - retryDelay: 100, + expect(removed).toEqual([ + path.join(configuredBase, "v22.13.1-x64-efe9a9df-1001"), + path.join(defaultBase, "v24.14.1-x64-efe9a9df-1001"), + ]); + expect(removed).not.toContain(path.join(configuredBase, "openclaw")); + for (const cacheDir of removed) { + expect(rmSync).toHaveBeenCalledWith(cacheDir, { + recursive: true, + force: true, + maxRetries: 2, + retryDelay: 100, + }); + } + }); + + it("keeps pruning sibling compile cache dirs after one removal fails", () => { + const configuredBase = path.join("/tmp", "openclaw-cache"); + const attempted: string[] = []; + const warn = vi.fn(); + const firstCacheDir = path.join(configuredBase, "v22.13.1-x64-efe9a9df-1001"); + const secondCacheDir = path.join(configuredBase, "v22.13.1-x64-efe9a9df-1002"); + const rmSync = vi.fn((value: string) => { + attempted.push(value); + if (value === firstCacheDir) { + throw new Error("locked"); + } }); + + pruneOpenClawCompileCache({ + env: { NODE_COMPILE_CACHE: configuredBase }, + existsSync: vi.fn((value: string) => value === configuredBase), + readdirSync: vi.fn(() => [ + { name: path.basename(firstCacheDir), isDirectory: () => true }, + { name: path.basename(secondCacheDir), isDirectory: () => true }, + ]), + rmSync, + log: { warn }, + }); + + expect(attempted).toEqual([firstCacheDir, secondCacheDir]); + expect(warn).toHaveBeenCalledWith( + "[postinstall] could not prune OpenClaw compile cache: Error: locked", + ); }); it("prunes source-checkout bundled plugin node_modules", async () => {