fix: skip redundant bundled runtime dep repairs

This commit is contained in:
Peter Steinberger
2026-04-21 07:31:31 +01:00
parent 494cd78889
commit 0532feb0d3
2 changed files with 105 additions and 1 deletions

View File

@@ -275,6 +275,80 @@ describe("ensureBundledPluginRuntimeDeps", () => {
expect(result).toEqual({ installedSpecs: [], retainSpecs: [] });
});
it("skips install when runtime deps resolve from the package root", () => {
const packageRoot = makeTempDir();
const pluginRoot = path.join(packageRoot, "dist", "extensions", "openai");
fs.mkdirSync(path.join(packageRoot, "node_modules", "@mariozechner", "pi-ai"), {
recursive: true,
});
fs.mkdirSync(pluginRoot, { recursive: true });
fs.writeFileSync(
path.join(pluginRoot, "package.json"),
JSON.stringify({
dependencies: {
"@mariozechner/pi-ai": "0.67.68",
},
}),
);
fs.writeFileSync(
path.join(packageRoot, "node_modules", "@mariozechner", "pi-ai", "package.json"),
JSON.stringify({ name: "@mariozechner/pi-ai", version: "0.67.68" }),
);
const result = ensureBundledPluginRuntimeDeps({
env: {},
installDeps: () => {
throw new Error("package-root runtime deps should not reinstall");
},
pluginId: "openai",
pluginRoot,
});
expect(result).toEqual({ installedSpecs: [], retainSpecs: [] });
});
it("installs only deps missing from plugin and package-root resolution", () => {
const packageRoot = makeTempDir();
const pluginRoot = path.join(packageRoot, "dist", "extensions", "codex");
fs.mkdirSync(path.join(packageRoot, "node_modules", "ws"), { recursive: true });
fs.mkdirSync(pluginRoot, { recursive: true });
fs.writeFileSync(
path.join(pluginRoot, "package.json"),
JSON.stringify({
dependencies: {
ws: "^8.20.0",
zod: "^4.3.6",
},
}),
);
fs.writeFileSync(
path.join(packageRoot, "node_modules", "ws", "package.json"),
JSON.stringify({ name: "ws", version: "8.20.0" }),
);
const calls: BundledRuntimeDepsInstallParams[] = [];
const result = ensureBundledPluginRuntimeDeps({
env: {},
installDeps: (params) => {
calls.push(params);
},
pluginId: "codex",
pluginRoot,
});
expect(result).toEqual({
installedSpecs: ["zod@^4.3.6"],
retainSpecs: ["ws@^8.20.0", "zod@^4.3.6"],
});
expect(calls).toEqual([
{
installRoot: pluginRoot,
missingSpecs: ["zod@^4.3.6"],
installSpecs: ["ws@^8.20.0", "zod@^4.3.6"],
},
]);
});
it("does not treat sibling extension runtime deps as satisfying a plugin", () => {
const packageRoot = makeTempDir();
const extensionsRoot = path.join(packageRoot, "dist", "extensions");

View File

@@ -91,6 +91,26 @@ function resolveSourceCheckoutDistPackageRoot(pluginRoot: string): string | null
return isSourceCheckoutRoot(packageRoot) ? packageRoot : null;
}
function resolveBundledRuntimeDependencySearchRoots(params: {
installRoot: string;
pluginRoot: string;
}): string[] {
const roots = new Set<string>([params.installRoot]);
const pluginRoot = path.resolve(params.pluginRoot);
const extensionsDir = path.dirname(pluginRoot);
const buildDir = path.dirname(extensionsDir);
if (
path.basename(extensionsDir) !== "extensions" ||
(path.basename(buildDir) !== "dist" && path.basename(buildDir) !== "dist-runtime")
) {
return [...roots];
}
roots.add(extensionsDir);
roots.add(buildDir);
roots.add(path.dirname(buildDir));
return [...roots];
}
function createRuntimeDepsCacheKey(pluginId: string, specs: readonly string[]): string {
return createHash("sha256")
.update(pluginId)
@@ -121,6 +141,12 @@ function hasAllDependencySentinels(rootDir: string, deps: readonly { name: strin
return deps.every((dep) => fs.existsSync(path.join(rootDir, dependencySentinelPath(dep.name))));
}
function hasDependencySentinel(searchRoots: readonly string[], dep: { name: string }): boolean {
return searchRoots.some((rootDir) =>
fs.existsSync(path.join(rootDir, dependencySentinelPath(dep.name))),
);
}
function replaceNodeModulesDir(targetDir: string, sourceDir: string): void {
const parentDir = path.dirname(targetDir);
const tempDir = fs.mkdtempSync(path.join(parentDir, ".openclaw-runtime-deps-copy-"));
@@ -528,11 +554,15 @@ export function ensureBundledPluginRuntimeDeps(params: {
}
const installRoot = resolveBundledRuntimeDependencyInstallRoot(params.pluginRoot);
const dependencySearchRoots = resolveBundledRuntimeDependencySearchRoots({
installRoot,
pluginRoot: params.pluginRoot,
});
const dependencySpecs = deps
.map((dep) => `${dep.name}@${dep.version}`)
.toSorted((left, right) => left.localeCompare(right));
const missingSpecs = deps
.filter((dep) => !fs.existsSync(path.join(installRoot, dependencySentinelPath(dep.name))))
.filter((dep) => !hasDependencySentinel(dependencySearchRoots, dep))
.map((dep) => `${dep.name}@${dep.version}`)
.toSorted((left, right) => left.localeCompare(right));
if (missingSpecs.length === 0) {