Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193import { Effect, Stream } from "effect"import { ChildProcess } from "effect/unstable/process"import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"import { collectStream } from "@opencode-ai/util/process"import { Failed, NotFound, WrongKind, type FileInfo, type FileType, type FilesImpl } from "./files.js"
/** * Files derived from spawning processes: one process per intent, "$1" is * always the target path. Scripts report classification through an exit-code * protocol (44/45/46) so failures never require parsing localized error text; * LC_ALL=C pins the one stderr match that remains. Requires GNU coreutils and * findutils in the target image — BSD and busybox userlands will not work. * Malformed output from these scripts is our own bug and dies as a defect. */
const MAX_DATA_BYTES = 64 * 1024 * 1024const MAX_ERROR_BYTES = 64 * 1024const NOT_FOUND = 44const WRONG_KIND = 45const FAILED = 46const TAB = "\t"
const loadMetadata = (flags = "") => `metadata=$(stat ${flags} -c '%F${TAB}%s${TAB}%Y' -- "$1" 2>&1) || { case "$metadata" in *'No such file or directory'*|*'Not a directory'*) exit ${NOT_FOUND} ;; *) printf '%s' "$metadata" >&2; exit ${FAILED} ;; esac}`
const statScript = `${loadMetadata()}printf '%s\n' "$metadata"`
const readScript = `${loadMetadata("-L")}kind=\${metadata%%${TAB}*}if [ "$kind" != 'regular file' ] && [ "$kind" != 'regular empty file' ]; then printf '%s' "$kind" >&2 exit ${WRONG_KIND}fiprintf '%s\n' "$metadata"if [ "$2" = range ]; then dd if="$1" iflag=skip_bytes,count_bytes skip="$3" count="$4" status=noneelse cat -- "$1"fi`
const listScript = `${loadMetadata("-L")}kind=\${metadata%%${TAB}*}if [ "$kind" != directory ]; then printf '%s' "$kind" >&2 exit ${WRONG_KIND}fifind -H "$1" -mindepth 1 -maxdepth 1 -printf '%y\\0%f\\0'`
const moveScript = `${loadMetadata()}mv -- "$1" "$2"`
interface Result { readonly exitCode: number readonly stdout: Uint8Array readonly stderr: Uint8Array}
export const execDefaults = (spawner: ChildProcessSpawner["Service"]): FilesImpl => { const run = ( path: string, script: string, args: ReadonlyArray<string> = [], stdin?: Uint8Array, ): Effect.Effect<Result, Failed> => Effect.scoped( Effect.gen(function* () { const command = ChildProcess.make("sh", ["-c", script, "sh", path, ...args], { env: { LC_ALL: "C" }, extendEnv: true, stdin: stdin === undefined ? undefined : Stream.make(stdin), }) const handle = yield* spawner.spawn(command).pipe(Effect.mapError((cause) => new Failed({ path, cause }))) const [stdout, stderr, exitCode] = yield* Effect.all( [ collectStream(handle.stdout, MAX_DATA_BYTES), collectStream(handle.stderr, MAX_ERROR_BYTES), handle.exitCode, ], { concurrency: "unbounded" }, ).pipe(Effect.mapError((cause) => new Failed({ path, cause }))) if (stdout.truncated || stderr.truncated) { return yield* new Failed({ path, cause: new Error("Process output exceeded its collection limit") }) } return { exitCode, stdout: stdout.buffer, stderr: stderr.buffer } }), )
const classify = <A>( path: string, result: Result, success: (stdout: Uint8Array) => A, ): Effect.Effect<A, NotFound | WrongKind | Failed> => { if (result.exitCode === 0) return Effect.sync(() => success(result.stdout)) if (result.exitCode === NOT_FOUND) return Effect.fail(new NotFound({ path })) if (result.exitCode === WRONG_KIND) { return Effect.fail(new WrongKind({ path, actual: parseType(new TextDecoder().decode(result.stderr)) })) } return Effect.fail(processFailure(path, result)) }
const complete = (path: string, result: Result) => result.exitCode === 0 ? Effect.void : Effect.fail(processFailure(path, result))
return { stat: (path) => run(path, statScript).pipe(Effect.flatMap((result) => classifyPlain(path, result, parseInfo))), read: (path, range) => run( path, readScript, range === undefined ? ["whole"] : ["range", String(range.offset), String(range.length)], ).pipe( Effect.flatMap((result) => classify(path, result, (stdout) => { const newline = stdout.indexOf(10) if (newline < 0) throw new Error("Missing read metadata header") return { info: parseInfo(stdout.slice(0, newline)), bytes: stdout.slice(newline + 1), } }), ), ), write: (path, bytes) => run(path, `mkdir -p "$(dirname "$1")" && cat > "$1"`, [], bytes).pipe( Effect.flatMap((result) => complete(path, result)), ), list: (path) => run(path, listScript).pipe(Effect.flatMap((result) => classify(path, result, parseList))), remove: (path) => run(path, `rm -rf -- "$1"`).pipe(Effect.flatMap((result) => complete(path, result))), move: (from, to) => run(from, moveScript, [to]).pipe(Effect.flatMap((result) => classifyPlain(from, result, () => undefined))), mkdir: (path) => run(path, `mkdir -p -- "$1"`).pipe(Effect.flatMap((result) => complete(path, result))), }}
/** `classify` for scripts whose protocol never reports WrongKind. */const classifyPlain = <A>( path: string, result: Result, success: (stdout: Uint8Array) => A,): Effect.Effect<A, NotFound | Failed> => { if (result.exitCode === 0) return Effect.sync(() => success(result.stdout)) if (result.exitCode === NOT_FOUND) return Effect.fail(new NotFound({ path })) return Effect.fail(processFailure(path, result))}
const processFailure = (path: string, result: Result) => new Failed({ path, cause: new Error(new TextDecoder().decode(result.stderr).trim() || `Process exited with code ${result.exitCode}`), })
const parseInfo = (bytes: Uint8Array): FileInfo => { const [rawType, rawSize, rawMtime] = new TextDecoder().decode(bytes).trim().split(TAB) const size = Number(rawSize) const mtimeMs = Number(rawMtime) * 1_000 if (!rawType || !Number.isFinite(size) || !Number.isFinite(mtimeMs)) throw new Error("Invalid stat output") return { type: parseType(rawType), size, mtimeMs }}
const parseType = (value: string): FileType => { if (value === "regular file" || value === "regular empty file" || value === "f") return "file" if (value === "directory" || value === "d") return "directory" if (value === "symbolic link" || value === "l") return "symlink" return "other"}
const parseList = (bytes: Uint8Array) => { const fields = new TextDecoder().decode(bytes).split("\0") fields.pop() if (fields.length % 2 !== 0) throw new Error("Invalid find output") return Array.from({ length: fields.length / 2 }, (_, index) => ({ name: fields[index * 2 + 1], type: parseType(fields[index * 2]), }))}
export * as EnvironmentExecDefaults from "./exec-defaults.js"