effect(git): migrate to AppProcess.run

This commit is contained in:
Kit Langton
2026-05-12 20:42:17 -04:00
parent fa1c8765d6
commit d51d141403

View File

@@ -1,6 +1,6 @@
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppProcess } from "@opencode-ai/core/process"
import { Effect, Layer, Context, Stream } from "effect"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { ChildProcess } from "effect/unstable/process"
const cfg = [
"--no-optional-locks",
@@ -102,49 +102,31 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Gi
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
const appProcess = yield* AppProcess.Service
const encoder = new TextEncoder()
const stdin = (text: string) => Stream.make(encoder.encode(text))
const run = Effect.fn("Git.run")(
function* (args: string[], opts: Options) {
const proc = ChildProcess.make("git", [...cfg, ...args], {
cwd: opts.cwd,
env: opts.env,
extendEnv: true,
stdin: opts.stdin ?? "ignore",
stdout: "pipe",
stderr: "pipe",
})
const handle = yield* spawner.spawn(proc)
const collect = (stream: typeof handle.stdout) =>
Stream.runFold(
stream,
() => ({ chunks: [] as Uint8Array[], bytes: 0, truncated: false }),
(acc, chunk) => {
if (opts.maxOutputBytes === undefined) {
acc.chunks.push(chunk)
acc.bytes += chunk.length
return acc
}
const remaining = opts.maxOutputBytes - acc.bytes
if (remaining > 0) acc.chunks.push(remaining >= chunk.length ? chunk : chunk.slice(0, remaining))
acc.bytes += chunk.length
acc.truncated = acc.truncated || acc.bytes > opts.maxOutputBytes
return acc
},
).pipe(Effect.map((x) => ({ buffer: Buffer.concat(x.chunks), truncated: x.truncated })))
const [stdout, stderr] = yield* Effect.all([collect(handle.stdout), collect(handle.stderr)], { concurrency: 2 })
const result = yield* appProcess.run(
ChildProcess.make("git", [...cfg, ...args], {
cwd: opts.cwd,
env: opts.env,
extendEnv: true,
stdin: opts.stdin ?? "ignore",
stdout: "pipe",
stderr: "pipe",
}),
{ maxOutputBytes: opts.maxOutputBytes },
)
return {
exitCode: yield* handle.exitCode,
text: () => stdout.buffer.toString("utf8"),
stdout: stdout.buffer,
stderr: stderr.buffer,
truncated: stdout.truncated || stderr.truncated,
exitCode: result.exitCode,
text: () => result.stdout.toString("utf8"),
stdout: result.stdout,
stderr: result.stderr,
truncated: result.truncated,
} satisfies Result
},
Effect.scoped,
Effect.catch((err) => Effect.succeed(fail(err))),
)
@@ -360,6 +342,6 @@ export const layer = Layer.effect(
}),
)
export const defaultLayer = layer.pipe(Layer.provide(CrossSpawnSpawner.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(AppProcess.defaultLayer))
export * as Git from "."