fix: stage npm updates under global root

This commit is contained in:
Shakker
2026-04-27 14:26:24 +01:00
parent b0127b9f1f
commit 2186080963
4 changed files with 16 additions and 2 deletions

View File

@@ -66,6 +66,16 @@ describe("inferUpdateFailureHints", () => {
expect(hints.join("\n")).toContain("npm config set prefix ~/.local");
});
it("returns EACCES hint for staged package permission failures", () => {
const result = makeResult(
"global install stage",
"EACCES: permission denied, mkdtemp '/usr/local/lib/node_modules/.openclaw-update-stage-'",
);
const hints = inferUpdateFailureHints(result);
expect(hints.join("\n")).toContain("EACCES");
expect(hints.join("\n")).toContain("npm config set prefix ~/.local");
});
it("returns native optional dependency hint for node-gyp failures", () => {
const result = makeResult("global update", "node-pre-gyp ERR!\nnode-gyp rebuild failed");
const hints = inferUpdateFailureHints(result);

View File

@@ -78,8 +78,10 @@ export function inferUpdateFailureHints(result: UpdateRunResult): string[] {
const stderr = normalizeLowercaseStringOrEmpty(failedStep.stderrTail);
const hints: string[] = [];
const isGlobalPackageInstallStep =
failedStep.name.startsWith("global update") || failedStep.name.startsWith("global install");
if (failedStep.name.startsWith("global update") && stderr.includes("eacces")) {
if (isGlobalPackageInstallStep && stderr.includes("eacces")) {
hints.push(
"Detected permission failure (EACCES). Re-run with a writable global prefix or sudo (for system-managed Node installs).",
);

View File

@@ -66,6 +66,7 @@ describe("runGlobalPackageUpdateSteps", () => {
if (!stagePrefix) {
throw new Error("missing staged prefix");
}
expect(path.dirname(stagePrefix)).toBe(globalRoot);
await writePackageRoot(
path.join(stagePrefix, "lib", "node_modules", "openclaw"),
"2.0.0",

View File

@@ -62,7 +62,8 @@ async function createStagedNpmInstall(
if (!targetLayout) {
return null;
}
const prefix = await fs.mkdtemp(path.join(targetLayout.prefix, ".openclaw-update-stage-"));
await fs.mkdir(targetLayout.globalRoot, { recursive: true });
const prefix = await fs.mkdtemp(path.join(targetLayout.globalRoot, ".openclaw-update-stage-"));
const layout = resolveNpmGlobalPrefixLayoutFromPrefix(prefix);
return {
prefix,