@orkestrel/console 0.0.12 → 0.0.14
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 +8 -8
- package/dist/src/browser/index.d.ts +47 -38
- package/dist/src/browser/index.js +27 -18
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +121 -82
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +263 -174
- package/dist/src/core/index.d.ts +263 -174
- package/dist/src/core/index.js +121 -82
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +64 -45
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +115 -80
- package/dist/src/server/index.d.ts +115 -80
- package/dist/src/server/index.js +64 -45
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -16
|
@@ -1,8 +1,8 @@
|
|
|
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
|
|
@@ -25,21 +25,20 @@ export declare interface CapturedChunk {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Creates the server TTY {@link ServerSinkInterface} — the server output backend,
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* {@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.
|
|
33
32
|
*
|
|
34
33
|
* @param options - See {@link ServerSinkOptions}
|
|
35
|
-
* @returns A {@link ServerSinkInterface} — a {@link import('@
|
|
34
|
+
* @returns A {@link ServerSinkInterface} — a {@link import('@orkestrel/console').SinkInterface} that also
|
|
36
35
|
* exposes the terminal `columns` width
|
|
37
36
|
*
|
|
38
37
|
* @remarks
|
|
39
38
|
* - **Routes by level.** `error` / `warn` → the error stream (`process.stderr` by default), every
|
|
40
39
|
* other level (and an omitted level) → the `stdout` stream (`process.stdout`) — the same routing
|
|
41
40
|
* as core's `createConsoleSink`, so a logger's `error` reaches `stderr`. Both call the one
|
|
42
|
-
* {@link import('@
|
|
41
|
+
* {@link import('@orkestrel/console').selectWriter} leaf, which is what keeps them identical.
|
|
43
42
|
* - **Per-target styled facts.** At construction, each target uses `options.styled` when supplied;
|
|
44
43
|
* otherwise {@link inferStyled} applies the injected `environment` (default `process.env`) and
|
|
45
44
|
* then that target's `isTTY`.
|
|
@@ -55,16 +54,29 @@ export declare interface CapturedChunk {
|
|
|
55
54
|
* the sink (and the isTTY-strip path) with a fake stream that never touches the real process
|
|
56
55
|
* streams.
|
|
57
56
|
*
|
|
58
|
-
* @example
|
|
57
|
+
* @example The server — a TTY sink and a process capture
|
|
59
58
|
* ```ts
|
|
60
59
|
* import { createStyler, Logger, Reporter } from '@orkestrel/console'
|
|
61
|
-
* import { createServerSink } from '@orkestrel/console/server'
|
|
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
|
|
62
71
|
*
|
|
63
|
-
*
|
|
64
|
-
* const
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
|
|
68
80
|
* ```
|
|
69
81
|
*/
|
|
70
82
|
export declare function createServerSink(options?: ServerSinkOptions): ServerSinkInterface;
|
|
@@ -103,24 +115,29 @@ export declare function decodeChunk(chunk: unknown, encoding?: unknown): string;
|
|
|
103
115
|
|
|
104
116
|
/**
|
|
105
117
|
* 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
|
|
107
|
-
* `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
|
|
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
|
|
108
120
|
* 80-column default a non-interactive context (a pipe, a CI log) assumes.
|
|
109
121
|
*/
|
|
110
122
|
export declare const DEFAULT_COLUMNS = 80;
|
|
111
123
|
|
|
112
124
|
/**
|
|
113
|
-
* Sets the default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} —
|
|
114
|
-
* most
|
|
115
|
-
* total buffer and each per-stream bucket; oldest dropped first)
|
|
116
|
-
*
|
|
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`.
|
|
117
133
|
*/
|
|
118
134
|
export declare const DEFAULT_STREAM_LIMIT = 1000;
|
|
119
135
|
|
|
120
136
|
/**
|
|
121
|
-
* Infers the width in character cells of a stream target — its live `columns` when it is a TTY,
|
|
122
|
-
* the non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a
|
|
123
|
-
* reports through `columns` so a `Reporter` /
|
|
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.
|
|
124
141
|
*
|
|
125
142
|
* @remarks
|
|
126
143
|
* Reads `target.columns` on each call (so a getter-backed real stream reflects a live resize) and
|
|
@@ -133,14 +150,14 @@ export declare const DEFAULT_STREAM_LIMIT = 1000;
|
|
|
133
150
|
export declare function inferColumns(target: StreamTargetInterface): number;
|
|
134
151
|
|
|
135
152
|
/**
|
|
136
|
-
* Infers whether one stream target receives styled output
|
|
137
|
-
*
|
|
138
|
-
* 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`.
|
|
139
155
|
*
|
|
140
156
|
* @remarks
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
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.
|
|
144
161
|
*
|
|
145
162
|
* @param target - The stream target whose terminal capability is the fallback
|
|
146
163
|
* @param environment - The environment record supplying `FORCE_COLOR` and `NO_COLOR`
|
|
@@ -155,9 +172,9 @@ export declare function inferColumns(target: StreamTargetInterface): number;
|
|
|
155
172
|
export declare function inferStyled(target: StreamTargetInterface, environment: Readonly<Record<string, string | undefined>>): boolean;
|
|
156
173
|
|
|
157
174
|
/**
|
|
158
|
-
* Checks whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a
|
|
159
|
-
* guard used by {@link import('./helpers.js').decodeChunk} to honor a process-write
|
|
160
|
-
* argument only when it is a 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).
|
|
161
178
|
*
|
|
162
179
|
* @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
|
|
163
180
|
* @returns True if `encoding` names a supported buffer encoding; false otherwise
|
|
@@ -250,10 +267,13 @@ export declare class ProcessCapture implements ProcessCaptureInterface {
|
|
|
250
267
|
}
|
|
251
268
|
|
|
252
269
|
/**
|
|
253
|
-
* Declares the observable events a {@link ProcessCaptureInterface} emits —
|
|
254
|
-
*
|
|
270
|
+
* Declares the observable events a {@link ProcessCaptureInterface} emits — `capture(chunk)` per
|
|
271
|
+
* intercepted write, plus the `start` and `stop` signals.
|
|
255
272
|
*
|
|
256
273
|
* @remarks
|
|
274
|
+
* The map mirrors the core `Capture`'s `CaptureEventMap`, and the captured record is a
|
|
275
|
+
* {@link CapturedChunk} (stream-keyed).
|
|
276
|
+
*
|
|
257
277
|
* - `capture` — an intercepted `process.stdout` / `process.stderr` write, carrying the frozen
|
|
258
278
|
* {@link CapturedChunk}. The hook a live log viewer / tee subscribes to.
|
|
259
279
|
* - `start` / `stop` — the interception toggled on / off (pure signals, empty tuples).
|
|
@@ -275,13 +295,14 @@ export declare type ProcessCaptureEventMap = {
|
|
|
275
295
|
};
|
|
276
296
|
|
|
277
297
|
/**
|
|
278
|
-
* Declares an observable interceptor of the raw process output streams — the server's
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* catches direct `process.stdout.write`, third-party library output, and child-process pipes —
|
|
282
|
-
* everything that reaches the streams, not only `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.*`.
|
|
283
301
|
*
|
|
284
302
|
* @remarks
|
|
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
|
+
*
|
|
285
306
|
* - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the current
|
|
286
307
|
* `process[stream].write` for each configured {@link StreamLevel}, then installs the wrappers. The
|
|
287
308
|
* mirror replays through that snapshot — so a server sink created from the same streams before the
|
|
@@ -307,7 +328,10 @@ export declare interface ProcessCaptureInterface {
|
|
|
307
328
|
start(): void;
|
|
308
329
|
/** Restores the pristine `process.*.write` references (idempotent; emits `stop`). */
|
|
309
330
|
stop(): void;
|
|
310
|
-
/**
|
|
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
|
+
*/
|
|
311
335
|
messages(): readonly CapturedChunk[];
|
|
312
336
|
/** Returns a copy of the captured buffer for one {@link StreamLevel}, oldest first (capped at `limit`). */
|
|
313
337
|
messages(level: StreamLevel): readonly CapturedChunk[];
|
|
@@ -318,9 +342,10 @@ export declare interface ProcessCaptureInterface {
|
|
|
318
342
|
}
|
|
319
343
|
|
|
320
344
|
/**
|
|
321
|
-
* Holds the options for the {@link import('./ProcessCapture.js').ProcessCapture} constructor —
|
|
322
|
-
*
|
|
323
|
-
*
|
|
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.
|
|
324
349
|
*
|
|
325
350
|
* @remarks
|
|
326
351
|
* - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (for example
|
|
@@ -332,7 +357,7 @@ export declare interface ProcessCaptureInterface {
|
|
|
332
357
|
* `write` (bound to its stream), so the output still reaches the terminal while being captured;
|
|
333
358
|
* defaults to `false` (capture-only, the program's output is swallowed into the buffer).
|
|
334
359
|
* - `sink` — an optional {@link SinkInterface} each intercepted chunk is also written to
|
|
335
|
-
* (`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
|
|
336
361
|
* {@link import('./constants.js').STREAM_LEVEL_MAP}), to tee captured output into the logging
|
|
337
362
|
* pipeline / a file. Absent by default.
|
|
338
363
|
* - `limit` — the bounded-buffer cap (total and each per-stream bucket); defaults to
|
|
@@ -348,13 +373,16 @@ export declare interface ProcessCaptureOptions {
|
|
|
348
373
|
}
|
|
349
374
|
|
|
350
375
|
/**
|
|
351
|
-
* Declares a {@link SinkInterface} that also exposes the target
|
|
352
|
-
*
|
|
353
|
-
* (
|
|
354
|
-
* getter lets a consumer size a `Reporter`'s layout to the live terminal. Its `styled` fact lets
|
|
355
|
-
* the same consumer enable or disable its styler for the `stdout` 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.
|
|
356
379
|
*
|
|
357
380
|
* @remarks
|
|
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
|
+
*
|
|
358
386
|
* - `styled` is the `stdout` target's construction-time fact. The sink handles `stderr` through its
|
|
359
387
|
* own independently inferred fact because the two targets can differ.
|
|
360
388
|
* - `columns` is a getter, re-read on every access — so it reflects the current terminal width (a
|
|
@@ -366,8 +394,10 @@ export declare interface ServerSinkInterface extends SinkInterface {
|
|
|
366
394
|
}
|
|
367
395
|
|
|
368
396
|
/**
|
|
369
|
-
* Holds the options for {@link import('./factories.js').createServerSink} —
|
|
370
|
-
* `
|
|
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.
|
|
371
401
|
*
|
|
372
402
|
* @remarks
|
|
373
403
|
* - `stdout` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
|
|
@@ -391,48 +421,51 @@ export declare interface ServerSinkOptions {
|
|
|
391
421
|
}
|
|
392
422
|
|
|
393
423
|
/**
|
|
394
|
-
* Maps each {@link StreamLevel} to its {@link LogLevel} for the optional sink forward — the
|
|
395
|
-
* process capture routes through when writing an intercepted chunk to a
|
|
396
|
-
* {@link import('@
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
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`).
|
|
400
430
|
*/
|
|
401
431
|
export declare const STREAM_LEVEL_MAP: Readonly<Record<StreamLevel, LogLevel>>;
|
|
402
432
|
|
|
403
433
|
/**
|
|
404
|
-
* Lists the
|
|
405
|
-
* `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.
|
|
406
437
|
*/
|
|
407
438
|
export declare const STREAM_LEVELS: readonly StreamLevel[];
|
|
408
439
|
|
|
409
440
|
/**
|
|
410
|
-
* Names which process stream a {@link CapturedChunk} came from —
|
|
411
|
-
* {@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`.
|
|
412
444
|
*
|
|
413
445
|
* @remarks
|
|
414
|
-
* distinct from {@link import('@
|
|
446
|
+
* distinct from {@link import('@orkestrel/console').LogLevel}: a `StreamLevel` names the originating process stream
|
|
415
447
|
* (`process.stdout` vs `process.stderr`), not a severity. It is a named value family (it indexes
|
|
416
|
-
* {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('@
|
|
448
|
+
* {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('@orkestrel/console').LogLevel} for the optional sink
|
|
417
449
|
* forward), never a binary toggle — so it stays a union.
|
|
418
450
|
*/
|
|
419
451
|
export declare type StreamLevel = 'stdout' | 'stderr';
|
|
420
452
|
|
|
421
453
|
/**
|
|
422
|
-
* Declares the minimal writable-stream shape the server sink and process capture address — exactly
|
|
423
|
-
* slice of a Node `tty.WriteStream` / `process.stdout` they touch
|
|
424
|
-
* {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
|
|
425
|
-
* narrowed to this through {@link import('./validators.js').isStreamTarget} (narrow the
|
|
426
|
-
* boundary, never `as`), so a test can drive either with a hand-built fake stream that never
|
|
427
|
-
* 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.
|
|
428
456
|
*
|
|
429
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
|
+
*
|
|
430
463
|
* - `write(text)` — the one required method: push a chunk to the stream, returning the host's
|
|
431
464
|
* backpressure boolean (`false` when the kernel buffer is full). A `process` stream returns it;
|
|
432
465
|
* a fake may return `void` (read as truthy / no backpressure).
|
|
433
466
|
* - `isTTY` — present and `true` on a real terminal, absent / `false` when the stream is piped to a
|
|
434
467
|
* file or another process. When no explicit styling override exists, the sink reads it at
|
|
435
|
-
* 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
|
|
436
469
|
* text.
|
|
437
470
|
* - `columns` — the terminal width in character cells when the stream is a TTY, `undefined`
|
|
438
471
|
* otherwise; the sink surfaces it as {@link ServerSinkInterface.columns} so a consumer can feed a
|
|
@@ -445,8 +478,9 @@ export declare interface StreamTargetInterface {
|
|
|
445
478
|
}
|
|
446
479
|
|
|
447
480
|
/**
|
|
448
|
-
* Names the completion callback `process.*.write` accepts as its last argument —
|
|
449
|
-
* callback shape
|
|
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.
|
|
450
484
|
*
|
|
451
485
|
* @remarks
|
|
452
486
|
* The capture wrapper forwards the callback verbatim to the mirror, so a caller's
|
|
@@ -455,11 +489,12 @@ export declare interface StreamTargetInterface {
|
|
|
455
489
|
export declare type StreamWriteCallback = (error?: Error | null) => void;
|
|
456
490
|
|
|
457
491
|
/**
|
|
458
|
-
* Names the process-stream `write` method a {@link ProcessCaptureInterface} snapshots and swaps
|
|
459
|
-
* the patch boundary — the write-side analogue of
|
|
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}.
|
|
460
495
|
*
|
|
461
496
|
* @remarks
|
|
462
|
-
*
|
|
497
|
+
* The type is the overloaded
|
|
463
498
|
* `(chunk, encoding?, callback?) => boolean` of `process.stdout.write` / `process.stderr.write`.
|
|
464
499
|
* Using the canonical type rather than a hand-rolled approximation keeps snapshot and restore
|
|
465
500
|
* exact and lets the wrapper assign cleanly. A {@link StreamLevel} (`'stdout' | 'stderr'`) is
|
package/dist/src/server/index.js
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
|
+
import { isFiniteNumber, isFunction, isNumber, isObject, isString, isUint8Array } from "@orkestrel/contract";
|
|
1
2
|
import { StringDecoder } from "node:string_decoder";
|
|
2
3
|
import { Emitter } from "@orkestrel/emitter";
|
|
3
4
|
import { Retention, selectWriter, strip, stripControls } from "../core/index.js";
|
|
4
5
|
//#region src/server/constants.ts
|
|
5
6
|
/**
|
|
6
|
-
* Lists the
|
|
7
|
-
* `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured
|
|
7
|
+
* Lists the process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept,
|
|
8
|
+
* in `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured
|
|
9
|
+
* set.
|
|
8
10
|
*/
|
|
9
11
|
var STREAM_LEVELS = Object.freeze(["stdout", "stderr"]);
|
|
10
12
|
/**
|
|
11
|
-
* Sets the default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} —
|
|
12
|
-
* most
|
|
13
|
-
* total buffer and each per-stream bucket; oldest dropped first)
|
|
14
|
-
*
|
|
13
|
+
* Sets the default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} —
|
|
14
|
+
* `1000`, so at most that many recent {@link import('./types.js').CapturedChunk}s are retained per
|
|
15
|
+
* buffer (the total buffer and each per-stream bucket; oldest dropped first) and retention is
|
|
16
|
+
* always bounded.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* It mirrors the core `Capture`'s `DEFAULT_CAPTURE_LIMIT`; a consumer overrides the cap through
|
|
20
|
+
* `options.limit`.
|
|
15
21
|
*/
|
|
16
22
|
var DEFAULT_STREAM_LIMIT = 1e3;
|
|
17
23
|
/**
|
|
18
24
|
* Sets the terminal width {@link import('./factories.js').createServerSink} reports through
|
|
19
|
-
* {@link import('./types.js').ServerSinkInterface.columns} when the `stdout` stream is not a TTY
|
|
20
|
-
* `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
|
|
25
|
+
* {@link import('./types.js').ServerSinkInterface.columns} when the `stdout` stream is not a TTY
|
|
26
|
+
* (so `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
|
|
21
27
|
* 80-column default a non-interactive context (a pipe, a CI log) assumes.
|
|
22
28
|
*/
|
|
23
29
|
var DEFAULT_COLUMNS = 80;
|
|
24
30
|
/**
|
|
25
|
-
* Maps each {@link StreamLevel} to its {@link LogLevel} for the optional sink forward — the
|
|
26
|
-
* process capture routes through when writing an intercepted chunk to a
|
|
27
|
-
* {@link import('@src/core').SinkInterface}
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
+
* Maps each {@link StreamLevel} to its {@link LogLevel} for the optional sink forward — the
|
|
32
|
+
* projection a process capture routes through when writing an intercepted chunk to a
|
|
33
|
+
* {@link import('@src/core').SinkInterface}. `sink.write(text, STREAM_LEVEL_MAP[level])` is the
|
|
34
|
+
* call this map backs. `stderr` is conventionally the error/diagnostic stream → `error`; `stdout`
|
|
35
|
+
* is the normal output stream → `info`. The source of truth for the stream-to-log projection (the
|
|
36
|
+
* server analogue of the core `CAPTURE_LEVEL_MAP`).
|
|
31
37
|
*/
|
|
32
38
|
var STREAM_LEVEL_MAP = Object.freeze({
|
|
33
39
|
stdout: "info",
|
|
@@ -57,25 +63,26 @@ var STREAM_LEVEL_MAP = Object.freeze({
|
|
|
57
63
|
* ```
|
|
58
64
|
*/
|
|
59
65
|
function isStreamTarget(value) {
|
|
60
|
-
return
|
|
66
|
+
return isObject(value) && "write" in value && isFunction(value.write);
|
|
61
67
|
}
|
|
62
68
|
/**
|
|
63
|
-
* Checks whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a
|
|
64
|
-
* guard used by {@link import('./helpers.js').decodeChunk} to honor a process-write
|
|
65
|
-
* argument only when it is a real Node encoding (otherwise utf-8 is assumed).
|
|
69
|
+
* Checks whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a
|
|
70
|
+
* total guard used by {@link import('./helpers.js').decodeChunk} to honor a process-write
|
|
71
|
+
* `encoding` argument only when it is a real Node encoding (otherwise utf-8 is assumed).
|
|
66
72
|
*
|
|
67
73
|
* @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
|
|
68
74
|
* @returns True if `encoding` names a supported buffer encoding; false otherwise
|
|
69
75
|
*/
|
|
70
76
|
function isBufferEncoding(encoding) {
|
|
71
|
-
return
|
|
77
|
+
return isString(encoding) && Buffer.isEncoding(encoding);
|
|
72
78
|
}
|
|
73
79
|
//#endregion
|
|
74
80
|
//#region src/server/helpers.ts
|
|
75
81
|
/**
|
|
76
|
-
* Infers the width in character cells of a stream target — its live `columns` when it is a TTY,
|
|
77
|
-
* the non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a
|
|
78
|
-
* reports through `columns` so a `Reporter` /
|
|
82
|
+
* Infers the width in character cells of a stream target — its live `columns` when it is a TTY,
|
|
83
|
+
* else the non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a
|
|
84
|
+
* {@link import('./types.js').ServerSinkInterface} reports through `columns` so a `Reporter` /
|
|
85
|
+
* `Progress` can size its layout to the terminal.
|
|
79
86
|
*
|
|
80
87
|
* @remarks
|
|
81
88
|
* Reads `target.columns` on each call (so a getter-backed real stream reflects a live resize) and
|
|
@@ -87,18 +94,18 @@ function isBufferEncoding(encoding) {
|
|
|
87
94
|
*/
|
|
88
95
|
function inferColumns(target) {
|
|
89
96
|
const columns = target.columns;
|
|
90
|
-
if (
|
|
97
|
+
if (isFiniteNumber(columns) && columns > 0) return columns;
|
|
91
98
|
return 80;
|
|
92
99
|
}
|
|
93
100
|
/**
|
|
94
|
-
* Infers whether one stream target receives styled output
|
|
95
|
-
*
|
|
96
|
-
* reads process globals itself.
|
|
101
|
+
* Infers whether one stream target receives styled output — a present `FORCE_COLOR` first, then a
|
|
102
|
+
* non-empty `NO_COLOR`, then `target.isTTY === true`.
|
|
97
103
|
*
|
|
98
104
|
* @remarks
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
105
|
+
* The result is a construction-time target fact for
|
|
106
|
+
* {@link import('./factories.js').createServerSink}; this helper is pure and
|
|
107
|
+
* never reads process globals itself. Under `FORCE_COLOR` only the exact value `'0'` disables
|
|
108
|
+
* styling.
|
|
102
109
|
*
|
|
103
110
|
* @param target - The stream target whose terminal capability is the fallback
|
|
104
111
|
* @param environment - The environment record supplying `FORCE_COLOR` and `NO_COLOR`
|
|
@@ -147,10 +154,10 @@ function inferStyled(target, environment) {
|
|
|
147
154
|
* ```
|
|
148
155
|
*/
|
|
149
156
|
function decodeChunk(chunk, encoding) {
|
|
150
|
-
if (
|
|
157
|
+
if (isString(chunk)) return chunk;
|
|
151
158
|
try {
|
|
152
159
|
if (Buffer.isBuffer(chunk)) return chunk.toString(isBufferEncoding(encoding) ? encoding : "utf8");
|
|
153
|
-
if (chunk
|
|
160
|
+
if (isUint8Array(chunk)) return new TextDecoder().decode(chunk);
|
|
154
161
|
return String(chunk);
|
|
155
162
|
} catch {
|
|
156
163
|
return "[unprintable]";
|
|
@@ -281,7 +288,7 @@ var ProcessCapture = class {
|
|
|
281
288
|
return mirror(chunk, encoding, callback);
|
|
282
289
|
}
|
|
283
290
|
#decode(level, chunk, encoding) {
|
|
284
|
-
if (
|
|
291
|
+
if (isString(chunk)) return chunk;
|
|
285
292
|
const decoder = this.#decoders.get(level);
|
|
286
293
|
if (decoder !== void 0 && this.#streams(encoding)) return decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
287
294
|
return decodeChunk(chunk, encoding);
|
|
@@ -314,11 +321,10 @@ var ProcessCapture = class {
|
|
|
314
321
|
//#endregion
|
|
315
322
|
//#region src/server/factories.ts
|
|
316
323
|
/**
|
|
317
|
-
* Creates the server TTY {@link ServerSinkInterface} — the server output backend,
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* {@link import('@src/core').strip}s ANSI to clean text for a plain target.
|
|
324
|
+
* Creates the server TTY {@link ServerSinkInterface} — the server output backend, whose
|
|
325
|
+
* `write(text, level?)` routes by level to the process streams and uses construction-time styled
|
|
326
|
+
* facts: it sends ANSI straight to a styled target (with a leading `\r` overwriting a terminal
|
|
327
|
+
* line natively) but {@link import('@src/core').strip}s ANSI to clean text for a plain target.
|
|
322
328
|
*
|
|
323
329
|
* @param options - See {@link ServerSinkOptions}
|
|
324
330
|
* @returns A {@link ServerSinkInterface} — a {@link import('@src/core').SinkInterface} that also
|
|
@@ -344,16 +350,29 @@ var ProcessCapture = class {
|
|
|
344
350
|
* the sink (and the isTTY-strip path) with a fake stream that never touches the real process
|
|
345
351
|
* streams.
|
|
346
352
|
*
|
|
347
|
-
* @example
|
|
353
|
+
* @example The server — a TTY sink and a process capture
|
|
348
354
|
* ```ts
|
|
349
355
|
* import { createStyler, Logger, Reporter } from '@orkestrel/console'
|
|
350
|
-
* import { createServerSink } from '@orkestrel/console/server'
|
|
356
|
+
* import { createServerSink, ProcessCapture } from '@orkestrel/console/server'
|
|
357
|
+
*
|
|
358
|
+
* const sink = createServerSink() // FORCE_COLOR, then NO_COLOR, then isTTY — per target, at construction
|
|
359
|
+
* const styler = createStyler({ enabled: sink.styled }) // keep generated ANSI paired with the sink's stdout stripping
|
|
360
|
+
* const logger = new Logger({ name: 'server', sink, styler })
|
|
361
|
+
* logger.error('boom') // → process.stderr (the error stream)
|
|
362
|
+
* const reporter = new Reporter({ sink, width: sink.columns }) // size the layout to the live terminal
|
|
351
363
|
*
|
|
352
|
-
*
|
|
353
|
-
* const
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
364
|
+
* // `styled` overrides the inference outright — for a CI log that renders ANSI off a TTY, say.
|
|
365
|
+
* const forced = createServerSink({ styled: true })
|
|
366
|
+
* forced.styled // true, whatever the environment and the streams say
|
|
367
|
+
*
|
|
368
|
+
* // Own every output path — a direct process.stdout.write, library output, child-process pipes:
|
|
369
|
+
* const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
|
|
370
|
+
* capture.start()
|
|
371
|
+
* process.stderr.write('a library diagnostic\n') // captured and still shown (mirror: true)
|
|
372
|
+
* capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
|
|
373
|
+
* capture.clear() // drop buffered chunks; interception is unaffected
|
|
374
|
+
* capture.stop()
|
|
375
|
+
* capture.destroy() // stop() then tear down the emitter
|
|
357
376
|
* ```
|
|
358
377
|
*/
|
|
359
378
|
function createServerSink(options) {
|
|
@@ -381,7 +400,7 @@ function createServerSink(options) {
|
|
|
381
400
|
target.write(keep ? line : stripControls(strip(line)));
|
|
382
401
|
},
|
|
383
402
|
get columns() {
|
|
384
|
-
return
|
|
403
|
+
return isNumber(fixed) ? fixed : inferColumns(out);
|
|
385
404
|
}
|
|
386
405
|
});
|
|
387
406
|
}
|