@osdlc/command-seam 0.0.0-stage → 0.0.1-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +328 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +21 -0
- package/README.md +741 -2
- package/SECURITY.md +23 -0
- package/async.ts +466 -0
- package/defaults.ts +83 -0
- package/dist/async.d.ts +146 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +368 -0
- package/dist/defaults.d.ts +52 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +51 -0
- package/dist/groups.d.ts +155 -0
- package/dist/groups.d.ts.map +1 -0
- package/dist/groups.js +331 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/process.d.ts +146 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +175 -0
- package/dist/process.test.d.ts +1 -0
- package/dist/process.test.d.ts.map +1 -0
- package/dist/process.test.js +1 -0
- package/dist/quote.d.ts +18 -0
- package/dist/quote.d.ts.map +1 -0
- package/dist/quote.js +28 -0
- package/dist/result.d.ts +211 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +219 -0
- package/dist/shell.d.ts +84 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +83 -0
- package/dist/stream.d.ts +90 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +73 -0
- package/dist/sync.d.ts +311 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +406 -0
- package/dist/testing.d.ts +158 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +173 -0
- package/groups.ts +360 -0
- package/index.ts +98 -0
- package/package.json +94 -3
- package/process.ts +270 -0
- package/quote.ts +31 -0
- package/result.ts +403 -0
- package/shell.ts +131 -0
- package/smoke.mjs +61 -0
- package/stream.ts +134 -0
- package/sync.ts +604 -0
- package/testing.ts +263 -0
package/stream.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The streaming runner: the {@link StreamRunner} interface and its
|
|
3
|
+
* `spawnSync`-backed default, {@link streamRunner}.
|
|
4
|
+
*
|
|
5
|
+
* Use it for a command whose output a person must see live: a build, a test
|
|
6
|
+
* run, or an interactive program. The capture runners would hold all output
|
|
7
|
+
* until the child exited. Options are {@link CommandRunOptions} without
|
|
8
|
+
* `input`, since stdin is inherited, not piped. Code takes a `StreamRunner`
|
|
9
|
+
* the same way it takes a `CommandRunner`, and tests pass a fake. Mixed stdio
|
|
10
|
+
* (say, a child's stdout sent to the parent's stderr) is out of scope; call
|
|
11
|
+
* `child_process` directly for that.
|
|
12
|
+
*
|
|
13
|
+
* There is no async version: `run` blocks until the child exits, as
|
|
14
|
+
* `spawnRunner` does.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
cwdError,
|
|
21
|
+
mergedEnv,
|
|
22
|
+
normalizeTimeout,
|
|
23
|
+
syncStop,
|
|
24
|
+
type CommandRunOptions,
|
|
25
|
+
type NeverExitedReason,
|
|
26
|
+
} from './sync.ts'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Per-call options for a {@link StreamRunner}. Same as {@link
|
|
30
|
+
* CommandRunOptions} minus `input`: stdin is inherited from the parent
|
|
31
|
+
* process, not fed programmatically, so there is nothing to write to.
|
|
32
|
+
*/
|
|
33
|
+
export type StreamRunOptions = Omit<CommandRunOptions, 'input'>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Result of a streamed run. There is no output to report, since it went
|
|
37
|
+
* straight to the inherited stdio; only how the child ended.
|
|
38
|
+
*/
|
|
39
|
+
export interface StreamResult {
|
|
40
|
+
/** The child's exit code, or `1` when it never reported one (see `neverExited`). */
|
|
41
|
+
exitCode: number
|
|
42
|
+
/**
|
|
43
|
+
* The child never reported an exit status: the spawn failed, the timeout
|
|
44
|
+
* killed it, or a signal took it down. Same meaning as
|
|
45
|
+
* `CommandResult.neverExited`. A message is also written to the parent's
|
|
46
|
+
* stderr.
|
|
47
|
+
*/
|
|
48
|
+
neverExited?: boolean | undefined
|
|
49
|
+
/**
|
|
50
|
+
* Why the child never reported an exit status: `spawn`, `timeout` or
|
|
51
|
+
* `signal` (a streamed run cannot be aborted, and its output is not
|
|
52
|
+
* captured, so there is no cap). Set whenever `neverExited`
|
|
53
|
+
* is, absent otherwise.
|
|
54
|
+
*/
|
|
55
|
+
reason?: Exclude<NeverExitedReason, 'abort' | 'output'> | undefined
|
|
56
|
+
/**
|
|
57
|
+
* The signal that ended the child, for `reason: 'signal'` and
|
|
58
|
+
* `reason: 'timeout'` (`SIGKILL`).
|
|
59
|
+
*/
|
|
60
|
+
signal?: NodeJS.Signals | undefined
|
|
61
|
+
/**
|
|
62
|
+
* The runner's error message when the spawn failed or the timeout fired,
|
|
63
|
+
* such as `spawnSync npm ENOENT`. The same text is written to the parent's
|
|
64
|
+
* stderr.
|
|
65
|
+
*/
|
|
66
|
+
error?: string | undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Runs a binary with inherited stdio instead of capturing it. */
|
|
70
|
+
export interface StreamRunner {
|
|
71
|
+
/**
|
|
72
|
+
* Run the binary with `argv` (without the binary name) and wait for it to
|
|
73
|
+
* exit. Never throws for a failed command. Programmer errors throw: an
|
|
74
|
+
* invalid `timeoutMs` (see `CommandRunOptions.timeoutMs`) or a NUL byte in
|
|
75
|
+
* argv or env.
|
|
76
|
+
*/
|
|
77
|
+
run(argv: string[], opts: StreamRunOptions): StreamResult
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The default {@link StreamRunner}: runs `binary` with `spawnSync` and
|
|
82
|
+
* `stdio: 'inherit'`, so the child's prompts, progress and output reach the
|
|
83
|
+
* terminal directly. It behaves like a direct `spawnSync` call, but code
|
|
84
|
+
* takes it as a parameter, so tests can pass a fake. On timeout the direct
|
|
85
|
+
* child gets `SIGKILL`. Processes it started keep running: this runner has
|
|
86
|
+
* no `processGroup` option.
|
|
87
|
+
*
|
|
88
|
+
* When the spawn fails, the timeout fires or a signal ends the child, a
|
|
89
|
+
* message is written to the parent's stderr, since there is no captured
|
|
90
|
+
* stderr to carry it. The result says why in `reason`, and carries the spawn
|
|
91
|
+
* or timeout message in `error`.
|
|
92
|
+
*
|
|
93
|
+
* @param binary - A name looked up on `PATH`, or a path.
|
|
94
|
+
* @returns A runner for that binary.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const npm = streamRunner('npm')
|
|
99
|
+
* const { exitCode } = npm.run(['run', 'build'], { cwd: '.' })
|
|
100
|
+
* process.exitCode = exitCode
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function streamRunner(binary: string): StreamRunner {
|
|
104
|
+
return {
|
|
105
|
+
run(argv: string[], opts: StreamRunOptions): StreamResult {
|
|
106
|
+
const timeout = normalizeTimeout(opts.timeoutMs)
|
|
107
|
+
const cwdMessage = cwdError(`spawnSync ${binary}`, binary, argv, opts)
|
|
108
|
+
if (cwdMessage !== undefined) {
|
|
109
|
+
process.stderr.write(`${cwdMessage}\n`)
|
|
110
|
+
return { exitCode: 1, neverExited: true, reason: 'spawn', error: cwdMessage }
|
|
111
|
+
}
|
|
112
|
+
const proc = spawnSync(binary, argv, {
|
|
113
|
+
cwd: opts.cwd,
|
|
114
|
+
stdio: 'inherit',
|
|
115
|
+
timeout,
|
|
116
|
+
// See `sync.ts`'s `runViaChildRedirect`: the default SIGTERM is not a
|
|
117
|
+
// hard cap.
|
|
118
|
+
killSignal: 'SIGKILL',
|
|
119
|
+
...mergedEnv(opts),
|
|
120
|
+
})
|
|
121
|
+
// `spawnSync` reports a stop as a missing `status` (null, or undefined
|
|
122
|
+
// under Bun for a failed spawn), with `error` and `signal` saying why.
|
|
123
|
+
if (proc.status != null) return { exitCode: proc.status }
|
|
124
|
+
// Output is inherited, not buffered, so the output cap cannot apply.
|
|
125
|
+
const stop = syncStop(proc) as Pick<StreamResult, 'reason' | 'signal'>
|
|
126
|
+
if (proc.error !== undefined) {
|
|
127
|
+
process.stderr.write(`${proc.error.message}\n`)
|
|
128
|
+
return { exitCode: 1, neverExited: true, ...stop, error: proc.error.message }
|
|
129
|
+
}
|
|
130
|
+
process.stderr.write(`${binary} was killed by ${proc.signal}\n`)
|
|
131
|
+
return { exitCode: 1, neverExited: true, ...stop }
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|