



























@@ -373,6 +373,100 @@ describe("update global helpers", () => {
373373});
374374});
375375376+it("keeps npm ownership but avoids per-Node npm commands for reinstall", async () => {
377+await withMockedPlatform("darwin", async () => {
378+await withTempDir({ prefix: "openclaw-update-node-version-prefix-" }, async (base) => {
379+const pathNpmRoot = path.join(base, "path-npm", "lib", "node_modules");
380+const layouts = [
381+{
382+name: "homebrew-cellar",
383+prefix: path.join(base, "opt", "homebrew", "Cellar", "node", "24.5.0"),
384+},
385+{
386+name: "nvm",
387+prefix: path.join(base, "home", ".nvm", "versions", "node", "v24.5.0"),
388+},
389+{
390+name: "asdf",
391+prefix: path.join(base, "home", ".asdf", "installs", "nodejs", "24.5.0"),
392+},
393+{
394+name: "volta",
395+prefix: path.join(base, "home", ".volta", "tools", "image", "node", "24.5.0"),
396+},
397+{
398+name: "fnm",
399+prefix: path.join(
400+base,
401+"home",
402+".local",
403+"share",
404+"fnm",
405+"node-versions",
406+"v24.5.0",
407+"installation",
408+),
409+},
410+{
411+name: "n",
412+prefix: path.join(base, "usr", "local", "n", "versions", "node", "24.5.0"),
413+},
414+];
415+416+for (const layout of layouts) {
417+const nodeManagedRoot = path.join(layout.prefix, "lib", "node_modules");
418+const pkgRoot = path.join(nodeManagedRoot, "openclaw");
419+const nodeManagedNpm = path.join(layout.prefix, "bin", "npm");
420+await fs.mkdir(pkgRoot, { recursive: true });
421+await fs.mkdir(path.dirname(nodeManagedNpm), { recursive: true });
422+await fs.writeFile(nodeManagedNpm, "", "utf8");
423+424+const runCommand = createNpmRootRunner({
425+defaultNpmRoot: pathNpmRoot,
426+overrideCommand: nodeManagedNpm,
427+overrideNpmRoot: nodeManagedRoot,
428+});
429+430+await expect(
431+detectGlobalInstallManagerForRoot(runCommand, pkgRoot, 1000),
432+layout.name,
433+).resolves.toBe("npm");
434+await expect(
435+resolveGlobalRoot("npm", runCommand, 1000, pkgRoot),
436+layout.name,
437+).resolves.toBe(pathNpmRoot);
438+expect(resolveGlobalInstallCommand("npm", pkgRoot), layout.name).toEqual({
439+manager: "npm",
440+command: "npm",
441+});
442+expect(globalInstallArgs("npm", "openclaw@latest", pkgRoot), layout.name).toEqual([
443+"npm",
444+"i",
445+"-g",
446+"openclaw@latest",
447+"--no-fund",
448+"--no-audit",
449+"--loglevel=error",
450+"--min-release-age=0",
451+]);
452+expect(globalInstallFallbackArgs("npm", "openclaw@latest", pkgRoot), layout.name).toEqual(
453+[
454+"npm",
455+"i",
456+"-g",
457+"openclaw@latest",
458+"--omit=optional",
459+"--no-fund",
460+"--no-audit",
461+"--loglevel=error",
462+"--min-release-age=0",
463+],
464+);
465+}
466+});
467+});
468+});
469+376470it("does not infer npm ownership from path shape alone when the owning npm binary is absent", async () => {
377471await withTempDir({ prefix: "openclaw-update-npm-missing-bin-" }, async (base) => {
378472const brewRoot = path.join(base, "opt", "homebrew", "lib", "node_modules");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。