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