@osdlc/command-seam 0.0.0-stage → 0.4.0
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 +315 -0
- package/CONTRIBUTING.md +61 -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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e30f155: **Breaking:** the package is now `@osdlc/command-seam`, renamed from `@sksizer/command-seam`. The repository moved from `sksizer/sdlc` to [osdlc/osdlc](https://github.com/osdlc/osdlc).
|
|
8
|
+
|
|
9
|
+
To move, replace `@sksizer/command-seam` with `@osdlc/command-seam` in `package.json` and in every import, including `@osdlc/command-seam/testing`. `@sksizer/command-seam` gets no further releases.
|
|
10
|
+
- e30f155: Add process-group signal forwarding and `unref` on the managed-process runner. These exports are experimental (see the README's Stability section).
|
|
11
|
+
|
|
12
|
+
- `installGroupSignalForwarding(opts?)`, with options type
|
|
13
|
+
`GroupSignalForwardingOptions`: opt-in. While a group started with
|
|
14
|
+
`processGroup: true` (async and managed runners) is live, `SIGINT`,
|
|
15
|
+
`SIGTERM` and `SIGHUP` send `SIGTERM` to every group, `SIGKILL` after
|
|
16
|
+
`graceMs` (default 1000), then exit with `128 + signal number`. A repeat
|
|
17
|
+
signal escalates to `SIGKILL` at once; an `exit` handler `SIGKILL`s groups
|
|
18
|
+
whose run is still in flight. Listeners are attached only while a group is
|
|
19
|
+
starting or live, ahead of the host's own. A host with its own listener,
|
|
20
|
+
`process.once` included, keeps the exit; its groups still get `SIGKILL`
|
|
21
|
+
after `graceMs`, or on exit if sooner and the run is in flight. Returns an
|
|
22
|
+
uninstall function. POSIX only; a no-op on Windows. `SIGKILL` of the host
|
|
23
|
+
still orphans its groups. When forwarding owns the exit, a new
|
|
24
|
+
`processGroup: true` run after the first forwarded signal is refused: it
|
|
25
|
+
starts nothing and settles with `reason: 'abort'`. The uninstall function
|
|
26
|
+
resets this.
|
|
27
|
+
- `forwardedSignal()`: the signal forwarded when forwarding owns the exit, or
|
|
28
|
+
`undefined`. A host that runs one command after another checks it to stop.
|
|
29
|
+
- `forwardedExitCode()`: `128 + signal number` for `forwardedSignal()`, or
|
|
30
|
+
`undefined`. A host that sets its own exit code keeps this one over it.
|
|
31
|
+
- `liveProcessGroups()`: the pids of the groups currently registered. A group
|
|
32
|
+
stays registered until it is empty, also after its run settles or its
|
|
33
|
+
leader exits, so a forwarded signal still kills a background member or one
|
|
34
|
+
waiting for the `SIGKILL` follow-up. The host's normal exit kills only the
|
|
35
|
+
groups whose run is still in flight, so a daemon a settled run left behind
|
|
36
|
+
keeps running.
|
|
37
|
+
- `unref` on the managed-process runner's options: the parent can exit while
|
|
38
|
+
the (usually `processGroup`) child keeps running. Such a group is not
|
|
39
|
+
tracked by `installGroupSignalForwarding`: a signal to the parent does not
|
|
40
|
+
reach it and the parent's exit does not `SIGKILL` it.
|
|
41
|
+
|
|
42
|
+
## [0.3.0] - 2026-10-15
|
|
43
|
+
|
|
44
|
+
The first release on npm. Version numbers before 0.3.0 were used only during
|
|
45
|
+
development and were never published, so this entry lists everything the
|
|
46
|
+
package does.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
#### Runners
|
|
51
|
+
|
|
52
|
+
Each runner factory takes a binary, as a name looked up on `PATH` or a path,
|
|
53
|
+
and returns an object with one `run` method. Code takes the runner as a
|
|
54
|
+
parameter, so a test can pass a fake.
|
|
55
|
+
|
|
56
|
+
- `spawnRunner(binary)`: the sync capture runner (`CommandRunner`). `run`
|
|
57
|
+
returns a `CommandResult` with `exitCode`, `stdout` and `stderr`. When the
|
|
58
|
+
child never reported an exit status, the result also has
|
|
59
|
+
`neverExited: true` and a `reason`, plus `signal` when the runner knows it.
|
|
60
|
+
`error` holds the runner's own message (see Results and output). A death by
|
|
61
|
+
a signal from elsewhere has none. The options (`CommandRunOptions`) are
|
|
62
|
+
`cwd` (required), `timeoutMs`, `input` and `env`.
|
|
63
|
+
- `spawnRunnerAsync(binary)`: the async capture runner
|
|
64
|
+
(`AsyncCommandRunner`). Its options (`AsyncCommandRunOptions`) add
|
|
65
|
+
`signal` (an `AbortSignal`), `onSpawn` (called with the child's pid as soon
|
|
66
|
+
as it starts) and `processGroup`.
|
|
67
|
+
- `streamRunner(binary)`: runs with inherited stdio, so the child's output
|
|
68
|
+
and prompts reach the terminal live (`StreamRunner`, `StreamRunOptions`,
|
|
69
|
+
`StreamResult`). The result says only how the child ended.
|
|
70
|
+
- `managedProcessRunner(binary)`: starts a long-running process and returns a
|
|
71
|
+
handle at once (`ManagedProcessRunner`, `ManagedProcessRunOptions`,
|
|
72
|
+
`ManagedProcessHandle`, `ManagedProcessExit`). The handle has `pid`,
|
|
73
|
+
`kill()` and an `exited` promise. `processGroup: true` makes `kill()`
|
|
74
|
+
signal the whole process group.
|
|
75
|
+
- `NeverExitedReason`: why a child never reported an exit status. `spawn`:
|
|
76
|
+
it never started. `timeout`: `timeoutMs` ran out. `abort`: the caller
|
|
77
|
+
stopped it (an `AbortSignal`, an `onSpawn` that threw, or `kill()` on a
|
|
78
|
+
managed process). `signal`: a signal from elsewhere ended it. `output`:
|
|
79
|
+
the sync runner killed it for passing its 256 MB output cap.
|
|
80
|
+
- `EnvOverrides`: variables merged over `process.env`. An `undefined` value
|
|
81
|
+
removes that variable from the child's environment.
|
|
82
|
+
- `asAsync(runner)`: wraps a sync runner as an async one, so one test fake
|
|
83
|
+
serves both.
|
|
84
|
+
- `resolveRunner(binary, injected?)` and `resolveRunnerAsync`: the injected
|
|
85
|
+
runner when given, else the default runner for `binary`.
|
|
86
|
+
- `withDefaults(runner, opts)`, with `DefaultedRunner`: a runner that fills
|
|
87
|
+
in default options on every call. It works with every runner type. A
|
|
88
|
+
per-call option replaces the default of the same name, and a per-call
|
|
89
|
+
`undefined` keeps the default. The exception is `env`: a per-call `env` is
|
|
90
|
+
merged over the bound `env`, key by key, and a per-call `undefined` value
|
|
91
|
+
removes a bound variable.
|
|
92
|
+
- `stopProcess(handle, { graceMs })`: stops a managed process with
|
|
93
|
+
`SIGTERM`, then `SIGKILL` if it is still running after `graceMs` (default
|
|
94
|
+
5 s). Resolves with the exit. Works with any `ManagedProcessHandle`.
|
|
95
|
+
|
|
96
|
+
#### Shell helpers
|
|
97
|
+
|
|
98
|
+
- `runShell(verb, opts)` and `runShellAsync`: run a shell string once as
|
|
99
|
+
`sh -c -- <verb>` and capture the result. `scriptName` sets `$0` and
|
|
100
|
+
`args` sets `$1` onward. They are passed as separate arguments, so they
|
|
101
|
+
need no quoting (`ShellPositionals`, `ShellRunOptions`,
|
|
102
|
+
`AsyncShellRunOptions`).
|
|
103
|
+
- `shellRunner(sh?)` and `shellRunnerAsync`: shell runners over a given
|
|
104
|
+
runner, for injection (`ShellRunner`, `AsyncShellRunner`). The injected
|
|
105
|
+
runner receives `['-c', '--', verb]`, then `scriptName` and `args` when
|
|
106
|
+
given.
|
|
107
|
+
- `shellQuote(word)` and `shellJoin(argv)`: POSIX quoting for a command line
|
|
108
|
+
printed for a person to paste. `shellJoin` quotes a first word that
|
|
109
|
+
contains `=`, so it runs as a command, not as an assignment.
|
|
110
|
+
|
|
111
|
+
#### JSON helpers
|
|
112
|
+
|
|
113
|
+
- `runJson(runner, argv, opts)` and `runJsonAsync`: run a command and parse
|
|
114
|
+
its stdout as JSON.
|
|
115
|
+
- `parseJsonResult(result, opts?)`: parse the stdout of a finished run. It
|
|
116
|
+
takes the same `okExitCodes`.
|
|
117
|
+
- `JsonRun`: the outcome. It is `{ ok: true, value, result }`, or
|
|
118
|
+
`ok: false` with a `reason`: `failed` (the command did not run or exited
|
|
119
|
+
non-zero), `unparseable` (stdout was not JSON) or `invalid` (the `parse`
|
|
120
|
+
option threw, and `cause` holds what it threw).
|
|
121
|
+
- `RunJsonOptions`: `tolerateLeadingLog` skips log lines and banners before
|
|
122
|
+
the JSON. The JSON starts at the first line that begins with `{` or `[`
|
|
123
|
+
and parses to the end of stdout. `okExitCodes` lists the exit codes that
|
|
124
|
+
count as an answer, such as `[0, 1]` for `npm outdated --json`. Default
|
|
125
|
+
`[0]`. A run that never exited is still `failed`, and the output must
|
|
126
|
+
still parse.
|
|
127
|
+
- `runJson` and `runJsonAsync` take an explicit type with a runner that needs
|
|
128
|
+
no options, such as one with `cwd` bound: `runJson<T>(bound, argv)`
|
|
129
|
+
compiles.
|
|
130
|
+
- The `parse` option takes a function that checks the parsed value and
|
|
131
|
+
returns it typed, such as a schema's `parse`. The value's type is what
|
|
132
|
+
`parse` returns. `parse` must be sync: one that returns a promise gives
|
|
133
|
+
`invalid`.
|
|
134
|
+
|
|
135
|
+
#### Failure helpers
|
|
136
|
+
|
|
137
|
+
- `runChecked(runner, argv, opts)` and `runCheckedAsync`: run a command and
|
|
138
|
+
throw `CommandFailed` when `exitCode` is not 0. That includes a missing
|
|
139
|
+
binary and a timeout. They take any runner whose `run` returns a
|
|
140
|
+
`CommandResult`, such as a `withDefaults` runner.
|
|
141
|
+
- `CommandFailed`: an error with `argv`, the full `result` and `detail`.
|
|
142
|
+
`detail` is both output streams, trimmed and newline-joined, for showing a
|
|
143
|
+
person why the command failed. `message` quotes argv with `shellJoin`, so
|
|
144
|
+
`['a b']` and `['a', 'b']` look different. It carries the full argv, so
|
|
145
|
+
pass secrets through `env` or `input`, not argv.
|
|
146
|
+
- `mapFailed` and `mapFailedAsync`: rethrow a `CommandFailed` as another
|
|
147
|
+
error. `catchFailed` and `catchFailedAsync`: turn a `CommandFailed` into a
|
|
148
|
+
value. Other errors pass through. The async mapper and handler may return
|
|
149
|
+
a promise.
|
|
150
|
+
- `ran(result)`: `true` when the child started and exited 0. It reads the
|
|
151
|
+
result of any runner.
|
|
152
|
+
- `failureText(result, opts?)`, with `FailureTextOptions`: the text to show
|
|
153
|
+
a person for a failed run. By default it is trimmed stderr, else the
|
|
154
|
+
trimmed `error`, else trimmed stdout, else `exit N`. `{ both: true }` joins both streams, as
|
|
155
|
+
`CommandFailed.detail` does.
|
|
156
|
+
- `firstLine(text, fallback?)`: the first non-blank line, trimmed.
|
|
157
|
+
- `stderrOrError(result)`: trimmed stderr, else the runner's trimmed
|
|
158
|
+
`error`, else `''`. `failureText` and `CommandFailed` use it.
|
|
159
|
+
|
|
160
|
+
#### Test fakes
|
|
161
|
+
|
|
162
|
+
A second entry point, `@sksizer/command-seam/testing`, holds the fakes. It
|
|
163
|
+
has the same `bun`, `types` and `default` export conditions as the main
|
|
164
|
+
entry.
|
|
165
|
+
|
|
166
|
+
- `fakeRunner(answers)`: a `CommandRunner` that answers from a table, or from
|
|
167
|
+
a function of the call, and records each call. The table is keyed by each
|
|
168
|
+
argument quoted with `shellQuote`, joined with spaces, so `['-m', 'a b']`
|
|
169
|
+
and `['-m', 'a', 'b']` do not share a key. A key whose arguments hold a
|
|
170
|
+
space or a shell character must be quoted, such as
|
|
171
|
+
`"tag -m 'Release 1.2.0'"`. A call with no answer reports a failed spawn,
|
|
172
|
+
so `ran()` is false and `runChecked` throws. The "no answer for" message
|
|
173
|
+
prints the same key. It also returns `asyncRunner`, the same fake as an
|
|
174
|
+
`AsyncCommandRunner`.
|
|
175
|
+
- `fakeProcess(opts?)`: a `ManagedProcessHandle` that starts nothing. The
|
|
176
|
+
test ends it with `exit()`, and `kill()` ends it as an abort, unless
|
|
177
|
+
`exitOnKill: false`. `exitOnKill` also takes a list of signals, such as
|
|
178
|
+
`['SIGKILL']`, for a process that survives `SIGTERM`. It records the
|
|
179
|
+
signals sent and comes with a runner that returns it.
|
|
180
|
+
- `fakeNeverExited(reason, fields?)`: builds a fake answer for a run that
|
|
181
|
+
never exited, with the fields the runners set together for that reason.
|
|
182
|
+
- The types `FakeRunner`, `FakeCall`, `FakeProcess` and
|
|
183
|
+
`FakeProcessOptions`, so a test helper can name what the fakes return.
|
|
184
|
+
|
|
185
|
+
### Behaviour
|
|
186
|
+
|
|
187
|
+
#### Timeouts
|
|
188
|
+
|
|
189
|
+
- `timeoutMs` follows one rule on every runner that has it: the sync, async,
|
|
190
|
+
stream and shell runners. `undefined` and `Infinity` mean no cap. A
|
|
191
|
+
fraction rounds up, so `1.2` is `2`. A value above 2^31 - 1 ms (about 24.8
|
|
192
|
+
days) is clamped to it. `0`, a negative value and `NaN` are a caller error
|
|
193
|
+
(`RangeError`). A value that is not a number is a `TypeError`.
|
|
194
|
+
- A timeout kills the child with `SIGKILL`, so a child that ignores
|
|
195
|
+
`SIGTERM` still stops. The child gets no chance to clean up, so a timed-out
|
|
196
|
+
git can leave its lock files. For a graceful stop on the async runners,
|
|
197
|
+
pass `signal: AbortSignal.timeout(ms)` instead: an abort sends `SIGTERM`
|
|
198
|
+
first.
|
|
199
|
+
- The result of a timeout has `neverExited: true`, `reason: 'timeout'` and
|
|
200
|
+
the output captured so far. On the async runner, a deadline that fires
|
|
201
|
+
after the child exited, inside the 250 ms exit grace, keeps the child's
|
|
202
|
+
exit code instead.
|
|
203
|
+
- The async runner resolves at the deadline. It does not wait for output
|
|
204
|
+
pipes that a surviving grandchild still holds open. Its timeout message
|
|
205
|
+
is `timed out after <N>ms`, in `error`.
|
|
206
|
+
- The streaming runner writes the spawn error, the timeout message or
|
|
207
|
+
`<binary> was killed by <signal>` to the parent's stderr, since it has no
|
|
208
|
+
captured stderr.
|
|
209
|
+
|
|
210
|
+
#### Process groups
|
|
211
|
+
|
|
212
|
+
- The async runners (`spawnRunnerAsync`, `runShellAsync`,
|
|
213
|
+
`shellRunnerAsync`) take `processGroup: true`. The child then runs as its
|
|
214
|
+
own process-group leader. A timeout or an abort signals the whole group,
|
|
215
|
+
so processes the child started do not outlive it.
|
|
216
|
+
- `managedProcessRunner` takes `processGroup: true` too. `kill()` then
|
|
217
|
+
signals the whole group.
|
|
218
|
+
- The sync runners (`spawnRunner`, `runShell`, `shellRunner`) and
|
|
219
|
+
`streamRunner` kill the direct child only on timeout. Processes it started
|
|
220
|
+
keep running. These runners have no `processGroup` option.
|
|
221
|
+
- A process group does not get the terminal's Ctrl-C. To stop it on Ctrl-C,
|
|
222
|
+
listen for `SIGINT` and abort through `signal`, or call `kill()`.
|
|
223
|
+
- Process groups are POSIX only.
|
|
224
|
+
|
|
225
|
+
#### Abort and exit
|
|
226
|
+
|
|
227
|
+
- An abort sends `SIGTERM`, then `SIGKILL` 1 s later if the child is still running. The `SIGTERM`
|
|
228
|
+
lets a child such as git remove its lock files. The promise resolves at once with
|
|
229
|
+
`neverExited: true`, `reason: 'abort'` and `The operation was aborted` in `error`. An abort that
|
|
230
|
+
lands after the child exited keeps the child's exit code. While a child ignores `SIGTERM`, the
|
|
231
|
+
pending `SIGKILL` keeps the host alive for up to 1 s.
|
|
232
|
+
- The async runner settles when the child's stdio closes, or 250 ms after
|
|
233
|
+
the child exits if a process it started still holds the pipes. In the
|
|
234
|
+
second case `exitCode` is the child's own, and the pipes are closed: that
|
|
235
|
+
process loses any later output. With `processGroup`, the group gets
|
|
236
|
+
`SIGKILL` at that point.
|
|
237
|
+
- If `onSpawn` throws, the child is killed and the run resolves with
|
|
238
|
+
`neverExited: true`, `reason: 'abort'` and `onSpawn threw: <message>`.
|
|
239
|
+
- Without `processGroup`, a managed process's `kill()` does nothing once the
|
|
240
|
+
process has exited, so a reused pid is never signalled. With
|
|
241
|
+
`processGroup`, it still signals any member of the group that is left. A
|
|
242
|
+
death by a signal sent through `kill()` reports `reason: 'abort'`.
|
|
243
|
+
|
|
244
|
+
#### What throws
|
|
245
|
+
|
|
246
|
+
- `run` never throws, and an async `run` never rejects, for a failed
|
|
247
|
+
command. A missing binary, a timeout or an abort is a result with a
|
|
248
|
+
non-zero `exitCode`, `neverExited: true` and a `reason`.
|
|
249
|
+
- Programmer errors throw from a sync runner and reject from an async one:
|
|
250
|
+
an invalid `timeoutMs` and a NUL byte in argv or env.
|
|
251
|
+
- A managed process reports a failed spawn through `exited`, which never
|
|
252
|
+
rejects. A NUL byte in argv or env throws from `run`.
|
|
253
|
+
- `runChecked` and `runCheckedAsync` throw `CommandFailed` for a failed run.
|
|
254
|
+
`runJson`, `parseJsonResult`, `ran` and `failureText` never throw for one.
|
|
255
|
+
- `asAsync` turns a throw from the sync runner into a rejected promise.
|
|
256
|
+
`shellRunnerAsync` does the same for the runner it wraps.
|
|
257
|
+
|
|
258
|
+
#### `bun test`
|
|
259
|
+
|
|
260
|
+
- Under `bun test`, once a large module graph is loaded, Bun can hand child
|
|
261
|
+
processes stdio that the parent reads back as empty. `spawnRunner` works
|
|
262
|
+
around this when it runs under Bun with `NODE_ENV=test`. It runs the
|
|
263
|
+
command through `/bin/sh` with stdin, stdout and stderr redirected to temp
|
|
264
|
+
files, then reads the files back. Arguments are still not parsed by the
|
|
265
|
+
shell.
|
|
266
|
+
- Results on that path match `spawnRunner` outside `bun test`. For example,
|
|
267
|
+
a missing binary is a failed spawn with `exitCode: 1`, `neverExited: true`
|
|
268
|
+
and a spawn message. The path has no output cap.
|
|
269
|
+
- Jest and Vitest on Node do not take that path. The async runner has no
|
|
270
|
+
workaround.
|
|
271
|
+
|
|
272
|
+
#### Results and output
|
|
273
|
+
|
|
274
|
+
- `stderr` is only the child's output. When a capture runner has its own
|
|
275
|
+
message (spawn failure, timeout, abort, `onSpawn` throw, output cap,
|
|
276
|
+
failed stdin write), it is only in `error`, never copied into `stderr`.
|
|
277
|
+
`stderrOrError(result)` reads both: trimmed stderr, else trimmed `error`.
|
|
278
|
+
`failureText` and `CommandFailed` use it, so their text names the failure
|
|
279
|
+
either way.
|
|
280
|
+
- `error` can come with a real exit code and no `reason`: after a failed
|
|
281
|
+
stdin write, or, under Bun, a child that exits right at the output cap.
|
|
282
|
+
- If the child closes stdin before `input` is fully written, the result
|
|
283
|
+
keeps the child's exit code, and the host process does not crash. The
|
|
284
|
+
async runner, and the sync runner on Node, put the write error in `error`.
|
|
285
|
+
The sync runner under Bun does not report it.
|
|
286
|
+
- Output is decoded as UTF-8. A character split across pipe chunks is kept
|
|
287
|
+
whole. Output that ends in the middle of a character shows U+FFFD.
|
|
288
|
+
- `spawnRunner` (outside `bun test`) caps each stream at 256 MB and usually
|
|
289
|
+
kills a child that passes it, with `reason: 'output'`. The async runner
|
|
290
|
+
has no cap.
|
|
291
|
+
|
|
292
|
+
#### Arguments, environment and `cwd`
|
|
293
|
+
|
|
294
|
+
- `argv` is never parsed by a shell. Spaces, quotes and `$()` in an argument
|
|
295
|
+
reach the binary unchanged. Only the shell helpers run a shell string.
|
|
296
|
+
- `env` merges over `process.env`, read at call time on every runner, so a
|
|
297
|
+
variable set after startup reaches the child.
|
|
298
|
+
- `cwd` is required on every call. An empty, missing or non-directory `cwd`
|
|
299
|
+
is a failed spawn whose message names the `cwd`.
|
|
300
|
+
- Optional option fields are typed `?: T | undefined`, so callers can pass
|
|
301
|
+
an explicit `undefined` under `exactOptionalPropertyTypes`. A custom
|
|
302
|
+
runner must accept `undefined` for these fields.
|
|
303
|
+
|
|
304
|
+
#### Requirements and stability
|
|
305
|
+
|
|
306
|
+
- Node.js 22 or later, tested on Node 22 and 24 on Linux and macOS. Windows
|
|
307
|
+
is not supported. Bun also works, and resolves to the TypeScript source
|
|
308
|
+
through the `bun` export condition. ESM only. TypeScript 5.4 or later, with
|
|
309
|
+
`@types/node` as an optional peer dependency.
|
|
310
|
+
- Each export is stable or experimental. Any release may change or remove an
|
|
311
|
+
experimental export. The README's Stability section lists the experimental
|
|
312
|
+
exports. A breaking change to a stable export will be listed under
|
|
313
|
+
"Breaking".
|
|
314
|
+
|
|
315
|
+
[0.3.0]: https://github.com/sksizer/sdlc/tree/command-seam/v0.3.0/packages/command-seam
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in `@osdlc/command-seam`.
|
|
4
|
+
|
|
5
|
+
## Issues
|
|
6
|
+
|
|
7
|
+
Report bugs and ask for features in the
|
|
8
|
+
[issue tracker](https://github.com/osdlc/osdlc/issues). For a bug,
|
|
9
|
+
include the library version, your runtime (Node or Bun) and version, your
|
|
10
|
+
operating system, and a minimal example. Report security problems privately.
|
|
11
|
+
See [SECURITY.md](./SECURITY.md).
|
|
12
|
+
|
|
13
|
+
## Pull requests
|
|
14
|
+
|
|
15
|
+
This package lives in [osdlc/osdlc](https://github.com/osdlc/osdlc) under
|
|
16
|
+
`packages/ts/command-seam`. Open pull requests there. They are reviewed and
|
|
17
|
+
merged there.
|
|
18
|
+
|
|
19
|
+
## Running the tests
|
|
20
|
+
|
|
21
|
+
The repository is a pnpm workspace. Install from the repository root, then
|
|
22
|
+
work in the package directory. The tests run under Bun today, so install
|
|
23
|
+
[Bun](https://bun.sh) as well as Node 22 or later.
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pnpm install
|
|
27
|
+
cd packages/ts/command-seam
|
|
28
|
+
pnpm run build
|
|
29
|
+
pnpm test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Build before testing: the examples import the package by name, and under Node
|
|
33
|
+
that name resolves to `dist/`. After a build, `pnpm run smoke` runs a short
|
|
34
|
+
check of the built package under Node.
|
|
35
|
+
|
|
36
|
+
Other checks:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
pnpm run typecheck
|
|
40
|
+
pnpm run lint
|
|
41
|
+
pnpm run format:check
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`npm run <script>` and `bun run <script>` work the same way once the workspace
|
|
45
|
+
is installed.
|
|
46
|
+
|
|
47
|
+
## Code style
|
|
48
|
+
|
|
49
|
+
- TypeScript, ES modules, and explicit `.ts` import paths.
|
|
50
|
+
- Prettier formats the code. Run `npm run format` before you commit.
|
|
51
|
+
- oxlint checks the code. Run `npm run lint`.
|
|
52
|
+
- No semicolons and single quotes, as Prettier enforces.
|
|
53
|
+
- Add a test for each behavior change or bug fix.
|
|
54
|
+
- For an API addition or change, add an example in `examples/` that shows the
|
|
55
|
+
code a user would write. The change is judged by how that code reads. See
|
|
56
|
+
[D-15](https://github.com/osdlc/osdlc/blob/main/packages/ts/command-seam/docs/planning/decisions/D-15-api-changes-start-from-consumer-code.md).
|
|
57
|
+
- Keep comments short. Say why, not what.
|
|
58
|
+
- For a change users can see, add a changeset: run `pnpm changeset` at the
|
|
59
|
+
repository root. Start a breaking change's summary with `**Breaking:**`.
|
|
60
|
+
Do not edit `CHANGELOG.md` or `version` by hand; the release writes them.
|
|
61
|
+
See [packages/README.md](https://github.com/osdlc/osdlc/blob/main/packages/README.md).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SK Sizer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|