diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 00000000..dcc3c970 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,14 @@ +{ + "features": { + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.8", + "resolved": "ghcr.io/devcontainers/features/git@sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2", + "integrity": "sha256:fd75977de13a9979000e0e78baf949adb0ca71d2398995fa22e0a36d7e7e7fe2" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "1.8.0", + "resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511", + "integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e47c22c3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,8 @@ +{ + "name": "opencode", + "image": "oven/bun:1.3.4", + "features": { + "ghcr.io/devcontainers/features/python:1": {}, + "ghcr.io/devcontainers/features/git:1": {} + } +} \ No newline at end of file diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index ac8ea52d..6f1dda8f 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -89,6 +89,27 @@ const failure = (message: string, cause?: unknown) => new Error({ message, cause const isInvalidPattern = (stderr: string) => stderr.includes("regex parse error") || stderr.includes("error parsing regex") +const handleExitCode = ( + code: number, + stderr: string, + pattern: string | undefined, + rows: readonly A[], +) => { + if (pattern && code === 2 && isInvalidPattern(stderr)) { + return Effect.fail(new InvalidPatternError({ pattern, message: stderr.trim() })) + } + + if (code !== 0 && code !== 1 && code !== 2) { + return Effect.fail(failure(stderr.trim() || `ripgrep failed with code ${code}`)) + } + + return Effect.succeed({ + items: code === 1 ? [] : rows, + truncated: false, + partial: code === 2, + }) +} + const layer = Layer.effect( Service, Effect.gen(function* () { @@ -132,13 +153,7 @@ const layer = Layer.effect( const code = yield* handle.exitCode const stderr = yield* Fiber.join(stderrFiber) - if (input.pattern && code === 2 && isInvalidPattern(stderr)) { - return yield* new InvalidPatternError({ pattern: input.pattern, message: stderr.trim() }) - } - if (code !== 0 && code !== 1 && code !== 2) { - return yield* failure(stderr.trim() || `ripgrep failed with code ${code}`) - } - return { items: code === 1 ? [] : rows, truncated: false, partial: code === 2 } + return yield* handleExitCode(code, stderr, input.pattern, rows) }), ) const abortable = input.signal ? program.pipe(Effect.raceFirst(waitForAbort(input.signal))) : program @@ -278,4 +293,4 @@ const layer = Layer.effect( }), ) -export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] }) +export const node = makeGlobalNode({ service: Service, layer: layer, deps: [RipgrepBinary.node, AppProcess.node] }) \ No newline at end of file diff --git a/packages/core/test/ripgrep.test.ts b/packages/core/test/ripgrep.test.ts index 3abce1c0..9a3cd172 100644 --- a/packages/core/test/ripgrep.test.ts +++ b/packages/core/test/ripgrep.test.ts @@ -62,4 +62,20 @@ describe("Ripgrep", () => { (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), ), ) + + it.live("reports an unparseable regex as an InvalidPatternError", () => + Effect.acquireUseRelease( + Effect.promise(() => tmpdir()), + (tmp) => + Effect.gen(function* () { + yield* Effect.promise(() => fs.writeFile(path.join(tmp.path, "file.txt"), "content\n")) + + const error = yield* (yield* Ripgrep.Service) + .grep({ cwd: tmp.path, pattern: "(", limit: 10 }) + .pipe(Effect.flip) + expect(error._tag).toBe("Ripgrep.InvalidPatternError") + }), + (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()), + ), + ) })