From 1e98167b0e027825b3a77a2dab0be539011e7376 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 25 Apr 2026 14:30:16 -0400 Subject: core: move cross-spawn-spawner to root and remove unused types The cross-spawn-spawner module has been moved from src/effect/ to src/ to simplify the core package structure. The src/types.d.ts file which contained unused type declarations has also been removed. All imports throughout the codebase have been updated to reflect the new location. This change reduces the package's internal complexity by flattening the module hierarchy and removing dead code, making future maintenance easier. --- packages/core/src/cross-spawn-spawner.ts | 505 +++++++++++++++++++++ packages/core/src/effect/cross-spawn-spawner.ts | 505 --------------------- packages/core/src/types.d.ts | 46 -- .../core/test/effect/cross-spawn-spawner.test.ts | 2 +- packages/opencode/src/file/ripgrep.ts | 2 +- packages/opencode/src/format/index.ts | 2 +- packages/opencode/src/git/index.ts | 2 +- packages/opencode/src/installation/index.ts | 2 +- packages/opencode/src/mcp/index.ts | 2 +- packages/opencode/src/npm/index.ts | 2 +- packages/opencode/src/project/project.ts | 2 +- packages/opencode/src/session/prompt.ts | 2 +- packages/opencode/src/snapshot/index.ts | 2 +- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/src/worktree/index.ts | 2 +- packages/opencode/test/auth/auth.test.ts | 2 +- packages/opencode/test/bus/bus-effect.test.ts | 2 +- packages/opencode/test/config/config.test.ts | 2 +- packages/opencode/test/format/format.test.ts | 2 +- packages/opencode/test/lsp/index.test.ts | 2 +- packages/opencode/test/lsp/lifecycle.test.ts | 2 +- packages/opencode/test/permission/next.test.ts | 2 +- packages/opencode/test/project/project.test.ts | 2 +- .../opencode/test/project/worktree-remove.test.ts | 2 +- packages/opencode/test/project/worktree.test.ts | 2 +- packages/opencode/test/session/compaction.test.ts | 2 +- .../opencode/test/session/processor-effect.test.ts | 2 +- packages/opencode/test/session/prompt.test.ts | 2 +- .../opencode/test/session/revert-compact.test.ts | 2 +- .../test/session/snapshot-tool-race.test.ts | 2 +- packages/opencode/test/share/share-next.test.ts | 2 +- packages/opencode/test/skill/skill.test.ts | 2 +- packages/opencode/test/storage/storage.test.ts | 2 +- packages/opencode/test/tool/bash.test.ts | 2 +- packages/opencode/test/tool/glob.test.ts | 2 +- packages/opencode/test/tool/grep.test.ts | 2 +- packages/opencode/test/tool/lsp.test.ts | 2 +- packages/opencode/test/tool/question.test.ts | 2 +- packages/opencode/test/tool/read.test.ts | 2 +- packages/opencode/test/tool/registry.test.ts | 2 +- packages/opencode/test/tool/skill.test.ts | 2 +- packages/opencode/test/tool/task.test.ts | 2 +- packages/opencode/test/tool/write.test.ts | 2 +- 43 files changed, 545 insertions(+), 591 deletions(-) create mode 100644 packages/core/src/cross-spawn-spawner.ts delete mode 100644 packages/core/src/effect/cross-spawn-spawner.ts delete mode 100644 packages/core/src/types.d.ts diff --git a/packages/core/src/cross-spawn-spawner.ts b/packages/core/src/cross-spawn-spawner.ts new file mode 100644 index 000000000..ad8d4126d --- /dev/null +++ b/packages/core/src/cross-spawn-spawner.ts @@ -0,0 +1,505 @@ +import type * as Arr from "effect/Array" +import { NodeFileSystem, NodeSink, NodeStream } from "@effect/platform-node" +import * as NodePath from "@effect/platform-node/NodePath" +import * as Deferred from "effect/Deferred" +import * as Effect from "effect/Effect" +import * as Exit from "effect/Exit" +import * as FileSystem from "effect/FileSystem" +import * as Layer from "effect/Layer" +import * as Path from "effect/Path" +import * as PlatformError from "effect/PlatformError" +import * as Predicate from "effect/Predicate" +import type * as Scope from "effect/Scope" +import * as Sink from "effect/Sink" +import * as Stream from "effect/Stream" +import * as ChildProcess from "effect/unstable/process/ChildProcess" +import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner" +import { + ChildProcessSpawner, + ExitCode, + make as makeSpawner, + makeHandle, + ProcessId, +} from "effect/unstable/process/ChildProcessSpawner" +import * as NodeChildProcess from "node:child_process" +import { PassThrough } from "node:stream" +import launch from "cross-spawn" + +const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err))) + +const toTag = (err: NodeJS.ErrnoException): PlatformError.SystemErrorTag => { + switch (err.code) { + case "ENOENT": + return "NotFound" + case "EACCES": + return "PermissionDenied" + case "EEXIST": + return "AlreadyExists" + case "EISDIR": + return "BadResource" + case "ENOTDIR": + return "BadResource" + case "EBUSY": + return "Busy" + case "ELOOP": + return "BadResource" + default: + return "Unknown" + } +} + +const flatten = (command: ChildProcess.Command) => { + const commands: Array = [] + const opts: Array = [] + + const walk = (cmd: ChildProcess.Command): void => { + switch (cmd._tag) { + case "StandardCommand": + commands.push(cmd) + return + case "PipedCommand": + walk(cmd.left) + opts.push(cmd.options) + walk(cmd.right) + return + } + } + + walk(command) + if (commands.length === 0) throw new Error("flatten produced empty commands array") + const [head, ...tail] = commands + return { + commands: [head, ...tail] as Arr.NonEmptyReadonlyArray, + opts, + } +} + +const toPlatformError = ( + method: string, + err: NodeJS.ErrnoException, + command: ChildProcess.Command, +): PlatformError.PlatformError => { + const cmd = flatten(command) + .commands.map((x) => `${x.command} ${x.args.join(" ")}`) + .join(" | ") + return PlatformError.systemError({ + _tag: toTag(err), + module: "ChildProcess", + method, + pathOrDescriptor: cmd, + syscall: err.syscall, + cause: err, + }) +} + +type ExitSignal = Deferred.Deferred + +export const make = Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem + const path = yield* Path.Path + + const cwd = Effect.fnUntraced(function* (opts: ChildProcess.CommandOptions) { + if (Predicate.isUndefined(opts.cwd)) return undefined + yield* fs.access(opts.cwd) + return path.resolve(opts.cwd) + }) + + const env = (opts: ChildProcess.CommandOptions) => + opts.extendEnv ? { ...globalThis.process.env, ...opts.env } : opts.env + + const input = (x: ChildProcess.CommandInput | undefined): NodeChildProcess.IOType | undefined => + Stream.isStream(x) ? "pipe" : x + + const output = (x: ChildProcess.CommandOutput | undefined): NodeChildProcess.IOType | undefined => + Sink.isSink(x) ? "pipe" : x + + const stdin = (opts: ChildProcess.CommandOptions): ChildProcess.StdinConfig => { + const cfg: ChildProcess.StdinConfig = { stream: "pipe", encoding: "utf-8", endOnDone: true } + if (Predicate.isUndefined(opts.stdin)) return cfg + if (typeof opts.stdin === "string") return { ...cfg, stream: opts.stdin } + if (Stream.isStream(opts.stdin)) return { ...cfg, stream: opts.stdin } + return { + stream: opts.stdin.stream, + encoding: opts.stdin.encoding ?? cfg.encoding, + endOnDone: opts.stdin.endOnDone ?? cfg.endOnDone, + } + } + + const stdio = (opts: ChildProcess.CommandOptions, key: "stdout" | "stderr"): ChildProcess.StdoutConfig => { + const cfg = opts[key] + if (Predicate.isUndefined(cfg)) return { stream: "pipe" } + if (typeof cfg === "string") return { stream: cfg } + if (Sink.isSink(cfg)) return { stream: cfg } + return { stream: cfg.stream } + } + + const fds = (opts: ChildProcess.CommandOptions) => { + if (Predicate.isUndefined(opts.additionalFds)) return [] + return Object.entries(opts.additionalFds) + .flatMap(([name, config]) => { + const fd = ChildProcess.parseFdName(name) + return Predicate.isUndefined(fd) ? [] : [{ fd, config }] + }) + .toSorted((a, b) => a.fd - b.fd) + } + + const stdios = ( + sin: ChildProcess.StdinConfig, + sout: ChildProcess.StdoutConfig, + serr: ChildProcess.StderrConfig, + extra: ReadonlyArray<{ fd: number; config: ChildProcess.AdditionalFdConfig }>, + ): NodeChildProcess.StdioOptions => { + const pipe = (x: NodeChildProcess.IOType | undefined) => + process.platform === "win32" && x === "pipe" ? "overlapped" : x + const arr: Array = [ + pipe(input(sin.stream)), + pipe(output(sout.stream)), + pipe(output(serr.stream)), + ] + if (extra.length === 0) return arr as NodeChildProcess.StdioOptions + const max = extra.reduce((acc, x) => Math.max(acc, x.fd), 2) + for (let i = 3; i <= max; i++) arr[i] = "ignore" + for (const x of extra) arr[x.fd] = pipe("pipe") + return arr as NodeChildProcess.StdioOptions + } + + const setupFds = Effect.fnUntraced(function* ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + extra: ReadonlyArray<{ fd: number; config: ChildProcess.AdditionalFdConfig }>, + ) { + if (extra.length === 0) { + return { + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + } + } + + const ins = new Map>() + const outs = new Map>() + + for (const x of extra) { + const node = proc.stdio[x.fd] + switch (x.config.type) { + case "input": { + let sink: Sink.Sink = Sink.drain + if (node && "write" in node) { + sink = NodeSink.fromWritable({ + evaluate: () => node, + onError: (err) => toPlatformError(`fromWritable(fd${x.fd})`, toError(err), command), + endOnDone: true, + }) + } + if (x.config.stream) yield* Effect.forkScoped(Stream.run(x.config.stream, sink)) + ins.set(x.fd, sink) + break + } + case "output": { + let stream: Stream.Stream = Stream.empty + if (node && "read" in node) { + const tap = new PassThrough() + node.on("error", (err) => tap.destroy(toError(err))) + node.pipe(tap) + stream = NodeStream.fromReadable({ + evaluate: () => tap, + onError: (err) => toPlatformError(`fromReadable(fd${x.fd})`, toError(err), command), + }) + } + if (x.config.sink) stream = Stream.transduce(stream, x.config.sink) + outs.set(x.fd, stream) + break + } + } + } + + return { + getInputFd: (fd: number) => ins.get(fd) ?? Sink.drain, + getOutputFd: (fd: number) => outs.get(fd) ?? Stream.empty, + } + }) + + const setupStdin = ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + cfg: ChildProcess.StdinConfig, + ) => + Effect.suspend(() => { + let sink: Sink.Sink = Sink.drain + if (Predicate.isNotNull(proc.stdin)) { + sink = NodeSink.fromWritable({ + evaluate: () => proc.stdin!, + onError: (err) => toPlatformError("fromWritable(stdin)", toError(err), command), + endOnDone: cfg.endOnDone, + encoding: cfg.encoding, + }) + } + if (Stream.isStream(cfg.stream)) return Effect.as(Effect.forkScoped(Stream.run(cfg.stream, sink)), sink) + return Effect.succeed(sink) + }) + + const setupOutput = ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + out: ChildProcess.StdoutConfig, + err: ChildProcess.StderrConfig, + ) => { + let stdout = proc.stdout + ? NodeStream.fromReadable({ + evaluate: () => proc.stdout!, + onError: (cause) => toPlatformError("fromReadable(stdout)", toError(cause), command), + }) + : Stream.empty + let stderr = proc.stderr + ? NodeStream.fromReadable({ + evaluate: () => proc.stderr!, + onError: (cause) => toPlatformError("fromReadable(stderr)", toError(cause), command), + }) + : Stream.empty + + if (Sink.isSink(out.stream)) stdout = Stream.transduce(stdout, out.stream) + if (Sink.isSink(err.stream)) stderr = Stream.transduce(stderr, err.stream) + + return { stdout, stderr, all: Stream.merge(stdout, stderr) } + } + + const spawn = (command: ChildProcess.StandardCommand, opts: NodeChildProcess.SpawnOptions) => + Effect.callback((resume) => { + const signal = Deferred.makeUnsafe() + const proc = launch(command.command, command.args, opts) + let end = false + let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined + proc.on("error", (err) => { + resume(Effect.fail(toPlatformError("spawn", err, command))) + }) + proc.on("exit", (...args) => { + exit = args + }) + proc.on("close", (...args) => { + if (end) return + end = true + Deferred.doneUnsafe(signal, Exit.succeed(exit ?? args)) + }) + proc.on("spawn", () => { + resume(Effect.succeed([proc, signal])) + }) + return Effect.sync(() => { + proc.kill("SIGTERM") + }) + }) + + const killGroup = ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + signal: NodeJS.Signals, + ) => { + if (globalThis.process.platform === "win32") { + return Effect.callback((resume) => { + NodeChildProcess.exec(`taskkill /pid ${proc.pid} /T /F`, { windowsHide: true }, (err) => { + if (err) return resume(Effect.fail(toPlatformError("kill", toError(err), command))) + resume(Effect.void) + }) + }) + } + + return Effect.try({ + try: () => { + globalThis.process.kill(-proc.pid!, signal) + }, + catch: (err) => toPlatformError("kill", toError(err), command), + }) + } + + const killOne = ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + signal: NodeJS.Signals, + ) => + Effect.suspend(() => { + if (proc.kill(signal)) return Effect.void + return Effect.fail(toPlatformError("kill", new Error("Failed to kill child process"), command)) + }) + + const timeout = + ( + proc: NodeChildProcess.ChildProcess, + command: ChildProcess.StandardCommand, + opts: ChildProcess.KillOptions | undefined, + ) => + ( + f: ( + command: ChildProcess.StandardCommand, + proc: NodeChildProcess.ChildProcess, + signal: NodeJS.Signals, + ) => Effect.Effect, + ) => { + const signal = opts?.killSignal ?? "SIGTERM" + if (Predicate.isUndefined(opts?.forceKillAfter)) return f(command, proc, signal) + return Effect.timeoutOrElse(f(command, proc, signal), { + duration: opts.forceKillAfter, + orElse: () => f(command, proc, "SIGKILL"), + }) + } + + const source = (handle: ChildProcessHandle, from: ChildProcess.PipeFromOption | undefined) => { + const opt = from ?? "stdout" + switch (opt) { + case "stdout": + return handle.stdout + case "stderr": + return handle.stderr + case "all": + return handle.all + default: { + const fd = ChildProcess.parseFdName(opt) + return Predicate.isNotUndefined(fd) ? handle.getOutputFd(fd) : handle.stdout + } + } + } + + const spawnCommand: ( + command: ChildProcess.Command, + ) => Effect.Effect = Effect.fnUntraced( + function* (command) { + switch (command._tag) { + case "StandardCommand": { + const sin = stdin(command.options) + const sout = stdio(command.options, "stdout") + const serr = stdio(command.options, "stderr") + const extra = fds(command.options) + const dir = yield* cwd(command.options) + + const [proc, signal] = yield* Effect.acquireRelease( + spawn(command, { + cwd: dir, + env: env(command.options), + stdio: stdios(sin, sout, serr, extra), + detached: command.options.detached ?? process.platform !== "win32", + shell: command.options.shell, + windowsHide: process.platform === "win32", + }), + Effect.fnUntraced(function* ([proc, signal]) { + const done = yield* Deferred.isDone(signal) + const kill = timeout(proc, command, command.options) + if (done) { + const [code] = yield* Deferred.await(signal) + if (process.platform === "win32") return yield* Effect.void + if (code !== 0 && Predicate.isNotNull(code)) return yield* Effect.ignore(kill(killGroup)) + return yield* Effect.void + } + const send = (s: NodeJS.Signals) => + Effect.catch(killGroup(command, proc, s), () => killOne(command, proc, s)) + const sig = command.options.killSignal ?? "SIGTERM" + const attempt = send(sig).pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid) + const escalated = command.options.forceKillAfter + ? Effect.timeoutOrElse(attempt, { + duration: command.options.forceKillAfter, + orElse: () => send("SIGKILL").pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid), + }) + : attempt + return yield* Effect.ignore(escalated) + }), + ) + + const fd = yield* setupFds(command, proc, extra) + const out = setupOutput(command, proc, sout, serr) + let ref = true + return makeHandle({ + pid: ProcessId(proc.pid!), + stdin: yield* setupStdin(command, proc, sin), + stdout: out.stdout, + stderr: out.stderr, + all: out.all, + getInputFd: fd.getInputFd, + getOutputFd: fd.getOutputFd, + isRunning: Effect.map(Deferred.isDone(signal), (done) => !done), + exitCode: Effect.flatMap(Deferred.await(signal), ([code, signal]) => { + if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code)) + return Effect.fail( + toPlatformError( + "exitCode", + new Error(`Process interrupted due to receipt of signal: '${signal}'`), + command, + ), + ) + }), + kill: (opts?: ChildProcess.KillOptions) => { + const sig = opts?.killSignal ?? "SIGTERM" + const send = (s: NodeJS.Signals) => + Effect.catch(killGroup(command, proc, s), () => killOne(command, proc, s)) + const attempt = send(sig).pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid) + if (!opts?.forceKillAfter) return attempt + return Effect.timeoutOrElse(attempt, { + duration: opts.forceKillAfter, + orElse: () => send("SIGKILL").pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid), + }) + }, + unref: Effect.sync(() => { + if (ref) { + proc.unref() + ref = false + } + return Effect.sync(() => { + if (!ref) { + proc.ref() + ref = true + } + }) + }), + }) + } + case "PipedCommand": { + const flat = flatten(command) + const [head, ...tail] = flat.commands + let handle = spawnCommand(head) + for (let i = 0; i < tail.length; i++) { + const next = tail[i] + const opts = flat.opts[i] ?? {} + const sin = stdin(next.options) + const stream = Stream.unwrap(Effect.map(handle, (x) => source(x, opts.from))) + const to = opts.to ?? "stdin" + if (to === "stdin") { + handle = spawnCommand( + ChildProcess.make(next.command, next.args, { + ...next.options, + stdin: { ...sin, stream }, + }), + ) + continue + } + const fd = ChildProcess.parseFdName(to) + if (Predicate.isUndefined(fd)) { + handle = spawnCommand( + ChildProcess.make(next.command, next.args, { + ...next.options, + stdin: { ...sin, stream }, + }), + ) + continue + } + handle = spawnCommand( + ChildProcess.make(next.command, next.args, { + ...next.options, + additionalFds: { + ...next.options.additionalFds, + [ChildProcess.fdName(fd) as `fd${number}`]: { type: "input", stream }, + }, + }), + ) + } + return yield* handle + } + } + }, + ) + + return makeSpawner(spawnCommand) +}) + +export const layer: Layer.Layer = Layer.effect( + ChildProcessSpawner, + make, +) + +export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer)) + +export * as CrossSpawnSpawner from "./cross-spawn-spawner" diff --git a/packages/core/src/effect/cross-spawn-spawner.ts b/packages/core/src/effect/cross-spawn-spawner.ts deleted file mode 100644 index ad8d4126d..000000000 --- a/packages/core/src/effect/cross-spawn-spawner.ts +++ /dev/null @@ -1,505 +0,0 @@ -import type * as Arr from "effect/Array" -import { NodeFileSystem, NodeSink, NodeStream } from "@effect/platform-node" -import * as NodePath from "@effect/platform-node/NodePath" -import * as Deferred from "effect/Deferred" -import * as Effect from "effect/Effect" -import * as Exit from "effect/Exit" -import * as FileSystem from "effect/FileSystem" -import * as Layer from "effect/Layer" -import * as Path from "effect/Path" -import * as PlatformError from "effect/PlatformError" -import * as Predicate from "effect/Predicate" -import type * as Scope from "effect/Scope" -import * as Sink from "effect/Sink" -import * as Stream from "effect/Stream" -import * as ChildProcess from "effect/unstable/process/ChildProcess" -import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner" -import { - ChildProcessSpawner, - ExitCode, - make as makeSpawner, - makeHandle, - ProcessId, -} from "effect/unstable/process/ChildProcessSpawner" -import * as NodeChildProcess from "node:child_process" -import { PassThrough } from "node:stream" -import launch from "cross-spawn" - -const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err))) - -const toTag = (err: NodeJS.ErrnoException): PlatformError.SystemErrorTag => { - switch (err.code) { - case "ENOENT": - return "NotFound" - case "EACCES": - return "PermissionDenied" - case "EEXIST": - return "AlreadyExists" - case "EISDIR": - return "BadResource" - case "ENOTDIR": - return "BadResource" - case "EBUSY": - return "Busy" - case "ELOOP": - return "BadResource" - default: - return "Unknown" - } -} - -const flatten = (command: ChildProcess.Command) => { - const commands: Array = [] - const opts: Array = [] - - const walk = (cmd: ChildProcess.Command): void => { - switch (cmd._tag) { - case "StandardCommand": - commands.push(cmd) - return - case "PipedCommand": - walk(cmd.left) - opts.push(cmd.options) - walk(cmd.right) - return - } - } - - walk(command) - if (commands.length === 0) throw new Error("flatten produced empty commands array") - const [head, ...tail] = commands - return { - commands: [head, ...tail] as Arr.NonEmptyReadonlyArray, - opts, - } -} - -const toPlatformError = ( - method: string, - err: NodeJS.ErrnoException, - command: ChildProcess.Command, -): PlatformError.PlatformError => { - const cmd = flatten(command) - .commands.map((x) => `${x.command} ${x.args.join(" ")}`) - .join(" | ") - return PlatformError.systemError({ - _tag: toTag(err), - module: "ChildProcess", - method, - pathOrDescriptor: cmd, - syscall: err.syscall, - cause: err, - }) -} - -type ExitSignal = Deferred.Deferred - -export const make = Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem - const path = yield* Path.Path - - const cwd = Effect.fnUntraced(function* (opts: ChildProcess.CommandOptions) { - if (Predicate.isUndefined(opts.cwd)) return undefined - yield* fs.access(opts.cwd) - return path.resolve(opts.cwd) - }) - - const env = (opts: ChildProcess.CommandOptions) => - opts.extendEnv ? { ...globalThis.process.env, ...opts.env } : opts.env - - const input = (x: ChildProcess.CommandInput | undefined): NodeChildProcess.IOType | undefined => - Stream.isStream(x) ? "pipe" : x - - const output = (x: ChildProcess.CommandOutput | undefined): NodeChildProcess.IOType | undefined => - Sink.isSink(x) ? "pipe" : x - - const stdin = (opts: ChildProcess.CommandOptions): ChildProcess.StdinConfig => { - const cfg: ChildProcess.StdinConfig = { stream: "pipe", encoding: "utf-8", endOnDone: true } - if (Predicate.isUndefined(opts.stdin)) return cfg - if (typeof opts.stdin === "string") return { ...cfg, stream: opts.stdin } - if (Stream.isStream(opts.stdin)) return { ...cfg, stream: opts.stdin } - return { - stream: opts.stdin.stream, - encoding: opts.stdin.encoding ?? cfg.encoding, - endOnDone: opts.stdin.endOnDone ?? cfg.endOnDone, - } - } - - const stdio = (opts: ChildProcess.CommandOptions, key: "stdout" | "stderr"): ChildProcess.StdoutConfig => { - const cfg = opts[key] - if (Predicate.isUndefined(cfg)) return { stream: "pipe" } - if (typeof cfg === "string") return { stream: cfg } - if (Sink.isSink(cfg)) return { stream: cfg } - return { stream: cfg.stream } - } - - const fds = (opts: ChildProcess.CommandOptions) => { - if (Predicate.isUndefined(opts.additionalFds)) return [] - return Object.entries(opts.additionalFds) - .flatMap(([name, config]) => { - const fd = ChildProcess.parseFdName(name) - return Predicate.isUndefined(fd) ? [] : [{ fd, config }] - }) - .toSorted((a, b) => a.fd - b.fd) - } - - const stdios = ( - sin: ChildProcess.StdinConfig, - sout: ChildProcess.StdoutConfig, - serr: ChildProcess.StderrConfig, - extra: ReadonlyArray<{ fd: number; config: ChildProcess.AdditionalFdConfig }>, - ): NodeChildProcess.StdioOptions => { - const pipe = (x: NodeChildProcess.IOType | undefined) => - process.platform === "win32" && x === "pipe" ? "overlapped" : x - const arr: Array = [ - pipe(input(sin.stream)), - pipe(output(sout.stream)), - pipe(output(serr.stream)), - ] - if (extra.length === 0) return arr as NodeChildProcess.StdioOptions - const max = extra.reduce((acc, x) => Math.max(acc, x.fd), 2) - for (let i = 3; i <= max; i++) arr[i] = "ignore" - for (const x of extra) arr[x.fd] = pipe("pipe") - return arr as NodeChildProcess.StdioOptions - } - - const setupFds = Effect.fnUntraced(function* ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - extra: ReadonlyArray<{ fd: number; config: ChildProcess.AdditionalFdConfig }>, - ) { - if (extra.length === 0) { - return { - getInputFd: () => Sink.drain, - getOutputFd: () => Stream.empty, - } - } - - const ins = new Map>() - const outs = new Map>() - - for (const x of extra) { - const node = proc.stdio[x.fd] - switch (x.config.type) { - case "input": { - let sink: Sink.Sink = Sink.drain - if (node && "write" in node) { - sink = NodeSink.fromWritable({ - evaluate: () => node, - onError: (err) => toPlatformError(`fromWritable(fd${x.fd})`, toError(err), command), - endOnDone: true, - }) - } - if (x.config.stream) yield* Effect.forkScoped(Stream.run(x.config.stream, sink)) - ins.set(x.fd, sink) - break - } - case "output": { - let stream: Stream.Stream = Stream.empty - if (node && "read" in node) { - const tap = new PassThrough() - node.on("error", (err) => tap.destroy(toError(err))) - node.pipe(tap) - stream = NodeStream.fromReadable({ - evaluate: () => tap, - onError: (err) => toPlatformError(`fromReadable(fd${x.fd})`, toError(err), command), - }) - } - if (x.config.sink) stream = Stream.transduce(stream, x.config.sink) - outs.set(x.fd, stream) - break - } - } - } - - return { - getInputFd: (fd: number) => ins.get(fd) ?? Sink.drain, - getOutputFd: (fd: number) => outs.get(fd) ?? Stream.empty, - } - }) - - const setupStdin = ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - cfg: ChildProcess.StdinConfig, - ) => - Effect.suspend(() => { - let sink: Sink.Sink = Sink.drain - if (Predicate.isNotNull(proc.stdin)) { - sink = NodeSink.fromWritable({ - evaluate: () => proc.stdin!, - onError: (err) => toPlatformError("fromWritable(stdin)", toError(err), command), - endOnDone: cfg.endOnDone, - encoding: cfg.encoding, - }) - } - if (Stream.isStream(cfg.stream)) return Effect.as(Effect.forkScoped(Stream.run(cfg.stream, sink)), sink) - return Effect.succeed(sink) - }) - - const setupOutput = ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - out: ChildProcess.StdoutConfig, - err: ChildProcess.StderrConfig, - ) => { - let stdout = proc.stdout - ? NodeStream.fromReadable({ - evaluate: () => proc.stdout!, - onError: (cause) => toPlatformError("fromReadable(stdout)", toError(cause), command), - }) - : Stream.empty - let stderr = proc.stderr - ? NodeStream.fromReadable({ - evaluate: () => proc.stderr!, - onError: (cause) => toPlatformError("fromReadable(stderr)", toError(cause), command), - }) - : Stream.empty - - if (Sink.isSink(out.stream)) stdout = Stream.transduce(stdout, out.stream) - if (Sink.isSink(err.stream)) stderr = Stream.transduce(stderr, err.stream) - - return { stdout, stderr, all: Stream.merge(stdout, stderr) } - } - - const spawn = (command: ChildProcess.StandardCommand, opts: NodeChildProcess.SpawnOptions) => - Effect.callback((resume) => { - const signal = Deferred.makeUnsafe() - const proc = launch(command.command, command.args, opts) - let end = false - let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined - proc.on("error", (err) => { - resume(Effect.fail(toPlatformError("spawn", err, command))) - }) - proc.on("exit", (...args) => { - exit = args - }) - proc.on("close", (...args) => { - if (end) return - end = true - Deferred.doneUnsafe(signal, Exit.succeed(exit ?? args)) - }) - proc.on("spawn", () => { - resume(Effect.succeed([proc, signal])) - }) - return Effect.sync(() => { - proc.kill("SIGTERM") - }) - }) - - const killGroup = ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - signal: NodeJS.Signals, - ) => { - if (globalThis.process.platform === "win32") { - return Effect.callback((resume) => { - NodeChildProcess.exec(`taskkill /pid ${proc.pid} /T /F`, { windowsHide: true }, (err) => { - if (err) return resume(Effect.fail(toPlatformError("kill", toError(err), command))) - resume(Effect.void) - }) - }) - } - - return Effect.try({ - try: () => { - globalThis.process.kill(-proc.pid!, signal) - }, - catch: (err) => toPlatformError("kill", toError(err), command), - }) - } - - const killOne = ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - signal: NodeJS.Signals, - ) => - Effect.suspend(() => { - if (proc.kill(signal)) return Effect.void - return Effect.fail(toPlatformError("kill", new Error("Failed to kill child process"), command)) - }) - - const timeout = - ( - proc: NodeChildProcess.ChildProcess, - command: ChildProcess.StandardCommand, - opts: ChildProcess.KillOptions | undefined, - ) => - ( - f: ( - command: ChildProcess.StandardCommand, - proc: NodeChildProcess.ChildProcess, - signal: NodeJS.Signals, - ) => Effect.Effect, - ) => { - const signal = opts?.killSignal ?? "SIGTERM" - if (Predicate.isUndefined(opts?.forceKillAfter)) return f(command, proc, signal) - return Effect.timeoutOrElse(f(command, proc, signal), { - duration: opts.forceKillAfter, - orElse: () => f(command, proc, "SIGKILL"), - }) - } - - const source = (handle: ChildProcessHandle, from: ChildProcess.PipeFromOption | undefined) => { - const opt = from ?? "stdout" - switch (opt) { - case "stdout": - return handle.stdout - case "stderr": - return handle.stderr - case "all": - return handle.all - default: { - const fd = ChildProcess.parseFdName(opt) - return Predicate.isNotUndefined(fd) ? handle.getOutputFd(fd) : handle.stdout - } - } - } - - const spawnCommand: ( - command: ChildProcess.Command, - ) => Effect.Effect = Effect.fnUntraced( - function* (command) { - switch (command._tag) { - case "StandardCommand": { - const sin = stdin(command.options) - const sout = stdio(command.options, "stdout") - const serr = stdio(command.options, "stderr") - const extra = fds(command.options) - const dir = yield* cwd(command.options) - - const [proc, signal] = yield* Effect.acquireRelease( - spawn(command, { - cwd: dir, - env: env(command.options), - stdio: stdios(sin, sout, serr, extra), - detached: command.options.detached ?? process.platform !== "win32", - shell: command.options.shell, - windowsHide: process.platform === "win32", - }), - Effect.fnUntraced(function* ([proc, signal]) { - const done = yield* Deferred.isDone(signal) - const kill = timeout(proc, command, command.options) - if (done) { - const [code] = yield* Deferred.await(signal) - if (process.platform === "win32") return yield* Effect.void - if (code !== 0 && Predicate.isNotNull(code)) return yield* Effect.ignore(kill(killGroup)) - return yield* Effect.void - } - const send = (s: NodeJS.Signals) => - Effect.catch(killGroup(command, proc, s), () => killOne(command, proc, s)) - const sig = command.options.killSignal ?? "SIGTERM" - const attempt = send(sig).pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid) - const escalated = command.options.forceKillAfter - ? Effect.timeoutOrElse(attempt, { - duration: command.options.forceKillAfter, - orElse: () => send("SIGKILL").pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid), - }) - : attempt - return yield* Effect.ignore(escalated) - }), - ) - - const fd = yield* setupFds(command, proc, extra) - const out = setupOutput(command, proc, sout, serr) - let ref = true - return makeHandle({ - pid: ProcessId(proc.pid!), - stdin: yield* setupStdin(command, proc, sin), - stdout: out.stdout, - stderr: out.stderr, - all: out.all, - getInputFd: fd.getInputFd, - getOutputFd: fd.getOutputFd, - isRunning: Effect.map(Deferred.isDone(signal), (done) => !done), - exitCode: Effect.flatMap(Deferred.await(signal), ([code, signal]) => { - if (Predicate.isNotNull(code)) return Effect.succeed(ExitCode(code)) - return Effect.fail( - toPlatformError( - "exitCode", - new Error(`Process interrupted due to receipt of signal: '${signal}'`), - command, - ), - ) - }), - kill: (opts?: ChildProcess.KillOptions) => { - const sig = opts?.killSignal ?? "SIGTERM" - const send = (s: NodeJS.Signals) => - Effect.catch(killGroup(command, proc, s), () => killOne(command, proc, s)) - const attempt = send(sig).pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid) - if (!opts?.forceKillAfter) return attempt - return Effect.timeoutOrElse(attempt, { - duration: opts.forceKillAfter, - orElse: () => send("SIGKILL").pipe(Effect.andThen(Deferred.await(signal)), Effect.asVoid), - }) - }, - unref: Effect.sync(() => { - if (ref) { - proc.unref() - ref = false - } - return Effect.sync(() => { - if (!ref) { - proc.ref() - ref = true - } - }) - }), - }) - } - case "PipedCommand": { - const flat = flatten(command) - const [head, ...tail] = flat.commands - let handle = spawnCommand(head) - for (let i = 0; i < tail.length; i++) { - const next = tail[i] - const opts = flat.opts[i] ?? {} - const sin = stdin(next.options) - const stream = Stream.unwrap(Effect.map(handle, (x) => source(x, opts.from))) - const to = opts.to ?? "stdin" - if (to === "stdin") { - handle = spawnCommand( - ChildProcess.make(next.command, next.args, { - ...next.options, - stdin: { ...sin, stream }, - }), - ) - continue - } - const fd = ChildProcess.parseFdName(to) - if (Predicate.isUndefined(fd)) { - handle = spawnCommand( - ChildProcess.make(next.command, next.args, { - ...next.options, - stdin: { ...sin, stream }, - }), - ) - continue - } - handle = spawnCommand( - ChildProcess.make(next.command, next.args, { - ...next.options, - additionalFds: { - ...next.options.additionalFds, - [ChildProcess.fdName(fd) as `fd${number}`]: { type: "input", stream }, - }, - }), - ) - } - return yield* handle - } - } - }, - ) - - return makeSpawner(spawnCommand) -}) - -export const layer: Layer.Layer = Layer.effect( - ChildProcessSpawner, - make, -) - -export const defaultLayer = layer.pipe(Layer.provide(NodeFileSystem.layer), Layer.provide(NodePath.layer)) - -export * as CrossSpawnSpawner from "./cross-spawn-spawner" diff --git a/packages/core/src/types.d.ts b/packages/core/src/types.d.ts deleted file mode 100644 index 60e1639ad..000000000 --- a/packages/core/src/types.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -declare module "@npmcli/arborist" { - export interface ArboristOptions { - path: string - binLinks?: boolean - progress?: boolean - savePrefix?: string - ignoreScripts?: boolean - [key: string]: unknown - } - - export interface ArboristNode { - name: string - path: string - } - - export interface ArboristEdge { - to?: ArboristNode - } - - export interface ArboristTree { - edgesOut: Map - } - - export interface ReifyOptions { - add?: string[] - save?: boolean - saveType?: "prod" | "dev" | "optional" | "peer" - [key: string]: unknown - } - - export class Arborist { - constructor(options: ArboristOptions) - loadVirtual(): Promise - reify(options?: ReifyOptions): Promise - } -} - -declare var Bun: - | { - file(path: string): { - text(): Promise - json(): Promise - } - write(path: string, content: string | Uint8Array): Promise - } - | undefined diff --git a/packages/core/test/effect/cross-spawn-spawner.test.ts b/packages/core/test/effect/cross-spawn-spawner.test.ts index d53725797..2612b75e4 100644 --- a/packages/core/test/effect/cross-spawn-spawner.test.ts +++ b/packages/core/test/effect/cross-spawn-spawner.test.ts @@ -5,7 +5,7 @@ import path from "node:path" import { Effect, Exit, Stream } from "effect" import type * as PlatformError from "effect/PlatformError" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" const live = CrossSpawnSpawner.defaultLayer diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index dd794ef6f..da5981737 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -6,7 +6,7 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/ import { ChildProcess } from "effect/unstable/process" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Global } from "@opencode-ai/core/global" import { Log } from "@/util" import { sanitizedProcessEnv } from "@opencode-ai/core/util/opencode-process" diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index 2c5943c7d..9fa53293f 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Context, Schema } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" import path from "path" import { mergeDeep } from "remeda" diff --git a/packages/opencode/src/git/index.ts b/packages/opencode/src/git/index.ts index d2e04910a..16a862447 100644 --- a/packages/opencode/src/git/index.ts +++ b/packages/opencode/src/git/index.ts @@ -1,4 +1,4 @@ -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Effect, Layer, Context, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index 55c4092e2..84fd02cb3 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -1,6 +1,6 @@ import { Effect, Layer, Schema, Context, Stream } from "effect" import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { withTransientReadRetry } from "@/util/effect-http-client" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import path from "path" diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 9652a1258..c2479372d 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -29,7 +29,7 @@ import { Effect, Exit, Layer, Option, Context, Schema, Stream } from "effect" import { EffectBridge } from "@/effect" import { InstanceState } from "@/effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { zod as effectZod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/npm/index.ts b/packages/opencode/src/npm/index.ts index 23368b29b..a7538b9c3 100644 --- a/packages/opencode/src/npm/index.ts +++ b/packages/opencode/src/npm/index.ts @@ -14,7 +14,7 @@ import { EffectFlock } from "@opencode-ai/core/util/effect-flock" import { makeRuntime } from "@opencode-ai/core/effect/runtime" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" export class InstallFailedError extends Schema.TaggedErrorClass()("NpmInstallFailedError", { add: Schema.Array(Schema.String).pipe(Schema.optional), diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index c437fedb2..fc34a6296 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -12,7 +12,7 @@ import { Effect, Layer, Path, Scope, Context, Stream, Types, Schema } from "effe import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { zod } from "@/util/effect-zod" import { withStatics } from "@/util/schema" diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 87f914a80..600eb42f7 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -27,7 +27,7 @@ import { LSP } from "../lsp" import { Flag } from "@opencode-ai/core/flag/flag" import { ulid } from "ulid" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import * as Stream from "effect/Stream" import { Command } from "../command" import { pathToFileURL, fileURLToPath } from "url" diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 32d65633c..88ea274f8 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -3,7 +3,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { formatPatch, structuredPatch } from "diff" import path from "path" import z from "zod" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Hash } from "@opencode-ai/core/util/hash" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 422fd8e3a..4e3d3d714 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -34,7 +34,7 @@ import { pathToFileURL } from "url" import { Effect, Layer, Context } from "effect" import { FetchHttpClient, HttpClient } from "effect/unstable/http" import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../file/ripgrep" import { Format } from "../format" import { InstanceState } from "@/effect" diff --git a/packages/opencode/src/worktree/index.ts b/packages/opencode/src/worktree/index.ts index f39d9ad04..b9c7226b6 100644 --- a/packages/opencode/src/worktree/index.ts +++ b/packages/opencode/src/worktree/index.ts @@ -18,7 +18,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { BootstrapRuntime } from "@/effect/bootstrap-runtime" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { InstanceState } from "@/effect" const log = Log.create({ service: "worktree" }) diff --git a/packages/opencode/test/auth/auth.test.ts b/packages/opencode/test/auth/auth.test.ts index 8688eafaf..55e950aab 100644 --- a/packages/opencode/test/auth/auth.test.ts +++ b/packages/opencode/test/auth/auth.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Auth } from "../../src/auth" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/bus/bus-effect.test.ts b/packages/opencode/test/bus/bus-effect.test.ts index d8b4a275b..0daf8fe6a 100644 --- a/packages/opencode/test/bus/bus-effect.test.ts +++ b/packages/opencode/test/bus/bus-effect.test.ts @@ -3,7 +3,7 @@ import { Deferred, Effect, Layer, Schema, Stream } from "effect" import { Bus } from "../../src/bus" import { BusEvent } from "../../src/bus/bus-event" import { Instance } from "../../src/project/instance" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdirScoped } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index bdd361e7a..f6fb04ea0 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -13,7 +13,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Env } from "../../src/env" import { provideTmpdirInstance } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { testEffect } from "../lib/effect" /** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */ diff --git a/packages/opencode/test/format/format.test.ts b/packages/opencode/test/format/format.test.ts index 544359c60..674d2767c 100644 --- a/packages/opencode/test/format/format.test.ts +++ b/packages/opencode/test/format/format.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Format } from "../../src/format" import * as Formatter from "../../src/format/formatter" diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 8cb098826..092bfef51 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/lsp/lifecycle.test.ts b/packages/opencode/test/lsp/lifecycle.test.ts index 98ac600f4..e39290973 100644 --- a/packages/opencode/test/lsp/lifecycle.test.ts +++ b/packages/opencode/test/lsp/lifecycle.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Effect, Layer } from "effect" import { LSP } from "../../src/lsp" import { LSPServer } from "../../src/lsp" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 80601cd9a..0064185f4 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -2,7 +2,7 @@ import { afterEach, test, expect } from "bun:test" import os from "os" import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Bus } from "../../src/bus" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Permission } from "../../src/permission" import { PermissionID } from "../../src/permission/schema" import { Instance } from "../../src/project/instance" diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index 6579b414f..c3aba55de 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -10,7 +10,7 @@ import { Effect, Layer, Stream } from "effect" import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process" import { NodePath } from "@effect/platform-node" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/project/worktree-remove.test.ts b/packages/opencode/test/project/worktree-remove.test.ts index b0cb626b1..fa70ecb89 100644 --- a/packages/opencode/test/project/worktree-remove.test.ts +++ b/packages/opencode/test/project/worktree-remove.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Effect, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Worktree } from "../../src/worktree" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/project/worktree.test.ts b/packages/opencode/test/project/worktree.test.ts index b20914c96..44a25a8e6 100644 --- a/packages/opencode/test/project/worktree.test.ts +++ b/packages/opencode/test/project/worktree.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect } from "bun:test" import * as fs from "fs/promises" import path from "path" import { Cause, Effect, Exit, Layer } from "effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Worktree } from "../../src/worktree" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 79bdfe41f..1b2b120b6 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -25,7 +25,7 @@ import * as SessionProcessorModule from "../../src/session/processor" import { Snapshot } from "../../src/snapshot" import { ProviderTest } from "../fake/provider" import { testEffect } from "../lib/effect" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" void Log.init({ print: false }) diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index d665022fd..fee42a939 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -19,7 +19,7 @@ import { SessionStatus } from "../../src/session/status" import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirServer } from "../fixture/fixture" import { testEffect } from "../lib/effect" import { raw, reply, TestLLMServer } from "../lib/llm-server" diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 288ca8f99..7e3377746 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -38,7 +38,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { Log } from "../../src/util" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" import { provideTmpdirInstance, provideTmpdirServer } from "../fixture/fixture" diff --git a/packages/opencode/test/session/revert-compact.test.ts b/packages/opencode/test/session/revert-compact.test.ts index 213e59632..d77ef3e05 100644 --- a/packages/opencode/test/session/revert-compact.test.ts +++ b/packages/opencode/test/session/revert-compact.test.ts @@ -9,7 +9,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util" import { MessageID, PartID, SessionID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 8c8c4da3b..269c23148 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -52,7 +52,7 @@ import { Snapshot } from "../../src/snapshot" import { ToolRegistry } from "../../src/tool" import { Truncate } from "../../src/tool" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { Format } from "../../src/format" diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index 41763fe97..5470d654a 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -6,7 +6,7 @@ import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstab import { AccessToken, AccountID, OrgID, RefreshToken } from "../../src/account/schema" import { Account } from "../../src/account/account" import { AccountRepo } from "../../src/account/repo" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Bus } from "../../src/bus" import { Config } from "../../src/config" import { Provider } from "../../src/provider" diff --git a/packages/opencode/test/skill/skill.test.ts b/packages/opencode/test/skill/skill.test.ts index 13f25be5b..bfcb0dcd6 100644 --- a/packages/opencode/test/skill/skill.test.ts +++ b/packages/opencode/test/skill/skill.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Skill } from "../../src/skill" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideInstance, provideTmpdirInstance, tmpdir } from "../fixture/fixture" import { testEffect } from "../lib/effect" import path from "path" diff --git a/packages/opencode/test/storage/storage.test.ts b/packages/opencode/test/storage/storage.test.ts index f1b245f9b..a3d5a8ac5 100644 --- a/packages/opencode/test/storage/storage.test.ts +++ b/packages/opencode/test/storage/storage.test.ts @@ -2,7 +2,7 @@ import { describe, expect } from "bun:test" import path from "path" import { Effect, Exit, Layer } from "effect" import { AppFileSystem } from "@opencode-ai/core/filesystem" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Git } from "../../src/git" import { Global } from "@opencode-ai/core/global" import { Storage } from "../../src/storage" diff --git a/packages/opencode/test/tool/bash.test.ts b/packages/opencode/test/tool/bash.test.ts index 23f18f989..32cd43100 100644 --- a/packages/opencode/test/tool/bash.test.ts +++ b/packages/opencode/test/tool/bash.test.ts @@ -11,7 +11,7 @@ import type { Permission } from "../../src/permission" import { Agent } from "../../src/agent/agent" import { Truncate } from "../../src/tool" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Plugin } from "../../src/plugin" diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 8d496509a..a8637ea9c 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -3,7 +3,7 @@ import path from "path" import { Cause, Effect, Exit, Layer } from "effect" import { GlobTool } from "../../src/tool/glob" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Ripgrep } from "../../src/file/ripgrep" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Truncate } from "../../src/tool" diff --git a/packages/opencode/test/tool/grep.test.ts b/packages/opencode/test/tool/grep.test.ts index 3e147dddc..acdaff03a 100644 --- a/packages/opencode/test/tool/grep.test.ts +++ b/packages/opencode/test/tool/grep.test.ts @@ -4,7 +4,7 @@ import { Effect, Layer } from "effect" import { GrepTool } from "../../src/tool/grep" import { provideInstance, provideTmpdirInstance } from "../fixture/fixture" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { Ripgrep } from "../../src/file/ripgrep" diff --git a/packages/opencode/test/tool/lsp.test.ts b/packages/opencode/test/tool/lsp.test.ts index 07de4a0da..b7a52da19 100644 --- a/packages/opencode/test/tool/lsp.test.ts +++ b/packages/opencode/test/tool/lsp.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/question.test.ts b/packages/opencode/test/tool/question.test.ts index 53c413186..537d1f950 100644 --- a/packages/opencode/test/tool/question.test.ts +++ b/packages/opencode/test/tool/question.test.ts @@ -4,7 +4,7 @@ import { QuestionTool } from "../../src/tool/question" import { Question } from "../../src/question" import { SessionID, MessageID } from "../../src/session/schema" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Truncate } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/read.test.ts b/packages/opencode/test/tool/read.test.ts index 27e6b71c5..b9c313bdc 100644 --- a/packages/opencode/test/tool/read.test.ts +++ b/packages/opencode/test/tool/read.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Cause, Effect, Exit, Layer } from "effect" import path from "path" import { Agent } from "../../src/agent/agent" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { LSP } from "../../src/lsp" import { Permission } from "../../src/permission" diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 54c1d7706..523352d41 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -3,7 +3,7 @@ import path from "path" import fs from "fs/promises" import { Effect, Layer } from "effect" import { Instance } from "../../src/project/instance" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { ToolRegistry } from "../../src/tool" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index c67121e3c..43659187f 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -1,4 +1,4 @@ -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Effect, Layer } from "effect" import { afterEach, describe, expect } from "bun:test" import path from "path" diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 1eaa0cfc8..490b3f200 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect } from "bun:test" import { Effect, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { Config } from "../../src/config" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { Instance } from "../../src/project/instance" import { Session } from "../../src/session" import { MessageV2 } from "../../src/session/message-v2" diff --git a/packages/opencode/test/tool/write.test.ts b/packages/opencode/test/tool/write.test.ts index 706ddb372..e6e383189 100644 --- a/packages/opencode/test/tool/write.test.ts +++ b/packages/opencode/test/tool/write.test.ts @@ -12,7 +12,7 @@ import { Truncate } from "../../src/tool" import { Tool } from "../../src/tool" import { Agent } from "../../src/agent/agent" import { SessionID, MessageID } from "../../src/session/schema" -import { CrossSpawnSpawner } from "@opencode-ai/core/effect/cross-spawn-spawner" +import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner" import { provideTmpdirInstance } from "../fixture/fixture" import { testEffect } from "../lib/effect" -- cgit v1.2.3