@orkestrel/console 0.0.1
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/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/src/browser/index.d.ts +214 -0
- package/dist/src/browser/index.js +275 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/core/index.cjs +2183 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +2299 -0
- package/dist/src/core/index.d.ts +2299 -0
- package/dist/src/core/index.js +2105 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/server/index.cjs +375 -0
- package/dist/src/server/index.cjs.map +1 -0
- package/dist/src/server/index.d.cts +443 -0
- package/dist/src/server/index.d.ts +443 -0
- package/dist/src/server/index.js +363 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { EmitterErrorHandler } from '@orkestrel/emitter';
|
|
2
|
+
import { EmitterHooks } from '@orkestrel/emitter';
|
|
3
|
+
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
|
+
import { LogLevel } from '../core/index.js';
|
|
5
|
+
import { SinkInterface } from '../core/index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One intercepted process-stream write — the immutable, serializable record a
|
|
9
|
+
* {@link ProcessCaptureInterface} buffers and emits, the server analogue of the core
|
|
10
|
+
* `CapturedMessage`.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* - `level` — the {@link StreamLevel} naming which stream (`stdout` / `stderr`) was written.
|
|
14
|
+
* - `text` — the chunk decoded to a string (via {@link import('./helpers.js').decodeChunk} —
|
|
15
|
+
* total, never throws), VERBATIM: no trailing-newline trimming and no ANSI stripping, so the
|
|
16
|
+
* captured text is exactly the bytes the program emitted.
|
|
17
|
+
* - `time` — the capture instant as epoch milliseconds (`Date.now()`); a plain number so the record
|
|
18
|
+
* stays serializable and orderable.
|
|
19
|
+
* - Frozen at construction — a consumer (or a `capture` listener) reads it, never mutates it.
|
|
20
|
+
*/
|
|
21
|
+
export declare interface CapturedChunk {
|
|
22
|
+
readonly level: StreamLevel;
|
|
23
|
+
readonly text: string;
|
|
24
|
+
readonly time: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The width in character cells of a stream target — its live `columns` when it is a TTY, else the
|
|
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('../core/index.js').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 is isTTY-aware: it sends ANSI straight to a terminal
|
|
76
|
+
* (which renders it, with a leading `\r` overwriting the line natively — that is how the C-e
|
|
77
|
+
* animations become a LIVE redraw here, with no extra code) but {@link import('../core/index.js').strip}s
|
|
78
|
+
* the ANSI to clean text when the stream is piped to a file or another process.
|
|
79
|
+
*
|
|
80
|
+
* @param options - See {@link ServerSinkOptions}
|
|
81
|
+
* @returns A {@link ServerSinkInterface} — a {@link import('../core/index.js').SinkInterface} that also
|
|
82
|
+
* exposes the terminal `columns` width
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* - **Routes by level.** `error` / `warn` → the error stream (`process.stderr` by default), every
|
|
86
|
+
* other level (and an omitted level) → the out stream (`process.stdout`) — the SAME routing as
|
|
87
|
+
* core's `createConsoleSink`, so a logger's `error` reaches `stderr`.
|
|
88
|
+
* - **isTTY-aware ANSI.** For each write, if the TARGET stream is a TTY the styled `text` is written
|
|
89
|
+
* VERBATIM (the terminal renders the ANSI, and a leading `\r` overwrites the current line — live
|
|
90
|
+
* animations for free); if it is NOT a TTY (a pipe / redirect to a log file), the ANSI is stripped
|
|
91
|
+
* so the file gets clean text. The decision is per-stream and per-write, re-read live, so it stays
|
|
92
|
+
* correct if a stream's TTY-ness ever differs between out and err.
|
|
93
|
+
* - **Width.** `columns` reflects the live `out.columns` (so it tracks a terminal resize), falling
|
|
94
|
+
* back to {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY — or a
|
|
95
|
+
* fixed value when `options.columns` is supplied. Feed it to a `Reporter` / `Progress` `width`.
|
|
96
|
+
* - **Injectable + guard-narrowed.** `options.out` / `options.err` default to `process.stdout` /
|
|
97
|
+
* `process.stderr` but accept ANY {@link import('./types.js').StreamTargetInterface}, resolved
|
|
98
|
+
* through {@link isStreamTarget} (AGENTS §14 — narrow the boundary, never `as`), so a test drives
|
|
99
|
+
* the sink (and the isTTY-strip path) with a fake stream that never touches the real process
|
|
100
|
+
* streams.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { createLogger, createReporter } from '../core/index.js'
|
|
105
|
+
* import { createServerSink } from '@src/server'
|
|
106
|
+
*
|
|
107
|
+
* const sink = createServerSink()
|
|
108
|
+
* const logger = createLogger({ name: 'app', sink })
|
|
109
|
+
* logger.error('boom') // → process.stderr, ANSI rendered on a TTY / stripped to a pipe
|
|
110
|
+
* const reporter = createReporter({ sink, width: sink.columns })
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function createServerSink(options?: ServerSinkOptions): ServerSinkInterface;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Decode one `process.stdout.write` / `process.stderr.write` chunk to a string — TOTAL, never
|
|
117
|
+
* throws (AGENTS §14). The process write signature accepts `string | Uint8Array` plus an optional
|
|
118
|
+
* encoding; the capture wrapper reuses this so intercepting a raw stream write can never crash the
|
|
119
|
+
* host (a throw inside `process.stdout.write` would take the program down).
|
|
120
|
+
*
|
|
121
|
+
* @remarks
|
|
122
|
+
* - A `string` chunk is returned verbatim — the common case (`console.log`, most library output,
|
|
123
|
+
* and `process.stdout.write('text')` all pass a string).
|
|
124
|
+
* - A `Buffer` chunk is decoded with the supplied `encoding` when it is a recognized
|
|
125
|
+
* {@link BufferEncoding} (`process` write supports `'utf8'` / `'hex'` / `'base64'` / …), defaulting
|
|
126
|
+
* to `'utf8'`; a bare `Uint8Array` is decoded via `TextDecoder` (always utf-8 — the `encoding`
|
|
127
|
+
* argument applies ONLY to a `Buffer`, never a plain `Uint8Array`).
|
|
128
|
+
* - Anything else is coerced with `String(chunk)` (a number / object / bigint / symbol a misbehaving
|
|
129
|
+
* writer hands the stream). The coercion is itself guarded: a value whose `toString` /
|
|
130
|
+
* `Symbol.toPrimitive` throws yields the stable `'[unprintable]'` placeholder. So the helper is
|
|
131
|
+
* TOTAL on every input — it always yields SOME string, never an exception (a throw here would
|
|
132
|
+
* escape into `process.*.write` and crash the host).
|
|
133
|
+
*
|
|
134
|
+
* @param chunk - The chunk passed to the stream's `write`
|
|
135
|
+
* @param encoding - The optional encoding argument passed alongside the chunk
|
|
136
|
+
* @returns The chunk as text
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* decodeChunk('hi') // 'hi'
|
|
141
|
+
* decodeChunk(Buffer.from('hi')) // 'hi'
|
|
142
|
+
* decodeChunk(new Uint8Array([104, 105])) // 'hi'
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export declare function decodeChunk(chunk: unknown, encoding?: unknown): string;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The default set of {@link StreamLevel}s a process capture patches when `options.levels` is omitted
|
|
149
|
+
* — BOTH streams ({@link STREAM_LEVELS}). A consumer narrows it (e.g. just `['stderr']`) via
|
|
150
|
+
* `options.levels`.
|
|
151
|
+
*/
|
|
152
|
+
export declare const DEFAULT_CAPTURE_LEVELS: readonly StreamLevel[];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} — at
|
|
156
|
+
* most this many recent {@link import('./types.js').CapturedChunk}s are retained per buffer (the
|
|
157
|
+
* total buffer AND each per-stream bucket; oldest dropped first). Mirrors the core `Capture`'s
|
|
158
|
+
* `DEFAULT_CAPTURE_LIMIT`; a consumer overrides it via `options.limit`.
|
|
159
|
+
*/
|
|
160
|
+
export declare const DEFAULT_CAPTURE_LIMIT = 1000;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The terminal width {@link import('./factories.js').createServerSink} reports through
|
|
164
|
+
* {@link import('./types.js').ServerSinkInterface.columns} when the out stream is NOT a TTY (so
|
|
165
|
+
* `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
|
|
166
|
+
* 80-column default a non-interactive context (a pipe, a CI log) assumes.
|
|
167
|
+
*/
|
|
168
|
+
export declare const DEFAULT_COLUMNS = 80;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a total
|
|
172
|
+
* guard used by {@link decodeChunk} to honor a process-write `encoding` argument only when it is a
|
|
173
|
+
* real Node encoding (otherwise utf-8 is assumed).
|
|
174
|
+
*
|
|
175
|
+
* @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
|
|
176
|
+
* @returns `true` when `encoding` names a supported buffer encoding
|
|
177
|
+
*/
|
|
178
|
+
export declare function isBufferEncoding(encoding: unknown): encoding is BufferEncoding;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Whether `value` is a usable {@link StreamTargetInterface} — a record with a callable `write`. A
|
|
182
|
+
* total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it
|
|
183
|
+
* narrows the one unavoidable boundary (the real `process.stdout` / `process.stderr`, or a fake
|
|
184
|
+
* stream a test injects) to the exact slice the sink + capture touch — no `as`.
|
|
185
|
+
*
|
|
186
|
+
* @remarks
|
|
187
|
+
* Only `write` is required (the irreducible output method); `isTTY` and `columns` are optional on
|
|
188
|
+
* {@link StreamTargetInterface}, so their absence does not disqualify a target — a piped stream
|
|
189
|
+
* (no `isTTY`) is still a valid write target, just a non-terminal one.
|
|
190
|
+
*
|
|
191
|
+
* @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
|
|
192
|
+
* @returns `true` when `value` has a callable `write`
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* isStreamTarget(process.stdout) // true
|
|
197
|
+
* isStreamTarget({ write: () => true }) // true
|
|
198
|
+
* isStreamTarget({}) // false (no write)
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export declare function isStreamTarget(value: unknown): value is StreamTargetInterface;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* An observable interceptor of the RAW process output streams (AGENTS §13) — it takes control of
|
|
205
|
+
* `process.stdout.write` / `process.stderr.write` on the WRITE side. While `active`, every write to
|
|
206
|
+
* a configured {@link StreamLevel} is captured as a frozen {@link CapturedChunk}, buffered (total +
|
|
207
|
+
* per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream and/or
|
|
208
|
+
* forwarded to a {@link SinkInterface}.
|
|
209
|
+
*
|
|
210
|
+
* @remarks
|
|
211
|
+
* Where the core `Capture` patches `console.*` (the high-level read side), this patches the
|
|
212
|
+
* low-level stream `write`, so it owns ALL server output: a direct `process.stdout.write`, a
|
|
213
|
+
* third-party library's writes, a child-process pipe — not only `console.*`.
|
|
214
|
+
*
|
|
215
|
+
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
|
|
216
|
+
* `process[stream].write` for each configured level, then installs the wrappers. The mirror
|
|
217
|
+
* replays through that snapshot (bound to its stream) — so a server sink created from the same
|
|
218
|
+
* streams BEFORE the capture is never re-captured: this catches OTHER writers, not the mirror's
|
|
219
|
+
* own replay. Create your sinks before installing a capture.
|
|
220
|
+
* - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
|
|
221
|
+
* double-patches — that would snapshot the wrapper as the "original" and break restore); `stop()`
|
|
222
|
+
* while inactive is a no-op. It patches the ONE global `process`, so at most ONE process capture
|
|
223
|
+
* may be active at a time — two concurrently would interleave buffers and clobber each other's
|
|
224
|
+
* restore.
|
|
225
|
+
* - **The wrapper NEVER throws and passes backpressure through.** A throw inside
|
|
226
|
+
* `process.stdout.write` would crash the host, so the wrapper decodes the chunk through the total
|
|
227
|
+
* {@link decodeChunk}, and returns the snapshot-original's `boolean` when mirroring (so a caller's
|
|
228
|
+
* `write` backpressure handling still works) or `true` when capture-only (the buffer never fills).
|
|
229
|
+
* - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
|
|
230
|
+
* (oldest dropped first), never unbounded — the same retention precedent as the core `Capture`.
|
|
231
|
+
* - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
|
|
232
|
+
* `destroy()` stops (restoring the PRISTINE `write`) then destroys the emitter.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
|
|
237
|
+
* capture.start()
|
|
238
|
+
* process.stderr.write('a library diagnostic\n') // captured AND still written to the terminal
|
|
239
|
+
* capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
|
|
240
|
+
* capture.stop() // process.stderr.write restored
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export declare class ProcessCapture implements ProcessCaptureInterface {
|
|
244
|
+
#private;
|
|
245
|
+
constructor(options?: ProcessCaptureOptions);
|
|
246
|
+
get emitter(): EmitterInterface<ProcessCaptureEventMap>;
|
|
247
|
+
get active(): boolean;
|
|
248
|
+
start(): void;
|
|
249
|
+
stop(): void;
|
|
250
|
+
messages(): readonly CapturedChunk[];
|
|
251
|
+
messages(level: StreamLevel): readonly CapturedChunk[];
|
|
252
|
+
clear(): void;
|
|
253
|
+
destroy(): void;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The observable events a {@link ProcessCaptureInterface} emits (AGENTS §13) — mirrors the core
|
|
258
|
+
* `Capture`'s `CaptureEventMap`, but the captured record is a {@link CapturedChunk} (stream-keyed).
|
|
259
|
+
*
|
|
260
|
+
* @remarks
|
|
261
|
+
* - `capture` — an intercepted `process.stdout` / `process.stderr` write, carrying the frozen
|
|
262
|
+
* {@link CapturedChunk}. The hook a live log viewer / tee subscribes to.
|
|
263
|
+
* - `start` / `stop` — the interception toggled on / off (pure signals, empty tuples).
|
|
264
|
+
*
|
|
265
|
+
* Listener isolation is the emitter's (§13): a listener throw routes to the emitter's `error`
|
|
266
|
+
* handler, never onto this map — so a buggy `capture` listener can never escape into the host's
|
|
267
|
+
* `process.stdout.write` call (which would crash the program).
|
|
268
|
+
*
|
|
269
|
+
* Declared as a `type` alias (not `interface extends EventMap`, §4.5): a type-literal satisfies the
|
|
270
|
+
* `EventMap` constraint structurally, whereas an interface lacks the index signature.
|
|
271
|
+
*/
|
|
272
|
+
export declare type ProcessCaptureEventMap = {
|
|
273
|
+
/** An intercepted process-stream write — the frozen {@link CapturedChunk}. */
|
|
274
|
+
readonly capture: readonly [chunk: CapturedChunk];
|
|
275
|
+
/** Interception began (`process.*.write` patched). */
|
|
276
|
+
readonly start: readonly [];
|
|
277
|
+
/** Interception ended (`process.*.write` restored). */
|
|
278
|
+
readonly stop: readonly [];
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* An observable interceptor of the RAW process output streams (AGENTS §13) — the server's
|
|
283
|
+
* "own ALL output" capture. Where the core `Capture` patches `console.*` (the high-level read
|
|
284
|
+
* side), this patches `process.stdout.write` / `process.stderr.write` (the low-level stream), so it
|
|
285
|
+
* catches DIRECT `process.stdout.write`, third-party library output, and child-process pipes —
|
|
286
|
+
* everything that reaches the streams, not just `console.*`.
|
|
287
|
+
*
|
|
288
|
+
* @remarks
|
|
289
|
+
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
|
|
290
|
+
* `process[stream].write` for each configured {@link StreamLevel}, then installs the wrappers. The
|
|
291
|
+
* mirror replays through that snapshot — so a server sink created from the same streams BEFORE the
|
|
292
|
+
* capture is never re-captured. Create your sinks before installing a capture.
|
|
293
|
+
* - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
|
|
294
|
+
* double-patches); `stop()` while inactive is a no-op. It patches the ONE global `process`, so at
|
|
295
|
+
* most ONE process capture may be active at a time — running two concurrently interleaves their
|
|
296
|
+
* buffers and clobbers each other's restore.
|
|
297
|
+
* - **The wrapper NEVER throws and passes through backpressure.** A throw inside
|
|
298
|
+
* `process.stdout.write` would crash the host, so the wrapper builds its record through a total
|
|
299
|
+
* decode, and returns the snapshot-original's boolean (or `true` when mirroring is off) so a
|
|
300
|
+
* caller's backpressure handling keeps working.
|
|
301
|
+
* - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
|
|
302
|
+
* (oldest dropped first), never unbounded.
|
|
303
|
+
* - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
|
|
304
|
+
* `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
|
|
305
|
+
*/
|
|
306
|
+
export declare interface ProcessCaptureInterface {
|
|
307
|
+
readonly emitter: EmitterInterface<ProcessCaptureEventMap>;
|
|
308
|
+
/** Whether interception is currently installed (`start`ed and not yet `stop`ped). */
|
|
309
|
+
readonly active: boolean;
|
|
310
|
+
/** Begin intercepting the configured process streams (idempotent; emits `start`). */
|
|
311
|
+
start(): void;
|
|
312
|
+
/** Restore the pristine `process.*.write` references (idempotent; emits `stop`). */
|
|
313
|
+
stop(): void;
|
|
314
|
+
/** A copy of the full captured buffer, oldest first (capped at `limit`). */
|
|
315
|
+
messages(): readonly CapturedChunk[];
|
|
316
|
+
/** A copy of the captured buffer for ONE {@link StreamLevel}, oldest first (capped at `limit`). */
|
|
317
|
+
messages(level: StreamLevel): readonly CapturedChunk[];
|
|
318
|
+
/** Drop every buffered chunk (total + per-stream); interception is unaffected. */
|
|
319
|
+
clear(): void;
|
|
320
|
+
/** Stop interception (restoring the streams) and tear down the emitter. */
|
|
321
|
+
destroy(): void;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Options for {@link import('./factories.js').createProcessCapture} — every field optional, so a
|
|
326
|
+
* bare `createProcessCapture()` buffers both streams without mirroring or forwarding.
|
|
327
|
+
*
|
|
328
|
+
* @remarks
|
|
329
|
+
* - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (e.g.
|
|
330
|
+
* `{ capture: (c) => tee(c) }`).
|
|
331
|
+
* - `error` — the listener-error handler forwarded to the entity's emitter (§13).
|
|
332
|
+
* - `levels` — which streams to intercept; defaults to {@link import('./constants.js').DEFAULT_CAPTURE_LEVELS}
|
|
333
|
+
* (both `stdout` and `stderr`). Narrow it (e.g. just `['stderr']`) to capture one stream.
|
|
334
|
+
* - `mirror` — when `true`, each intercepted write is ALSO replayed to the snapshot-original
|
|
335
|
+
* `write` (bound to its stream), so the output still reaches the terminal while being captured;
|
|
336
|
+
* defaults to `false` (capture-only, the program's output is swallowed into the buffer).
|
|
337
|
+
* - `sink` — an optional {@link SinkInterface} each intercepted chunk is also written to
|
|
338
|
+
* (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('../core/index.js').LogLevel} via
|
|
339
|
+
* {@link import('./constants.js').STREAM_LEVEL_MAP}), to tee captured output into the logging
|
|
340
|
+
* pipeline / a file. Absent by default.
|
|
341
|
+
* - `limit` — the bounded-buffer cap (total AND each per-stream bucket); defaults to
|
|
342
|
+
* {@link import('./constants.js').DEFAULT_CAPTURE_LIMIT}. Retention is ALWAYS bounded.
|
|
343
|
+
*/
|
|
344
|
+
export declare interface ProcessCaptureOptions {
|
|
345
|
+
readonly on?: EmitterHooks<ProcessCaptureEventMap>;
|
|
346
|
+
readonly error?: EmitterErrorHandler;
|
|
347
|
+
readonly levels?: readonly StreamLevel[];
|
|
348
|
+
readonly mirror?: boolean;
|
|
349
|
+
readonly sink?: SinkInterface;
|
|
350
|
+
readonly limit?: number;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* A {@link SinkInterface} that also exposes the target terminal's {@link columns} width — the shape
|
|
355
|
+
* {@link import('./factories.js').createServerSink} returns. It is a drop-in {@link SinkInterface}
|
|
356
|
+
* (so a `Logger` / `Reporter` / `Spinner` / `Progress` takes it as `sink`) whose extra `columns`
|
|
357
|
+
* getter lets a consumer size a `Reporter`'s layout to the live terminal.
|
|
358
|
+
*
|
|
359
|
+
* @remarks
|
|
360
|
+
* `columns` is a getter, re-read on every access — so it reflects the CURRENT terminal width (a
|
|
361
|
+
* resize is observed) unless a fixed `options.columns` was supplied, in which case it is constant.
|
|
362
|
+
*/
|
|
363
|
+
export declare interface ServerSinkInterface extends SinkInterface {
|
|
364
|
+
readonly columns: number;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Options for {@link import('./factories.js').createServerSink} — all optional, so a bare
|
|
369
|
+
* `createServerSink()` writes to the real process streams.
|
|
370
|
+
*
|
|
371
|
+
* @remarks
|
|
372
|
+
* - `out` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
|
|
373
|
+
* `process.stdout`. Any {@link StreamTargetInterface} is accepted, so a test injects a fake.
|
|
374
|
+
* - `err` — the stream `error` / `warn` are written to; defaults to `process.stderr`.
|
|
375
|
+
* - `columns` — an explicit width override for {@link ServerSinkInterface.columns}. When omitted,
|
|
376
|
+
* the sink reads the live `out.columns` (so it tracks a terminal resize), falling back to
|
|
377
|
+
* {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY.
|
|
378
|
+
*/
|
|
379
|
+
export declare interface ServerSinkOptions {
|
|
380
|
+
readonly out?: StreamTargetInterface;
|
|
381
|
+
readonly err?: StreamTargetInterface;
|
|
382
|
+
readonly columns?: number;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Each {@link StreamLevel}'s {@link LogLevel} for the optional sink forward — the projection a
|
|
387
|
+
* process capture routes through when writing an intercepted chunk to a
|
|
388
|
+
* {@link import('../core/index.js').SinkInterface}
|
|
389
|
+
* (`sink.write(text, STREAM_LEVEL_MAP[level])`). `stderr` is conventionally the error/diagnostic
|
|
390
|
+
* stream → `error`; `stdout` is the normal output stream → `info`. The source of truth for the
|
|
391
|
+
* stream-to-log projection (the server analogue of the core `CAPTURE_LEVEL_MAP`).
|
|
392
|
+
*/
|
|
393
|
+
export declare const STREAM_LEVEL_MAP: Readonly<Record<StreamLevel, LogLevel>>;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The two process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept, in
|
|
397
|
+
* `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured set.
|
|
398
|
+
*/
|
|
399
|
+
export declare const STREAM_LEVELS: readonly StreamLevel[];
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Which process stream a {@link CapturedChunk} came from — the "level" axis of the process-stream
|
|
403
|
+
* {@link ProcessCaptureInterface}, the server analogue of the core `Capture`'s `CaptureLevel`.
|
|
404
|
+
*
|
|
405
|
+
* @remarks
|
|
406
|
+
* DISTINCT from {@link import('../core/index.js').LogLevel}: a `StreamLevel` names the ORIGINATING process stream
|
|
407
|
+
* (`process.stdout` vs `process.stderr`), not a severity. It is a named value family (it indexes
|
|
408
|
+
* {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('../core/index.js').LogLevel} for the optional sink
|
|
409
|
+
* forward), never a binary toggle — so it stays a union (AGENTS §4.4).
|
|
410
|
+
*/
|
|
411
|
+
export declare type StreamLevel = 'stdout' | 'stderr';
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The minimal writable-stream shape the C-g server sink and process capture address — exactly the
|
|
415
|
+
* slice of a Node `tty.WriteStream` / `process.stdout` they touch, and no more. A
|
|
416
|
+
* {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
|
|
417
|
+
* narrowed to this via {@link import('./helpers.js').isStreamTarget} (AGENTS §14 — narrow the
|
|
418
|
+
* boundary, never `as`), so a test can drive either with a hand-built fake stream that never
|
|
419
|
+
* touches the real `process` streams.
|
|
420
|
+
*
|
|
421
|
+
* @remarks
|
|
422
|
+
* - `write(text)` — the one required method: push a chunk to the stream, returning the host's
|
|
423
|
+
* backpressure boolean (`false` when the kernel buffer is full). A `process` stream returns it;
|
|
424
|
+
* a fake may return `void` (read as truthy / no backpressure).
|
|
425
|
+
* - `isTTY` — present and `true` on a real terminal, absent / `false` when the stream is piped to a
|
|
426
|
+
* file or another process. The sink reads it to decide whether to keep ANSI (a terminal renders
|
|
427
|
+
* it, and a leading `\r` overwrites natively) or {@link import('../core/index.js').strip} it to clean
|
|
428
|
+
* text (a log file should not carry escape codes).
|
|
429
|
+
* - `columns` — the terminal width in character cells when the stream is a TTY, `undefined`
|
|
430
|
+
* otherwise; the sink surfaces it as {@link ServerSinkInterface.columns} so a consumer can feed a
|
|
431
|
+
* `Reporter` / `Progress` its render width.
|
|
432
|
+
*/
|
|
433
|
+
export declare interface StreamTargetInterface {
|
|
434
|
+
write(text: string): boolean | void;
|
|
435
|
+
readonly isTTY?: boolean;
|
|
436
|
+
readonly columns?: number;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export declare type StreamWriteCallback = (error?: Error | null) => void;
|
|
440
|
+
|
|
441
|
+
export declare type StreamWriteFunction = NodeJS.WriteStream['write'];
|
|
442
|
+
|
|
443
|
+
export { }
|