mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 08:57:40 +00:00
refactor: dedupe openclaw root traversal and add coverage
This commit is contained in:
@@ -10,6 +10,7 @@ const FIXTURE_BASE = path.join(VITEST_FS_BASE, "openclaw-root");
|
|||||||
const state = vi.hoisted(() => ({
|
const state = vi.hoisted(() => ({
|
||||||
entries: new Map<string, FakeFsEntry>(),
|
entries: new Map<string, FakeFsEntry>(),
|
||||||
realpaths: new Map<string, string>(),
|
realpaths: new Map<string, string>(),
|
||||||
|
realpathErrors: new Set<string>(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const abs = (p: string) => path.resolve(p);
|
const abs = (p: string) => path.resolve(p);
|
||||||
@@ -56,7 +57,15 @@ vi.mock("node:fs", async (importOriginal) => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
realpathSync: (p: string) =>
|
realpathSync: (p: string) =>
|
||||||
isFixturePath(p) ? (state.realpaths.get(abs(p)) ?? abs(p)) : actual.realpathSync(p),
|
isFixturePath(p)
|
||||||
|
? (() => {
|
||||||
|
const resolved = abs(p);
|
||||||
|
if (state.realpathErrors.has(resolved)) {
|
||||||
|
throw new Error(`ENOENT: no such file or directory, realpath '${p}'`);
|
||||||
|
}
|
||||||
|
return state.realpaths.get(resolved) ?? resolved;
|
||||||
|
})()
|
||||||
|
: actual.realpathSync(p),
|
||||||
};
|
};
|
||||||
return { ...wrapped, default: wrapped };
|
return { ...wrapped, default: wrapped };
|
||||||
});
|
});
|
||||||
@@ -84,6 +93,7 @@ describe("resolveOpenClawPackageRoot", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state.entries.clear();
|
state.entries.clear();
|
||||||
state.realpaths.clear();
|
state.realpaths.clear();
|
||||||
|
state.realpathErrors.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves package root from .bin argv1", async () => {
|
it("resolves package root from .bin argv1", async () => {
|
||||||
@@ -109,6 +119,18 @@ describe("resolveOpenClawPackageRoot", () => {
|
|||||||
expect(resolveOpenClawPackageRootSync({ argv1: bin })).toBe(realPkg);
|
expect(resolveOpenClawPackageRootSync({ argv1: bin })).toBe(realPkg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("falls back when argv1 realpath throws", async () => {
|
||||||
|
const { resolveOpenClawPackageRootSync } = await import("./openclaw-root.js");
|
||||||
|
|
||||||
|
const project = fx("realpath-throw-scenario");
|
||||||
|
const argv1 = path.join(project, "node_modules", ".bin", "openclaw");
|
||||||
|
const pkgRoot = path.join(project, "node_modules", "openclaw");
|
||||||
|
state.realpathErrors.add(abs(argv1));
|
||||||
|
setFile(path.join(pkgRoot, "package.json"), JSON.stringify({ name: "openclaw" }));
|
||||||
|
|
||||||
|
expect(resolveOpenClawPackageRootSync({ argv1 })).toBe(pkgRoot);
|
||||||
|
});
|
||||||
|
|
||||||
it("prefers moduleUrl candidates", async () => {
|
it("prefers moduleUrl candidates", async () => {
|
||||||
const { resolveOpenClawPackageRootSync } = await import("./openclaw-root.js");
|
const { resolveOpenClawPackageRootSync } = await import("./openclaw-root.js");
|
||||||
|
|
||||||
@@ -136,4 +158,10 @@ describe("resolveOpenClawPackageRoot", () => {
|
|||||||
|
|
||||||
await expect(resolveOpenClawPackageRoot({ cwd: pkgRoot })).resolves.toBe(pkgRoot);
|
await expect(resolveOpenClawPackageRoot({ cwd: pkgRoot })).resolves.toBe(pkgRoot);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("async resolver returns null when no package roots exist", async () => {
|
||||||
|
const { resolveOpenClawPackageRoot } = await import("./openclaw-root.js");
|
||||||
|
|
||||||
|
await expect(resolveOpenClawPackageRoot({ cwd: fx("missing") })).resolves.toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,35 +26,35 @@ function readPackageNameSync(dir: string): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function findPackageRoot(startDir: string, maxDepth = 12): Promise<string | null> {
|
async function findPackageRoot(startDir: string, maxDepth = 12): Promise<string | null> {
|
||||||
let current = path.resolve(startDir);
|
for (const current of iterAncestorDirs(startDir, maxDepth)) {
|
||||||
for (let i = 0; i < maxDepth; i += 1) {
|
|
||||||
const name = await readPackageName(current);
|
const name = await readPackageName(current);
|
||||||
if (name && CORE_PACKAGE_NAMES.has(name)) {
|
if (name && CORE_PACKAGE_NAMES.has(name)) {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
const parent = path.dirname(current);
|
|
||||||
if (parent === current) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
current = parent;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPackageRootSync(startDir: string, maxDepth = 12): string | null {
|
function findPackageRootSync(startDir: string, maxDepth = 12): string | null {
|
||||||
let current = path.resolve(startDir);
|
for (const current of iterAncestorDirs(startDir, maxDepth)) {
|
||||||
for (let i = 0; i < maxDepth; i += 1) {
|
|
||||||
const name = readPackageNameSync(current);
|
const name = readPackageNameSync(current);
|
||||||
if (name && CORE_PACKAGE_NAMES.has(name)) {
|
if (name && CORE_PACKAGE_NAMES.has(name)) {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* iterAncestorDirs(startDir: string, maxDepth: number): Generator<string> {
|
||||||
|
let current = path.resolve(startDir);
|
||||||
|
for (let i = 0; i < maxDepth; i += 1) {
|
||||||
|
yield current;
|
||||||
const parent = path.dirname(current);
|
const parent = path.dirname(current);
|
||||||
if (parent === current) {
|
if (parent === current) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current = parent;
|
current = parent;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function candidateDirsFromArgv1(argv1: string): string[] {
|
function candidateDirsFromArgv1(argv1: string): string[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user