effect(snapshot): migrate to AppProcess.run (#27189)

This commit is contained in:
Kit Langton
2026-05-13 10:46:14 -04:00
committed by GitHub
parent 8d5aa584b4
commit 766318a4cf
+42 -14
View File
@@ -2,7 +2,7 @@ import { Cause, Duration, Effect, Layer, Schedule, Schema, Semaphore, Context, S
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { formatPatch, structuredPatch } from "diff" import { formatPatch, structuredPatch } from "diff"
import path from "path" import path from "path"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppProcess } from "@opencode-ai/core/process"
import { InstanceState } from "@/effect/instance-state" import { InstanceState } from "@/effect/instance-state"
import { AppFileSystem } from "@opencode-ai/core/filesystem" import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Hash } from "@opencode-ai/core/util/hash" import { Hash } from "@opencode-ai/core/util/hash"
@@ -58,12 +58,12 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Sn
export const layer: Layer.Layer< export const layer: Layer.Layer<
Service, Service,
never, never,
AppFileSystem.Service | ChildProcessSpawner.ChildProcessSpawner | Config.Service AppFileSystem.Service | AppProcess.Service | Config.Service
> = Layer.effect( > = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const fs = yield* AppFileSystem.Service const fs = yield* AppFileSystem.Service
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner const appProcess = yield* AppProcess.Service
const config = yield* Config.Service const config = yield* Config.Service
const locks = new Map<string, Semaphore.Semaphore>() const locks = new Map<string, Semaphore.Semaphore>()
@@ -90,18 +90,20 @@ export const layer: Layer.Layer<
const enc = new TextEncoder() const enc = new TextEncoder()
const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0")) const feed = (list: string[]) => Stream.make(enc.encode(list.join("\0") + "\0"))
const git = Effect.fnUntraced( const gitWithStdin = Effect.fnUntraced(
function* ( function* (
cmd: string[], cmd: string[],
opts?: { cwd?: string; env?: Record<string, string>; stdin?: ChildProcess.CommandInput }, opts: { cwd?: string; env?: Record<string, string>; stdin: ChildProcess.CommandInput },
) { ) {
// stdin-feed calls still need raw spawn — AppProcess.run does not yet
// expose a stdin Stream API. Tracked as future AppProcess helper.
const proc = ChildProcess.make("git", cmd, { const proc = ChildProcess.make("git", cmd, {
cwd: opts?.cwd, cwd: opts.cwd,
env: opts?.env, env: opts.env,
extendEnv: true, extendEnv: true,
stdin: opts?.stdin, stdin: opts.stdin,
}) })
const handle = yield* spawner.spawn(proc) const handle = yield* appProcess.spawn(proc)
const [text, stderr] = yield* Effect.all( const [text, stderr] = yield* Effect.all(
[Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], [Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
{ concurrency: 2 }, { concurrency: 2 },
@@ -119,9 +121,33 @@ export const layer: Layer.Layer<
), ),
) )
const git = Effect.fnUntraced(
function* (cmd: string[], opts?: { cwd?: string; env?: Record<string, string> }) {
const result = yield* appProcess.run(
ChildProcess.make("git", cmd, {
cwd: opts?.cwd,
env: opts?.env,
extendEnv: true,
}),
)
return {
code: ChildProcessSpawner.ExitCode(result.exitCode),
text: result.stdout.toString("utf8"),
stderr: result.stderr.toString("utf8"),
} satisfies GitResult
},
Effect.catch((err) =>
Effect.succeed({
code: ChildProcessSpawner.ExitCode(1),
text: "",
stderr: err instanceof Error ? err.message : String(err),
}),
),
)
const ignore = Effect.fnUntraced(function* (files: string[]) { const ignore = Effect.fnUntraced(function* (files: string[]) {
if (!files.length) return new Set<string>() if (!files.length) return new Set<string>()
const check = yield* git( const check = yield* gitWithStdin(
[ [
...quote, ...quote,
"--git-dir", "--git-dir",
@@ -144,7 +170,7 @@ export const layer: Layer.Layer<
const drop = Effect.fnUntraced(function* (files: string[]) { const drop = Effect.fnUntraced(function* (files: string[]) {
if (!files.length) return if (!files.length) return
yield* git( yield* gitWithStdin(
[ [
...cfg, ...cfg,
...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]), ...args(["rm", "--cached", "-f", "--ignore-unmatch", "--pathspec-from-file=-", "--pathspec-file-nul"]),
@@ -158,7 +184,7 @@ export const layer: Layer.Layer<
const stage = Effect.fnUntraced(function* (files: string[]) { const stage = Effect.fnUntraced(function* (files: string[]) {
if (!files.length) return if (!files.length) return
const result = yield* git( const result = yield* gitWithStdin(
[...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])], [...cfg, ...args(["add", "--all", "--sparse", "--pathspec-from-file=-", "--pathspec-file-nul"])],
{ {
cwd: state.directory, cwd: state.directory,
@@ -565,12 +591,14 @@ export const layer: Layer.Layer<
}) })
if (!refs.length) return new Map<string, { before: string; after: string }>() if (!refs.length) return new Map<string, { before: string; after: string }>()
// cat-file --batch is a stdin-feed call — kept on raw spawn
// until AppProcess.run exposes a stdin Stream API.
const proc = ChildProcess.make("git", [...cfg, ...args(["cat-file", "--batch"])], { const proc = ChildProcess.make("git", [...cfg, ...args(["cat-file", "--batch"])], {
cwd: state.directory, cwd: state.directory,
extendEnv: true, extendEnv: true,
stdin: Stream.make(new TextEncoder().encode(refs.map((item) => item.ref).join("\n") + "\n")), stdin: Stream.make(new TextEncoder().encode(refs.map((item) => item.ref).join("\n") + "\n")),
}) })
const handle = yield* spawner.spawn(proc) const handle = yield* appProcess.spawn(proc)
const [out, err] = yield* Effect.all( const [out, err] = yield* Effect.all(
[Stream.mkUint8Array(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))], [Stream.mkUint8Array(handle.stdout), Stream.mkString(Stream.decodeText(handle.stderr))],
{ concurrency: 2 }, { concurrency: 2 },
@@ -767,7 +795,7 @@ export const layer: Layer.Layer<
) )
export const defaultLayer = layer.pipe( export const defaultLayer = layer.pipe(
Layer.provide(CrossSpawnSpawner.defaultLayer), Layer.provide(AppProcess.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer), Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
) )