src/process defines the process execution boundary for stackctl. It centralizes all external
command invocation behind the ProcessRunner interface so Docker, SOPS, age, git, and other system
tools can be executed through one typed abstraction. The module supports real execution, dry-run
behavior, command availability checks, captured output, streamed output callbacks, and test
replacement through fake runners.
- Port and adapter:
ProcessRunneris the port used by application code.RealProcessRunneris the Deno-backed adapter that callsDeno.Command. - Dependency inversion: callers depend on
ProcessRunnerinstead ofDeno.Commanddirectly, which allows tests to inject fakes and avoids real external process execution in unit tests. - Immutable mode switching:
withDryRun(dryRun)returns a newProcessRunnerinstance with the requested dry-run mode rather than mutating the current runner. - Result object: every command returns a
ProcessResultcontainingstdout,stderr,code,success, and the originalcommandarray for diagnostics. - Callback-based streaming:
stream()accumulates full output while also emitting complete stdout and stderr lines to optional handlers.
- A caller passes a command as
string[], where the first element is the executable and remaining elements are arguments. - Empty command arrays short-circuit with
code: 1,success: false, empty output, and the original command. - In dry-run mode,
run()andstream()print the intended command, skip OS execution, and returncode: 0withsuccess: true. - In capture mode,
run()createsDeno.Commandwith piped stdout and stderr, optionalcwd, and optionalenv. It awaitscommand.output(), decodes both byte streams withTextDecoder, and returns the Deno status code and success flag. - In streaming mode,
stream()spawns the command, registers SIGINT and SIGTERM handlers, drains stdout and stderr streams, awaits child status, then removes signal handlers infinally. drainStream()readsReadableStream<Uint8Array>chunks, decodes them incrementally, preserves partial line buffers, emits only complete lines to callbacks, flushes the final residual line, releases the reader lock, and returns the full decoded text.which(name)invokes the platformwhichcommand with null stdout and stderr, returningtruewhen the lookup exits successfully andfalseon failure or exception.
Exit code semantics are normalized through ProcessResult: code is the process exit code and
success mirrors Deno's success flag, effectively successful when the exit code is 0. Internal
runner errors and invalid empty commands are represented as code: 1 with success: false. Dry-run
commands report success with code: 0 because no external command is executed.
RunOptions.timeout and timeoutSignal are part of the public type contract, but the current
RealProcessRunner implementation does not apply timeout cancellation.
src/dockeruses the process abstraction to call Docker and Docker Swarm commands.src/secretsuses the abstraction for SOPS, age, age-keygen, shred, and rm integrations.- CLI commands can create a real runner and toggle dry-run behavior while keeping downstream code independent from Deno process APIs.
src/testingprovides fake implementations for unit tests, enabling deterministic command responses and verification of expected invocations.src/main.tsand release build settings must grant Deno permissions for external commands that flow through this abstraction.