
























@@ -29,6 +29,15 @@ function createNpmTarget(globalRoot: string): ResolvedGlobalInstallTarget {
2929};
3030}
313132+function createPnpmTarget(globalRoot: string): ResolvedGlobalInstallTarget {
33+return {
34+manager: "pnpm",
35+command: "pnpm",
36+ globalRoot,
37+packageRoot: path.join(globalRoot, "openclaw"),
38+};
39+}
40+3241function createRootRunner(globalRoot: string): CommandRunner {
3342return async (argv) => {
3443if (argv.join(" ") === "npm root -g") {
@@ -115,6 +124,99 @@ describe("runGlobalPackageUpdateSteps", () => {
115124});
116125});
117126127+it("stages pnpm-detected updates through npm when the global root has npm prefix layout", async () => {
128+await withTempDir({ prefix: "openclaw-package-update-pnpm-staged-" }, async (base) => {
129+const prefix = path.join(base, "prefix");
130+const globalRoot = path.join(prefix, "lib", "node_modules");
131+const packageRoot = path.join(globalRoot, "openclaw");
132+const staleChunk = path.join(packageRoot, "dist", "install-C_GuuNz6.js");
133+await writePackageRoot(packageRoot, "1.0.0");
134+await fs.writeFile(staleChunk, 'import "./install.runtime-Xom5hOHq.js";\n', "utf8");
135+136+const runStep = vi.fn(async ({ name, argv, cwd }): Promise<PackageUpdateStepResult> => {
137+if (name !== "global update") {
138+throw new Error(`unexpected step ${name}`);
139+}
140+expect(argv[0]).toBe("npm");
141+expect(argv).toEqual(expect.arrayContaining(["i", "-g", "--prefix", "openclaw@2.0.0"]));
142+expect(argv).not.toContain("pnpm");
143+const prefixIndex = argv.indexOf("--prefix");
144+const stagePrefix = argv[prefixIndex + 1];
145+if (!stagePrefix) {
146+throw new Error("missing staged prefix");
147+}
148+await writePackageRoot(path.join(stagePrefix, "lib", "node_modules", "openclaw"), "2.0.0");
149+return {
150+ name,
151+command: argv.join(" "),
152+cwd: cwd ?? process.cwd(),
153+durationMs: 1,
154+exitCode: 0,
155+};
156+});
157+158+const result = await runGlobalPackageUpdateSteps({
159+installTarget: createPnpmTarget(globalRoot),
160+installSpec: "openclaw@2.0.0",
161+packageName: "openclaw",
162+ packageRoot,
163+runCommand: createRootRunner(globalRoot),
164+ runStep,
165+timeoutMs: 1000,
166+});
167+168+expect(result.failedStep).toBeNull();
169+expect(result.afterVersion).toBe("2.0.0");
170+expect(result.steps.map((step) => step.name)).toEqual([
171+"global update",
172+"global install swap",
173+]);
174+await expect(fs.access(staleChunk)).rejects.toMatchObject({ code: "ENOENT" });
175+});
176+});
177+178+it("keeps Windows pnpm global roots on the pnpm update path", async () => {
179+const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
180+try {
181+await withTempDir({ prefix: "openclaw-package-update-win32-pnpm-" }, async (base) => {
182+const globalRoot = path.join(base, "pnpm", "global", "5", "node_modules");
183+const packageRoot = path.join(globalRoot, "openclaw");
184+await writePackageRoot(packageRoot, "1.0.0");
185+186+const runStep = vi.fn(async ({ name, argv, cwd }): Promise<PackageUpdateStepResult> => {
187+if (name !== "global update") {
188+throw new Error(`unexpected step ${name}`);
189+}
190+expect(argv).toEqual(["pnpm", "add", "-g", "openclaw@2.0.0"]);
191+await writePackageRoot(packageRoot, "2.0.0");
192+return {
193+ name,
194+command: argv.join(" "),
195+cwd: cwd ?? process.cwd(),
196+durationMs: 1,
197+exitCode: 0,
198+};
199+});
200+201+const result = await runGlobalPackageUpdateSteps({
202+installTarget: createPnpmTarget(globalRoot),
203+installSpec: "openclaw@2.0.0",
204+packageName: "openclaw",
205+ packageRoot,
206+runCommand: createRootRunner(globalRoot),
207+ runStep,
208+timeoutMs: 1000,
209+});
210+211+expect(result.failedStep).toBeNull();
212+expect(result.afterVersion).toBe("2.0.0");
213+expect(result.steps.map((step) => step.name)).toEqual(["global update"]);
214+});
215+} finally {
216+platformSpy.mockRestore();
217+}
218+});
219+118220it("keeps a successful staged swap when old package cleanup hits a transient Windows native module error", async () => {
119221await withTempDir({ prefix: "openclaw-package-update-staged-cleanup-" }, async (base) => {
120222const prefix = path.join(base, "prefix");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。