@orkestrel/console 0.0.11 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -15
- package/dist/src/browser/index.d.ts +57 -55
- package/dist/src/browser/index.js +52 -62
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +922 -1114
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +665 -685
- package/dist/src/core/index.d.ts +665 -685
- package/dist/src/core/index.js +919 -1105
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +90 -131
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +147 -159
- package/dist/src/server/index.d.ts +147 -159
- package/dist/src/server/index.js +90 -129
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @orkestrel/console
|
|
2
2
|
|
|
3
3
|
A unified output-control system for the `@orkestrel` line — one
|
|
4
|
-
environment-agnostic engine composing
|
|
4
|
+
environment-agnostic engine composing style, logging, reporting, capture, and
|
|
5
|
+
animation over a shared substrate:
|
|
5
6
|
a **style engine** (`Styler` + `ANSIRenderer`, style as data), **structured
|
|
6
7
|
logging** (`Logger`, `LoggerManager`), **narrative reporting** (`Reporter`),
|
|
7
8
|
**console & stream capture** (`Capture`, `ProcessCapture`), and **live
|
|
@@ -25,27 +26,27 @@ npm install @orkestrel/console
|
|
|
25
26
|
The same code retargets to any environment by swapping the `sink`:
|
|
26
27
|
|
|
27
28
|
```ts
|
|
28
|
-
import {
|
|
29
|
+
import { Logger, Reporter, Spinner } from '@orkestrel/console'
|
|
29
30
|
|
|
30
|
-
const logger =
|
|
31
|
+
const logger = new Logger({ name: 'http', level: 'info' }) // ANSI to the console by default
|
|
31
32
|
logger.info('request', { method: 'GET', path: '/' }) // a styled, leveled line + an `entry` event
|
|
32
33
|
logger.emitter.on('entry', (record) => archive(record)) // the transport seam — file / JSON / remote
|
|
33
34
|
|
|
34
|
-
const reporter =
|
|
35
|
+
const reporter = new Reporter()
|
|
35
36
|
reporter.section('Build')
|
|
36
37
|
reporter.step('bundling', { index: 2, total: 5 }) // [2/5] bundling
|
|
37
38
|
reporter.status('success', 'built in 1.2s') // ✔ built in 1.2s
|
|
38
39
|
|
|
39
|
-
const spinner =
|
|
40
|
+
const spinner = new Spinner({ message: 'deploying' })
|
|
40
41
|
spinner.start() // a self-driving glyph cycle, `\r`-redrawn by an overwrite-capable sink
|
|
41
|
-
spinner.
|
|
42
|
+
spinner.succeed('deployed') // ✔ deployed — the timer cleared, the line committed
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
Style is data — a `Style` is a frozen record rendered through a swappable
|
|
45
46
|
`RendererInterface` (`ANSIRenderer` by default):
|
|
46
47
|
|
|
47
48
|
```ts
|
|
48
|
-
import { createStyler } from '@
|
|
49
|
+
import { createStyler } from '@orkestrel/console'
|
|
49
50
|
|
|
50
51
|
const styler = createStyler()
|
|
51
52
|
console.log(styler.red.bold('hi')) // renders through the injected renderer
|
|
@@ -54,9 +55,9 @@ console.log(styler.red.bold('hi')) // renders through the injected renderer
|
|
|
54
55
|
Take control of `console.*` on the read side with `Capture`:
|
|
55
56
|
|
|
56
57
|
```ts
|
|
57
|
-
import {
|
|
58
|
+
import { Capture } from '@orkestrel/console'
|
|
58
59
|
|
|
59
|
-
const capture =
|
|
60
|
+
const capture = new Capture({ mirror: true })
|
|
60
61
|
capture.start()
|
|
61
62
|
console.log('hello')
|
|
62
63
|
capture.messages() // [{ level: 'log', text: 'hello', time: ... }]
|
|
@@ -64,24 +65,25 @@ capture.stop()
|
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
On the server, `ProcessCapture` takes over the whole `process` output surface
|
|
67
|
-
(direct `process.stdout`/`stderr` writes, not
|
|
68
|
+
(direct `process.stdout`/`stderr` writes, not `console.*`):
|
|
68
69
|
|
|
69
70
|
```ts
|
|
70
|
-
import {
|
|
71
|
+
import { ProcessCapture } from '@orkestrel/console/server'
|
|
71
72
|
|
|
72
|
-
const capture =
|
|
73
|
+
const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
|
|
73
74
|
capture.start()
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
## Guide
|
|
77
78
|
|
|
78
|
-
See [guides/
|
|
79
|
+
See [guides/console.md](./guides/console.md) for the full documented
|
|
79
80
|
surface — styling, logging, reporting, capture, and animations.
|
|
80
81
|
|
|
81
82
|
## Package
|
|
82
83
|
|
|
83
|
-
Published as
|
|
84
|
-
in `package.json`: `.` (the shared,
|
|
84
|
+
Published as the `.`, `./server`, and `./browser` environment-scoped entry
|
|
85
|
+
points per the `exports` field in `package.json`: `.` (the shared,
|
|
86
|
+
environment-agnostic core engine, the
|
|
85
87
|
default ANSI renderer, and the console sink), `./server` (adds the server
|
|
86
88
|
sink and `ProcessCapture`), and `./browser` (adds the browser sink
|
|
87
89
|
translating ANSI to `console.log('%c…', css)`). Core and `./server` ship
|
|
@@ -3,22 +3,22 @@ import { Color } from '@orkestrel/console';
|
|
|
3
3
|
import { SinkInterface } from '@orkestrel/console';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
|
|
7
7
|
* `%c`-segmented format string and the parallel array of CSS declarations, so a DevTools console
|
|
8
|
-
* renders the
|
|
8
|
+
* renders the same styling a terminal would (the browser sink calls `console[method](format, ...styles)`).
|
|
9
9
|
*
|
|
10
10
|
* @remarks
|
|
11
11
|
* - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —
|
|
12
|
-
* `ESC[…m`); each delimits a run. A run carrying
|
|
12
|
+
* `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that
|
|
13
13
|
* text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at
|
|
14
14
|
* each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare
|
|
15
|
-
* `ESC[m`) clears the accumulated style back to none. A later color of the same channel
|
|
15
|
+
* `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces
|
|
16
16
|
* the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style
|
|
17
17
|
* and are left in the text verbatim.
|
|
18
|
-
* - **`%`-safe.** Every
|
|
18
|
+
* - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as
|
|
19
19
|
* a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`
|
|
20
20
|
* count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.
|
|
21
|
-
* - **Plain text short-circuits.** A string with
|
|
21
|
+
* - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,
|
|
22
22
|
* styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).
|
|
23
23
|
* - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every
|
|
24
24
|
* omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and
|
|
@@ -40,11 +40,11 @@ import { SinkInterface } from '@orkestrel/console';
|
|
|
40
40
|
export declare function ansiToConsole(text: string, palette?: BrowserPalette): ConsoleOutput;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Maps each text-{@link Attribute}'s SGR "on" number to its equivalent CSS declaration — the browser
|
|
44
44
|
* counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2 →
|
|
45
45
|
* `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,
|
|
46
46
|
* `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`). Keyed by the SGR
|
|
47
|
-
*
|
|
47
|
+
* number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
|
|
48
48
|
* while scanning a run.
|
|
49
49
|
*
|
|
50
50
|
* @remarks
|
|
@@ -55,7 +55,7 @@ export declare function ansiToConsole(text: string, palette?: BrowserPalette): C
|
|
|
55
55
|
export declare const ATTRIBUTE_CSS: Readonly<Record<number, string>>;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Holds partial browser CSS overrides for the core color and attribute axes. Omitted entries retain the
|
|
59
59
|
* built-in browser mappings, so one override changes only its named value.
|
|
60
60
|
*
|
|
61
61
|
* @remarks
|
|
@@ -69,7 +69,7 @@ export declare interface BrowserPalette {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Configures {@link import('./factories.js').createBrowserSink}.
|
|
73
73
|
*
|
|
74
74
|
* @remarks
|
|
75
75
|
* `palette` partially overrides the browser's default color and attribute CSS mappings.
|
|
@@ -79,8 +79,8 @@ export declare interface BrowserSinkOptions {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
*
|
|
83
|
-
* console renders the
|
|
82
|
+
* Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools
|
|
83
|
+
* console renders the same {@link Color} names as. The source of truth for the browser color
|
|
84
84
|
* axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to
|
|
85
85
|
* the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.
|
|
86
86
|
*
|
|
@@ -91,20 +91,20 @@ export declare interface BrowserSinkOptions {
|
|
|
91
91
|
export declare const COLOR_HEX: Readonly<Record<Exclude<Color, 'default'>, string>>;
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* Represents the `console.log`-ready output {@link import('./helpers.js').ansiToConsole} produces from
|
|
95
95
|
* an ANSI-styled string — a format string of `%c`-prefixed segments and the parallel array
|
|
96
96
|
* of CSS declarations, ready to spread into a browser `console` call as
|
|
97
97
|
* `console.log(format, ...styles)`.
|
|
98
98
|
*
|
|
99
99
|
* @remarks
|
|
100
100
|
* - `format` — the text with each styled run prefixed by one `%c` directive (the directive
|
|
101
|
-
* the browser console consumes to switch the active style) and every
|
|
101
|
+
* the browser console consumes to switch the active style) and every literal `%` doubled to
|
|
102
102
|
* `%%` so it is not mistaken for a directive. A plain (no-ANSI) input yields the text
|
|
103
|
-
* verbatim with
|
|
103
|
+
* verbatim with no `%c` and an empty `styles` (still `%`-escaped).
|
|
104
104
|
* - `styles` — one CSS declaration string per `%c` in `format`, in order: the browser applies
|
|
105
105
|
* `styles[n]` from the n-th `%c` onward. Each entry is the accumulated style for that run
|
|
106
|
-
* (an SGR reset clears it back to
|
|
107
|
-
* so the spread `console.log(format, ...styles)` lines up exactly.
|
|
106
|
+
* (an SGR reset clears it back to the empty declaration string). `format`'s `%c` count always
|
|
107
|
+
* equals `styles.length`, so the spread `console.log(format, ...styles)` lines up exactly.
|
|
108
108
|
*/
|
|
109
109
|
export declare interface ConsoleOutput {
|
|
110
110
|
readonly format: string;
|
|
@@ -112,10 +112,10 @@ export declare interface ConsoleOutput {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`
|
|
116
116
|
* translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)
|
|
117
|
-
*
|
|
118
|
-
* as a logger / reporter / spinner sink (`
|
|
117
|
+
* through {@link ansiToConsole}, so a DevTools console renders the same styling a terminal does. Drop it in
|
|
118
|
+
* as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`) to retarget the
|
|
119
119
|
* core output to the browser console with no change to the core.
|
|
120
120
|
*
|
|
121
121
|
* @param options - See {@link BrowserSinkOptions}
|
|
@@ -127,38 +127,39 @@ export declare interface ConsoleOutput {
|
|
|
127
127
|
* — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.
|
|
128
128
|
* `options.palette` supplies partial named color and attribute overrides to that translation.
|
|
129
129
|
* - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an
|
|
130
|
-
* omitted level) → `console.log` — the
|
|
131
|
-
* reaches the matching DevTools stream.
|
|
130
|
+
* omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level
|
|
131
|
+
* reaches the matching DevTools stream. Both call the one
|
|
132
|
+
* {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.
|
|
132
133
|
* - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with
|
|
133
|
-
* a carriage return `\r` (a spinner / progress redraw) has the leading `\r`
|
|
134
|
-
* fresh, non-overwriting line — the locked browser degrade. Only a
|
|
134
|
+
* a carriage return `\r` (a spinner / progress redraw) has the leading `\r` stripped and is written as a
|
|
135
|
+
* fresh, non-overwriting line — the locked browser degrade. Only a leading `\r` is stripped; an interior
|
|
135
136
|
* one is left to the console.
|
|
136
|
-
* - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error`
|
|
137
|
-
*
|
|
138
|
-
* feed this sink's output back into itself (the no-capture-loop principle,
|
|
139
|
-
* precedent). Create the sink (or the logger)
|
|
137
|
+
* - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at
|
|
138
|
+
* creation and writes through those references, so a later `Capture` that patches `console.*` can never
|
|
139
|
+
* feed this sink's output back into itself (the no-capture-loop principle, following the core
|
|
140
|
+
* sink's precedent). Create the sink (or the logger) before installing a capture.
|
|
140
141
|
*
|
|
141
142
|
* @example
|
|
142
143
|
* ```ts
|
|
143
|
-
* import {
|
|
144
|
-
* import { createBrowserSink } from '@
|
|
144
|
+
* import { Logger } from '@orkestrel/console'
|
|
145
|
+
* import { createBrowserSink } from '@orkestrel/console/browser'
|
|
145
146
|
*
|
|
146
|
-
* const logger =
|
|
147
|
+
* const logger = new Logger({ name: 'app', sink: createBrowserSink() })
|
|
147
148
|
* logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools
|
|
148
149
|
* ```
|
|
149
150
|
*/
|
|
150
151
|
export declare function createBrowserSink(options?: BrowserSinkOptions): SinkInterface;
|
|
151
152
|
|
|
152
153
|
/**
|
|
153
|
-
*
|
|
154
|
+
* Names the browser console directive that switches the active style — one `%c` prefixes every styled run
|
|
154
155
|
* in the {@link import('./types.js').ConsoleOutput} format string, consuming the next entry of the
|
|
155
156
|
* parallel CSS array. The single source of truth for the directive token.
|
|
156
157
|
*/
|
|
157
158
|
export declare const DIRECTIVE = "%c";
|
|
158
159
|
|
|
159
160
|
/**
|
|
160
|
-
*
|
|
161
|
-
* reading a stray `%` (
|
|
161
|
+
* Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
|
|
162
|
+
* reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the
|
|
162
163
|
* {@link ansiToConsole} translation applies to every text segment before assembling the format
|
|
163
164
|
* string (so only the `%c`s it inserts are real directives).
|
|
164
165
|
*
|
|
@@ -173,56 +174,57 @@ export declare const DIRECTIVE = "%c";
|
|
|
173
174
|
export declare function escapePercent(text: string): string;
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* likewise counts as a `0` reset,
|
|
177
|
+
* Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
|
|
178
|
+
* and returns its numeric codes — `'1;31'` → `[1, 31]`. It is total: every input yields a code
|
|
179
|
+
* list. An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless
|
|
180
|
+
* sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,
|
|
181
|
+
* matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.
|
|
180
182
|
*
|
|
181
183
|
* @param parameters - The raw `;`-separated parameter string (the regex capture)
|
|
182
|
-
* @returns The
|
|
184
|
+
* @returns The SGR codes found (a parameterless / empty field becoming `0`)
|
|
183
185
|
*
|
|
184
186
|
* @example
|
|
185
187
|
* ```ts
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
+
* scanParameters('1;31') // [1, 31]
|
|
189
|
+
* scanParameters('') // [0]
|
|
188
190
|
* ```
|
|
189
191
|
*/
|
|
190
|
-
export declare function
|
|
192
|
+
export declare function scanParameters(parameters: string): readonly number[];
|
|
191
193
|
|
|
192
194
|
/**
|
|
193
|
-
* Matches one SGR sequence (`ESC[ <params> m`) and
|
|
194
|
-
* the subset of ANSI {@link import('@src/core').strip} cares about that carries
|
|
195
|
+
* Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —
|
|
196
|
+
* the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /
|
|
195
197
|
* attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks
|
|
196
198
|
* every SGR run in a string; built from core's {@link ESC} so no control-character literal appears
|
|
197
199
|
* in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,
|
|
198
200
|
* which the spec treats as a reset).
|
|
199
201
|
*
|
|
200
202
|
* @remarks
|
|
201
|
-
* A global `RegExp` carries a mutable `lastIndex`; a scan builds a
|
|
203
|
+
* A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's
|
|
202
204
|
* `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the
|
|
203
205
|
* canonical definition, not a shared scanner.
|
|
204
206
|
*/
|
|
205
207
|
export declare const SGR_PATTERN: RegExp;
|
|
206
208
|
|
|
207
209
|
/**
|
|
208
|
-
*
|
|
210
|
+
* Represents the immutable accumulator {@link import('./helpers.js').ansiToConsole} carries across a run while
|
|
209
211
|
* translating SGR codes to CSS — a single `foreground` and `background` declaration (each channel
|
|
210
|
-
*
|
|
211
|
-
* declarations. An SGR reset
|
|
212
|
-
* it into the `;`-joined CSS string a run emits.
|
|
212
|
+
* replaceable by a later color of the same channel) plus an ordered, de-duplicated list of attribute
|
|
213
|
+
* declarations. An SGR reset drops both channels and empties the list;
|
|
214
|
+
* {@link import('./helpers.js').ansiToConsole} folds it into the `;`-joined CSS string a run emits.
|
|
213
215
|
*
|
|
214
216
|
* @remarks
|
|
215
217
|
* Each SGR sequence produces a new frozen value; earlier run snapshots never drift when a later
|
|
216
|
-
* sequence changes a channel. A channel holds the
|
|
217
|
-
* bare hex),
|
|
218
|
-
* - `foreground` — the current `color:<hex>` declaration
|
|
219
|
-
* - `background` — the current `background:<hex>` declaration
|
|
218
|
+
* sequence changes a channel. A channel holds the full CSS declaration (`'color:#cd0000'`, not a
|
|
219
|
+
* bare hex), and is absent when unset.
|
|
220
|
+
* - `foreground` — the current `color:<hex>` declaration; absent when unset or after a reset.
|
|
221
|
+
* - `background` — the current `background:<hex>` declaration; absent on the same terms.
|
|
220
222
|
* - `attributes` — the active text-effect declarations in insertion order (`'font-weight:bold'`, …),
|
|
221
223
|
* each present at most once.
|
|
222
224
|
*/
|
|
223
225
|
export declare interface StyleAccumulator {
|
|
224
|
-
readonly foreground
|
|
225
|
-
readonly background
|
|
226
|
+
readonly foreground?: string;
|
|
227
|
+
readonly background?: string;
|
|
226
228
|
readonly attributes: readonly string[];
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ATTRIBUTES, ATTRIBUTE_CODES, BACKGROUND_CODES, COLORS, ESC, FOREGROUND_CODES, RESET_CODE } from "../core/index.js";
|
|
1
|
+
import { ATTRIBUTES, ATTRIBUTE_CODES, BACKGROUND_CODES, COLORS, ESC, FOREGROUND_CODES, RESET_CODE, selectWriter } from "../core/index.js";
|
|
2
2
|
//#region src/browser/constants.ts
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* console renders the
|
|
4
|
+
* Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools
|
|
5
|
+
* console renders the same {@link Color} names as. The source of truth for the browser color
|
|
6
6
|
* axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to
|
|
7
7
|
* the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.
|
|
8
8
|
*
|
|
@@ -29,11 +29,11 @@ var COLOR_HEX = Object.freeze({
|
|
|
29
29
|
brightWhite: "#ffffff"
|
|
30
30
|
});
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Maps each text-{@link Attribute}'s SGR "on" number to its equivalent CSS declaration — the browser
|
|
33
33
|
* counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2 →
|
|
34
34
|
* `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,
|
|
35
35
|
* `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`). Keyed by the SGR
|
|
36
|
-
*
|
|
36
|
+
* number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
|
|
37
37
|
* while scanning a run.
|
|
38
38
|
*
|
|
39
39
|
* @remarks
|
|
@@ -50,21 +50,21 @@ var ATTRIBUTE_CSS = Object.freeze({
|
|
|
50
50
|
[ATTRIBUTE_CODES.strikethrough]: "text-decoration:line-through"
|
|
51
51
|
});
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Names the browser console directive that switches the active style — one `%c` prefixes every styled run
|
|
54
54
|
* in the {@link import('./types.js').ConsoleOutput} format string, consuming the next entry of the
|
|
55
55
|
* parallel CSS array. The single source of truth for the directive token.
|
|
56
56
|
*/
|
|
57
57
|
var DIRECTIVE = "%c";
|
|
58
58
|
/**
|
|
59
|
-
* Matches one SGR sequence (`ESC[ <params> m`) and
|
|
60
|
-
* the subset of ANSI {@link import('@src/core').strip} cares about that carries
|
|
59
|
+
* Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —
|
|
60
|
+
* the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /
|
|
61
61
|
* attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks
|
|
62
62
|
* every SGR run in a string; built from core's {@link ESC} so no control-character literal appears
|
|
63
63
|
* in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,
|
|
64
64
|
* which the spec treats as a reset).
|
|
65
65
|
*
|
|
66
66
|
* @remarks
|
|
67
|
-
* A global `RegExp` carries a mutable `lastIndex`; a scan builds a
|
|
67
|
+
* A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's
|
|
68
68
|
* `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the
|
|
69
69
|
* canonical definition, not a shared scanner.
|
|
70
70
|
*/
|
|
@@ -72,22 +72,22 @@ var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
|
|
|
72
72
|
//#endregion
|
|
73
73
|
//#region src/browser/helpers.ts
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
|
|
76
76
|
* `%c`-segmented format string and the parallel array of CSS declarations, so a DevTools console
|
|
77
|
-
* renders the
|
|
77
|
+
* renders the same styling a terminal would (the browser sink calls `console[method](format, ...styles)`).
|
|
78
78
|
*
|
|
79
79
|
* @remarks
|
|
80
80
|
* - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —
|
|
81
|
-
* `ESC[…m`); each delimits a run. A run carrying
|
|
81
|
+
* `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that
|
|
82
82
|
* text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at
|
|
83
83
|
* each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare
|
|
84
|
-
* `ESC[m`) clears the accumulated style back to none. A later color of the same channel
|
|
84
|
+
* `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces
|
|
85
85
|
* the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style
|
|
86
86
|
* and are left in the text verbatim.
|
|
87
|
-
* - **`%`-safe.** Every
|
|
87
|
+
* - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as
|
|
88
88
|
* a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`
|
|
89
89
|
* count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.
|
|
90
|
-
* - **Plain text short-circuits.** A string with
|
|
90
|
+
* - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,
|
|
91
91
|
* styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).
|
|
92
92
|
* - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every
|
|
93
93
|
* omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and
|
|
@@ -108,11 +108,7 @@ var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
|
|
|
108
108
|
*/
|
|
109
109
|
function ansiToConsole(text, palette) {
|
|
110
110
|
const scanner = new RegExp(SGR_PATTERN.source, SGR_PATTERN.flags);
|
|
111
|
-
let active = Object.freeze({
|
|
112
|
-
foreground: "",
|
|
113
|
-
background: "",
|
|
114
|
-
attributes: Object.freeze([])
|
|
115
|
-
});
|
|
111
|
+
let active = Object.freeze({ attributes: Object.freeze([]) });
|
|
116
112
|
const segments = [];
|
|
117
113
|
const styles = [];
|
|
118
114
|
let cursor = 0;
|
|
@@ -128,19 +124,15 @@ function ansiToConsole(text, palette) {
|
|
|
128
124
|
if (pending !== "") {
|
|
129
125
|
segments.push(`%c${pending}`);
|
|
130
126
|
const declarations = [...active.attributes];
|
|
131
|
-
if (active.foreground !==
|
|
132
|
-
if (active.background !==
|
|
127
|
+
if (active.foreground !== void 0) declarations.push(active.foreground);
|
|
128
|
+
if (active.background !== void 0) declarations.push(active.background);
|
|
133
129
|
styles.push(declarations.join(";"));
|
|
134
130
|
pending = "";
|
|
135
131
|
}
|
|
136
132
|
if (match === null) break;
|
|
137
|
-
for (const code of
|
|
133
|
+
for (const code of scanParameters(match[1] ?? "")) {
|
|
138
134
|
if (code === RESET_CODE) {
|
|
139
|
-
active = Object.freeze({
|
|
140
|
-
foreground: "",
|
|
141
|
-
background: "",
|
|
142
|
-
attributes: Object.freeze([])
|
|
143
|
-
});
|
|
135
|
+
active = Object.freeze({ attributes: Object.freeze([]) });
|
|
144
136
|
continue;
|
|
145
137
|
}
|
|
146
138
|
const foreground = COLORS.find((color) => FOREGROUND_CODES[color] === code);
|
|
@@ -177,8 +169,8 @@ function ansiToConsole(text, palette) {
|
|
|
177
169
|
};
|
|
178
170
|
}
|
|
179
171
|
/**
|
|
180
|
-
*
|
|
181
|
-
* reading a stray `%` (
|
|
172
|
+
* Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
|
|
173
|
+
* reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the
|
|
182
174
|
* {@link ansiToConsole} translation applies to every text segment before assembling the format
|
|
183
175
|
* string (so only the `%c`s it inserts are real directives).
|
|
184
176
|
*
|
|
@@ -194,31 +186,32 @@ function escapePercent(text) {
|
|
|
194
186
|
return text.replace(/%/g, "%%");
|
|
195
187
|
}
|
|
196
188
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* likewise counts as a `0` reset,
|
|
189
|
+
* Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
|
|
190
|
+
* and returns its numeric codes — `'1;31'` → `[1, 31]`. It is total: every input yields a code
|
|
191
|
+
* list. An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless
|
|
192
|
+
* sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,
|
|
193
|
+
* matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.
|
|
201
194
|
*
|
|
202
195
|
* @param parameters - The raw `;`-separated parameter string (the regex capture)
|
|
203
|
-
* @returns The
|
|
196
|
+
* @returns The SGR codes found (a parameterless / empty field becoming `0`)
|
|
204
197
|
*
|
|
205
198
|
* @example
|
|
206
199
|
* ```ts
|
|
207
|
-
*
|
|
208
|
-
*
|
|
200
|
+
* scanParameters('1;31') // [1, 31]
|
|
201
|
+
* scanParameters('') // [0]
|
|
209
202
|
* ```
|
|
210
203
|
*/
|
|
211
|
-
function
|
|
204
|
+
function scanParameters(parameters) {
|
|
212
205
|
if (parameters === "") return [RESET_CODE];
|
|
213
206
|
return parameters.split(";").map((field) => field === "" ? RESET_CODE : Number(field));
|
|
214
207
|
}
|
|
215
208
|
//#endregion
|
|
216
209
|
//#region src/browser/factories.ts
|
|
217
210
|
/**
|
|
218
|
-
*
|
|
211
|
+
* Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`
|
|
219
212
|
* translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)
|
|
220
|
-
*
|
|
221
|
-
* as a logger / reporter / spinner sink (`
|
|
213
|
+
* through {@link ansiToConsole}, so a DevTools console renders the same styling a terminal does. Drop it in
|
|
214
|
+
* as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`) to retarget the
|
|
222
215
|
* core output to the browser console with no change to the core.
|
|
223
216
|
*
|
|
224
217
|
* @param options - See {@link BrowserSinkOptions}
|
|
@@ -230,23 +223,24 @@ function parseParameters(parameters) {
|
|
|
230
223
|
* — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.
|
|
231
224
|
* `options.palette` supplies partial named color and attribute overrides to that translation.
|
|
232
225
|
* - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an
|
|
233
|
-
* omitted level) → `console.log` — the
|
|
234
|
-
* reaches the matching DevTools stream.
|
|
226
|
+
* omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level
|
|
227
|
+
* reaches the matching DevTools stream. Both call the one
|
|
228
|
+
* {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.
|
|
235
229
|
* - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with
|
|
236
|
-
* a carriage return `\r` (a spinner / progress redraw) has the leading `\r`
|
|
237
|
-
* fresh, non-overwriting line — the locked browser degrade. Only a
|
|
230
|
+
* a carriage return `\r` (a spinner / progress redraw) has the leading `\r` stripped and is written as a
|
|
231
|
+
* fresh, non-overwriting line — the locked browser degrade. Only a leading `\r` is stripped; an interior
|
|
238
232
|
* one is left to the console.
|
|
239
|
-
* - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error`
|
|
240
|
-
*
|
|
241
|
-
* feed this sink's output back into itself (the no-capture-loop principle,
|
|
242
|
-
* precedent). Create the sink (or the logger)
|
|
233
|
+
* - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at
|
|
234
|
+
* creation and writes through those references, so a later `Capture` that patches `console.*` can never
|
|
235
|
+
* feed this sink's output back into itself (the no-capture-loop principle, following the core
|
|
236
|
+
* sink's precedent). Create the sink (or the logger) before installing a capture.
|
|
243
237
|
*
|
|
244
238
|
* @example
|
|
245
239
|
* ```ts
|
|
246
|
-
* import {
|
|
247
|
-
* import { createBrowserSink } from '@
|
|
240
|
+
* import { Logger } from '@orkestrel/console'
|
|
241
|
+
* import { createBrowserSink } from '@orkestrel/console/browser'
|
|
248
242
|
*
|
|
249
|
-
* const logger =
|
|
243
|
+
* const logger = new Logger({ name: 'app', sink: createBrowserSink() })
|
|
250
244
|
* logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools
|
|
251
245
|
* ```
|
|
252
246
|
*/
|
|
@@ -256,18 +250,14 @@ function createBrowserSink(options) {
|
|
|
256
250
|
const error = console.error.bind(console);
|
|
257
251
|
return { write(text, level) {
|
|
258
252
|
const { format, styles } = ansiToConsole(text.startsWith("\r") ? text.slice(1) : text, options?.palette);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
warn(format, ...styles);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
log(format, ...styles);
|
|
253
|
+
selectWriter(level, {
|
|
254
|
+
log,
|
|
255
|
+
warn,
|
|
256
|
+
error
|
|
257
|
+
})(format, ...styles);
|
|
268
258
|
} };
|
|
269
259
|
}
|
|
270
260
|
//#endregion
|
|
271
|
-
export { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN, ansiToConsole, createBrowserSink, escapePercent,
|
|
261
|
+
export { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN, ansiToConsole, createBrowserSink, escapePercent, scanParameters };
|
|
272
262
|
|
|
273
263
|
//# sourceMappingURL=index.js.map
|