@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +315 -0
  2. package/CONTRIBUTING.md +61 -0
  3. package/LICENSE +21 -0
  4. package/README.md +741 -2
  5. package/SECURITY.md +23 -0
  6. package/async.ts +466 -0
  7. package/defaults.ts +83 -0
  8. package/dist/async.d.ts +146 -0
  9. package/dist/async.d.ts.map +1 -0
  10. package/dist/async.js +368 -0
  11. package/dist/defaults.d.ts +52 -0
  12. package/dist/defaults.d.ts.map +1 -0
  13. package/dist/defaults.js +51 -0
  14. package/dist/groups.d.ts +155 -0
  15. package/dist/groups.d.ts.map +1 -0
  16. package/dist/groups.js +331 -0
  17. package/dist/index.d.ts +30 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +29 -0
  20. package/dist/process.d.ts +146 -0
  21. package/dist/process.d.ts.map +1 -0
  22. package/dist/process.js +175 -0
  23. package/dist/process.test.d.ts +1 -0
  24. package/dist/process.test.d.ts.map +1 -0
  25. package/dist/process.test.js +1 -0
  26. package/dist/quote.d.ts +18 -0
  27. package/dist/quote.d.ts.map +1 -0
  28. package/dist/quote.js +28 -0
  29. package/dist/result.d.ts +211 -0
  30. package/dist/result.d.ts.map +1 -0
  31. package/dist/result.js +219 -0
  32. package/dist/shell.d.ts +84 -0
  33. package/dist/shell.d.ts.map +1 -0
  34. package/dist/shell.js +83 -0
  35. package/dist/stream.d.ts +90 -0
  36. package/dist/stream.d.ts.map +1 -0
  37. package/dist/stream.js +73 -0
  38. package/dist/sync.d.ts +311 -0
  39. package/dist/sync.d.ts.map +1 -0
  40. package/dist/sync.js +406 -0
  41. package/dist/testing.d.ts +158 -0
  42. package/dist/testing.d.ts.map +1 -0
  43. package/dist/testing.js +173 -0
  44. package/groups.ts +360 -0
  45. package/index.ts +98 -0
  46. package/package.json +94 -3
  47. package/process.ts +270 -0
  48. package/quote.ts +31 -0
  49. package/result.ts +403 -0
  50. package/shell.ts +131 -0
  51. package/smoke.mjs +61 -0
  52. package/stream.ts +134 -0
  53. package/sync.ts +604 -0
  54. 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
@@ -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.