Add tee OutputDestination for live + captured streaming#21
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 forShellOutput. 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.fileexisted.Changes
OutputDestination.swift: newcase teewith doc comment + a.teeusage example in the type-level docs. Enum remainsSendable, Equatableautomatically.SubprocessExecutor.swift: handle.teeinrouteData(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 sharedNSLock-guardedTeeSink(an@unchecked Sendablewrapper, consistent withFileDescriptorClosure) so concurrent stdout/stderr tee writes can't interleave mid-buffer..capture/.discard/.fileare byte-for-byte unchanged;openFileHandleIfNeededstill returnsnilfor.tee.OutputDestinationTeeTests.swift(new,.serializedsuite): capture still populatesShellOutput; live bytes reach the parent stdout; stdout+stderr both.teeinterleave without corruption (lock smoke test);.teehonorsoutputLimit→outputLimitExceeded. The helper redirects realSTDOUT/STDERRFDs viadup/dup2..claude/skills/swiftyshell.md: updated theOutputDestinationAPI reference to include.teeand theEquatableconformance.Testing
swift test --enable-all-traits(full surface) — 399 tests, 42 suites passswift test(default = Core only) — 83 tests passAlso:
swift-format lint --strictclean on all touched files;Scripts/validate-traits.swiftandScripts/validate-docc-coverage.swiftpass; DocC builds cleanly.Notes
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)patternSpawnTests.swiftalready uses in Linux CI. Verified locally on macOS; the CI matrix confirms Linux.-warnings-as-errorsruns hit a pre-existingconflicting options '-warnings-as-errors' and '-suppress-warnings'error while building theswift-systemdependency (reproducible on cleanmain, an Xcode-beta/toolchain issue) — gates were run without that flag locally..tee.