Skip to content

Add tee OutputDestination for live + captured streaming#21

Merged
maniramezan merged 2 commits into
mainfrom
feature/output-destination-tee
Jun 15, 2026
Merged

Add tee OutputDestination for live + captured streaming#21
maniramezan merged 2 commits into
mainfrom
feature/output-destination-tee

Conversation

@maniramezan

Copy link
Copy Markdown
Owner

Summary

Adds OutputDestination.tee, which forwards a command's stream to the parent process's stdout/stderr in real time and captures it in memory for ShellOutput. This serves long-running commands (e.g. a multi-minute build) that need live console progress while still needing the full captured text for post-run parsing — previously only .capture (in-memory, surfaced after exit), .discard, and .file existed.

Changes

  • OutputDestination.swift: new case tee with doc comment + a .tee usage example in the type-level docs. Enum remains Sendable, Equatable automatically.
  • SubprocessExecutor.swift: handle .tee in routeData (the single routing chokepoint) — it mirrors .capture (so captured bytes count toward the output limit identically) plus a live write. Live writes go through a shared NSLock-guarded TeeSink (an @unchecked Sendable wrapper, consistent with FileDescriptorClosure) so concurrent stdout/stderr tee writes can't interleave mid-buffer. .capture/.discard/.file are byte-for-byte unchanged; openFileHandleIfNeeded still returns nil for .tee.
  • OutputDestinationTeeTests.swift (new, .serialized suite): capture still populates ShellOutput; live bytes reach the parent stdout; stdout+stderr both .tee interleave without corruption (lock smoke test); .tee honors outputLimitoutputLimitExceeded. The helper redirects real STDOUT/STDERR FDs via dup/dup2.
  • .claude/skills/swiftyshell.md: updated the OutputDestination API reference to include .tee and the Equatable conformance.

Testing

  • swift test --enable-all-traits (full surface) — 399 tests, 42 suites pass
  • swift test (default = Core only) — 83 tests pass

Also: swift-format lint --strict clean on all touched files; Scripts/validate-traits.swift and Scripts/validate-docc-coverage.swift pass; DocC builds cleanly.

Notes

  • Not a new command family — the New Command Family Checklist is N/A.
  • Linux: source uses only cross-platform Foundation APIs (FileHandle.standardOutput/.standardError, NSLock). The test helper's POSIX calls (dup/dup2/close/STDOUT_FILENO) are guarded by the same #if canImport(Darwin) … #elseif canImport(Glibc) pattern SpawnTests.swift already uses in Linux CI. Verified locally on macOS; the CI matrix confirms Linux.
  • Local -warnings-as-errors runs hit a pre-existing conflicting options '-warnings-as-errors' and '-suppress-warnings' error while building the swift-system dependency (reproducible on clean main, an Xcode-beta/toolchain issue) — gates were run without that flag locally.
  • Follow-up after merge: tag a new SwiftyShell version so the shipit repo can bump its dependency to consume .tee.

Add OutputDestination.tee, which forwards a command's stream to the
parent process's stdout/stderr in real time while also capturing it in
memory for ShellOutput. This serves long-running commands (e.g. a
multi-minute build) that need live console progress and the full
captured text for post-run parsing.

Implemented entirely in routeData: .tee mirrors .capture (so bytes count
toward the output limit identically) plus a live write through a shared
NSLock-guarded TeeSink so concurrent stdout/stderr tee writes can't
interleave mid-buffer. .capture/.discard/.file are unchanged, and
openFileHandleIfNeeded still returns nil for .tee.

Add a serialized executor test suite covering capture, live streaming,
stdout+stderr interleaving, and output-limit enforcement. The test
helper redirects real STDOUT/STDERR FDs via dup/dup2, guarded by the
Darwin/Glibc conditional import the repo already uses for Linux CI.
swift-corelibs-foundation's FileManager.createFile is not annotated
@discardableResult (unlike Apple's Foundation), so the unused Bool
result tripped -warnings-as-errors on the Linux CI matrix only.
@maniramezan maniramezan merged commit 2baa849 into main Jun 15, 2026
55 checks passed
@maniramezan maniramezan deleted the feature/output-destination-tee branch June 15, 2026 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant