fix(update): preserve pnpm and Bun global installs (#107802)

* fix(update): preserve pnpm and bun global installs

* fix(update): anchor pnpm updates to invoking install

* fix(update): recover skipped pnpm lifecycle

* fix(update): fail closed on ambiguous pnpm ownership

* fix(update): tolerate pnpm probe warnings

* fix(update): bind pnpm installs to project owners

* fix(update): isolate pnpm mutations from caller pins

* docs(changelog): credit pnpm 11 report

Co-authored-by: jincheng-xydt <xu.jincheng@xydigit.com>

* chore(changelog): defer release note ownership

* fix(update): make package-root fallback explicit

* test(update): split pnpm scenario coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
jincheng-xydt
2026-07-15 23:03:35 +08:00
committed by GitHub
parent c41f46260c
commit b2e42e3645
12 changed files with 2055 additions and 89 deletions

View File

@@ -1,7 +1,10 @@
// Shared command runner tests cover update helper command execution and error capture.
import fs from "node:fs/promises";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { defaultRuntime } from "../../runtime.js";
import { createGlobalCommandRunner, parseTimeoutMsOrExit } from "./shared.js";
import { withTempDir } from "../../test-helpers/temp-dir.js";
import { createGlobalCommandRunner, parseTimeoutMsOrExit, resolveUpdateRoot } from "./shared.js";
const runCommandWithTimeout = vi.hoisted(() => vi.fn());
@@ -90,4 +93,30 @@ describe("createGlobalCommandRunner", () => {
exit.mockRestore();
}
});
it.runIf(process.platform !== "win32")(
"resolves update ownership from the lexical invocation path",
async () => {
await withTempDir({ prefix: "openclaw-update-root-" }, async (base) => {
const storeRoot = path.join(base, "store", "openclaw");
const packageRoot = path.join(base, "global", "v11", "install", "node_modules", "openclaw");
await fs.mkdir(path.dirname(packageRoot), { recursive: true });
await fs.mkdir(storeRoot, { recursive: true });
await fs.writeFile(
path.join(storeRoot, "package.json"),
JSON.stringify({ name: "openclaw", version: "1.0.0" }),
"utf8",
);
await fs.symlink(storeRoot, packageRoot, "dir");
const previousArgv = [...process.argv];
process.argv[1] = path.join(packageRoot, "openclaw.mjs");
try {
await expect(resolveUpdateRoot()).resolves.toBe(packageRoot);
} finally {
process.argv.splice(0, process.argv.length, ...previousArgv);
}
});
},
);
});

View File

@@ -173,12 +173,15 @@ export function resolveNodeRunner(): string {
/** Locate the installed OpenClaw package root that should receive update operations. */
export async function resolveUpdateRoot(): Promise<string> {
// Preserve the lexical package path from the invoking shim. pnpm 11 package
// modules realpath into a shared store, which is not the install owner.
const invocationRoot = process.argv[1]
? await resolveOpenClawPackageRoot({ cwd: path.dirname(path.resolve(process.argv[1])) })
: null;
return (
(await resolveOpenClawPackageRoot({
moduleUrl: import.meta.url,
argv1: process.argv[1],
cwd: process.cwd(),
})) ?? process.cwd()
invocationRoot ??
(await resolveOpenClawPackageRoot({ moduleUrl: import.meta.url, cwd: process.cwd() })) ??
process.cwd()
);
}