@orkestrel/console 0.0.11 → 0.0.12
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/README.md +17 -15
- package/dist/src/browser/index.d.ts +57 -55
- package/dist/src/browser/index.js +52 -62
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +922 -1114
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +665 -685
- package/dist/src/core/index.d.ts +665 -685
- package/dist/src/core/index.js +919 -1105
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +90 -131
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +147 -159
- package/dist/src/server/index.d.ts +147 -159
- package/dist/src/server/index.js +90 -129
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
|
@@ -5,14 +5,14 @@ import { LogLevel } from '@orkestrel/console';
|
|
|
5
5
|
import { SinkInterface } from '@orkestrel/console';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Represents one intercepted process-stream write — the immutable, serializable record a
|
|
9
9
|
* {@link ProcessCaptureInterface} buffers and emits, the server analogue of the core
|
|
10
10
|
* `CapturedMessage`.
|
|
11
11
|
*
|
|
12
12
|
* @remarks
|
|
13
13
|
* - `level` — the {@link StreamLevel} naming which stream (`stdout` / `stderr`) was written.
|
|
14
|
-
* - `text` — the chunk decoded to a string (
|
|
15
|
-
* total, never throws),
|
|
14
|
+
* - `text` — the chunk decoded to a string (through {@link import('./helpers.js').decodeChunk} —
|
|
15
|
+
* total, never throws), verbatim: no trailing-newline trimming and no ANSI stripping, so the
|
|
16
16
|
* captured text is exactly the bytes the program emitted.
|
|
17
17
|
* - `time` — the capture instant as epoch milliseconds (`Date.now()`); a plain number so the record
|
|
18
18
|
* stays serializable and orderable.
|
|
@@ -25,52 +25,7 @@ export declare interface CapturedChunk {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a {@link import('./types.js').ServerSinkInterface}
|
|
30
|
-
* reports through `columns` so a `Reporter` / `Progress` can size its layout to the terminal.
|
|
31
|
-
*
|
|
32
|
-
* @remarks
|
|
33
|
-
* Reads `target.columns` ON EACH CALL (so a getter-backed real stream reflects a live resize) and
|
|
34
|
-
* accepts it only when it is a positive finite number; a missing / `0` / non-finite `columns` (a
|
|
35
|
-
* piped, non-TTY stream) falls back to {@link DEFAULT_COLUMNS}. Total — never throws.
|
|
36
|
-
*
|
|
37
|
-
* @param target - The stream whose width to probe
|
|
38
|
-
* @returns The terminal column count, or {@link DEFAULT_COLUMNS} when not a TTY
|
|
39
|
-
*/
|
|
40
|
-
export declare function columnsOf(target: StreamTargetInterface): number;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Create an observable {@link ProcessCaptureInterface} — the server "own ALL output" capture. It
|
|
44
|
-
* intercepts the RAW `process.stdout.write` / `process.stderr.write` (not just `console.*`, which is
|
|
45
|
-
* the core `Capture`), so it catches direct `process` writes, library output, and child-process
|
|
46
|
-
* pipes. Each intercepted write becomes a frozen {@link import('./types.js').CapturedChunk},
|
|
47
|
-
* buffered (bounded, per-stream) and emitted on `capture`; per options it is mirrored back to the
|
|
48
|
-
* real stream and/or forwarded to a {@link import('@src/core').SinkInterface}.
|
|
49
|
-
*
|
|
50
|
-
* @param options - See {@link ProcessCaptureOptions}
|
|
51
|
-
* @returns A {@link ProcessCaptureInterface}
|
|
52
|
-
*
|
|
53
|
-
* @remarks
|
|
54
|
-
* - **The wrapper never throws and passes backpressure through** — a throw in `process.stdout.write`
|
|
55
|
-
* would crash the host, so chunks are decoded totally and the original's `boolean` is returned.
|
|
56
|
-
* - **Snapshot-at-start + non-reentrant + process-global** — `start()` snapshots and swaps the
|
|
57
|
-
* pristine `write`; `stop()` restores the EXACT original. At most ONE may be active at a time.
|
|
58
|
-
* Create any server sink BEFORE installing a capture so the mirror's replay is not re-captured.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```ts
|
|
62
|
-
* import { createProcessCapture } from '@src/server'
|
|
63
|
-
*
|
|
64
|
-
* const capture = createProcessCapture({ levels: ['stderr'], mirror: true })
|
|
65
|
-
* capture.start()
|
|
66
|
-
* process.stderr.write('a library diagnostic\n') // captured AND still shown
|
|
67
|
-
* capture.stop()
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
export declare function createProcessCapture(options?: ProcessCaptureOptions): ProcessCaptureInterface;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Create the server TTY {@link ServerSinkInterface} — the C-g server output backend, the
|
|
28
|
+
* Creates the server TTY {@link ServerSinkInterface} — the server output backend, the
|
|
74
29
|
* env-symmetric sibling of `createBrowserSink` / core's `createConsoleSink`. `write(text, level?)`
|
|
75
30
|
* routes by level to the process streams and uses construction-time styled facts: it sends ANSI
|
|
76
31
|
* straight to a styled target (with a leading `\r` overwriting a terminal line natively) but
|
|
@@ -82,39 +37,41 @@ export declare function createProcessCapture(options?: ProcessCaptureOptions): P
|
|
|
82
37
|
*
|
|
83
38
|
* @remarks
|
|
84
39
|
* - **Routes by level.** `error` / `warn` → the error stream (`process.stderr` by default), every
|
|
85
|
-
* other level (and an omitted level) → the
|
|
86
|
-
* core's `createConsoleSink`, so a logger's `error` reaches `stderr`.
|
|
40
|
+
* other level (and an omitted level) → the `stdout` stream (`process.stdout`) — the same routing
|
|
41
|
+
* as core's `createConsoleSink`, so a logger's `error` reaches `stderr`. Both call the one
|
|
42
|
+
* {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.
|
|
87
43
|
* - **Per-target styled facts.** At construction, each target uses `options.styled` when supplied;
|
|
88
44
|
* otherwise {@link inferStyled} applies the injected `environment` (default `process.env`) and
|
|
89
45
|
* then that target's `isTTY`.
|
|
90
|
-
* Writes use those stored facts, so `styled` and the
|
|
91
|
-
* the
|
|
92
|
-
* - **Width.** `columns` reflects the live `
|
|
93
|
-
* back to {@link import('./constants.js').DEFAULT_COLUMNS} when the
|
|
94
|
-
* fixed value when `options.columns` is supplied. Feed it to a `Reporter` /
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
46
|
+
* Writes use those stored facts, so `styled` and the `stdout` target's strip decision never
|
|
47
|
+
* disagree; the `stderr` target keeps its own fact internally.
|
|
48
|
+
* - **Width.** `columns` reflects the live `stdout.columns` (so it tracks a terminal resize),
|
|
49
|
+
* falling back to {@link import('./constants.js').DEFAULT_COLUMNS} when the `stdout` stream is not
|
|
50
|
+
* a TTY — or a fixed value when `options.columns` is supplied. Feed it to a `Reporter` /
|
|
51
|
+
* `Progress` `width`.
|
|
52
|
+
* - **Injectable + guard-narrowed.** `options.stdout` / `options.stderr` default to `process.stdout`
|
|
53
|
+
* / `process.stderr` but accept any {@link import('./types.js').StreamTargetInterface}, resolved
|
|
54
|
+
* through {@link isStreamTarget} (narrow the boundary, never `as`), so a test drives
|
|
98
55
|
* the sink (and the isTTY-strip path) with a fake stream that never touches the real process
|
|
99
56
|
* streams.
|
|
100
57
|
*
|
|
101
58
|
* @example
|
|
102
59
|
* ```ts
|
|
103
|
-
* import {
|
|
104
|
-
* import { createServerSink } from '@
|
|
60
|
+
* import { createStyler, Logger, Reporter } from '@orkestrel/console'
|
|
61
|
+
* import { createServerSink } from '@orkestrel/console/server'
|
|
105
62
|
*
|
|
106
63
|
* const sink = createServerSink()
|
|
107
64
|
* const styler = createStyler({ enabled: sink.styled })
|
|
108
|
-
* const logger =
|
|
65
|
+
* const logger = new Logger({ name: 'app', sink, styler })
|
|
109
66
|
* logger.error('boom') // → process.stderr, ANSI rendered on a TTY / stripped to a pipe
|
|
110
|
-
* const reporter =
|
|
67
|
+
* const reporter = new Reporter({ sink, width: sink.columns })
|
|
111
68
|
* ```
|
|
112
69
|
*/
|
|
113
70
|
export declare function createServerSink(options?: ServerSinkOptions): ServerSinkInterface;
|
|
114
71
|
|
|
115
72
|
/**
|
|
116
|
-
*
|
|
117
|
-
* throws
|
|
73
|
+
* Decodes one `process.stdout.write` / `process.stderr.write` chunk to a string — total, never
|
|
74
|
+
* throws. The process write signature accepts `string | Uint8Array` plus an optional
|
|
118
75
|
* encoding; the capture wrapper reuses this so intercepting a raw stream write can never crash the
|
|
119
76
|
* host (a throw inside `process.stdout.write` would take the program down).
|
|
120
77
|
*
|
|
@@ -123,12 +80,12 @@ export declare function createServerSink(options?: ServerSinkOptions): ServerSin
|
|
|
123
80
|
* and `process.stdout.write('text')` all pass a string).
|
|
124
81
|
* - A `Buffer` chunk is decoded with the supplied `encoding` when it is a recognized
|
|
125
82
|
* {@link BufferEncoding} (`process` write supports `'utf8'` / `'hex'` / `'base64'` / …), defaulting
|
|
126
|
-
* to `'utf8'`; a bare `Uint8Array` is decoded
|
|
127
|
-
* argument applies
|
|
83
|
+
* to `'utf8'`; a bare `Uint8Array` is decoded through `TextDecoder` (always utf-8 — the `encoding`
|
|
84
|
+
* argument applies only to a `Buffer`, never a plain `Uint8Array`).
|
|
128
85
|
* - Anything else is coerced with `String(chunk)` (a number / object / bigint / symbol a misbehaving
|
|
129
86
|
* writer hands the stream). The coercion is itself guarded: a value whose `toString` /
|
|
130
87
|
* `Symbol.toPrimitive` throws yields the stable `'[unprintable]'` placeholder. So the helper is
|
|
131
|
-
*
|
|
88
|
+
* total on every input — it always yields some string, never an exception (a throw here would
|
|
132
89
|
* escape into `process.*.write` and crash the host).
|
|
133
90
|
*
|
|
134
91
|
* @param chunk - The chunk passed to the stream's `write`
|
|
@@ -145,30 +102,38 @@ export declare function createServerSink(options?: ServerSinkOptions): ServerSin
|
|
|
145
102
|
export declare function decodeChunk(chunk: unknown, encoding?: unknown): string;
|
|
146
103
|
|
|
147
104
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* `options.
|
|
105
|
+
* Sets the terminal width {@link import('./factories.js').createServerSink} reports through
|
|
106
|
+
* {@link import('./types.js').ServerSinkInterface.columns} when the `stdout` stream is not a TTY (so
|
|
107
|
+
* `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
|
|
108
|
+
* 80-column default a non-interactive context (a pipe, a CI log) assumes.
|
|
151
109
|
*/
|
|
152
|
-
export declare const
|
|
110
|
+
export declare const DEFAULT_COLUMNS = 80;
|
|
153
111
|
|
|
154
112
|
/**
|
|
155
|
-
*
|
|
113
|
+
* Sets the default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} — at
|
|
156
114
|
* most this many recent {@link import('./types.js').CapturedChunk}s are retained per buffer (the
|
|
157
|
-
* total buffer
|
|
158
|
-
* `DEFAULT_CAPTURE_LIMIT`; a consumer overrides it
|
|
115
|
+
* total buffer and each per-stream bucket; oldest dropped first). Mirrors the core `Capture`'s
|
|
116
|
+
* `DEFAULT_CAPTURE_LIMIT`; a consumer overrides it through `options.limit`.
|
|
159
117
|
*/
|
|
160
|
-
export declare const
|
|
118
|
+
export declare const DEFAULT_STREAM_LIMIT = 1000;
|
|
161
119
|
|
|
162
120
|
/**
|
|
163
|
-
*
|
|
164
|
-
* {@link import('./types.js').ServerSinkInterface
|
|
165
|
-
*
|
|
166
|
-
*
|
|
121
|
+
* Infers the width in character cells of a stream target — its live `columns` when it is a TTY, else
|
|
122
|
+
* the non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a {@link import('./types.js').ServerSinkInterface}
|
|
123
|
+
* reports through `columns` so a `Reporter` / `Progress` can size its layout to the terminal.
|
|
124
|
+
*
|
|
125
|
+
* @remarks
|
|
126
|
+
* Reads `target.columns` on each call (so a getter-backed real stream reflects a live resize) and
|
|
127
|
+
* accepts it only when it is a positive finite number; a missing / `0` / non-finite `columns` (a
|
|
128
|
+
* piped, non-TTY stream) falls back to {@link DEFAULT_COLUMNS}. Total — never throws.
|
|
129
|
+
*
|
|
130
|
+
* @param target - The stream whose width to probe
|
|
131
|
+
* @returns The terminal column count, or {@link DEFAULT_COLUMNS} when not a TTY
|
|
167
132
|
*/
|
|
168
|
-
export declare
|
|
133
|
+
export declare function inferColumns(target: StreamTargetInterface): number;
|
|
169
134
|
|
|
170
135
|
/**
|
|
171
|
-
*
|
|
136
|
+
* Infers whether one stream target receives styled output. The result is a construction-time
|
|
172
137
|
* target fact for {@link import('./factories.js').createServerSink}; this helper is pure and never
|
|
173
138
|
* reads process globals itself.
|
|
174
139
|
*
|
|
@@ -179,7 +144,7 @@ export declare const DEFAULT_COLUMNS = 80;
|
|
|
179
144
|
*
|
|
180
145
|
* @param target - The stream target whose terminal capability is the fallback
|
|
181
146
|
* @param environment - The environment record supplying `FORCE_COLOR` and `NO_COLOR`
|
|
182
|
-
* @returns
|
|
147
|
+
* @returns True if output for the target retains styling and control sequences; false otherwise
|
|
183
148
|
*
|
|
184
149
|
* @example
|
|
185
150
|
* ```ts
|
|
@@ -190,28 +155,28 @@ export declare const DEFAULT_COLUMNS = 80;
|
|
|
190
155
|
export declare function inferStyled(target: StreamTargetInterface, environment: Readonly<Record<string, string | undefined>>): boolean;
|
|
191
156
|
|
|
192
157
|
/**
|
|
193
|
-
*
|
|
194
|
-
* guard used by {@link decodeChunk} to honor a process-write `encoding`
|
|
195
|
-
* real Node encoding (otherwise utf-8 is assumed).
|
|
158
|
+
* Checks whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a total
|
|
159
|
+
* guard used by {@link import('./helpers.js').decodeChunk} to honor a process-write `encoding`
|
|
160
|
+
* argument only when it is a real Node encoding (otherwise utf-8 is assumed).
|
|
196
161
|
*
|
|
197
162
|
* @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
|
|
198
|
-
* @returns
|
|
163
|
+
* @returns True if `encoding` names a supported buffer encoding; false otherwise
|
|
199
164
|
*/
|
|
200
165
|
export declare function isBufferEncoding(encoding: unknown): encoding is BufferEncoding;
|
|
201
166
|
|
|
202
167
|
/**
|
|
203
|
-
*
|
|
204
|
-
* total type guard
|
|
168
|
+
* Checks whether `value` is a usable {@link StreamTargetInterface} — a record with a callable `write`. A
|
|
169
|
+
* total type guard: it never throws and returns `false` for anything off-shape, so it
|
|
205
170
|
* narrows the one unavoidable boundary (the real `process.stdout` / `process.stderr`, or a fake
|
|
206
171
|
* stream a test injects) to the exact slice the sink + capture touch — no `as`.
|
|
207
172
|
*
|
|
208
173
|
* @remarks
|
|
209
174
|
* Only `write` is required (the irreducible output method); `isTTY` and `columns` are optional on
|
|
210
175
|
* {@link StreamTargetInterface}, so their absence does not disqualify a target — a piped stream
|
|
211
|
-
* (no `isTTY`) is still a valid write target,
|
|
176
|
+
* (no `isTTY`) is still a valid write target, only a non-terminal one.
|
|
212
177
|
*
|
|
213
178
|
* @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
|
|
214
|
-
* @returns
|
|
179
|
+
* @returns True if `value` has a callable `write`; false otherwise
|
|
215
180
|
*
|
|
216
181
|
* @example
|
|
217
182
|
* ```ts
|
|
@@ -223,28 +188,28 @@ export declare function isBufferEncoding(encoding: unknown): encoding is BufferE
|
|
|
223
188
|
export declare function isStreamTarget(value: unknown): value is StreamTargetInterface;
|
|
224
189
|
|
|
225
190
|
/**
|
|
226
|
-
*
|
|
227
|
-
* `process.stdout.write` / `process.stderr.write` on the
|
|
191
|
+
* Implements an observable interceptor of the raw process output streams — it takes control of
|
|
192
|
+
* `process.stdout.write` / `process.stderr.write` on the write side. While `active`, every write to
|
|
228
193
|
* a configured {@link StreamLevel} is captured as a frozen {@link CapturedChunk}, buffered (total +
|
|
229
|
-
* per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream
|
|
230
|
-
* forwarded to a {@link SinkInterface}.
|
|
194
|
+
* per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream,
|
|
195
|
+
* forwarded to a {@link SinkInterface}, or both.
|
|
231
196
|
*
|
|
232
197
|
* @remarks
|
|
233
198
|
* Where the core `Capture` patches `console.*` (the high-level read side), this patches the
|
|
234
|
-
* low-level stream `write`, so it owns
|
|
199
|
+
* low-level stream `write`, so it owns all server output: a direct `process.stdout.write`, a
|
|
235
200
|
* third-party library's writes, a child-process pipe — not only `console.*`.
|
|
236
201
|
*
|
|
237
|
-
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the
|
|
202
|
+
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the current
|
|
238
203
|
* `process[stream].write` for each configured level, then installs the wrappers. The mirror
|
|
239
204
|
* replays through that snapshot (bound to its stream) — so a server sink created from the same
|
|
240
|
-
* streams
|
|
205
|
+
* streams before the capture is never re-captured: this catches other writers, not the mirror's
|
|
241
206
|
* own replay. Create your sinks before installing a capture.
|
|
242
|
-
* - **Idempotent +
|
|
207
|
+
* - **Idempotent + process-global + non-reentrant.** `start()` while `active` is a no-op (never
|
|
243
208
|
* double-patches — that would snapshot the wrapper as the "original" and break restore); `stop()`
|
|
244
|
-
* while inactive is a no-op. It patches the
|
|
209
|
+
* while inactive is a no-op. It patches the one global `process`, so at most one process capture
|
|
245
210
|
* may be active at a time — two concurrently would interleave buffers and clobber each other's
|
|
246
211
|
* restore.
|
|
247
|
-
* - **The wrapper
|
|
212
|
+
* - **The wrapper never throws and passes backpressure through.** A throw inside
|
|
248
213
|
* `process.stdout.write` would crash the host, so the wrapper decodes each chunk totally (a byte
|
|
249
214
|
* chunk through the per-level streaming decoder below, everything else through the total
|
|
250
215
|
* {@link decodeChunk}), and returns the snapshot-original's `boolean` when mirroring (so a caller's
|
|
@@ -255,18 +220,18 @@ export declare function isStreamTarget(value: unknown): value is StreamTargetInt
|
|
|
255
220
|
* child-process pipe, a library, or OS buffering all produce this — carries its partial bytes to
|
|
256
221
|
* the next write instead of decoding each half to `U+FFFD`. `stop()` flushes each decoder once, so
|
|
257
222
|
* a codepoint left half-written at stop is still surfaced. A `string` chunk is already text and
|
|
258
|
-
* passes through; an explicit
|
|
223
|
+
* passes through; an explicit non-utf-8 buffer encoding (`latin1` / `hex` / `base64` / …) names a
|
|
259
224
|
* self-contained per-write decode and is honored one-shot through {@link decodeChunk}.
|
|
260
225
|
* - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
|
|
261
226
|
* (oldest dropped first), never unbounded — the same retention precedent as the core `Capture`.
|
|
262
|
-
* - **Lifecycle
|
|
263
|
-
* `destroy()` stops (restoring the
|
|
227
|
+
* - **Lifecycle.** `start` / `stop` toggle interception (emitting `start` / `stop`);
|
|
228
|
+
* `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
|
|
264
229
|
*
|
|
265
230
|
* @example
|
|
266
231
|
* ```ts
|
|
267
232
|
* const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
|
|
268
233
|
* capture.start()
|
|
269
|
-
* process.stderr.write('a library diagnostic\n') // captured
|
|
234
|
+
* process.stderr.write('a library diagnostic\n') // captured and still written to the terminal
|
|
270
235
|
* capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
|
|
271
236
|
* capture.stop() // process.stderr.write restored
|
|
272
237
|
* ```
|
|
@@ -285,7 +250,7 @@ export declare class ProcessCapture implements ProcessCaptureInterface {
|
|
|
285
250
|
}
|
|
286
251
|
|
|
287
252
|
/**
|
|
288
|
-
*
|
|
253
|
+
* Declares the observable events a {@link ProcessCaptureInterface} emits — mirrors the core
|
|
289
254
|
* `Capture`'s `CaptureEventMap`, but the captured record is a {@link CapturedChunk} (stream-keyed).
|
|
290
255
|
*
|
|
291
256
|
* @remarks
|
|
@@ -293,84 +258,85 @@ export declare class ProcessCapture implements ProcessCaptureInterface {
|
|
|
293
258
|
* {@link CapturedChunk}. The hook a live log viewer / tee subscribes to.
|
|
294
259
|
* - `start` / `stop` — the interception toggled on / off (pure signals, empty tuples).
|
|
295
260
|
*
|
|
296
|
-
* Listener isolation is the emitter's
|
|
261
|
+
* Listener isolation is the emitter's: a listener throw routes to the emitter's `error`
|
|
297
262
|
* handler, never onto this map — so a buggy `capture` listener can never escape into the host's
|
|
298
263
|
* `process.stdout.write` call (which would crash the program).
|
|
299
264
|
*
|
|
300
|
-
* Declared as a `type` alias (not `interface extends EventMap
|
|
265
|
+
* Declared as a `type` alias (not `interface extends EventMap`): a type-literal satisfies the
|
|
301
266
|
* `EventMap` constraint structurally, whereas an interface lacks the index signature.
|
|
302
267
|
*/
|
|
303
268
|
export declare type ProcessCaptureEventMap = {
|
|
304
|
-
/**
|
|
269
|
+
/** Fires on an intercepted process-stream write — the frozen {@link CapturedChunk}. */
|
|
305
270
|
readonly capture: readonly [chunk: CapturedChunk];
|
|
306
|
-
/**
|
|
271
|
+
/** Fires after interception began (`process.*.write` patched). */
|
|
307
272
|
readonly start: readonly [];
|
|
308
|
-
/**
|
|
273
|
+
/** Fires after interception ended (`process.*.write` restored). */
|
|
309
274
|
readonly stop: readonly [];
|
|
310
275
|
};
|
|
311
276
|
|
|
312
277
|
/**
|
|
313
|
-
*
|
|
314
|
-
* "own
|
|
278
|
+
* Declares an observable interceptor of the raw process output streams — the server's
|
|
279
|
+
* "own all output" capture. Where the core `Capture` patches `console.*` (the high-level read
|
|
315
280
|
* side), this patches `process.stdout.write` / `process.stderr.write` (the low-level stream), so it
|
|
316
|
-
* catches
|
|
317
|
-
* everything that reaches the streams, not
|
|
281
|
+
* catches direct `process.stdout.write`, third-party library output, and child-process pipes —
|
|
282
|
+
* everything that reaches the streams, not only `console.*`.
|
|
318
283
|
*
|
|
319
284
|
* @remarks
|
|
320
|
-
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the
|
|
285
|
+
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the current
|
|
321
286
|
* `process[stream].write` for each configured {@link StreamLevel}, then installs the wrappers. The
|
|
322
|
-
* mirror replays through that snapshot — so a server sink created from the same streams
|
|
287
|
+
* mirror replays through that snapshot — so a server sink created from the same streams before the
|
|
323
288
|
* capture is never re-captured. Create your sinks before installing a capture.
|
|
324
|
-
* - **Idempotent +
|
|
325
|
-
* double-patches); `stop()` while inactive is a no-op. It patches the
|
|
326
|
-
* most
|
|
289
|
+
* - **Idempotent + process-global + non-reentrant.** `start()` while `active` is a no-op (never
|
|
290
|
+
* double-patches); `stop()` while inactive is a no-op. It patches the one global `process`, so at
|
|
291
|
+
* most one process capture may be active at a time — running two concurrently interleaves their
|
|
327
292
|
* buffers and clobbers each other's restore.
|
|
328
|
-
* - **The wrapper
|
|
293
|
+
* - **The wrapper never throws and passes through backpressure.** A throw inside
|
|
329
294
|
* `process.stdout.write` would crash the host, so the wrapper builds its record through a total
|
|
330
295
|
* decode, and returns the snapshot-original's boolean (or `true` when mirroring is off) so a
|
|
331
296
|
* caller's backpressure handling keeps working.
|
|
332
297
|
* - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
|
|
333
298
|
* (oldest dropped first), never unbounded.
|
|
334
|
-
* - **Lifecycle
|
|
299
|
+
* - **Lifecycle.** `start` / `stop` toggle interception (emitting `start` / `stop`);
|
|
335
300
|
* `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
|
|
336
301
|
*/
|
|
337
302
|
export declare interface ProcessCaptureInterface {
|
|
338
303
|
readonly emitter: EmitterInterface<ProcessCaptureEventMap>;
|
|
339
|
-
/**
|
|
304
|
+
/** Reports whether interception is installed (`start`ed and not yet `stop`ped). */
|
|
340
305
|
readonly active: boolean;
|
|
341
|
-
/**
|
|
306
|
+
/** Begins intercepting the configured process streams (idempotent; emits `start`). */
|
|
342
307
|
start(): void;
|
|
343
|
-
/**
|
|
308
|
+
/** Restores the pristine `process.*.write` references (idempotent; emits `stop`). */
|
|
344
309
|
stop(): void;
|
|
345
|
-
/**
|
|
310
|
+
/** Returns a copy of the full captured buffer, oldest first (capped at `limit`). */
|
|
346
311
|
messages(): readonly CapturedChunk[];
|
|
347
|
-
/**
|
|
312
|
+
/** Returns a copy of the captured buffer for one {@link StreamLevel}, oldest first (capped at `limit`). */
|
|
348
313
|
messages(level: StreamLevel): readonly CapturedChunk[];
|
|
349
|
-
/**
|
|
314
|
+
/** Drops every buffered chunk (total + per-stream); interception is unaffected. */
|
|
350
315
|
clear(): void;
|
|
351
|
-
/**
|
|
316
|
+
/** Stops interception (restoring the streams) and tears down the emitter. */
|
|
352
317
|
destroy(): void;
|
|
353
318
|
}
|
|
354
319
|
|
|
355
320
|
/**
|
|
356
|
-
*
|
|
357
|
-
* bare `
|
|
321
|
+
* Holds the options for the {@link import('./ProcessCapture.js').ProcessCapture} constructor — every
|
|
322
|
+
* field optional, so a bare `new ProcessCapture()` buffers both streams without mirroring or
|
|
323
|
+
* forwarding.
|
|
358
324
|
*
|
|
359
325
|
* @remarks
|
|
360
|
-
* - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (
|
|
326
|
+
* - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (for example
|
|
361
327
|
* `{ capture: (c) => tee(c) }`).
|
|
362
|
-
* - `error` — the listener-error handler forwarded to the entity's emitter
|
|
363
|
-
* - `levels` — which streams to intercept; defaults to {@link import('./constants.js').
|
|
364
|
-
* (
|
|
365
|
-
* - `mirror` — when `true`, each intercepted write is
|
|
328
|
+
* - `error` — the listener-error handler forwarded to the entity's emitter.
|
|
329
|
+
* - `levels` — which streams to intercept; defaults to {@link import('./constants.js').STREAM_LEVELS}
|
|
330
|
+
* (`stdout` and `stderr`). Narrow it (for example to `['stderr']`) to capture one stream.
|
|
331
|
+
* - `mirror` — when `true`, each intercepted write is also replayed to the snapshot-original
|
|
366
332
|
* `write` (bound to its stream), so the output still reaches the terminal while being captured;
|
|
367
333
|
* defaults to `false` (capture-only, the program's output is swallowed into the buffer).
|
|
368
334
|
* - `sink` — an optional {@link SinkInterface} each intercepted chunk is also written to
|
|
369
|
-
* (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('@src/core').LogLevel}
|
|
335
|
+
* (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('@src/core').LogLevel} through
|
|
370
336
|
* {@link import('./constants.js').STREAM_LEVEL_MAP}), to tee captured output into the logging
|
|
371
337
|
* pipeline / a file. Absent by default.
|
|
372
|
-
* - `limit` — the bounded-buffer cap (total
|
|
373
|
-
* {@link import('./constants.js').
|
|
338
|
+
* - `limit` — the bounded-buffer cap (total and each per-stream bucket); defaults to
|
|
339
|
+
* {@link import('./constants.js').DEFAULT_STREAM_LIMIT}. Retention is always bounded.
|
|
374
340
|
*/
|
|
375
341
|
export declare interface ProcessCaptureOptions {
|
|
376
342
|
readonly on?: EmitterHooks<ProcessCaptureEventMap>;
|
|
@@ -382,16 +348,16 @@ export declare interface ProcessCaptureOptions {
|
|
|
382
348
|
}
|
|
383
349
|
|
|
384
350
|
/**
|
|
385
|
-
*
|
|
351
|
+
* Declares a {@link SinkInterface} that also exposes the target terminal's {@link columns} width — the shape
|
|
386
352
|
* {@link import('./factories.js').createServerSink} returns. It is a drop-in {@link SinkInterface}
|
|
387
353
|
* (so a `Logger` / `Reporter` / `Spinner` / `Progress` takes it as `sink`) whose extra `columns`
|
|
388
354
|
* getter lets a consumer size a `Reporter`'s layout to the live terminal. Its `styled` fact lets
|
|
389
|
-
* the same consumer enable or disable its styler for the
|
|
355
|
+
* the same consumer enable or disable its styler for the `stdout` target.
|
|
390
356
|
*
|
|
391
357
|
* @remarks
|
|
392
|
-
* - `styled` is the `
|
|
393
|
-
* independently inferred fact because the two targets can differ.
|
|
394
|
-
* - `columns` is a getter, re-read on every access — so it reflects the
|
|
358
|
+
* - `styled` is the `stdout` target's construction-time fact. The sink handles `stderr` through its
|
|
359
|
+
* own independently inferred fact because the two targets can differ.
|
|
360
|
+
* - `columns` is a getter, re-read on every access — so it reflects the current terminal width (a
|
|
395
361
|
* resize is observed) unless a fixed `options.columns` was supplied, in which case it is constant.
|
|
396
362
|
*/
|
|
397
363
|
export declare interface ServerSinkInterface extends SinkInterface {
|
|
@@ -400,30 +366,32 @@ export declare interface ServerSinkInterface extends SinkInterface {
|
|
|
400
366
|
}
|
|
401
367
|
|
|
402
368
|
/**
|
|
403
|
-
*
|
|
369
|
+
* Holds the options for {@link import('./factories.js').createServerSink} — all optional, so a bare
|
|
404
370
|
* `createServerSink()` writes to the real process streams.
|
|
405
371
|
*
|
|
406
372
|
* @remarks
|
|
407
|
-
* - `
|
|
408
|
-
* `process.stdout
|
|
409
|
-
*
|
|
373
|
+
* - `stdout` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
|
|
374
|
+
* `process.stdout`, whose name it mirrors. Any {@link StreamTargetInterface} is accepted, so a
|
|
375
|
+
* test injects a fake.
|
|
376
|
+
* - `stderr` — the stream `error` / `warn` are written to; defaults to `process.stderr`, whose
|
|
377
|
+
* name it mirrors.
|
|
410
378
|
* - `styled` — an explicit styling decision for both targets. When omitted, each target infers its
|
|
411
379
|
* own fact from `FORCE_COLOR`, `NO_COLOR`, and `isTTY` at construction.
|
|
412
380
|
* - `environment` — the environment used for inference; defaults to `process.env`.
|
|
413
381
|
* - `columns` — an explicit width override for {@link ServerSinkInterface.columns}. When omitted,
|
|
414
|
-
* the sink reads the live `
|
|
415
|
-
* {@link import('./constants.js').DEFAULT_COLUMNS} when the
|
|
382
|
+
* the sink reads the live `stdout.columns` (so it tracks a terminal resize), falling back to
|
|
383
|
+
* {@link import('./constants.js').DEFAULT_COLUMNS} when the `stdout` stream is not a TTY.
|
|
416
384
|
*/
|
|
417
385
|
export declare interface ServerSinkOptions {
|
|
418
|
-
readonly
|
|
419
|
-
readonly
|
|
386
|
+
readonly stdout?: StreamTargetInterface;
|
|
387
|
+
readonly stderr?: StreamTargetInterface;
|
|
420
388
|
readonly styled?: boolean;
|
|
421
389
|
readonly environment?: Readonly<Record<string, string | undefined>>;
|
|
422
390
|
readonly columns?: number;
|
|
423
391
|
}
|
|
424
392
|
|
|
425
393
|
/**
|
|
426
|
-
*
|
|
394
|
+
* Maps each {@link StreamLevel} to its {@link LogLevel} for the optional sink forward — the projection a
|
|
427
395
|
* process capture routes through when writing an intercepted chunk to a
|
|
428
396
|
* {@link import('@src/core').SinkInterface}
|
|
429
397
|
* (`sink.write(text, STREAM_LEVEL_MAP[level])`). `stderr` is conventionally the error/diagnostic
|
|
@@ -433,28 +401,28 @@ export declare interface ServerSinkOptions {
|
|
|
433
401
|
export declare const STREAM_LEVEL_MAP: Readonly<Record<StreamLevel, LogLevel>>;
|
|
434
402
|
|
|
435
403
|
/**
|
|
436
|
-
*
|
|
404
|
+
* Lists the two process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept, in
|
|
437
405
|
* `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured set.
|
|
438
406
|
*/
|
|
439
407
|
export declare const STREAM_LEVELS: readonly StreamLevel[];
|
|
440
408
|
|
|
441
409
|
/**
|
|
442
|
-
*
|
|
410
|
+
* Names which process stream a {@link CapturedChunk} came from — the "level" axis of the process-stream
|
|
443
411
|
* {@link ProcessCaptureInterface}, the server analogue of the core `Capture`'s `CaptureLevel`.
|
|
444
412
|
*
|
|
445
413
|
* @remarks
|
|
446
|
-
*
|
|
414
|
+
* distinct from {@link import('@src/core').LogLevel}: a `StreamLevel` names the originating process stream
|
|
447
415
|
* (`process.stdout` vs `process.stderr`), not a severity. It is a named value family (it indexes
|
|
448
416
|
* {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('@src/core').LogLevel} for the optional sink
|
|
449
|
-
* forward), never a binary toggle — so it stays a union
|
|
417
|
+
* forward), never a binary toggle — so it stays a union.
|
|
450
418
|
*/
|
|
451
419
|
export declare type StreamLevel = 'stdout' | 'stderr';
|
|
452
420
|
|
|
453
421
|
/**
|
|
454
|
-
*
|
|
422
|
+
* Declares the minimal writable-stream shape the server sink and process capture address — exactly the
|
|
455
423
|
* slice of a Node `tty.WriteStream` / `process.stdout` they touch, and no more. A
|
|
456
424
|
* {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
|
|
457
|
-
* narrowed to this
|
|
425
|
+
* narrowed to this through {@link import('./validators.js').isStreamTarget} (narrow the
|
|
458
426
|
* boundary, never `as`), so a test can drive either with a hand-built fake stream that never
|
|
459
427
|
* touches the real `process` streams.
|
|
460
428
|
*
|
|
@@ -476,8 +444,28 @@ export declare interface StreamTargetInterface {
|
|
|
476
444
|
readonly columns?: number;
|
|
477
445
|
}
|
|
478
446
|
|
|
447
|
+
/**
|
|
448
|
+
* Names the completion callback `process.*.write` accepts as its last argument — the Node `write`
|
|
449
|
+
* callback shape, and the {@link StreamWriteFunction} companion.
|
|
450
|
+
*
|
|
451
|
+
* @remarks
|
|
452
|
+
* The capture wrapper forwards the callback verbatim to the mirror, so a caller's
|
|
453
|
+
* write-completion handler still fires.
|
|
454
|
+
*/
|
|
479
455
|
export declare type StreamWriteCallback = (error?: Error | null) => void;
|
|
480
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Names the process-stream `write` method a {@link ProcessCaptureInterface} snapshots and swaps at
|
|
459
|
+
* the patch boundary — the write-side analogue of {@link import('@src/core').ConsoleMethod}.
|
|
460
|
+
*
|
|
461
|
+
* @remarks
|
|
462
|
+
* It is taken verbatim as `NodeJS.WriteStream['write']`, the overloaded
|
|
463
|
+
* `(chunk, encoding?, callback?) => boolean` of `process.stdout.write` / `process.stderr.write`.
|
|
464
|
+
* Using the canonical type rather than a hand-rolled approximation keeps snapshot and restore
|
|
465
|
+
* exact and lets the wrapper assign cleanly. A {@link StreamLevel} (`'stdout' | 'stderr'`) is
|
|
466
|
+
* itself the `process` property key, so `process[level]` indexes the matching `WriteStream`
|
|
467
|
+
* directly, with no lookup map.
|
|
468
|
+
*/
|
|
481
469
|
export declare type StreamWriteFunction = NodeJS.WriteStream['write'];
|
|
482
470
|
|
|
483
471
|
export { }
|