From 0261f04b90e10902ddb6275bc50ef2acb0d4e174 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Mon, 27 Jul 2026 03:16:49 -0400 Subject: [PATCH] fix(core): handle oversized ripgrep matches --- packages/core/src/ripgrep.ts | 7 ++----- packages/core/test/ripgrep.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index a6aa9ed426c..4837a7fb2fd 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -16,7 +16,6 @@ import { RipgrepBinary } from "./ripgrep/binary" */ const ERROR_BYTES = 8 * 1024 -const MAX_RECORD_BYTES = 64 * 1024 const MAX_SUBMATCHES = 100 const RawMatch = Schema.Struct({ @@ -231,10 +230,8 @@ const layer = Layer.effect( input.file ?? ".", ], parse: (line) => - (Buffer.byteLength(line, "utf8") > MAX_RECORD_BYTES - ? Effect.fail(failure(`Ripgrep JSON record exceeded ${MAX_RECORD_BYTES} bytes`)) - : decodeJsonRecord(line).pipe(Effect.mapError((cause) => failure("Invalid ripgrep JSON output", cause))) - ).pipe( + decodeJsonRecord(line).pipe( + Effect.mapError((cause) => failure("Invalid ripgrep JSON output", cause)), Effect.flatMap((json) => { if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match") return Effect.succeed(undefined) diff --git a/packages/core/test/ripgrep.test.ts b/packages/core/test/ripgrep.test.ts index 15d1ec63ce5..8b8ca3ff0ad 100644 --- a/packages/core/test/ripgrep.test.ts +++ b/packages/core/test/ripgrep.test.ts @@ -62,4 +62,27 @@ describe("Ripgrep", () => { (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ), ) + + it.live("returns a bounded preview for matches on oversized lines", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "generated.ts"), `Cloudflare${"x".repeat(70 * 1024)}\n`)) + + const matches = yield* (yield* Ripgrep.Service).grep({ + cwd: tmp.path, + pattern: "Cloudflare", + limit: 10, + }) + + expect(matches).toHaveLength(1) + expect(matches[0]?.entry.path).toBe(RelativePath.make("generated.ts")) + expect(matches[0]?.text).toHaveLength(2_003) + expect(matches[0]?.text.endsWith("...")).toBe(true) + expect(matches[0]?.submatches).toEqual([{ text: "Cloudflare", start: 0, end: 10 }]) + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) })