@orkestrel/console 0.0.15 → 0.0.17
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/dist/src/browser/index.d.ts +8 -0
- package/dist/src/browser/index.js +37 -45
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +76 -46
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +23 -0
- package/dist/src/core/index.d.ts +23 -0
- package/dist/src/core/index.js +76 -48
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +5 -10
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +5 -10
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -17
|
@@ -113,6 +113,14 @@ export declare interface ConsoleOutput {
|
|
|
113
113
|
readonly styles: readonly string[];
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/** Records the style and visible text at one ANSI scan boundary. */
|
|
117
|
+
export declare interface ConsoleSegment {
|
|
118
|
+
readonly active: StyleAccumulator;
|
|
119
|
+
readonly cursor: number;
|
|
120
|
+
readonly text: string;
|
|
121
|
+
readonly style: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
/**
|
|
117
125
|
* Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`
|
|
118
126
|
* translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ATTRIBUTES, ATTRIBUTE_CODES, BACKGROUND_CODES, COLORS, ESC, FOREGROUND_CODES, RESET_CODE, selectWriter } from "../core/index.js";
|
|
2
|
-
|
|
2
|
+
// src/browser/constants.ts
|
|
3
3
|
/**
|
|
4
4
|
* Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools
|
|
5
5
|
* console renders the same {@link Color} names as. The source of truth for the browser color
|
|
@@ -70,8 +70,7 @@ var DIRECTIVE = "%c";
|
|
|
70
70
|
* canonical definition, not a shared scanner.
|
|
71
71
|
*/
|
|
72
72
|
var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
|
|
73
|
-
|
|
74
|
-
//#region src/browser/helpers.ts
|
|
73
|
+
// src/browser/helpers.ts
|
|
75
74
|
/**
|
|
76
75
|
* Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
|
|
77
76
|
* `%c`-segmented format string and the parallel array of CSS declarations, an optional partial
|
|
@@ -111,65 +110,60 @@ var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
|
|
|
111
110
|
* ```
|
|
112
111
|
*/
|
|
113
112
|
function ansiToConsole(text, palette) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const segments = [];
|
|
117
|
-
const styles = [];
|
|
118
|
-
let cursor = 0;
|
|
119
|
-
let pending = "";
|
|
120
|
-
let match = scanner.exec(text);
|
|
121
|
-
if (match === null) return {
|
|
113
|
+
const matches = [...text.matchAll(new RegExp(SGR_PATTERN.source, SGR_PATTERN.flags))];
|
|
114
|
+
if (matches.length === 0) return {
|
|
122
115
|
format: escapePercent(text),
|
|
123
116
|
styles: []
|
|
124
117
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
118
|
+
const initial = Object.freeze({ attributes: Object.freeze([]) });
|
|
119
|
+
const output = [];
|
|
120
|
+
for (const match of [...matches, void 0]) {
|
|
121
|
+
const previous = output.at(-1) ?? {
|
|
122
|
+
active: initial,
|
|
123
|
+
cursor: 0
|
|
124
|
+
};
|
|
125
|
+
const segment = escapePercent(text.slice(previous.cursor, match?.index ?? text.length));
|
|
126
|
+
const declarations = [
|
|
127
|
+
...previous.active.attributes,
|
|
128
|
+
...previous.active.foreground === void 0 ? [] : [previous.active.foreground],
|
|
129
|
+
...previous.active.background === void 0 ? [] : [previous.active.background]
|
|
130
|
+
];
|
|
131
|
+
const active = scanParameters(match?.[1] ?? "").reduce((style, code) => {
|
|
132
|
+
if (code === RESET_CODE) return initial;
|
|
142
133
|
const foreground = COLORS.find((color) => FOREGROUND_CODES[color] === code);
|
|
143
134
|
if (foreground !== void 0) {
|
|
144
135
|
const color = palette?.color?.[foreground] ?? COLOR_HEX[foreground];
|
|
145
|
-
|
|
146
|
-
...
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
...style,
|
|
147
138
|
foreground: `color:${color}`
|
|
148
139
|
});
|
|
149
|
-
continue;
|
|
150
140
|
}
|
|
151
141
|
const background = COLORS.find((color) => BACKGROUND_CODES[color] === code);
|
|
152
142
|
if (background !== void 0) {
|
|
153
143
|
const color = palette?.color?.[background] ?? COLOR_HEX[background];
|
|
154
|
-
|
|
155
|
-
...
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
...style,
|
|
156
146
|
background: `background:${color}`
|
|
157
147
|
});
|
|
158
|
-
continue;
|
|
159
148
|
}
|
|
160
149
|
const name = ATTRIBUTES.find((attribute) => ATTRIBUTE_CODES[attribute] === code);
|
|
161
150
|
const attribute = name === void 0 ? void 0 : palette?.attribute?.[name] ?? ATTRIBUTE_CSS[code];
|
|
162
|
-
|
|
163
|
-
...
|
|
164
|
-
attributes: Object.freeze([...
|
|
151
|
+
return attribute === void 0 || style.attributes.includes(attribute) ? style : Object.freeze({
|
|
152
|
+
...style,
|
|
153
|
+
attributes: Object.freeze([...style.attributes, attribute])
|
|
165
154
|
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
155
|
+
}, previous.active);
|
|
156
|
+
output.push({
|
|
157
|
+
active,
|
|
158
|
+
cursor: match === void 0 ? text.length : match.index + match[0].length,
|
|
159
|
+
text: segment,
|
|
160
|
+
style: declarations.join(";")
|
|
161
|
+
});
|
|
169
162
|
}
|
|
163
|
+
const visible = output.filter((segment) => segment.text !== "");
|
|
170
164
|
return {
|
|
171
|
-
format:
|
|
172
|
-
styles
|
|
165
|
+
format: visible.map((segment) => `%c${segment.text}`).join(""),
|
|
166
|
+
styles: visible.map((segment) => segment.style)
|
|
173
167
|
};
|
|
174
168
|
}
|
|
175
169
|
/**
|
|
@@ -212,8 +206,7 @@ function scanParameters(parameters) {
|
|
|
212
206
|
if (parameters === "") return [RESET_CODE];
|
|
213
207
|
return parameters.split(";").map((field) => field === "" ? RESET_CODE : Number(field));
|
|
214
208
|
}
|
|
215
|
-
|
|
216
|
-
//#region src/browser/factories.ts
|
|
209
|
+
// 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)`)
|
|
@@ -266,7 +259,6 @@ function createBrowserSink(options) {
|
|
|
266
259
|
})(format, ...styles);
|
|
267
260
|
} };
|
|
268
261
|
}
|
|
269
|
-
//#endregion
|
|
270
262
|
export { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN, ansiToConsole, createBrowserSink, escapePercent, scanParameters };
|
|
271
263
|
|
|
272
264
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { Color } from '@src/core'\nimport { ATTRIBUTE_CODES, ESC } from '@src/core'\n\n// The SGR → CSS translation data the browser sink maps ANSI runs through (the browser branch).\n// The core `src/core/console` is the source of truth for the SGR numbers (which code is which\n// color / attribute); this module owns only the browser-side mapping — a named-color → hex\n// palette and the attribute CSS declarations. The live translator resolves color SGR numbers\n// through core's code maps and then reads this named palette, so no second number→CSS table drifts.\n// The SGR-scan pattern is built from core's `ESC` so no control-character literal appears in\n// source. UPPER_SNAKE, deeply `Object.freeze`d, every member exported.\n\n/**\n * Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools\n * console renders the same {@link Color} names as. The source of truth for the browser color\n * axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to\n * the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.\n *\n * @remarks\n * The conventional VGA/xterm 16-color palette (the base 8 plus their bright variants); `default`\n * is intentionally absent (it leaves the console's own ink and emits no CSS). Deeply frozen.\n */\nexport const COLOR_HEX: Readonly<Record<Exclude<Color, 'default'>, string>> = Object.freeze({\n\tblack: '#000000',\n\tred: '#cd0000',\n\tgreen: '#00cd00',\n\tyellow: '#cdcd00',\n\tblue: '#0000ee',\n\tmagenta: '#cd00cd',\n\tcyan: '#00cdcd',\n\twhite: '#e5e5e5',\n\tbrightBlack: '#7f7f7f',\n\tbrightRed: '#ff0000',\n\tbrightGreen: '#00ff00',\n\tbrightYellow: '#ffff00',\n\tbrightBlue: '#5c5cff',\n\tbrightMagenta: '#ff00ff',\n\tbrightCyan: '#00ffff',\n\tbrightWhite: '#ffffff',\n})\n\n/**\n * Maps each text-{@link Attribute}'s SGR \"on\" number to its equivalent CSS declaration — the\n * browser counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2\n * → `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,\n * `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`).\n *\n * @remarks\n * Keyed by the SGR number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a\n * parameter up directly while scanning a run.\n *\n * `inverse` (SGR 7) has no faithful single-declaration CSS equivalent (it swaps the fore/back inks,\n * which depends on the live colors); it maps to a best-effort `filter:invert(100%)` — documented as\n * approximate, never silently dropped. Deeply frozen.\n */\nexport const ATTRIBUTE_CSS: Readonly<Record<number, string>> = Object.freeze({\n\t[ATTRIBUTE_CODES.bold]: 'font-weight:bold',\n\t[ATTRIBUTE_CODES.dim]: 'opacity:0.6',\n\t[ATTRIBUTE_CODES.italic]: 'font-style:italic',\n\t[ATTRIBUTE_CODES.underline]: 'text-decoration:underline',\n\t[ATTRIBUTE_CODES.inverse]: 'filter:invert(100%)',\n\t[ATTRIBUTE_CODES.strikethrough]: 'text-decoration:line-through',\n})\n\n/**\n * Names the browser console directive that switches the active style — one `%c` prefixes every\n * styled run in the {@link import('./types.js').ConsoleOutput} format string, consuming the next\n * entry of the parallel CSS array. The single source of truth for the directive token.\n */\nexport const DIRECTIVE = '%c'\n\n/**\n * Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —\n * the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /\n * attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks\n * every SGR run in a string; built from core's {@link ESC} so no control-character literal appears\n * in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,\n * which the spec treats as a reset).\n *\n * @remarks\n * A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's\n * `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the\n * canonical definition, not a shared scanner.\n */\nexport const SGR_PATTERN = new RegExp(`${ESC}\\\\[([0-9;]*)m`, 'g')\n","import type { BrowserPalette, ConsoleOutput, StyleAccumulator } from './types.js'\nimport {\n\tATTRIBUTE_CODES,\n\tATTRIBUTES,\n\tBACKGROUND_CODES,\n\tCOLORS,\n\tFOREGROUND_CODES,\n\tRESET_CODE,\n} from '@src/core'\nimport { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN } from './constants.js'\n\n// The pure, browser-only translation behind the `%c` console sink (the browser branch). The core\n// styler / Logger / Reporter emit ANSI-styled strings; a DevTools console can't render ANSI but\n// can style through `console.log('%ctext', 'css')`, so `ansiToConsole` parses the SGR runs in the\n// incoming text and re-emits them as a `%c`-ready format string + parallel CSS array — the\n// translation happens at the output boundary, leaving the core unchanged. Pure + total + `%`-safe.\n// `ansiToConsole` carries immutable style snapshots while its local arrays assemble the final\n// `%c` output; only the standalone, reusable `escapePercent` / `scanParameters` utilities are\n// exported alongside it.\n\n/**\n * Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a\n * `%c`-segmented format string and the parallel array of CSS declarations, an optional partial\n * {@link BrowserPalette} overriding the CSS per named lookup.\n *\n * @remarks\n * A DevTools console then renders the same styling a terminal would; the browser sink calls\n * `console[method](format, ...styles)`.\n *\n * - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —\n * `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that\n * text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at\n * each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare\n * `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces\n * the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style\n * and are left in the text verbatim.\n * - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as\n * a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`\n * count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.\n * - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,\n * styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).\n * - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every\n * omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and\n * unrelated entries stay byte-identical.\n * - **Pure + total.** Same input → same output; it never throws on any string (adversarial escapes,\n * lone `ESC`, unterminated sequences all fall through as literal text).\n *\n * @param text - Any string, ANSI-styled or plain\n * @param palette - Optional partial browser CSS overrides\n * @returns The `%c` format string + parallel CSS array ({@link ConsoleOutput})\n *\n * @example\n * ```ts\n * ansiToConsole('\\x1b[31mred\\x1b[0m') // { format: '%cred', styles: ['color:#cd0000'] }\n * ansiToConsole('plain') // { format: 'plain', styles: [] }\n * ansiToConsole('50%') // { format: '50%%', styles: [] }\n * ```\n */\nexport function ansiToConsole(text: string, palette?: BrowserPalette): ConsoleOutput {\n\tconst scanner = new RegExp(SGR_PATTERN.source, SGR_PATTERN.flags)\n\t// The accumulated active style across a run — a separate foreground / background declaration\n\t// (each channel replaceable) plus an ordered, de-duplicated list of attribute declarations. An\n\t// SGR reset drops both channels and empties the list. Serialized to a `;`-joined CSS string per\n\t// emitted run.\n\tlet active: StyleAccumulator = Object.freeze({ attributes: Object.freeze([]) })\n\tconst segments: string[] = []\n\tconst styles: string[] = []\n\tlet cursor = 0\n\tlet pending = ''\n\tlet match: RegExpExecArray | null = scanner.exec(text)\n\tif (match === null) return { format: escapePercent(text), styles: [] }\n\n\t// A null match is the final text boundary, so every visible run passes through one flush path.\n\twhile (true) {\n\t\tconst boundary = match === null ? text.length : match.index\n\t\tpending += escapePercent(text.slice(cursor, boundary))\n\t\tif (pending !== '') {\n\t\t\tsegments.push(`${DIRECTIVE}${pending}`)\n\t\t\tconst declarations = [...active.attributes]\n\t\t\tif (active.foreground !== undefined) declarations.push(active.foreground)\n\t\t\tif (active.background !== undefined) declarations.push(active.background)\n\t\t\tstyles.push(declarations.join(';'))\n\t\t\tpending = ''\n\t\t}\n\t\tif (match === null) break\n\n\t\t// Apply one SGR sequence by replacing the readonly accumulator. A reset clears every channel;\n\t\t// colors replace their channel; attributes accumulate once; unknown extensions are ignored.\n\t\tfor (const code of scanParameters(match[1] ?? '')) {\n\t\t\tif (code === RESET_CODE) {\n\t\t\t\tactive = Object.freeze({ attributes: Object.freeze([]) })\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst foreground = COLORS.find((color) => FOREGROUND_CODES[color] === code)\n\t\t\tif (foreground !== undefined) {\n\t\t\t\tconst color = palette?.color?.[foreground] ?? COLOR_HEX[foreground]\n\t\t\t\tactive = Object.freeze({ ...active, foreground: `color:${color}` })\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst background = COLORS.find((color) => BACKGROUND_CODES[color] === code)\n\t\t\tif (background !== undefined) {\n\t\t\t\tconst color = palette?.color?.[background] ?? COLOR_HEX[background]\n\t\t\t\tactive = Object.freeze({ ...active, background: `background:${color}` })\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst name = ATTRIBUTES.find((attribute) => ATTRIBUTE_CODES[attribute] === code)\n\t\t\tconst attribute =\n\t\t\t\tname === undefined ? undefined : (palette?.attribute?.[name] ?? ATTRIBUTE_CSS[code])\n\t\t\tif (attribute !== undefined && !active.attributes.includes(attribute)) {\n\t\t\t\tactive = Object.freeze({\n\t\t\t\t\t...active,\n\t\t\t\t\tattributes: Object.freeze([...active.attributes, attribute]),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tcursor = match.index + match[0].length\n\t\tmatch = scanner.exec(text)\n\t}\n\treturn { format: segments.join(''), styles }\n}\n\n/**\n * Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from\n * reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the\n * {@link ansiToConsole} translation applies to every text segment before assembling the format\n * string (so only the `%c`s it inserts are real directives).\n *\n * @param text - A literal text segment (no inserted directives)\n * @returns `text` with each `%` doubled\n *\n * @example\n * ```ts\n * escapePercent('100% done') // '100%% done'\n * ```\n */\nexport function escapePercent(text: string): string {\n\treturn text.replace(/%/g, '%%')\n}\n\n/**\n * Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})\n * and returns its numeric codes — `'1;31'` → `[1, 31]`, a bare or empty field becoming a `0`\n * reset. It is total: every input yields a code list.\n *\n * @param parameters - The raw `;`-separated parameter string (the regex capture)\n * @returns The SGR codes found (a parameterless / empty field becoming `0`)\n *\n * @remarks\n * An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless\n * sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,\n * matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.\n *\n * @example\n * ```ts\n * scanParameters('1;31') // [1, 31]\n * scanParameters('') // [0]\n * ```\n */\nexport function scanParameters(parameters: string): readonly number[] {\n\tif (parameters === '') return [RESET_CODE]\n\treturn parameters.split(';').map((field) => (field === '' ? RESET_CODE : Number(field)))\n}\n","import type { LogLevel, SinkInterface } from '@src/core'\nimport type { BrowserSinkOptions } from './types.js'\nimport { selectWriter } from '@src/core'\nimport { ansiToConsole } from './helpers.js'\n\n// The browser `%c` console sink (the browser branch) — the platform-bound backend that satisfies core's\n// `SinkInterface` in a browser DevTools console. The core styler / Logger / Reporter emit ANSI-styled\n// strings; a DevTools console can't render ANSI but can style through `console.log('%ctext', 'css')`, so\n// this sink translates the incoming ANSI runs into a `%c` call at the output boundary (the env-split\n// rule: core owns the contract + universal logic, the browser provides the platform backend). A thin\n// stateless adapter, so a frozen-object factory — like core's `createConsoleSink` — not a class.\n// `SinkInterface` / `LogLevel` are imported from `@src/core`, never redeclared.\n\n/**\n * Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`\n * translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)\n * through {@link ansiToConsole}, an optional partial {@link BrowserPalette} overriding the named\n * color and attribute CSS.\n *\n * @param options - See {@link BrowserSinkOptions}\n * @returns A browser `%c` {@link SinkInterface}\n *\n * @remarks\n * Drop it in as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`)\n * to retarget the core output to the browser console with no change to the core.\n *\n * - **ANSI → `%c` at the sink.** The core produces ANSI strings; this sink parses the SGR runs and\n * re-emits them as a `console.log`-ready `%c` format string + parallel CSS array ({@link ansiToConsole}\n * — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.\n * `options.palette` supplies partial named color and attribute overrides to that translation.\n * - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an\n * omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level\n * reaches the matching DevTools stream. Both call the one\n * {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.\n * - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with\n * a carriage return `\\r` (a spinner / progress redraw) has the leading `\\r` stripped and is written as a\n * fresh, non-overwriting line — the locked browser degrade. Only a leading `\\r` is stripped; an interior\n * one is left to the console.\n * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at\n * creation and writes through those references, so a later `Capture` that patches `console.*` can never\n * feed this sink's output back into itself (the no-capture-loop principle, following the core\n * sink's precedent). Create the sink (or the logger) before installing a capture.\n *\n * @example\n * ```ts\n * import { Logger } from '@orkestrel/console'\n * import { createBrowserSink } from '@orkestrel/console/browser'\n *\n * const logger = new Logger({ name: 'app', sink: createBrowserSink() })\n * logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools\n * ```\n */\nexport function createBrowserSink(options?: BrowserSinkOptions): SinkInterface {\n\t// Snapshot the console writers at construction — bound to their `console` receiver — so a later patch of\n\t// `console.*` (by Capture) can never reach this sink's output (no capture loop), exactly as core's\n\t// `createConsoleSink` does.\n\tconst log = console.log.bind(console)\n\tconst warn = console.warn.bind(console)\n\tconst error = console.error.bind(console)\n\treturn {\n\t\twrite(text: string, level?: LogLevel): void {\n\t\t\t// Degrade the animation redraw first: a leading `\\r` can't overwrite a line in a browser\n\t\t\t// console, so drop it and write a fresh, non-overwriting line (the locked decision).\n\t\t\tconst line = text.startsWith('\\r') ? text.slice(1) : text\n\t\t\tconst { format, styles } = ansiToConsole(line, options?.palette)\n\t\t\tselectWriter(level, { log, warn, error })(format, ...styles)\n\t\t},\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { Color } from '@src/core'\nimport { ATTRIBUTE_CODES, ESC } from '@src/core'\n\n// The SGR → CSS translation data the browser sink maps ANSI runs through (the browser branch).\n// The core `src/core/console` is the source of truth for the SGR numbers (which code is which\n// color / attribute); this module owns only the browser-side mapping — a named-color → hex\n// palette and the attribute CSS declarations. The live translator resolves color SGR numbers\n// through core's code maps and then reads this named palette, so no second number→CSS table drifts.\n// The SGR-scan pattern is built from core's `ESC` so no control-character literal appears in\n// source. UPPER_SNAKE, deeply `Object.freeze`d, every member exported.\n\n/**\n * Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools\n * console renders the same {@link Color} names as. The source of truth for the browser color\n * axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to\n * the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.\n *\n * @remarks\n * The conventional VGA/xterm 16-color palette (the base 8 plus their bright variants); `default`\n * is intentionally absent (it leaves the console's own ink and emits no CSS). Deeply frozen.\n */\nexport const COLOR_HEX: Readonly<Record<Exclude<Color, 'default'>, string>> = Object.freeze({\n\tblack: '#000000',\n\tred: '#cd0000',\n\tgreen: '#00cd00',\n\tyellow: '#cdcd00',\n\tblue: '#0000ee',\n\tmagenta: '#cd00cd',\n\tcyan: '#00cdcd',\n\twhite: '#e5e5e5',\n\tbrightBlack: '#7f7f7f',\n\tbrightRed: '#ff0000',\n\tbrightGreen: '#00ff00',\n\tbrightYellow: '#ffff00',\n\tbrightBlue: '#5c5cff',\n\tbrightMagenta: '#ff00ff',\n\tbrightCyan: '#00ffff',\n\tbrightWhite: '#ffffff',\n})\n\n/**\n * Maps each text-{@link Attribute}'s SGR \"on\" number to its equivalent CSS declaration — the\n * browser counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2\n * → `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,\n * `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`).\n *\n * @remarks\n * Keyed by the SGR number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a\n * parameter up directly while scanning a run.\n *\n * `inverse` (SGR 7) has no faithful single-declaration CSS equivalent (it swaps the fore/back inks,\n * which depends on the live colors); it maps to a best-effort `filter:invert(100%)` — documented as\n * approximate, never silently dropped. Deeply frozen.\n */\nexport const ATTRIBUTE_CSS: Readonly<Record<number, string>> = Object.freeze({\n\t[ATTRIBUTE_CODES.bold]: 'font-weight:bold',\n\t[ATTRIBUTE_CODES.dim]: 'opacity:0.6',\n\t[ATTRIBUTE_CODES.italic]: 'font-style:italic',\n\t[ATTRIBUTE_CODES.underline]: 'text-decoration:underline',\n\t[ATTRIBUTE_CODES.inverse]: 'filter:invert(100%)',\n\t[ATTRIBUTE_CODES.strikethrough]: 'text-decoration:line-through',\n})\n\n/**\n * Names the browser console directive that switches the active style — one `%c` prefixes every\n * styled run in the {@link import('./types.js').ConsoleOutput} format string, consuming the next\n * entry of the parallel CSS array. The single source of truth for the directive token.\n */\nexport const DIRECTIVE = '%c'\n\n/**\n * Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —\n * the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /\n * attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks\n * every SGR run in a string; built from core's {@link ESC} so no control-character literal appears\n * in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,\n * which the spec treats as a reset).\n *\n * @remarks\n * A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's\n * `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the\n * canonical definition, not a shared scanner.\n */\nexport const SGR_PATTERN = new RegExp(`${ESC}\\\\[([0-9;]*)m`, 'g')\n","import type { BrowserPalette, ConsoleOutput, ConsoleSegment, StyleAccumulator } from './types.js'\nimport {\n\tATTRIBUTE_CODES,\n\tATTRIBUTES,\n\tBACKGROUND_CODES,\n\tCOLORS,\n\tFOREGROUND_CODES,\n\tRESET_CODE,\n} from '@src/core'\nimport { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN } from './constants.js'\n\n// The pure, browser-only translation behind the `%c` console sink (the browser branch). The core\n// styler / Logger / Reporter emit ANSI-styled strings; a DevTools console can't render ANSI but\n// can style through `console.log('%ctext', 'css')`, so `ansiToConsole` parses the SGR runs in the\n// incoming text and re-emits them as a `%c`-ready format string + parallel CSS array — the\n// translation happens at the output boundary, leaving the core unchanged. Pure + total + `%`-safe.\n// `ansiToConsole` carries immutable style snapshots while its local arrays assemble the final\n// `%c` output; only the standalone, reusable `escapePercent` / `scanParameters` utilities are\n// exported alongside it.\n\n/**\n * Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a\n * `%c`-segmented format string and the parallel array of CSS declarations, an optional partial\n * {@link BrowserPalette} overriding the CSS per named lookup.\n *\n * @remarks\n * A DevTools console then renders the same styling a terminal would; the browser sink calls\n * `console[method](format, ...styles)`.\n *\n * - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —\n * `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that\n * text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at\n * each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare\n * `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces\n * the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style\n * and are left in the text verbatim.\n * - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as\n * a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`\n * count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.\n * - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,\n * styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).\n * - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every\n * omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and\n * unrelated entries stay byte-identical.\n * - **Pure + total.** Same input → same output; it never throws on any string (adversarial escapes,\n * lone `ESC`, unterminated sequences all fall through as literal text).\n *\n * @param text - Any string, ANSI-styled or plain\n * @param palette - Optional partial browser CSS overrides\n * @returns The `%c` format string + parallel CSS array ({@link ConsoleOutput})\n *\n * @example\n * ```ts\n * ansiToConsole('\\x1b[31mred\\x1b[0m') // { format: '%cred', styles: ['color:#cd0000'] }\n * ansiToConsole('plain') // { format: 'plain', styles: [] }\n * ansiToConsole('50%') // { format: '50%%', styles: [] }\n * ```\n */\nexport function ansiToConsole(text: string, palette?: BrowserPalette): ConsoleOutput {\n\tconst matches = [...text.matchAll(new RegExp(SGR_PATTERN.source, SGR_PATTERN.flags))]\n\tif (matches.length === 0) return { format: escapePercent(text), styles: [] }\n\tconst initial: StyleAccumulator = Object.freeze({ attributes: Object.freeze([]) })\n\tconst output: ConsoleSegment[] = []\n\tfor (const match of [...matches, undefined]) {\n\t\tconst previous = output.at(-1) ?? { active: initial, cursor: 0 }\n\t\tconst segment = escapePercent(text.slice(previous.cursor, match?.index ?? text.length))\n\t\tconst declarations = [\n\t\t\t...previous.active.attributes,\n\t\t\t...(previous.active.foreground === undefined ? [] : [previous.active.foreground]),\n\t\t\t...(previous.active.background === undefined ? [] : [previous.active.background]),\n\t\t]\n\t\tconst active = scanParameters(match?.[1] ?? '').reduce<StyleAccumulator>((style, code) => {\n\t\t\tif (code === RESET_CODE) return initial\n\t\t\tconst foreground = COLORS.find((color) => FOREGROUND_CODES[color] === code)\n\t\t\tif (foreground !== undefined) {\n\t\t\t\tconst color = palette?.color?.[foreground] ?? COLOR_HEX[foreground]\n\t\t\t\treturn Object.freeze({ ...style, foreground: `color:${color}` })\n\t\t\t}\n\t\t\tconst background = COLORS.find((color) => BACKGROUND_CODES[color] === code)\n\t\t\tif (background !== undefined) {\n\t\t\t\tconst color = palette?.color?.[background] ?? COLOR_HEX[background]\n\t\t\t\treturn Object.freeze({ ...style, background: `background:${color}` })\n\t\t\t}\n\t\t\tconst name = ATTRIBUTES.find((attribute) => ATTRIBUTE_CODES[attribute] === code)\n\t\t\tconst attribute =\n\t\t\t\tname === undefined ? undefined : (palette?.attribute?.[name] ?? ATTRIBUTE_CSS[code])\n\t\t\treturn attribute === undefined || style.attributes.includes(attribute)\n\t\t\t\t? style\n\t\t\t\t: Object.freeze({ ...style, attributes: Object.freeze([...style.attributes, attribute]) })\n\t\t}, previous.active)\n\t\toutput.push({\n\t\t\tactive,\n\t\t\tcursor: match === undefined ? text.length : match.index + match[0].length,\n\t\t\ttext: segment,\n\t\t\tstyle: declarations.join(';'),\n\t\t})\n\t}\n\tconst visible = output.filter((segment) => segment.text !== '')\n\treturn {\n\t\tformat: visible.map((segment) => `${DIRECTIVE}${segment.text}`).join(''),\n\t\tstyles: visible.map((segment) => segment.style),\n\t}\n}\n\n/**\n * Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from\n * reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the\n * {@link ansiToConsole} translation applies to every text segment before assembling the format\n * string (so only the `%c`s it inserts are real directives).\n *\n * @param text - A literal text segment (no inserted directives)\n * @returns `text` with each `%` doubled\n *\n * @example\n * ```ts\n * escapePercent('100% done') // '100%% done'\n * ```\n */\nexport function escapePercent(text: string): string {\n\treturn text.replace(/%/g, '%%')\n}\n\n/**\n * Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})\n * and returns its numeric codes — `'1;31'` → `[1, 31]`, a bare or empty field becoming a `0`\n * reset. It is total: every input yields a code list.\n *\n * @param parameters - The raw `;`-separated parameter string (the regex capture)\n * @returns The SGR codes found (a parameterless / empty field becoming `0`)\n *\n * @remarks\n * An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless\n * sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,\n * matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.\n *\n * @example\n * ```ts\n * scanParameters('1;31') // [1, 31]\n * scanParameters('') // [0]\n * ```\n */\nexport function scanParameters(parameters: string): readonly number[] {\n\tif (parameters === '') return [RESET_CODE]\n\treturn parameters.split(';').map((field) => (field === '' ? RESET_CODE : Number(field)))\n}\n","import type { LogLevel, SinkInterface } from '@src/core'\nimport type { BrowserSinkOptions } from './types.js'\nimport { selectWriter } from '@src/core'\nimport { ansiToConsole } from './helpers.js'\n\n// The browser `%c` console sink (the browser branch) — the platform-bound backend that satisfies core's\n// `SinkInterface` in a browser DevTools console. The core styler / Logger / Reporter emit ANSI-styled\n// strings; a DevTools console can't render ANSI but can style through `console.log('%ctext', 'css')`, so\n// this sink translates the incoming ANSI runs into a `%c` call at the output boundary (the env-split\n// rule: core owns the contract + universal logic, the browser provides the platform backend). A thin\n// stateless adapter, so a frozen-object factory — like core's `createConsoleSink` — not a class.\n// `SinkInterface` / `LogLevel` are imported from `@src/core`, never redeclared.\n\n/**\n * Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`\n * translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)\n * through {@link ansiToConsole}, an optional partial {@link BrowserPalette} overriding the named\n * color and attribute CSS.\n *\n * @param options - See {@link BrowserSinkOptions}\n * @returns A browser `%c` {@link SinkInterface}\n *\n * @remarks\n * Drop it in as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`)\n * to retarget the core output to the browser console with no change to the core.\n *\n * - **ANSI → `%c` at the sink.** The core produces ANSI strings; this sink parses the SGR runs and\n * re-emits them as a `console.log`-ready `%c` format string + parallel CSS array ({@link ansiToConsole}\n * — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.\n * `options.palette` supplies partial named color and attribute overrides to that translation.\n * - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an\n * omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level\n * reaches the matching DevTools stream. Both call the one\n * {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.\n * - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with\n * a carriage return `\\r` (a spinner / progress redraw) has the leading `\\r` stripped and is written as a\n * fresh, non-overwriting line — the locked browser degrade. Only a leading `\\r` is stripped; an interior\n * one is left to the console.\n * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at\n * creation and writes through those references, so a later `Capture` that patches `console.*` can never\n * feed this sink's output back into itself (the no-capture-loop principle, following the core\n * sink's precedent). Create the sink (or the logger) before installing a capture.\n *\n * @example\n * ```ts\n * import { Logger } from '@orkestrel/console'\n * import { createBrowserSink } from '@orkestrel/console/browser'\n *\n * const logger = new Logger({ name: 'app', sink: createBrowserSink() })\n * logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools\n * ```\n */\nexport function createBrowserSink(options?: BrowserSinkOptions): SinkInterface {\n\t// Snapshot the console writers at construction — bound to their `console` receiver — so a later patch of\n\t// `console.*` (by Capture) can never reach this sink's output (no capture loop), exactly as core's\n\t// `createConsoleSink` does.\n\tconst log = console.log.bind(console)\n\tconst warn = console.warn.bind(console)\n\tconst error = console.error.bind(console)\n\treturn {\n\t\twrite(text: string, level?: LogLevel): void {\n\t\t\t// Degrade the animation redraw first: a leading `\\r` can't overwrite a line in a browser\n\t\t\t// console, so drop it and write a fresh, non-overwriting line (the locked decision).\n\t\t\tconst line = text.startsWith('\\r') ? text.slice(1) : text\n\t\t\tconst { format, styles } = ansiToConsole(line, options?.palette)\n\t\t\tselectWriter(level, { log, warn, error })(format, ...styles)\n\t\t},\n\t}\n}\n"],"mappings":"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"}
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
3
3
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
4
|
-
|
|
4
|
+
// src/core/constants.ts
|
|
5
5
|
/**
|
|
6
6
|
* Maps each {@link Color} to its SGR foreground parameter — the 8 base colors at 30–37 and their
|
|
7
7
|
* bright variants at 90–97. `default` is intentionally absent (it emits no code).
|
|
@@ -112,7 +112,7 @@ var BEL = String.fromCharCode(7);
|
|
|
112
112
|
/** Holds the Control Sequence Introducer (`ESC[`) that opens every SGR sequence. */
|
|
113
113
|
var CSI = `[`;
|
|
114
114
|
/** Holds the full SGR reset sequence (`ESC[0m`) appended after a styled run. */
|
|
115
|
-
var RESET =
|
|
115
|
+
var RESET = `[0m`;
|
|
116
116
|
/**
|
|
117
117
|
* Matches any ANSI/VT escape sequence — CSI (SGR color/style plus cursor/erase/scroll,
|
|
118
118
|
* including colon-parameterized SGR), OSC / DCS / PM / APC / SOS string sequences
|
|
@@ -485,8 +485,7 @@ var DEFAULT_THEME = Object.freeze({
|
|
|
485
485
|
}),
|
|
486
486
|
chrome: Object.freeze({ attributes: Object.freeze(["dim"]) })
|
|
487
487
|
});
|
|
488
|
-
|
|
489
|
-
//#region src/core/errors.ts
|
|
488
|
+
// src/core/errors.ts
|
|
490
489
|
/**
|
|
491
490
|
* Carries a {@link ConsoleErrorCode} and an optional `context` bag — the error the console layer
|
|
492
491
|
* throws for an internal invariant violated at a defensive guard.
|
|
@@ -500,6 +499,7 @@ var ConsoleError = class extends Error {
|
|
|
500
499
|
context;
|
|
501
500
|
constructor(code, message, context) {
|
|
502
501
|
super(message);
|
|
502
|
+
Object.defineProperty(this, Symbol.for("@orkestrel/console.ConsoleError"), { value: this });
|
|
503
503
|
this.name = "ConsoleError";
|
|
504
504
|
this.code = code;
|
|
505
505
|
if (context !== void 0) this.context = context;
|
|
@@ -521,10 +521,14 @@ var ConsoleError = class extends Error {
|
|
|
521
521
|
* ```
|
|
522
522
|
*/
|
|
523
523
|
function isConsoleError(value) {
|
|
524
|
-
return (0, _orkestrel_contract.
|
|
524
|
+
return (0, _orkestrel_contract.holds)(() => {
|
|
525
|
+
if (!(0, _orkestrel_contract.isInstance)(value, Error) || _orkestrel_contract.INTRINSICS.prototype(value) === Error.prototype) return false;
|
|
526
|
+
if (_orkestrel_contract.INTRINSICS.describe(value, Symbol.for("@orkestrel/console.ConsoleError"))?.value !== value) return false;
|
|
527
|
+
const context = _orkestrel_contract.INTRINSICS.reflect.read(value, "context");
|
|
528
|
+
return _orkestrel_contract.INTRINSICS.reflect.read(value, "code") === "INVARIANT" && (0, _orkestrel_contract.isString)(_orkestrel_contract.INTRINSICS.reflect.read(value, "message")) && (context === void 0 || (0, _orkestrel_contract.isObject)(context) || (0, _orkestrel_contract.isFunction)(context));
|
|
529
|
+
});
|
|
525
530
|
}
|
|
526
|
-
|
|
527
|
-
//#region src/core/helpers.ts
|
|
531
|
+
// src/core/helpers.ts
|
|
528
532
|
/**
|
|
529
533
|
* Removes every ANSI escape sequence from `text`, returning the plain visible string.
|
|
530
534
|
*
|
|
@@ -888,23 +892,32 @@ function renderBox(options) {
|
|
|
888
892
|
const inner = lines.reduce((max, line) => Math.max(max, width(line)), Math.max(0, titleRoom, budget));
|
|
889
893
|
const gutter = " ".repeat(padding);
|
|
890
894
|
const bar = paint(styler, chars.vertical, options.style);
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
895
|
+
const top = renderBoxTop(options, inner + padding * 2);
|
|
896
|
+
const bottom = paint(styler, `${chars.bottomLeft}${repeatTo(chars.horizontal, inner + padding * 2)}${chars.bottomRight}`, options.style);
|
|
897
|
+
return [
|
|
898
|
+
top,
|
|
899
|
+
...lines.map((line) => `${bar}${gutter}${align(line, inner)}${gutter}${bar}`),
|
|
900
|
+
bottom
|
|
901
|
+
].join("\n");
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Renders a box's top border across the supplied inner span.
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* renderBoxTop({ content: '' }, 3) // '┌───┐'
|
|
908
|
+
* ```
|
|
909
|
+
*/
|
|
910
|
+
function renderBoxTop(options, span) {
|
|
911
|
+
const chars = BORDER_CHARS[options.border ?? "single"];
|
|
912
|
+
const styler = options.styler;
|
|
913
|
+
if (options.title === void 0) return paint(styler, `${chars.topLeft}${repeatTo(chars.horizontal, span)}${chars.topRight}`, options.style);
|
|
894
914
|
else {
|
|
895
915
|
const caption = ` ${options.title} `;
|
|
896
916
|
const room = span - width(caption);
|
|
897
917
|
const lead = paint(styler, repeatTo(chars.horizontal, 1), options.style);
|
|
898
918
|
const rest = room - 1 <= 0 ? "" : paint(styler, repeatTo(chars.horizontal, room - 1), options.style);
|
|
899
|
-
|
|
919
|
+
return `${paint(styler, chars.topLeft, options.style)}${lead}${paint(styler, caption, options.style)}${rest}${paint(styler, chars.topRight, options.style)}`;
|
|
900
920
|
}
|
|
901
|
-
const bottom = paint(styler, `${chars.bottomLeft}${repeatTo(chars.horizontal, inner + padding * 2)}${chars.bottomRight}`, options.style);
|
|
902
|
-
const body = lines.map((line) => `${bar}${gutter}${align(line, inner)}${gutter}${bar}`);
|
|
903
|
-
return [
|
|
904
|
-
top,
|
|
905
|
-
...body,
|
|
906
|
-
bottom
|
|
907
|
-
].join("\n");
|
|
908
921
|
}
|
|
909
922
|
/**
|
|
910
923
|
* Renders a bordered grid of `columns` + `rows` with per-column alignment and width-aware
|
|
@@ -1128,8 +1141,7 @@ function renderBar(options) {
|
|
|
1128
1141
|
const filledCells = Math.round(fraction * track);
|
|
1129
1142
|
return `${`${paint(options.styler, repeatTo(fill, filledCells), options.style)}${repeatTo(empty, track - filledCells)}`} ${Math.round(fraction * 100)}% (${current}/${options.total})`;
|
|
1130
1143
|
}
|
|
1131
|
-
|
|
1132
|
-
//#region src/core/renderers/ANSIRenderer.ts
|
|
1144
|
+
// src/core/renderers/ANSIRenderer.ts
|
|
1133
1145
|
/**
|
|
1134
1146
|
* Implements the cross-environment default {@link RendererInterface} — renders style data as ANSI
|
|
1135
1147
|
* SGR escape codes, stateless and event-free.
|
|
@@ -1161,7 +1173,7 @@ var ANSIRenderer = class {
|
|
|
1161
1173
|
if (text === "") return text;
|
|
1162
1174
|
const codes = this.#codes(style);
|
|
1163
1175
|
if (codes.length === 0) return text;
|
|
1164
|
-
return
|
|
1176
|
+
return `[${codes.join(";")}m${text}${RESET}`;
|
|
1165
1177
|
}
|
|
1166
1178
|
#codes(style) {
|
|
1167
1179
|
const codes = [];
|
|
@@ -1171,8 +1183,7 @@ var ANSIRenderer = class {
|
|
|
1171
1183
|
return codes;
|
|
1172
1184
|
}
|
|
1173
1185
|
};
|
|
1174
|
-
|
|
1175
|
-
//#region src/core/Retention.ts
|
|
1186
|
+
// src/core/Retention.ts
|
|
1176
1187
|
/**
|
|
1177
1188
|
* Implements the bounded, level-keyed retention engine the console and process captures buffer
|
|
1178
1189
|
* through — one capped total buffer
|
|
@@ -1228,8 +1239,7 @@ var Retention = class {
|
|
|
1228
1239
|
if (buffer.length > this.#limit) buffer.shift();
|
|
1229
1240
|
}
|
|
1230
1241
|
};
|
|
1231
|
-
|
|
1232
|
-
//#region src/core/Capture.ts
|
|
1242
|
+
// src/core/Capture.ts
|
|
1233
1243
|
/**
|
|
1234
1244
|
* Implements an observable console interceptor — it takes control of the global `console.*` on
|
|
1235
1245
|
* the read side. While `active`, every configured `console.x` call is captured as a frozen
|
|
@@ -1336,8 +1346,7 @@ var Capture = class {
|
|
|
1336
1346
|
});
|
|
1337
1347
|
}
|
|
1338
1348
|
};
|
|
1339
|
-
|
|
1340
|
-
//#region src/core/Styler.ts
|
|
1349
|
+
// src/core/Styler.ts
|
|
1341
1350
|
/**
|
|
1342
1351
|
* Builds the fluent, composable styling surface — the consumer-facing API over the style
|
|
1343
1352
|
* engine. It
|
|
@@ -1479,8 +1488,7 @@ var Styler = class Styler {
|
|
|
1479
1488
|
}));
|
|
1480
1489
|
}
|
|
1481
1490
|
};
|
|
1482
|
-
|
|
1483
|
-
//#region src/core/factories.ts
|
|
1491
|
+
// src/core/factories.ts
|
|
1484
1492
|
/**
|
|
1485
1493
|
* Creates the fluent, composable {@link StylerInterface} — ANSI by default, retargeted by a
|
|
1486
1494
|
* `renderer` and stripped of color by `enabled: false`.
|
|
@@ -1627,8 +1635,7 @@ function createCaptureResult(fn, options) {
|
|
|
1627
1635
|
throw error;
|
|
1628
1636
|
}
|
|
1629
1637
|
}
|
|
1630
|
-
|
|
1631
|
-
//#region src/core/loggers/Logger.ts
|
|
1638
|
+
// src/core/loggers/Logger.ts
|
|
1632
1639
|
/**
|
|
1633
1640
|
* Implements an observable, leveled logger — the entry point into the structured-logging
|
|
1634
1641
|
* pipeline. Each `debug` / `info` / `warn` / `error` call builds a frozen {@link LogRecord},
|
|
@@ -1738,8 +1745,7 @@ var Logger = class {
|
|
|
1738
1745
|
if (this.#entries.length > this.#limit) this.#entries.shift();
|
|
1739
1746
|
}
|
|
1740
1747
|
};
|
|
1741
|
-
|
|
1742
|
-
//#region src/core/loggers/LoggerManager.ts
|
|
1748
|
+
// src/core/loggers/LoggerManager.ts
|
|
1743
1749
|
/**
|
|
1744
1750
|
* Implements an event-free registry of named {@link Logger}s plus a convenience fan-out — the
|
|
1745
1751
|
* manager over the logging layer (a registry, never observable itself; each {@link Logger}
|
|
@@ -1833,16 +1839,41 @@ var LoggerManager = class {
|
|
|
1833
1839
|
this.#loggers.clear();
|
|
1834
1840
|
return;
|
|
1835
1841
|
}
|
|
1836
|
-
if ((0, _orkestrel_contract.isArray)(names))
|
|
1837
|
-
let removed = true;
|
|
1838
|
-
for (const name of names) if (!this.#loggers.delete(name)) removed = false;
|
|
1839
|
-
return removed;
|
|
1840
|
-
}
|
|
1842
|
+
if ((0, _orkestrel_contract.isArray)(names)) return Iterator.from(names).reduce((removed, name) => this.#loggers.delete(name) && removed, true);
|
|
1841
1843
|
return this.#loggers.delete(names);
|
|
1842
1844
|
}
|
|
1843
1845
|
};
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
+
// src/core/LineIterator.ts
|
|
1847
|
+
/**
|
|
1848
|
+
* Yields a position for each requested blank line without allocating the whole sequence.
|
|
1849
|
+
* @example
|
|
1850
|
+
* ```ts
|
|
1851
|
+
* [...new LineIterator(2)] // [0, 1]
|
|
1852
|
+
* ```
|
|
1853
|
+
*/
|
|
1854
|
+
var LineIterator = class {
|
|
1855
|
+
#count;
|
|
1856
|
+
#index = 0;
|
|
1857
|
+
constructor(count) {
|
|
1858
|
+
this.#count = count;
|
|
1859
|
+
}
|
|
1860
|
+
next() {
|
|
1861
|
+
if (!(this.#index < this.#count)) return {
|
|
1862
|
+
done: true,
|
|
1863
|
+
value: void 0
|
|
1864
|
+
};
|
|
1865
|
+
const value = this.#index;
|
|
1866
|
+
this.#index += 1;
|
|
1867
|
+
return {
|
|
1868
|
+
done: false,
|
|
1869
|
+
value
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
[Symbol.iterator]() {
|
|
1873
|
+
return this;
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
// src/core/Reporter.ts
|
|
1846
1877
|
/**
|
|
1847
1878
|
* Implements a lean, event-free narrative reporter — the composable verb set for human /
|
|
1848
1879
|
* build-run output. Each verb formats its line through the shared {@link StylerInterface} and
|
|
@@ -1920,7 +1951,7 @@ var Reporter = class {
|
|
|
1920
1951
|
this.#sink.write(text);
|
|
1921
1952
|
}
|
|
1922
1953
|
blank(count = 1) {
|
|
1923
|
-
|
|
1954
|
+
Iterator.from(new LineIterator(count)).forEach(() => this.#sink.write(""));
|
|
1924
1955
|
}
|
|
1925
1956
|
#resolveStyle(options) {
|
|
1926
1957
|
return {
|
|
@@ -1930,8 +1961,7 @@ var Reporter = class {
|
|
|
1930
1961
|
};
|
|
1931
1962
|
}
|
|
1932
1963
|
};
|
|
1933
|
-
|
|
1934
|
-
//#region src/core/Spinner.ts
|
|
1964
|
+
// src/core/Spinner.ts
|
|
1935
1965
|
/**
|
|
1936
1966
|
* Implements a self-driving, observable activity spinner — a glyph cycle that advances on a
|
|
1937
1967
|
* periodic timer, writing each `\r` + frame line to its {@link SinkInterface} and emitting it on
|
|
@@ -2049,8 +2079,7 @@ var Spinner = class {
|
|
|
2049
2079
|
this.#sink.write(`\r${line}`);
|
|
2050
2080
|
}
|
|
2051
2081
|
};
|
|
2052
|
-
|
|
2053
|
-
//#region src/core/Progress.ts
|
|
2082
|
+
// src/core/Progress.ts
|
|
2054
2083
|
/**
|
|
2055
2084
|
* Implements an update-driven, observable progress bar — {@link update} recomputes the bar through
|
|
2056
2085
|
* {@link renderBar}, writes `\r` + bar to its {@link SinkInterface}, and emits the `{ current,
|
|
@@ -2169,7 +2198,6 @@ var Progress = class {
|
|
|
2169
2198
|
this.#sink.write(`\r${line}${final ? "\n" : ""}`, level);
|
|
2170
2199
|
}
|
|
2171
2200
|
};
|
|
2172
|
-
//#endregion
|
|
2173
2201
|
exports.ANSIRenderer = ANSIRenderer;
|
|
2174
2202
|
exports.ANSI_PATTERN = ANSI_PATTERN;
|
|
2175
2203
|
exports.ATTRIBUTES = ATTRIBUTES;
|
|
@@ -2202,6 +2230,7 @@ exports.FOREGROUND_CODES = FOREGROUND_CODES;
|
|
|
2202
2230
|
exports.LEVEL_COLORS = LEVEL_COLORS;
|
|
2203
2231
|
exports.LEVEL_SEVERITY = LEVEL_SEVERITY;
|
|
2204
2232
|
exports.LOG_LEVELS = LOG_LEVELS;
|
|
2233
|
+
exports.LineIterator = LineIterator;
|
|
2205
2234
|
exports.Logger = Logger;
|
|
2206
2235
|
exports.LoggerManager = LoggerManager;
|
|
2207
2236
|
exports.Progress = Progress;
|
|
@@ -2233,6 +2262,7 @@ exports.meetsLevel = meetsLevel;
|
|
|
2233
2262
|
exports.paint = paint;
|
|
2234
2263
|
exports.renderBar = renderBar;
|
|
2235
2264
|
exports.renderBox = renderBox;
|
|
2265
|
+
exports.renderBoxTop = renderBoxTop;
|
|
2236
2266
|
exports.renderSeparator = renderSeparator;
|
|
2237
2267
|
exports.renderTable = renderTable;
|
|
2238
2268
|
exports.renderTree = renderTree;
|