176 lines · 6.6 KB · TypeScript
RawDownload
import fs from "node:fs";import fsPromises from "node:fs/promises";import os from "node:os";import path from "node:path";import { spawnSync } from "node:child_process"; import { afterEach, assert, describe, it, vi } from "vitest"; import { searchWorkspaceEntries } from "./workspaceEntries"; const tempDirs: string[] = []; function makeTempDir(prefix: string): string {  const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));  tempDirs.push(dir);  return dir;} function writeFile(cwd: string, relativePath: string, contents = ""): void {  const absolutePath = path.join(cwd, relativePath);  fs.mkdirSync(path.dirname(absolutePath), { recursive: true });  fs.writeFileSync(absolutePath, contents, "utf8");} function runGit(cwd: string, args: string[]): void {  const result = spawnSync("git", args, { cwd, encoding: "utf8" });  if (result.status !== 0) {    throw new Error(result.stderr || `git ${args.join(" ")} failed`);  }} describe("searchWorkspaceEntries", () => {  afterEach(() => {    vi.restoreAllMocks();    for (const dir of tempDirs.splice(0, tempDirs.length)) {      fs.rmSync(dir, { recursive: true, force: true });    }  });   it("returns files and directories relative to cwd", async () => {    const cwd = makeTempDir("t3code-workspace-entries-");    writeFile(cwd, "src/components/Composer.tsx");    writeFile(cwd, "src/index.ts");    writeFile(cwd, "README.md");    writeFile(cwd, ".git/HEAD");    writeFile(cwd, "node_modules/pkg/index.js");     const result = await searchWorkspaceEntries({ cwd, query: "", limit: 100 });    const paths = result.entries.map((entry) => entry.path);     assert.include(paths, "src");    assert.include(paths, "src/components");    assert.include(paths, "src/components/Composer.tsx");    assert.include(paths, "README.md");    assert.isFalse(paths.some((entryPath) => entryPath.startsWith(".git")));    assert.isFalse(paths.some((entryPath) => entryPath.startsWith("node_modules")));    assert.isFalse(result.truncated);  });   it("filters and ranks entries by query", async () => {    const cwd = makeTempDir("t3code-workspace-query-");    writeFile(cwd, "src/components/Composer.tsx");    writeFile(cwd, "src/components/composePrompt.ts");    writeFile(cwd, "docs/composition.md");     const result = await searchWorkspaceEntries({ cwd, query: "compo", limit: 5 });     assert.isAbove(result.entries.length, 0);    assert.isTrue(result.entries.some((entry) => entry.path === "src/components"));    assert.isTrue(result.entries.every((entry) => entry.path.toLowerCase().includes("compo")));  });   it("excludes gitignored paths for git repositories", async () => {    const cwd = makeTempDir("t3code-workspace-gitignore-");    runGit(cwd, ["init"]);    writeFile(cwd, ".gitignore", ".convex/\nconvex/\nignored.txt\n");    writeFile(cwd, "src/keep.ts", "export {};");    writeFile(cwd, "ignored.txt", "ignore me");    writeFile(cwd, ".convex/local-storage/data.json", "{}");    writeFile(cwd, "convex/UOoS-l/convex_local_storage/modules/data.json", "{}");     const result = await searchWorkspaceEntries({ cwd, query: "", limit: 100 });    const paths = result.entries.map((entry) => entry.path);     assert.include(paths, "src");    assert.include(paths, "src/keep.ts");    assert.notInclude(paths, "ignored.txt");    assert.isFalse(paths.some((entryPath) => entryPath.startsWith(".convex/")));    assert.isFalse(paths.some((entryPath) => entryPath.startsWith("convex/")));  });   it("excludes tracked paths that match ignore rules", async () => {    const cwd = makeTempDir("t3code-workspace-tracked-gitignore-");    runGit(cwd, ["init"]);    writeFile(cwd, ".convex/local-storage/data.json", "{}");    writeFile(cwd, "src/keep.ts", "export {};");    runGit(cwd, ["add", ".convex/local-storage/data.json", "src/keep.ts"]);    writeFile(cwd, ".gitignore", ".convex/\n");     const result = await searchWorkspaceEntries({ cwd, query: "", limit: 100 });    const paths = result.entries.map((entry) => entry.path);     assert.include(paths, "src");    assert.include(paths, "src/keep.ts");    assert.isFalse(paths.some((entryPath) => entryPath.startsWith(".convex/")));  });   it("excludes .convex in non-git workspaces", async () => {    const cwd = makeTempDir("t3code-workspace-non-git-convex-");    writeFile(cwd, ".convex/local-storage/data.json", "{}");    writeFile(cwd, "src/keep.ts", "export {};");     const result = await searchWorkspaceEntries({ cwd, query: "", limit: 100 });    const paths = result.entries.map((entry) => entry.path);     assert.include(paths, "src");    assert.include(paths, "src/keep.ts");    assert.isFalse(paths.some((entryPath) => entryPath.startsWith(".convex/")));  });   it("deduplicates concurrent index builds for the same cwd", async () => {    const cwd = makeTempDir("t3code-workspace-concurrent-build-");    writeFile(cwd, "src/components/Composer.tsx");     let rootReadCount = 0;    const originalReaddir = fsPromises.readdir.bind(fsPromises);    vi.spyOn(fsPromises, "readdir").mockImplementation(      (async (...args: Parameters<typeof fsPromises.readdir>) => {        if (args[0] === cwd) {          rootReadCount += 1;          await new Promise((resolve) => setTimeout(resolve, 20));        }        return originalReaddir(...args);      }) as typeof fsPromises.readdir,    );     await Promise.all([      searchWorkspaceEntries({ cwd, query: "", limit: 100 }),      searchWorkspaceEntries({ cwd, query: "comp", limit: 100 }),      searchWorkspaceEntries({ cwd, query: "src", limit: 100 }),    ]);     assert.equal(rootReadCount, 1);  });   it("limits concurrent directory reads while walking the filesystem", async () => {    const cwd = makeTempDir("t3code-workspace-read-concurrency-");    for (let index = 0; index < 80; index += 1) {      writeFile(cwd, `group-${index}/entry-${index}.ts`, "export {};");    }     let activeReads = 0;    let peakReads = 0;    const originalReaddir = fsPromises.readdir.bind(fsPromises);    vi.spyOn(fsPromises, "readdir").mockImplementation(      (async (...args: Parameters<typeof fsPromises.readdir>) => {        const target = args[0];        if (typeof target === "string" && target.startsWith(cwd)) {          activeReads += 1;          peakReads = Math.max(peakReads, activeReads);          await new Promise((resolve) => setTimeout(resolve, 4));          try {            return await originalReaddir(...args);          } finally {            activeReads -= 1;          }        }        return originalReaddir(...args);      }) as typeof fsPromises.readdir,    );     await searchWorkspaceEntries({ cwd, query: "", limit: 200 });     assert.isAtMost(peakReads, 32);  });});