mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fd 10.5.1 fixes --full-path globs on Windows (sharkdp/fd#2067). The old [/\] separator rewrite breaks every path glob there, so it now applies only to fd before 10.5.1. Backslashes in Windows patterns are normalized to '/'. fixes #9262
115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { createFindToolDefinition, usesLegacyWindowsFullPathGlob } from "../../../src/core/tools/find.ts";
|
|
import { getToolPath } from "../../../src/utils/tools-manager.ts";
|
|
|
|
// fd before 10.5.1 on Windows only gets a best-effort workaround (sharkdp/fd#2067): it misses files
|
|
// directly under the prefix and lets '*' cross directories, so exact-result cases skip there.
|
|
const fdPath = getToolPath("fd");
|
|
const exactPathGlobs = !(process.platform === "win32" && fdPath && (await usesLegacyWindowsFullPathGlob(fdPath)));
|
|
|
|
/**
|
|
* Regression test for https://github.com/earendil-works/pi/issues/3302
|
|
*
|
|
* The `find` tool advertises glob patterns like `src/**\/*.spec.ts`, but the
|
|
* default fd-backed implementation used `fd --glob <pattern>` without
|
|
* `--full-path`, which makes fd match only against the basename. Any pattern
|
|
* containing a `/` therefore silently returned no matches.
|
|
*
|
|
* The fix switches fd into full-path mode when the pattern contains a `/`
|
|
* and prepends `**\/` so the pattern can match against the absolute candidate
|
|
* path that fd feeds to the matcher.
|
|
*/
|
|
describe("issue #3302 find returns no results for path-based glob patterns", () => {
|
|
let tempRoot: string;
|
|
|
|
beforeEach(() => {
|
|
tempRoot = mkdtempSync(join(tmpdir(), "pi-3302-"));
|
|
mkdirSync(join(tempRoot, "some", "parent", "child"), { recursive: true });
|
|
mkdirSync(join(tempRoot, "src", "foo", "bar"), { recursive: true });
|
|
writeFileSync(join(tempRoot, "some", "parent", "child", "file.ext"), "");
|
|
writeFileSync(join(tempRoot, "some", "parent", "child", "test.spec.ts"), "");
|
|
writeFileSync(join(tempRoot, "src", "foo", "bar", "example.spec.ts"), "");
|
|
writeFileSync(join(tempRoot, "src", "direct.spec.ts"), "");
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(tempRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
async function runFind(pattern: string): Promise<string[]> {
|
|
const def = createFindToolDefinition(tempRoot);
|
|
// The find tool implementation does not touch ctx; pass a minimal stub.
|
|
const ctx = {} as Parameters<typeof def.execute>[4];
|
|
const result = (await def.execute("call-1", { pattern }, undefined, undefined, ctx)) as {
|
|
content: Array<{ type: string; text?: string }>;
|
|
};
|
|
const text = result.content[0]?.text ?? "";
|
|
if (text === "No files found matching pattern") return [];
|
|
return text
|
|
.split("\n")
|
|
.map((l) => l.trim())
|
|
.filter((l) => l.length > 0 && !l.startsWith("["));
|
|
}
|
|
|
|
it("basename pattern still matches (regression-safe)", async () => {
|
|
const files = await runFind("*.spec.ts");
|
|
expect(files.sort()).toEqual([
|
|
"some/parent/child/test.spec.ts",
|
|
"src/direct.spec.ts",
|
|
"src/foo/bar/example.spec.ts",
|
|
]);
|
|
});
|
|
|
|
it("directory-prefixed pattern with ** tail matches subtree", async () => {
|
|
const files = await runFind("some/parent/child/**");
|
|
// Matches files (and possibly directories) under the subtree. Assert the two files are present.
|
|
expect(files).toContain("some/parent/child/file.ext");
|
|
expect(files).toContain("some/parent/child/test.spec.ts");
|
|
});
|
|
|
|
it("leading ** wildcard with path segments matches", async () => {
|
|
const files = await runFind("**/parent/child/*");
|
|
expect(files.sort()).toContain("some/parent/child/file.ext");
|
|
expect(files.sort()).toContain("some/parent/child/test.spec.ts");
|
|
});
|
|
|
|
it.runIf(exactPathGlobs)("src/**/*.spec.ts matches direct and nested spec files", async () => {
|
|
const files = await runFind("src/**/*.spec.ts");
|
|
expect(files.sort()).toEqual(["src/direct.spec.ts", "src/foo/bar/example.spec.ts"]);
|
|
});
|
|
|
|
it.runIf(exactPathGlobs)("src/*.spec.ts does not cross directories", async () => {
|
|
const files = await runFind("src/*.spec.ts");
|
|
expect(files).toEqual(["src/direct.spec.ts"]);
|
|
});
|
|
|
|
// https://github.com/earendil-works/pi/issues/9262: on Windows, `\` is a path separator.
|
|
// On POSIX it is a glob escape character, so these cases are Windows-only.
|
|
it.runIf(process.platform === "win32" && exactPathGlobs)(
|
|
"Windows backslash patterns match like forward-slash patterns",
|
|
async () => {
|
|
expect((await runFind(String.raw`src\**\*.spec.ts`)).sort()).toEqual([
|
|
"src/direct.spec.ts",
|
|
"src/foo/bar/example.spec.ts",
|
|
]);
|
|
expect(await runFind(String.raw`src\*.spec.ts`)).toEqual(["src/direct.spec.ts"]);
|
|
expect((await runFind(String.raw`**\parent\child\*`)).sort()).toEqual([
|
|
"some/parent/child/file.ext",
|
|
"some/parent/child/test.spec.ts",
|
|
]);
|
|
expect((await runFind(String.raw`src\**/*.spec.ts`)).sort()).toEqual([
|
|
"src/direct.spec.ts",
|
|
"src/foo/bar/example.spec.ts",
|
|
]);
|
|
},
|
|
);
|
|
|
|
it.runIf(process.platform !== "win32")("POSIX backslash still escapes glob characters", async () => {
|
|
writeFileSync(join(tempRoot, "src", "*.ts"), "");
|
|
writeFileSync(join(tempRoot, "src", "other.ts"), "");
|
|
expect(await runFind(String.raw`src/\*.ts`)).toEqual(["src/*.ts"]);
|
|
});
|
|
});
|