greptile fix

This commit is contained in:
jayeshp19 2026-04-03 13:55:43 +05:30 committed by Peter Steinberger
parent eb4b6f7024
commit b9ede82cc2
2 changed files with 90 additions and 22 deletions

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import { bundledDistPluginFile } from "../../test/helpers/bundled-plugin-paths.js";
import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/runtime-sidecar-paths.js";
import { captureEnv } from "../test-utils/env.js";
@ -143,10 +143,7 @@ describe("update global helpers", () => {
const brewRoot = path.join(brewPrefix, "lib", "node_modules");
const pkgRoot = path.join(brewRoot, "openclaw");
const pathNpmRoot = path.join(base, "nvm", "lib", "node_modules");
const brewNpm = path.join(
brewBin,
process.platform === "win32" ? "npm.cmd" : "npm",
);
const brewNpm = path.join(brewBin, process.platform === "win32" ? "npm.cmd" : "npm");
await fs.mkdir(pkgRoot, { recursive: true });
await fs.mkdir(brewBin, { recursive: true });
await fs.writeFile(brewNpm, "", "utf8");
@ -164,13 +161,9 @@ describe("update global helpers", () => {
throw new Error(`unexpected command: ${argv.join(" ")}`);
};
await expect(detectGlobalInstallManagerForRoot(runCommand, pkgRoot, 1000)).resolves.toBe(
"npm",
);
await expect(detectGlobalInstallManagerForRoot(runCommand, pkgRoot, 1000)).resolves.toBe("npm");
await expect(resolveGlobalRoot("npm", runCommand, 1000, pkgRoot)).resolves.toBe(brewRoot);
await expect(resolveGlobalPackageRoot("npm", runCommand, 1000, pkgRoot)).resolves.toBe(
pkgRoot,
);
await expect(resolveGlobalPackageRoot("npm", runCommand, 1000, pkgRoot)).resolves.toBe(pkgRoot);
expect(globalInstallArgs("npm", "openclaw@latest", pkgRoot)).toEqual([
brewNpm,
"i",
@ -192,6 +185,74 @@ describe("update global helpers", () => {
]);
});
it("does not infer npm ownership from path shape alone when the owning npm binary is absent", async () => {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-npm-missing-bin-"));
const brewRoot = path.join(base, "opt", "homebrew", "lib", "node_modules");
const pkgRoot = path.join(brewRoot, "openclaw");
const pathNpmRoot = path.join(base, "nvm", "lib", "node_modules");
await fs.mkdir(pkgRoot, { recursive: true });
const runCommand: CommandRunner = async (argv) => {
if (argv[0] === "npm") {
return { stdout: `${pathNpmRoot}\n`, stderr: "", code: 0 };
}
if (argv[0] === "pnpm") {
return { stdout: "", stderr: "", code: 1 };
}
throw new Error(`unexpected command: ${argv.join(" ")}`);
};
await expect(detectGlobalInstallManagerForRoot(runCommand, pkgRoot, 1000)).resolves.toBeNull();
expect(globalInstallArgs("npm", "openclaw@latest", pkgRoot)).toEqual([
"npm",
"i",
"-g",
"openclaw@latest",
"--no-fund",
"--no-audit",
"--loglevel=error",
]);
});
it("prefers npm.cmd for win32-style global npm roots", async () => {
const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
const base = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-update-win32-npm-prefix-"));
const npmPrefix = path.join(base, "Roaming", "npm");
const npmRoot = path.join(npmPrefix, "node_modules");
const pkgRoot = path.join(npmRoot, "openclaw");
const npmCmd = path.join(npmPrefix, "npm.cmd");
const pathNpmRoot = path.join(base, "nvm", "node_modules");
await fs.mkdir(pkgRoot, { recursive: true });
await fs.writeFile(npmCmd, "", "utf8");
const runCommand: CommandRunner = async (argv) => {
if (argv[0] === "npm") {
return { stdout: `${pathNpmRoot}\n`, stderr: "", code: 0 };
}
if (argv[0] === npmCmd) {
return { stdout: `${npmRoot}\n`, stderr: "", code: 0 };
}
if (argv[0] === "pnpm") {
return { stdout: "", stderr: "", code: 1 };
}
throw new Error(`unexpected command: ${argv.join(" ")}`);
};
await expect(detectGlobalInstallManagerForRoot(runCommand, pkgRoot, 1000)).resolves.toBe("npm");
await expect(resolveGlobalRoot("npm", runCommand, 1000, pkgRoot)).resolves.toBe(npmRoot);
expect(globalInstallArgs("npm", "openclaw@latest", pkgRoot)).toEqual([
npmCmd,
"i",
"-g",
"openclaw@latest",
"--no-fund",
"--no-audit",
"--loglevel=error",
]);
platformSpy.mockRestore();
});
it("builds install argv and npm fallback argv", () => {
expect(globalInstallArgs("npm", "openclaw@latest")).toEqual([
"npm",

View File

@ -191,11 +191,24 @@ function inferNpmPrefixFromPackageRoot(pkgRoot?: string | null): string | null {
if (path.basename(nodeModulesDir) !== "node_modules") {
return null;
}
const libDir = path.dirname(nodeModulesDir);
if (path.basename(libDir) !== "lib") {
const parentDir = path.dirname(nodeModulesDir);
if (path.basename(parentDir) === "lib") {
return path.dirname(parentDir);
}
if (process.platform === "win32" && path.basename(parentDir).toLowerCase() === "npm") {
return parentDir;
}
return null;
}
function resolvePreferredNpmCommand(pkgRoot?: string | null): string | null {
const prefix = inferNpmPrefixFromPackageRoot(pkgRoot);
if (!prefix) {
return null;
}
return path.dirname(libDir);
const candidate =
process.platform === "win32" ? path.join(prefix, "npm.cmd") : path.join(prefix, "bin", "npm");
return fsSync.existsSync(candidate) ? candidate : null;
}
function resolvePreferredGlobalManagerCommand(
@ -205,13 +218,7 @@ function resolvePreferredGlobalManagerCommand(
if (manager !== "npm") {
return manager;
}
const prefix = inferNpmPrefixFromPackageRoot(pkgRoot);
if (!prefix) {
return manager;
}
const candidate =
process.platform === "win32" ? path.join(prefix, "npm.cmd") : path.join(prefix, "bin", "npm");
return fsSync.existsSync(candidate) ? candidate : manager;
return resolvePreferredNpmCommand(pkgRoot) ?? manager;
}
export async function resolveGlobalRoot(
@ -290,7 +297,7 @@ export async function detectGlobalInstallManagerForRoot(
}
}
if (inferNpmPrefixFromPackageRoot(pkgRoot)) {
if (resolvePreferredNpmCommand(pkgRoot)) {
return "npm";
}