mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:00:43 +00:00
fix(plugins): clean bundled runtime install stage
This commit is contained in:
@@ -261,6 +261,47 @@ describe("installBundledRuntimeDeps", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("cleans an owned isolated execution root after copying node_modules back", () => {
|
||||
const installRoot = makeTempDir();
|
||||
const installExecutionRoot = path.join(installRoot, ".openclaw-install-stage");
|
||||
spawnSyncMock.mockImplementation((_command, _args, options) => {
|
||||
const cwd = String(options?.cwd ?? "");
|
||||
fs.mkdirSync(path.join(cwd, "node_modules", "tokenjuice"), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(cwd, "node_modules", "tokenjuice", "package.json"),
|
||||
JSON.stringify({ name: "tokenjuice", version: "0.6.1" }),
|
||||
);
|
||||
return {
|
||||
pid: 123,
|
||||
output: [],
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
signal: null,
|
||||
status: 0,
|
||||
};
|
||||
});
|
||||
|
||||
installBundledRuntimeDeps({
|
||||
installRoot,
|
||||
installExecutionRoot,
|
||||
missingSpecs: ["tokenjuice@0.6.1"],
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(fs.existsSync(installExecutionRoot)).toBe(false);
|
||||
expect(
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(installRoot, "node_modules", "tokenjuice", "package.json"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
).toEqual({
|
||||
name: "tokenjuice",
|
||||
version: "0.6.1",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fail an isolated runtime deps install when temp cleanup races", () => {
|
||||
const installRoot = makeTempDir();
|
||||
const installExecutionRoot = makeTempDir();
|
||||
@@ -483,7 +524,9 @@ describe("ensureBundledPluginRuntimeDeps", () => {
|
||||
]);
|
||||
// The stage dir must be distinct from the plugin root so npm does not read
|
||||
// the plugin's cwd manifest during install.
|
||||
expect(path.resolve(calls[0]!.installExecutionRoot!)).not.toEqual(path.resolve(pluginRoot));
|
||||
const installExecutionRoot = calls[0]?.installExecutionRoot;
|
||||
expect(installExecutionRoot).toBeDefined();
|
||||
expect(path.resolve(installExecutionRoot ?? "")).not.toEqual(path.resolve(pluginRoot));
|
||||
});
|
||||
|
||||
it("installs runtime deps into an external stage dir and exposes loader aliases", () => {
|
||||
|
||||
@@ -825,6 +825,15 @@ export function createBundledRuntimeDependencyAliasMap(params: {
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function shouldCleanBundledRuntimeDepsInstallExecutionRoot(params: {
|
||||
installRoot: string;
|
||||
installExecutionRoot: string;
|
||||
}): boolean {
|
||||
const installRoot = path.resolve(params.installRoot);
|
||||
const installExecutionRoot = path.resolve(params.installExecutionRoot);
|
||||
return installExecutionRoot.startsWith(`${installRoot}${path.sep}`);
|
||||
}
|
||||
|
||||
export function installBundledRuntimeDeps(params: {
|
||||
installRoot: string;
|
||||
installExecutionRoot?: string;
|
||||
@@ -832,39 +841,53 @@ export function installBundledRuntimeDeps(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
}): void {
|
||||
const installExecutionRoot = params.installExecutionRoot ?? params.installRoot;
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
fs.mkdirSync(installExecutionRoot, { recursive: true });
|
||||
if (path.resolve(installExecutionRoot) !== path.resolve(params.installRoot)) {
|
||||
fs.writeFileSync(
|
||||
path.join(installExecutionRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw-runtime-deps-install", private: true }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
const installEnv = createBundledRuntimeDepsInstallEnv(params.env);
|
||||
const npmRunner = resolveBundledRuntimeDepsNpmRunner({
|
||||
env: installEnv,
|
||||
npmArgs: createBundledRuntimeDepsInstallArgs(params.missingSpecs),
|
||||
});
|
||||
const result = spawnSync(npmRunner.command, npmRunner.args, {
|
||||
cwd: installExecutionRoot,
|
||||
encoding: "utf8",
|
||||
env: npmRunner.env ?? installEnv,
|
||||
stdio: "pipe",
|
||||
});
|
||||
if (result.status !== 0 || result.error) {
|
||||
const output = [result.error?.message, result.stderr, result.stdout]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
.trim();
|
||||
throw new Error(output || "npm install failed");
|
||||
}
|
||||
if (path.resolve(installExecutionRoot) !== path.resolve(params.installRoot)) {
|
||||
const stagedNodeModulesDir = path.join(installExecutionRoot, "node_modules");
|
||||
if (!fs.existsSync(stagedNodeModulesDir)) {
|
||||
throw new Error("npm install did not produce node_modules");
|
||||
const isolatedExecutionRoot =
|
||||
path.resolve(installExecutionRoot) !== path.resolve(params.installRoot);
|
||||
const cleanInstallExecutionRoot =
|
||||
isolatedExecutionRoot &&
|
||||
shouldCleanBundledRuntimeDepsInstallExecutionRoot({
|
||||
installRoot: params.installRoot,
|
||||
installExecutionRoot,
|
||||
});
|
||||
try {
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
fs.mkdirSync(installExecutionRoot, { recursive: true });
|
||||
if (isolatedExecutionRoot) {
|
||||
fs.writeFileSync(
|
||||
path.join(installExecutionRoot, "package.json"),
|
||||
`${JSON.stringify({ name: "openclaw-runtime-deps-install", private: true }, null, 2)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
const installEnv = createBundledRuntimeDepsInstallEnv(params.env);
|
||||
const npmRunner = resolveBundledRuntimeDepsNpmRunner({
|
||||
env: installEnv,
|
||||
npmArgs: createBundledRuntimeDepsInstallArgs(params.missingSpecs),
|
||||
});
|
||||
const result = spawnSync(npmRunner.command, npmRunner.args, {
|
||||
cwd: installExecutionRoot,
|
||||
encoding: "utf8",
|
||||
env: npmRunner.env ?? installEnv,
|
||||
stdio: "pipe",
|
||||
});
|
||||
if (result.status !== 0 || result.error) {
|
||||
const output = [result.error?.message, result.stderr, result.stdout]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
.trim();
|
||||
throw new Error(output || "npm install failed");
|
||||
}
|
||||
if (isolatedExecutionRoot) {
|
||||
const stagedNodeModulesDir = path.join(installExecutionRoot, "node_modules");
|
||||
if (!fs.existsSync(stagedNodeModulesDir)) {
|
||||
throw new Error("npm install did not produce node_modules");
|
||||
}
|
||||
replaceNodeModulesDir(path.join(params.installRoot, "node_modules"), stagedNodeModulesDir);
|
||||
}
|
||||
} finally {
|
||||
if (cleanInstallExecutionRoot) {
|
||||
fs.rmSync(installExecutionRoot, { recursive: true, force: true });
|
||||
}
|
||||
replaceNodeModulesDir(path.join(params.installRoot, "node_modules"), stagedNodeModulesDir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user