From cdb424a64282a073292a0b38f5b82aaa14c9f1eb Mon Sep 17 00:00:00 2001 From: "clawsweeper[bot]" <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:09:31 -0700 Subject: [PATCH] fix: Found one regression in the runtime dependency staging cache. The (#74517) Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com> --- .../lib/bundled-runtime-deps-package-tree.mjs | 19 ++++--- .../stage-bundled-plugin-runtime-deps.test.ts | 49 +++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/scripts/lib/bundled-runtime-deps-package-tree.mjs b/scripts/lib/bundled-runtime-deps-package-tree.mjs index 766f63ecbf4..4bc1556938b 100644 --- a/scripts/lib/bundled-runtime-deps-package-tree.mjs +++ b/scripts/lib/bundled-runtime-deps-package-tree.mjs @@ -236,15 +236,17 @@ function appendDirectoryFingerprint(hash, rootDir, currentDir = rootDir) { } } -function createInstalledRuntimeClosureFingerprint(rootNodeModulesDir, dependencyNames) { +function createInstalledRuntimeClosureFingerprint(records) { const hash = createHash("sha256"); - for (const depName of [...dependencyNames].toSorted((left, right) => left.localeCompare(right))) { - const depRoot = dependencyNodeModulesPath(rootNodeModulesDir, depName); - if (depRoot === null || !fs.existsSync(depRoot)) { + for (const record of [...records].toSorted( + (left, right) => + left.name.localeCompare(right.name) || left.realRoot.localeCompare(right.realRoot), + )) { + if (!fs.existsSync(record.realRoot)) { return null; } - hash.update(`package:${depName}:${fs.realpathSync(depRoot)}\n`); - appendDirectoryFingerprint(hash, depRoot); + hash.update(`package:${record.name}:${record.realRoot}\n`); + appendDirectoryFingerprint(hash, record.realRoot); } return hash.digest("hex"); } @@ -266,8 +268,5 @@ export function resolveInstalledRuntimeClosureFingerprint(params) { if (resolution === null) { return null; } - return createInstalledRuntimeClosureFingerprint( - params.rootNodeModulesDir, - selectRuntimeDependencyRootsToCopy(resolution).map((record) => record.name), - ); + return createInstalledRuntimeClosureFingerprint(selectRuntimeDependencyRootsToCopy(resolution)); } diff --git a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts index 421ff267338..c58c4f1acb7 100644 --- a/test/scripts/stage-bundled-plugin-runtime-deps.test.ts +++ b/test/scripts/stage-bundled-plugin-runtime-deps.test.ts @@ -574,6 +574,55 @@ describe("stageBundledPluginRuntimeDeps", () => { ).toBe("module.exports = 'second';\n"); }); + it("restages when plugin-local installed runtime dependency contents change", () => { + const { pluginDir, repoRoot } = createBundledPluginFixture({ + packageJson: { + name: "@openclaw/fixture-plugin", + version: "1.0.0", + dependencies: { direct: "1.0.0" }, + openclaw: { bundle: { stageRuntimeDependencies: true } }, + }, + }); + const rootDirectDir = path.join(repoRoot, "node_modules", "direct"); + const sourcePluginDir = path.join(repoRoot, "extensions", "fixture-plugin"); + const pluginDirectDir = path.join(sourcePluginDir, "node_modules", "direct"); + fs.mkdirSync(rootDirectDir, { recursive: true }); + fs.mkdirSync(pluginDirectDir, { recursive: true }); + fs.writeFileSync( + path.join(sourcePluginDir, "package.json"), + '{ "name": "@openclaw/fixture-plugin", "version": "1.0.0" }\n', + "utf8", + ); + fs.writeFileSync( + path.join(rootDirectDir, "package.json"), + '{ "name": "direct", "version": "1.0.0" }\n', + "utf8", + ); + fs.writeFileSync(path.join(rootDirectDir, "index.js"), "module.exports = 'root';\n", "utf8"); + fs.writeFileSync( + path.join(pluginDirectDir, "package.json"), + '{ "name": "direct", "version": "1.0.0" }\n', + "utf8", + ); + fs.writeFileSync(path.join(pluginDirectDir, "index.js"), "module.exports = 'first';\n", "utf8"); + + stageBundledPluginRuntimeDeps({ cwd: repoRoot }); + expect( + fs.readFileSync(path.join(pluginDir, "node_modules", "direct", "index.js"), "utf8"), + ).toBe("module.exports = 'first';\n"); + + fs.writeFileSync( + path.join(pluginDirectDir, "index.js"), + "module.exports = 'second';\n", + "utf8", + ); + stageBundledPluginRuntimeDeps({ cwd: repoRoot }); + + expect( + fs.readFileSync(path.join(pluginDir, "node_modules", "direct", "index.js"), "utf8"), + ).toBe("module.exports = 'second';\n"); + }); + it("fingerprints regular files when readdir reports symlink-like entries", () => { const { pluginDir, repoRoot } = createBundledPluginFixture({ packageJson: {