stdio 0.3.0
stdio: ^0.3.0 copied to clipboard
File-descriptor-level capture and redirection of stdout/stderr — including output from native/FFI code and child processes that bypasses Dart's stream objects. POSIX (Linux + macOS).
0.3.0 #
pause()/resume()/isPaused: temporarily point fd 1/2 back at the real terminal without tearing the session down — the terminal-handoff primitive for TUIs (spawn$EDITOR/a pager withinheritStdiomid-session; the child inherits the real descriptors). Buffered writers are flushed at both edges so bytes land on the side of the boundary they were written on.stop()remains safe while paused.
Changelog #
0.2.0 — 2026-07-04 #
API finalization (breaking, pre-publish) + production-hardening.
Renamed: stdio_capture → stdio (import package:stdio/stdio.dart).
The scope is the process's stdio descriptors — capture, scoped capture,
reroute, and the saved-terminal handle. stdin is deliberately untouched (a
TUI keeps reading keys); fd-level stdin injection is a natural future
addition.
API — the surface now mirrors Process.start/Process.run:
collect()→capture()(returnsCaptured, as before).divertToFile()→redirectToFile()(matchesStdioRedirect).- The combined feed is a real stream:
capture.listen(...)→capture.output(Stream<CapturedLine>, composable likestdout/stderr). stop()now returnsFuture<Captured>— the same transcriptcapture()yields.backlogLines:→historyLines:(names what it bounds:history).CapturedLine.rawBytes→bytes;CapturedLineis now immutable (sourceis final — the classifier tags via copy).startProcessgainedrunInShell/includeParentEnvironmentpass-throughs.FdTerminalSinkis exported (it was the hidden supertype ofStdoutTerminalSink).- If the reader isolate dies mid-session the line streams now close (listeners
get
onDone) with the cause onreaderError, instead of going silent.
Hardening (see the 0.1.x → 0.2.0 commit for the full story):
- Root-fixed a variadic-FFI ABI bug:
fcntl/ioctl(andopen) are variadic in C, and fixed-arity bindings pass the third arg in a register while arm64-macOS callees read the varargs stack —F_SETFLset random flags (O_NONBLOCK intermittently missing → blocking drains → data loss on fast stops) andTIOCGWINSZnever worked. Bound withVarArgs;setNonBlockingis verify-or-throw. stop()restores fd 2 from its own saved dup (it was restored from fd 1's, silently re-routing stderr whenever they differed).capture()restores the redirect even when the body throws.- Reader lifecycle notifications share the data port (cross-port delivery has no ordering guarantee; a separate exit port could outrun queued batches).
- Mirror file opens (and fails) at
start()instead of inside the reader. - O(1) ring buffers; bounded, verified non-blocking end to end.
- Platform coverage verified: the full suite + fast-stop stress probe pass on
macOS arm64 and in Linux arm64 + amd64 containers — all three varargs ABI
conventions the
VarArgsbindings must satisfy. maxLineBytes(default 64 KiB) bounds the in-progress line: a writer that never emits\nis delivered in cap-sized pieces instead of growing memory without bound (makes the "never OOMs" guarantee unconditional).- README no longer implies the package installs signal handlers (it never
did, deliberately): restore is
stop()'s job — wire your signal handling to call it.
0.1.0 — 2026-07-04 #
Initial implementation: fd-level dup2 capture of stdout/stderr with a
dedicated drain isolate (bounded backpressure, credit-based delivery,
self-pipe control), line assembly, history ring, durable mirror file,
subprocess tagging, classifier hook, scoped capture, file redirect, and the
saved-terminal render sinks (TerminalSink/StdoutTerminalSink).