@forwardimpact/libutil 0.1.85 → 0.1.86
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/package.json +1 -1
- package/src/calendar.js +10 -0
- package/src/index.js +1 -0
- package/src/runtime.js +69 -12
package/package.json
CHANGED
package/src/calendar.js
CHANGED
|
@@ -28,6 +28,16 @@ export function isoDate(input) {
|
|
|
28
28
|
return toDate(input).toISOString().slice(0, 10);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Format an input as a full ISO 8601 timestamp (`YYYY-MM-DDTHH:mm:ss.sssZ`,
|
|
33
|
+
* UTC). Pass `runtime.clock.now()` for "now"; never reads the wall clock.
|
|
34
|
+
* @param {Date|number|string} input
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
export function isoTimestamp(input) {
|
|
38
|
+
return toDate(input).toISOString();
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
/**
|
|
32
42
|
* Compute the ISO 8601 year-week for an input. `year` is the ISO week-year
|
|
33
43
|
* (not necessarily the calendar year for edge weeks).
|
package/src/index.js
CHANGED
package/src/runtime.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { spawn as nodeSpawn, execFile, spawnSync } from "node:child_process";
|
|
2
|
-
import nodeFsSync
|
|
2
|
+
import nodeFsSync, {
|
|
3
|
+
createReadStream as nodeCreateReadStream,
|
|
4
|
+
createWriteStream as nodeCreateWriteStream,
|
|
5
|
+
} from "node:fs";
|
|
3
6
|
import nodeFs from "node:fs/promises";
|
|
4
7
|
import { Finder } from "./finder.js";
|
|
5
8
|
|
|
@@ -16,17 +19,25 @@ import { Finder } from "./finder.js";
|
|
|
16
19
|
* @property {Object} fs
|
|
17
20
|
* Async filesystem surface (the `node:fs/promises` shape): `readFile`,
|
|
18
21
|
* `writeFile`, `readdir`, `stat`, `mkdir`, `access`, `copyFile`, `cp`, `rm`,
|
|
19
|
-
* `lstat`, `unlink`, `symlink`, `utimes`, `chmod
|
|
20
|
-
* `
|
|
22
|
+
* `lstat`, `unlink`, `symlink`, `utimes`, `chmod`, plus the two stream
|
|
23
|
+
* factories `createReadStream` / `createWriteStream` (the `node:fs` shape —
|
|
24
|
+
* the promises API has no stream factories, so they live on the async
|
|
25
|
+
* surface as the canonical streaming seam). A module destructures `fs` xor
|
|
26
|
+
* `fsSync`, never both (design Decision 7).
|
|
21
27
|
* @property {Object} fsSync
|
|
22
28
|
* Sync filesystem surface (the `node:fs` shape): `existsSync`,
|
|
23
29
|
* `readFileSync`, `writeFileSync`, `mkdirSync`, `readdirSync`, `statSync`,
|
|
24
|
-
* `openSync`, `closeSync`, `unlinkSync`.
|
|
30
|
+
* `openSync`, `readSync`, `closeSync`, `unlinkSync`.
|
|
25
31
|
* @property {Object} proc
|
|
26
32
|
* Process surface: `cwd()`, `env`, `argv`, `stdin`, `stdout.write`,
|
|
27
33
|
* `stderr.write`, `exit(code)`, `kill(pid, signal)` (a negative `pid`
|
|
28
|
-
* signals the process group, e.g. for daemon teardown),
|
|
29
|
-
*
|
|
34
|
+
* signals the process group, e.g. for daemon teardown), `pid` (this
|
|
35
|
+
* process's id — used to exclude self from process-group descendant scans),
|
|
36
|
+
* `platform` (the `process.platform` string — `"darwin"`/`"win32"`/`"linux"`
|
|
37
|
+
* — for per-platform path resolution), `on(event, handler)` (subscribe to
|
|
38
|
+
* process events such as `"SIGTERM"`/`"SIGINT"` so daemons register signal
|
|
39
|
+
* handlers through the collaborator instead of the global), and an
|
|
40
|
+
* `exitCode` accessor.
|
|
30
41
|
* @property {Object} clock
|
|
31
42
|
* Time surface: `now()`, `sleep(ms)`, `setTimeout(fn, ms)`,
|
|
32
43
|
* `clearTimeout(handle)`.
|
|
@@ -35,8 +46,12 @@ import { Finder } from "./finder.js";
|
|
|
35
46
|
* exitCode}>` (async, buffered), `runSync(cmd, args, opts) -> {stdout,
|
|
36
47
|
* stderr, exitCode}` (synchronous, buffered — for the rare caller that
|
|
37
48
|
* cannot go async, e.g. a sync config accessor shelling to `gh auth
|
|
38
|
-
* token`), and `spawn(cmd, args, opts) -> {stdout, stderr, exitCode,
|
|
39
|
-
* where `stdout`/`stderr` are AsyncIterables
|
|
49
|
+
* token`), and `spawn(cmd, args, opts) -> {stdout, stderr, stdin, exitCode,
|
|
50
|
+
* signal, kill, pid}` where `stdout`/`stderr` are AsyncIterables,
|
|
51
|
+
* `exitCode`/`signal` are Promises (the terminating signal name or `null`),
|
|
52
|
+
* `stdin` is the child's writable (only when `opts.stdio` pipes stdin, else
|
|
53
|
+
* `null`), `kill(signal)` signals the child, and `pid` is its id (`undefined`
|
|
54
|
+
* on spawn failure).
|
|
40
55
|
* @property {Object} finder
|
|
41
56
|
* A constructed `Finder` (project path resolution + symlink management).
|
|
42
57
|
*/
|
|
@@ -81,6 +96,9 @@ export function createDefaultProc({ source = process, env = source.env } = {}) {
|
|
|
81
96
|
stderr: { write: (s) => source.stderr.write(s) },
|
|
82
97
|
exit: (code) => source.exit(code),
|
|
83
98
|
kill: (pid, signal) => source.kill(pid, signal),
|
|
99
|
+
pid: source.pid,
|
|
100
|
+
platform: source.platform,
|
|
101
|
+
on: (event, handler) => source.on(event, handler),
|
|
84
102
|
};
|
|
85
103
|
Object.defineProperty(proc, "exitCode", {
|
|
86
104
|
enumerable: true,
|
|
@@ -172,13 +190,45 @@ export function createDefaultSubprocess() {
|
|
|
172
190
|
|
|
173
191
|
const spawn = (cmd, args = [], opts = {}) => {
|
|
174
192
|
const child = nodeSpawn(cmd, args, opts);
|
|
193
|
+
let resolveSignal;
|
|
194
|
+
const signal = new Promise((r) => {
|
|
195
|
+
resolveSignal = r;
|
|
196
|
+
});
|
|
197
|
+
let resolveExit;
|
|
198
|
+
const exitCode = new Promise((r) => {
|
|
199
|
+
resolveExit = r;
|
|
200
|
+
});
|
|
201
|
+
child.on("close", (code, sig) => {
|
|
202
|
+
resolveSignal(sig ?? null);
|
|
203
|
+
resolveExit(code ?? 0);
|
|
204
|
+
});
|
|
205
|
+
// A spawn failure (e.g. ENOENT for a missing binary) emits an `error`
|
|
206
|
+
// event. With no listener Node rethrows it as an uncaughtException and
|
|
207
|
+
// crashes the whole process — even for callers that synchronously guard
|
|
208
|
+
// `pid === undefined`, because the event fires on a later tick. Mirror the
|
|
209
|
+
// run()/runSync() contract instead: resolve a 127 exit code and a null
|
|
210
|
+
// signal so the surface never rejects and never crashes. `resolveExit` is
|
|
211
|
+
// idempotent, so a `close` arriving after `error` is a no-op.
|
|
212
|
+
child.on("error", () => {
|
|
213
|
+
resolveSignal(null);
|
|
214
|
+
resolveExit(127);
|
|
215
|
+
});
|
|
175
216
|
return {
|
|
176
217
|
stdout: child.stdout ?? emptyAsyncIterable(),
|
|
177
218
|
stderr: child.stderr ?? emptyAsyncIterable(),
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
219
|
+
// The child's writable stdin — present only when `opts.stdio` makes
|
|
220
|
+
// stdin a pipe (e.g. `["pipe", ...]`); `null` otherwise. A supervising
|
|
221
|
+
// caller writes its piped output into it.
|
|
222
|
+
stdin: child.stdin ?? null,
|
|
223
|
+
exitCode,
|
|
224
|
+
// Resolves with the terminating signal name (or `null` on a clean exit),
|
|
225
|
+
// alongside `exitCode`. Supervisors that distinguish a SIGTERM teardown
|
|
226
|
+
// from a crash read it; clean-exit callers can ignore it.
|
|
227
|
+
signal,
|
|
181
228
|
kill: (signal) => child.kill(signal),
|
|
229
|
+
// The child's pid — `undefined` if the spawn failed. Detached callers
|
|
230
|
+
// read it to derive the process-group id for group teardown.
|
|
231
|
+
pid: child.pid,
|
|
182
232
|
};
|
|
183
233
|
};
|
|
184
234
|
|
|
@@ -215,7 +265,14 @@ function emptyAsyncIterable() {
|
|
|
215
265
|
* @returns {Readonly<Runtime>}
|
|
216
266
|
*/
|
|
217
267
|
export function createDefaultRuntime({ env = process.env } = {}) {
|
|
218
|
-
|
|
268
|
+
// The async fs surface is `node:fs/promises` augmented with the two stream
|
|
269
|
+
// factories (which only exist on `node:fs`), so streaming consumers never
|
|
270
|
+
// import `node:fs` directly.
|
|
271
|
+
const fs = {
|
|
272
|
+
...nodeFs,
|
|
273
|
+
createReadStream: nodeCreateReadStream,
|
|
274
|
+
createWriteStream: nodeCreateWriteStream,
|
|
275
|
+
};
|
|
219
276
|
const fsSync = nodeFsSync;
|
|
220
277
|
const proc = createDefaultProc({ source: process, env });
|
|
221
278
|
const clock = createDefaultClock();
|