|
|
@@ -6,20 +6,8 @@ import path from "path"
|
|
|
import { tmpdir } from "../fixture/fixture"
|
|
|
import { Ripgrep } from "../../src/file/ripgrep"
|
|
|
|
|
|
-async function seed(dir: string, count: number, size = 16) {
|
|
|
- const txt = "a".repeat(size)
|
|
|
- await Promise.all(Array.from({ length: count }, (_, i) => Bun.write(path.join(dir, `file-${i}.txt`), `${txt}${i}\n`)))
|
|
|
-}
|
|
|
-
|
|
|
-function env(name: string, value: string | undefined) {
|
|
|
- const prev = process.env[name]
|
|
|
- if (value === undefined) delete process.env[name]
|
|
|
- else process.env[name] = value
|
|
|
- return () => {
|
|
|
- if (prev === undefined) delete process.env[name]
|
|
|
- else process.env[name] = prev
|
|
|
- }
|
|
|
-}
|
|
|
+const run = <A>(effect: Effect.Effect<A, unknown, Ripgrep.Service>) =>
|
|
|
+ effect.pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
|
|
|
|
|
describe("file.ripgrep", () => {
|
|
|
test("defaults to include hidden", async () => {
|
|
|
@@ -31,7 +19,14 @@ describe("file.ripgrep", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const files = await Array.fromAsync(await Ripgrep.files({ cwd: tmp.path }))
|
|
|
+ const files = await run(
|
|
|
+ Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: tmp.path }).pipe(
|
|
|
+ Stream.runCollect,
|
|
|
+ Effect.map((c) => [...c]),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
expect(files.includes("visible.txt")).toBe(true)
|
|
|
expect(files.includes(path.join(".opencode", "thing.json"))).toBe(true)
|
|
|
})
|
|
|
@@ -45,7 +40,14 @@ describe("file.ripgrep", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const files = await Array.fromAsync(await Ripgrep.files({ cwd: tmp.path, hidden: false }))
|
|
|
+ const files = await run(
|
|
|
+ Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: tmp.path, hidden: false }).pipe(
|
|
|
+ Stream.runCollect,
|
|
|
+ Effect.map((c) => [...c]),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
expect(files.includes("visible.txt")).toBe(true)
|
|
|
expect(files.includes(path.join(".opencode", "thing.json"))).toBe(false)
|
|
|
})
|
|
|
@@ -57,7 +59,7 @@ describe("file.ripgrep", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const result = await Ripgrep.search({ cwd: tmp.path, pattern: "needle" })
|
|
|
+ const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" })))
|
|
|
expect(result.partial).toBe(false)
|
|
|
expect(result.items).toEqual([])
|
|
|
})
|
|
|
@@ -70,7 +72,7 @@ describe("file.ripgrep", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const result = await Ripgrep.search({ cwd: tmp.path, pattern: "needle" })
|
|
|
+ const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" })))
|
|
|
expect(result.partial).toBe(false)
|
|
|
expect(result.items).toHaveLength(1)
|
|
|
expect(result.items[0]?.path.text).toBe(path.join("src", "match.ts"))
|
|
|
@@ -78,99 +80,7 @@ describe("file.ripgrep", () => {
|
|
|
expect(result.items[0]?.lines.text).toContain("needle")
|
|
|
})
|
|
|
|
|
|
- test("files returns empty when glob matches no files in worker mode", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await fs.mkdir(path.join(dir, "packages", "console"), { recursive: true })
|
|
|
- await Bun.write(path.join(dir, "packages", "console", "package.json"), "{}")
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const ctl = new AbortController()
|
|
|
- const files = await Array.fromAsync(
|
|
|
- await Ripgrep.files({
|
|
|
- cwd: tmp.path,
|
|
|
- glob: ["packages/*"],
|
|
|
- signal: ctl.signal,
|
|
|
- }),
|
|
|
- )
|
|
|
-
|
|
|
- expect(files).toEqual([])
|
|
|
- })
|
|
|
-
|
|
|
- test("ignores RIPGREP_CONFIG_PATH in direct mode", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n")
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const restore = env("RIPGREP_CONFIG_PATH", path.join(tmp.path, "missing-ripgreprc"))
|
|
|
- try {
|
|
|
- const result = await Ripgrep.search({ cwd: tmp.path, pattern: "needle" })
|
|
|
- expect(result.items).toHaveLength(1)
|
|
|
- } finally {
|
|
|
- restore()
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- test("ignores RIPGREP_CONFIG_PATH in worker mode", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n")
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const restore = env("RIPGREP_CONFIG_PATH", path.join(tmp.path, "missing-ripgreprc"))
|
|
|
- try {
|
|
|
- const ctl = new AbortController()
|
|
|
- const result = await Ripgrep.search({
|
|
|
- cwd: tmp.path,
|
|
|
- pattern: "needle",
|
|
|
- signal: ctl.signal,
|
|
|
- })
|
|
|
- expect(result.items).toHaveLength(1)
|
|
|
- } finally {
|
|
|
- restore()
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- test("aborts files scan in worker mode", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await seed(dir, 4000)
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const ctl = new AbortController()
|
|
|
- const iter = await Ripgrep.files({ cwd: tmp.path, signal: ctl.signal })
|
|
|
- const pending = Array.fromAsync(iter)
|
|
|
- setTimeout(() => ctl.abort(), 0)
|
|
|
-
|
|
|
- const err = await pending.catch((err) => err)
|
|
|
- expect(err).toBeInstanceOf(Error)
|
|
|
- expect(err.name).toBe("AbortError")
|
|
|
- }, 15_000)
|
|
|
-
|
|
|
- test("aborts search in worker mode", async () => {
|
|
|
- await using tmp = await tmpdir({
|
|
|
- init: async (dir) => {
|
|
|
- await seed(dir, 512, 64 * 1024)
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const ctl = new AbortController()
|
|
|
- const pending = Ripgrep.search({ cwd: tmp.path, pattern: "needle", signal: ctl.signal })
|
|
|
- setTimeout(() => ctl.abort(), 0)
|
|
|
-
|
|
|
- const err = await pending.catch((err) => err)
|
|
|
- expect(err).toBeInstanceOf(Error)
|
|
|
- expect(err.name).toBe("AbortError")
|
|
|
- }, 15_000)
|
|
|
-})
|
|
|
-
|
|
|
-describe("Ripgrep.Service", () => {
|
|
|
- test("search returns matched rows", async () => {
|
|
|
+ test("search returns matched rows with glob filter", async () => {
|
|
|
await using tmp = await tmpdir({
|
|
|
init: async (dir) => {
|
|
|
await Bun.write(path.join(dir, "match.ts"), "const value = 'needle'\n")
|
|
|
@@ -178,11 +88,9 @@ describe("Ripgrep.Service", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const result = await Effect.gen(function* () {
|
|
|
- const rg = yield* Ripgrep.Service
|
|
|
- return yield* rg.search({ cwd: tmp.path, pattern: "needle", glob: ["*.ts"] })
|
|
|
- }).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
|
|
-
|
|
|
+ const result = await run(
|
|
|
+ Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle", glob: ["*.ts"] })),
|
|
|
+ )
|
|
|
expect(result.partial).toBe(false)
|
|
|
expect(result.items).toHaveLength(1)
|
|
|
expect(result.items[0]?.path.text).toContain("match.ts")
|
|
|
@@ -198,16 +106,31 @@ describe("Ripgrep.Service", () => {
|
|
|
})
|
|
|
|
|
|
const file = path.join(tmp.path, "match.ts")
|
|
|
- const result = await Effect.gen(function* () {
|
|
|
- const rg = yield* Ripgrep.Service
|
|
|
- return yield* rg.search({ cwd: tmp.path, pattern: "needle", file: [file] })
|
|
|
- }).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
|
|
-
|
|
|
+ const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle", file: [file] })))
|
|
|
expect(result.partial).toBe(false)
|
|
|
expect(result.items).toHaveLength(1)
|
|
|
expect(result.items[0]?.path.text).toBe(file)
|
|
|
})
|
|
|
|
|
|
+ test("files returns empty when glob matches no files", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ await fs.mkdir(path.join(dir, "packages", "console"), { recursive: true })
|
|
|
+ await Bun.write(path.join(dir, "packages", "console", "package.json"), "{}")
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const files = await run(
|
|
|
+ Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: tmp.path, glob: ["packages/*"] }).pipe(
|
|
|
+ Stream.runCollect,
|
|
|
+ Effect.map((c) => [...c]),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ expect(files).toEqual([])
|
|
|
+ })
|
|
|
+
|
|
|
test("files returns stream of filenames", async () => {
|
|
|
await using tmp = await tmpdir({
|
|
|
init: async (dir) => {
|
|
|
@@ -216,14 +139,14 @@ describe("Ripgrep.Service", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const files = await Effect.gen(function* () {
|
|
|
- const rg = yield* Ripgrep.Service
|
|
|
- return yield* rg.files({ cwd: tmp.path }).pipe(
|
|
|
- Stream.runCollect,
|
|
|
- Effect.map((chunk) => [...chunk].sort()),
|
|
|
- )
|
|
|
- }).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
|
|
-
|
|
|
+ const files = await run(
|
|
|
+ Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: tmp.path }).pipe(
|
|
|
+ Stream.runCollect,
|
|
|
+ Effect.map((c) => [...c].sort()),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
expect(files).toEqual(["a.txt", "b.txt"])
|
|
|
})
|
|
|
|
|
|
@@ -235,23 +158,57 @@ describe("Ripgrep.Service", () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const files = await Effect.gen(function* () {
|
|
|
- const rg = yield* Ripgrep.Service
|
|
|
- return yield* rg.files({ cwd: tmp.path, glob: ["*.ts"] }).pipe(
|
|
|
- Stream.runCollect,
|
|
|
- Effect.map((chunk) => [...chunk]),
|
|
|
- )
|
|
|
- }).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromise)
|
|
|
-
|
|
|
+ const files = await run(
|
|
|
+ Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: tmp.path, glob: ["*.ts"] }).pipe(
|
|
|
+ Stream.runCollect,
|
|
|
+ Effect.map((c) => [...c]),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
expect(files).toEqual(["keep.ts"])
|
|
|
})
|
|
|
|
|
|
test("files dies on nonexistent directory", async () => {
|
|
|
- const exit = await Effect.gen(function* () {
|
|
|
- const rg = yield* Ripgrep.Service
|
|
|
- return yield* rg.files({ cwd: "/tmp/nonexistent-dir-12345" }).pipe(Stream.runCollect)
|
|
|
- }).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromiseExit)
|
|
|
-
|
|
|
+ const exit = await Ripgrep.Service.use((rg) =>
|
|
|
+ rg.files({ cwd: "/tmp/nonexistent-dir-12345" }).pipe(Stream.runCollect),
|
|
|
+ ).pipe(Effect.provide(Ripgrep.defaultLayer), Effect.runPromiseExit)
|
|
|
expect(exit._tag).toBe("Failure")
|
|
|
})
|
|
|
+
|
|
|
+ test("ignores RIPGREP_CONFIG_PATH in direct mode", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n")
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const prev = process.env["RIPGREP_CONFIG_PATH"]
|
|
|
+ process.env["RIPGREP_CONFIG_PATH"] = path.join(tmp.path, "missing-ripgreprc")
|
|
|
+ try {
|
|
|
+ const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" })))
|
|
|
+ expect(result.items).toHaveLength(1)
|
|
|
+ } finally {
|
|
|
+ if (prev === undefined) delete process.env["RIPGREP_CONFIG_PATH"]
|
|
|
+ else process.env["RIPGREP_CONFIG_PATH"] = prev
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ test("ignores RIPGREP_CONFIG_PATH in worker mode", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ await Bun.write(path.join(dir, "match.ts"), "const needle = 1\n")
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ const prev = process.env["RIPGREP_CONFIG_PATH"]
|
|
|
+ process.env["RIPGREP_CONFIG_PATH"] = path.join(tmp.path, "missing-ripgreprc")
|
|
|
+ try {
|
|
|
+ const result = await run(Ripgrep.Service.use((rg) => rg.search({ cwd: tmp.path, pattern: "needle" })))
|
|
|
+ expect(result.items).toHaveLength(1)
|
|
|
+ } finally {
|
|
|
+ if (prev === undefined) delete process.env["RIPGREP_CONFIG_PATH"]
|
|
|
+ else process.env["RIPGREP_CONFIG_PATH"] = prev
|
|
|
+ }
|
|
|
+ })
|
|
|
})
|