@loomcli/core 0.1.1 → 0.3.0
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/application.d.ts +73 -28
- package/dist/application.js +334 -99
- package/dist/chain.d.ts +68 -0
- package/dist/chain.js +372 -0
- package/dist/command.d.ts +201 -46
- package/dist/command.js +713 -57
- package/dist/environment.d.ts +22 -0
- package/dist/environment.js +1 -0
- package/dist/errors.d.ts +31 -51
- package/dist/errors.js +58 -90
- package/dist/extension.d.ts +99 -0
- package/dist/extension.js +330 -0
- package/dist/facts.d.ts +39 -0
- package/dist/facts.js +95 -0
- package/dist/globals.d.ts +49 -28
- package/dist/globals.js +104 -58
- package/dist/glyphs.generated.d.ts +464 -0
- package/dist/glyphs.generated.js +491 -0
- package/dist/host.js +2 -1
- package/dist/index.d.ts +21 -6
- package/dist/index.js +7 -2
- package/dist/inspect.d.ts +69 -12
- package/dist/inspect.js +83 -26
- package/dist/lanes.d.ts +26 -0
- package/dist/lanes.js +45 -0
- package/dist/options.d.ts +7 -0
- package/dist/options.js +9 -0
- package/dist/output.d.ts +93 -15
- package/dist/output.js +307 -34
- package/dist/plugin.d.ts +132 -0
- package/dist/plugin.js +278 -0
- package/dist/rendering.d.ts +21 -0
- package/dist/rendering.js +72 -0
- package/dist/sequence.d.ts +41 -0
- package/dist/sequence.js +225 -0
- package/dist/signals.d.ts +52 -0
- package/dist/signals.js +85 -0
- package/dist/style-ansi.d.ts +13 -0
- package/dist/style-ansi.js +306 -0
- package/dist/style-layout.d.ts +29 -0
- package/dist/style-layout.js +228 -0
- package/dist/style-resolve.d.ts +6 -0
- package/dist/style-resolve.js +26 -0
- package/dist/style-state.d.ts +14 -0
- package/dist/style-state.js +179 -0
- package/dist/style-wire.d.ts +31 -0
- package/dist/style-wire.js +201 -0
- package/dist/style.d.ts +86 -0
- package/dist/style.js +201 -0
- package/dist/theme.d.ts +3 -0
- package/dist/theme.js +22 -0
- package/dist/types.d.ts +222 -26
- package/dist/validation.d.ts +12 -3
- package/dist/validation.js +34 -17
- package/dist/view.d.ts +180 -0
- package/dist/view.js +307 -0
- package/package.json +2 -1
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ApplicationEnvironment, RegisteredEnvironment } from './environment.js';
|
|
2
|
+
import type { Plugin, ThemeOf } from './plugin.js';
|
|
3
|
+
import type { Alignment } from './style-wire.js';
|
|
4
|
+
/** Concrete terminal foregrounds; backgrounds use the same palette indices. */
|
|
5
|
+
declare const colors: {
|
|
6
|
+
black: number;
|
|
7
|
+
blue: number;
|
|
8
|
+
brightBlack: number;
|
|
9
|
+
brightBlue: number;
|
|
10
|
+
brightCyan: number;
|
|
11
|
+
brightGreen: number;
|
|
12
|
+
brightMagenta: number;
|
|
13
|
+
brightRed: number;
|
|
14
|
+
brightWhite: number;
|
|
15
|
+
brightYellow: number;
|
|
16
|
+
cyan: number;
|
|
17
|
+
green: number;
|
|
18
|
+
magenta: number;
|
|
19
|
+
red: number;
|
|
20
|
+
white: number;
|
|
21
|
+
yellow: number;
|
|
22
|
+
};
|
|
23
|
+
type Ansi16Color = keyof typeof colors;
|
|
24
|
+
type ColorName = Ansi16Color;
|
|
25
|
+
interface ColorFallbacks {
|
|
26
|
+
readonly ansi256?: number | undefined;
|
|
27
|
+
readonly ansi16?: Ansi16Color | undefined;
|
|
28
|
+
}
|
|
29
|
+
type Ansi256Fallbacks = Pick<ColorFallbacks, 'ansi16'>;
|
|
30
|
+
type RgbArguments = [
|
|
31
|
+
red: number,
|
|
32
|
+
green: number,
|
|
33
|
+
blue: number,
|
|
34
|
+
fallbacks?: ColorFallbacks | undefined
|
|
35
|
+
];
|
|
36
|
+
type Color = ColorName | readonly ['rgb', number, number, number, ColorFallbacks?] | readonly ['ansi256', number, Ansi256Fallbacks?];
|
|
37
|
+
declare const modifiers: readonly ['bold', 'faint', 'italic', 'underline', 'inverse', 'hidden', 'strikethrough', 'overline'];
|
|
38
|
+
type Modifier = (typeof modifiers)[number];
|
|
39
|
+
declare const tokens: readonly ['dim', 'primary', 'highlight', 'success', 'warning', 'error', 'info'];
|
|
40
|
+
type CoreToken = (typeof tokens)[number];
|
|
41
|
+
type Reset = 'reset' | 'resetForeground' | 'resetBackground';
|
|
42
|
+
type Operation = readonly ['foreground' | 'background', Color] | readonly ['modifier', Modifier] | readonly [Reset] | readonly ['token', string];
|
|
43
|
+
declare const semanticStyle: unique symbol;
|
|
44
|
+
type Style<Names extends string = CoreToken, Semantic extends boolean = false> = {
|
|
45
|
+
(text: string): string;
|
|
46
|
+
readonly [semanticStyle]: Semantic;
|
|
47
|
+
readonly escape: (text: string) => string;
|
|
48
|
+
readonly hex: (color: string, fallbacks?: ColorFallbacks) => Style<Names, Semantic>;
|
|
49
|
+
readonly bgHex: (color: string, fallbacks?: ColorFallbacks) => Style<Names, Semantic>;
|
|
50
|
+
readonly rgb: (...args: RgbArguments) => Style<Names, Semantic>;
|
|
51
|
+
readonly bgRgb: (...args: RgbArguments) => Style<Names, Semantic>;
|
|
52
|
+
readonly ansi256: (index: number, fallbacks?: Ansi256Fallbacks) => Style<Names, Semantic>;
|
|
53
|
+
readonly bgAnsi256: (index: number, fallbacks?: Ansi256Fallbacks) => Style<Names, Semantic>;
|
|
54
|
+
} & {
|
|
55
|
+
readonly [Name in ColorName | `bg${Capitalize<ColorName>}` | Modifier | Reset]: Style<Names, Semantic>;
|
|
56
|
+
} & {
|
|
57
|
+
readonly [Name in Names]: Style<Names, true>;
|
|
58
|
+
};
|
|
59
|
+
type ThemeNames<Contributor> = Contributor extends Plugin ? keyof ThemeOf<Contributor> & string : never;
|
|
60
|
+
type EnvironmentStyle<Environment extends ApplicationEnvironment<unknown, readonly Plugin[]>> = Style<CoreToken | ThemeNames<Environment['plugins'][number]>>;
|
|
61
|
+
type ContextualStyle = EnvironmentStyle<RegisteredEnvironment>;
|
|
62
|
+
type ConcreteStyle = Style;
|
|
63
|
+
type ThemeMapping = Readonly<Record<string, ConcreteStyle | undefined>>;
|
|
64
|
+
type ThemeConstraint<Mapping> = {
|
|
65
|
+
readonly [Key in keyof Mapping]: Key extends Exclude<keyof Style, CoreToken> | (typeof reservedCallableNames)[number] ? never : Mapping[Key];
|
|
66
|
+
};
|
|
67
|
+
declare const open = "\uE000";
|
|
68
|
+
declare const headerEnd = "\uE001";
|
|
69
|
+
declare const close = "\uE002";
|
|
70
|
+
declare const escape = "\uE003";
|
|
71
|
+
declare const chains: WeakMap<object, readonly Operation[]>;
|
|
72
|
+
declare const reservedCallableNames: readonly ['apply', 'arguments', 'bind', 'call', 'caller', 'constructor', 'length', 'name', 'prototype', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', '__proto__', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'then'];
|
|
73
|
+
declare const reservedStyleNames: Set<string>;
|
|
74
|
+
declare function frame(header: readonly unknown[], body: string): string;
|
|
75
|
+
declare function escapeText(text: string): string;
|
|
76
|
+
/** Copies options at the helper boundary; the wire parser uses the same value rules. */
|
|
77
|
+
declare function colorFallbacks(value: unknown, allow256: boolean): ColorFallbacks;
|
|
78
|
+
/** Properties are generated from the same catalogs the wire parser validates. */
|
|
79
|
+
declare function createStyle(names?: ReadonlySet<string>): ContextualStyle;
|
|
80
|
+
declare function isColorName(value: unknown): value is ColorName;
|
|
81
|
+
declare function pad(text: string, minimumWidth: number, options?: {
|
|
82
|
+
align?: Alignment;
|
|
83
|
+
}): string;
|
|
84
|
+
declare const style: Style;
|
|
85
|
+
export type { Ansi16Color, Ansi256Fallbacks, ColorFallbacks, ContextualStyle, Color, ColorName, ConcreteStyle, CoreToken, Modifier, Operation, Style, ThemeConstraint, ThemeMapping, };
|
|
86
|
+
export { colorFallbacks, chains, close, colors, createStyle, escape, escapeText, frame, headerEnd, isColorName, modifiers, open, pad, reservedStyleNames, style, tokens, };
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { isPlainObject } from './facts.js';
|
|
2
|
+
/** Concrete terminal foregrounds; backgrounds use the same palette indices. */
|
|
3
|
+
const colors = {
|
|
4
|
+
black: 0,
|
|
5
|
+
blue: 4,
|
|
6
|
+
brightBlack: 8,
|
|
7
|
+
brightBlue: 12,
|
|
8
|
+
brightCyan: 14,
|
|
9
|
+
brightGreen: 10,
|
|
10
|
+
brightMagenta: 13,
|
|
11
|
+
brightRed: 9,
|
|
12
|
+
brightWhite: 15,
|
|
13
|
+
brightYellow: 11,
|
|
14
|
+
cyan: 6,
|
|
15
|
+
green: 2,
|
|
16
|
+
magenta: 5,
|
|
17
|
+
red: 1,
|
|
18
|
+
white: 7,
|
|
19
|
+
yellow: 3,
|
|
20
|
+
};
|
|
21
|
+
const modifiers = [
|
|
22
|
+
'bold',
|
|
23
|
+
'faint',
|
|
24
|
+
'italic',
|
|
25
|
+
'underline',
|
|
26
|
+
'inverse',
|
|
27
|
+
'hidden',
|
|
28
|
+
'strikethrough',
|
|
29
|
+
'overline',
|
|
30
|
+
];
|
|
31
|
+
const tokens = ['dim', 'primary', 'highlight', 'success', 'warning', 'error', 'info'];
|
|
32
|
+
const open = '\uE000';
|
|
33
|
+
const headerEnd = '\uE001';
|
|
34
|
+
const close = '\uE002';
|
|
35
|
+
const escape = '\uE003';
|
|
36
|
+
const chains = new WeakMap();
|
|
37
|
+
const reservedCallableNames = [
|
|
38
|
+
'apply',
|
|
39
|
+
'arguments',
|
|
40
|
+
'bind',
|
|
41
|
+
'call',
|
|
42
|
+
'caller',
|
|
43
|
+
'constructor',
|
|
44
|
+
'length',
|
|
45
|
+
'name',
|
|
46
|
+
'prototype',
|
|
47
|
+
'toString',
|
|
48
|
+
'toLocaleString',
|
|
49
|
+
'valueOf',
|
|
50
|
+
'hasOwnProperty',
|
|
51
|
+
'isPrototypeOf',
|
|
52
|
+
'propertyIsEnumerable',
|
|
53
|
+
'__proto__',
|
|
54
|
+
'__defineGetter__',
|
|
55
|
+
'__defineSetter__',
|
|
56
|
+
'__lookupGetter__',
|
|
57
|
+
'__lookupSetter__',
|
|
58
|
+
'then',
|
|
59
|
+
];
|
|
60
|
+
const reservedStyleNames = new Set([
|
|
61
|
+
...Object.keys(colors),
|
|
62
|
+
...Object.keys(colors).map((name) => `bg${name.charAt(0).toUpperCase()}${name.slice(1)}`),
|
|
63
|
+
...modifiers,
|
|
64
|
+
'reset',
|
|
65
|
+
'resetForeground',
|
|
66
|
+
'resetBackground',
|
|
67
|
+
'hex',
|
|
68
|
+
'bgHex',
|
|
69
|
+
'rgb',
|
|
70
|
+
'bgRgb',
|
|
71
|
+
'ansi256',
|
|
72
|
+
'bgAnsi256',
|
|
73
|
+
'escape',
|
|
74
|
+
...reservedCallableNames,
|
|
75
|
+
]);
|
|
76
|
+
function textOnly(text) {
|
|
77
|
+
if (typeof text !== 'string') {
|
|
78
|
+
throw new TypeError('Style helpers require a string.');
|
|
79
|
+
}
|
|
80
|
+
return text;
|
|
81
|
+
}
|
|
82
|
+
function frame(header, body) {
|
|
83
|
+
const encoded = JSON.stringify(header).replace(/[\uE000-\uE003]/gu, (character) => `\\u${character.charCodeAt(0).toString(16)}`);
|
|
84
|
+
return `${open}${encoded}${headerEnd}${body}${close}`;
|
|
85
|
+
}
|
|
86
|
+
function escapeText(text) {
|
|
87
|
+
return textOnly(text).replace(/[\uE000-\uE003]/gu, (character) => `${escape}${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
88
|
+
}
|
|
89
|
+
function byte(value) {
|
|
90
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
91
|
+
throw new RangeError('Color channels and palette indices must be integers from 0 through 255.');
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
/** Copies options at the helper boundary; the wire parser uses the same value rules. */
|
|
96
|
+
function colorFallbacks(value, allow256) {
|
|
97
|
+
if (value === undefined) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
if (!isPlainObject(value) ||
|
|
101
|
+
Reflect.ownKeys(value).some((key) => key !== 'ansi16' && !(allow256 && key === 'ansi256'))) {
|
|
102
|
+
throw new TypeError('Color fallbacks must be a plain object with supported depth names.');
|
|
103
|
+
}
|
|
104
|
+
const ansi16 = value.ansi16;
|
|
105
|
+
const ansi256 = value.ansi256;
|
|
106
|
+
if (ansi16 !== undefined && !isColorName(ansi16)) {
|
|
107
|
+
throw new TypeError('The ansi16 fallback must be a terminal foreground color name.');
|
|
108
|
+
}
|
|
109
|
+
if (ansi256 !== undefined &&
|
|
110
|
+
(typeof ansi256 !== 'number' || !Number.isInteger(ansi256) || ansi256 < 0 || ansi256 > 255)) {
|
|
111
|
+
throw new RangeError('The ansi256 fallback must be an integer from 0 through 255.');
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...(ansi16 === undefined ? {} : { ansi16 }),
|
|
115
|
+
...(ansi256 === undefined ? {} : { ansi256 }),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function rgbColor([red, green, blue, fallbacks]) {
|
|
119
|
+
const rgb = ['rgb', byte(red), byte(green), byte(blue)];
|
|
120
|
+
const copied = colorFallbacks(fallbacks, true);
|
|
121
|
+
return Object.keys(copied).length === 0 ? rgb : [...rgb, copied];
|
|
122
|
+
}
|
|
123
|
+
function indexedColor(index, fallbacks) {
|
|
124
|
+
const color = ['ansi256', byte(index)];
|
|
125
|
+
const copied = colorFallbacks(fallbacks, false);
|
|
126
|
+
return Object.keys(copied).length === 0 ? color : [...color, copied];
|
|
127
|
+
}
|
|
128
|
+
function hexColor(value, fallbacks) {
|
|
129
|
+
if (typeof value !== 'string' || !/^#(?:[\da-f]{3}|[\da-f]{6})$/iu.test(value)) {
|
|
130
|
+
throw new TypeError('Hex colors must use #RGB or #RRGGBB.');
|
|
131
|
+
}
|
|
132
|
+
const full = value.length === 4
|
|
133
|
+
? value.slice(1).replace(/[\da-f]/giu, (digit) => digit + digit)
|
|
134
|
+
: value.slice(1);
|
|
135
|
+
return rgbColor([
|
|
136
|
+
Number.parseInt(full.slice(0, 2), 16),
|
|
137
|
+
Number.parseInt(full.slice(2, 4), 16),
|
|
138
|
+
Number.parseInt(full.slice(4, 6), 16),
|
|
139
|
+
fallbacks,
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
/** Properties are generated from the same catalogs the wire parser validates. */
|
|
143
|
+
function createStyle(names = new Set(tokens)) {
|
|
144
|
+
function chain(operations) {
|
|
145
|
+
const callable = (text) => frame(['style', operations], textOnly(text));
|
|
146
|
+
const next = (operation) => chain([...operations, operation]);
|
|
147
|
+
const helpers = {
|
|
148
|
+
ansi256: (index, fallbacks) => next(['foreground', indexedColor(index, fallbacks)]),
|
|
149
|
+
bgAnsi256: (index, fallbacks) => next(['background', indexedColor(index, fallbacks)]),
|
|
150
|
+
bgHex: (value, fallbacks) => next(['background', hexColor(value, fallbacks)]),
|
|
151
|
+
bgRgb: (...args) => next(['background', rgbColor(args)]),
|
|
152
|
+
escape: escapeText,
|
|
153
|
+
hex: (value, fallbacks) => next(['foreground', hexColor(value, fallbacks)]),
|
|
154
|
+
rgb: (...args) => next(['foreground', rgbColor(args)]),
|
|
155
|
+
};
|
|
156
|
+
const properties = new Map(Object.entries(helpers).map(([name, helper]) => [name, () => helper]));
|
|
157
|
+
for (const name of Object.keys(colors)) {
|
|
158
|
+
if (isColorName(name)) {
|
|
159
|
+
properties.set(name, () => next(['foreground', name]));
|
|
160
|
+
properties.set(`bg${name.charAt(0).toUpperCase()}${name.slice(1)}`, () => next(['background', name]));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const name of modifiers) {
|
|
164
|
+
properties.set(name, () => next(['modifier', name]));
|
|
165
|
+
}
|
|
166
|
+
for (const name of ['reset', 'resetForeground', 'resetBackground']) {
|
|
167
|
+
properties.set(name, () => next([name]));
|
|
168
|
+
}
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
properties.set(name, () => next(['token', name]));
|
|
171
|
+
}
|
|
172
|
+
const value = new Proxy(callable, {
|
|
173
|
+
get: (target, property, receiver) => typeof property === 'string' && properties.has(property)
|
|
174
|
+
? properties.get(property)?.()
|
|
175
|
+
: Reflect.get(target, property, receiver),
|
|
176
|
+
});
|
|
177
|
+
chains.set(value, operations);
|
|
178
|
+
Object.freeze(value);
|
|
179
|
+
// Last resort: no typed path exists from runtime-generated callable properties to mapped keys.
|
|
180
|
+
// It holds because the catalogs above supply every declared helper and token; chains retain operations for validation.
|
|
181
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
return chain([]);
|
|
185
|
+
}
|
|
186
|
+
function isColorName(value) {
|
|
187
|
+
return typeof value === 'string' && Object.hasOwn(colors, value);
|
|
188
|
+
}
|
|
189
|
+
function pad(text, minimumWidth, options) {
|
|
190
|
+
textOnly(text);
|
|
191
|
+
if (!Number.isSafeInteger(minimumWidth) || minimumWidth < 0) {
|
|
192
|
+
throw new RangeError('Padding width must be a nonnegative safe integer.');
|
|
193
|
+
}
|
|
194
|
+
const align = options?.align ?? 'left';
|
|
195
|
+
if (align !== 'left' && align !== 'right' && align !== 'center') {
|
|
196
|
+
throw new TypeError('Padding alignment must be left, right, or center.');
|
|
197
|
+
}
|
|
198
|
+
return frame(['pad', minimumWidth, align], text);
|
|
199
|
+
}
|
|
200
|
+
const style = createStyle();
|
|
201
|
+
export { colorFallbacks, chains, close, colors, createStyle, escape, escapeText, frame, headerEnd, isColorName, modifiers, open, pad, reservedStyleNames, style, tokens, };
|
package/dist/theme.d.ts
ADDED
package/dist/theme.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DeclarationError } from './errors.js';
|
|
2
|
+
import { isPlainObject } from './facts.js';
|
|
3
|
+
import { chains, reservedStyleNames } from './style.js';
|
|
4
|
+
function buildTheme(value, identity) {
|
|
5
|
+
if (!isPlainObject(value)) {
|
|
6
|
+
throw new DeclarationError(`Plugin "${identity}" must declare theme as a mapping of names to concrete style chains.`);
|
|
7
|
+
}
|
|
8
|
+
const palette = new Map();
|
|
9
|
+
for (const [name, chain] of Object.entries(value)) {
|
|
10
|
+
if (reservedStyleNames.has(name)) {
|
|
11
|
+
throw new DeclarationError(`Plugin "${identity}" theme name "${name}" shadows a built-in style member.`);
|
|
12
|
+
}
|
|
13
|
+
const operations = typeof chain === 'function' ? chains.get(chain) : undefined;
|
|
14
|
+
if (chain !== undefined &&
|
|
15
|
+
(operations === undefined || operations.some((entry) => entry[0] === 'token'))) {
|
|
16
|
+
throw new DeclarationError(`Plugin "${identity}" theme mapping "${name}" must be an unapplied concrete style chain without semantic tokens.`);
|
|
17
|
+
}
|
|
18
|
+
palette.set(name, operations ?? []);
|
|
19
|
+
}
|
|
20
|
+
return palette;
|
|
21
|
+
}
|
|
22
|
+
export { buildTheme };
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/// <reference types="node" preserve="true" />
|
|
2
2
|
import type { Readable, Writable } from 'node:stream';
|
|
3
3
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
4
|
+
import type { ExtensionValue } from './extension.js';
|
|
5
|
+
import type { ResultNode } from './inspect.js';
|
|
6
|
+
import type { RenderingPolicy } from './rendering.js';
|
|
7
|
+
import type { ContextualStyle } from './style.js';
|
|
4
8
|
type LowercaseLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
|
|
5
9
|
type ShortAlias = LowercaseLetter | Uppercase<LowercaseLetter>;
|
|
6
10
|
type OptionSpelling = {
|
|
@@ -32,6 +36,31 @@ type Omission = {
|
|
|
32
36
|
} | {
|
|
33
37
|
validateOmitted?: false;
|
|
34
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* The one-line summary every projection reads. It is a core fact: optional, and a string that holds
|
|
41
|
+
* a character other than whitespace and no line terminator.
|
|
42
|
+
*/
|
|
43
|
+
interface Described {
|
|
44
|
+
description?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The two core facts a listing reads on a named Command and on an option.
|
|
48
|
+
* `hidden` keeps the member off every listing, and an omitted one reads `false`.
|
|
49
|
+
* `deprecated` is the one-line migration message a listing shows beside the member.
|
|
50
|
+
* Neither belongs to an argument, which cannot leave the grammar it sits in, or to the root.
|
|
51
|
+
*/
|
|
52
|
+
interface Listed {
|
|
53
|
+
hidden?: boolean;
|
|
54
|
+
deprecated?: string;
|
|
55
|
+
}
|
|
56
|
+
/** The extension values one option declaration carries, whatever scope declares the option. */
|
|
57
|
+
interface OptionExtensions {
|
|
58
|
+
extensions?: readonly ExtensionValue<'option'>[];
|
|
59
|
+
}
|
|
60
|
+
/** The same slot on an argument declaration, typed by the target its values must name. */
|
|
61
|
+
interface ArgumentExtensions {
|
|
62
|
+
extensions?: readonly ExtensionValue<'argument'>[];
|
|
63
|
+
}
|
|
35
64
|
/**
|
|
36
65
|
* The tokens one declaration collects before validation: one string, or the whole collection. A
|
|
37
66
|
* multiple option and a variadic argument collect alike, so they share this raw shape.
|
|
@@ -54,7 +83,7 @@ export type GlobalNameConstraint<Name extends string, Globals> = Name extends ke
|
|
|
54
83
|
} : unknown;
|
|
55
84
|
/** A required record key excludes open strings; distribution rejects each union member. */
|
|
56
85
|
export type NameConstraint<Name extends string, Whole extends string = Name> = {} extends Record<Name, unknown> ? LiteralNameFault : Name extends Whole ? [Whole] extends [Name] ? unknown : LiteralNameFault : LiteralNameFault;
|
|
57
|
-
export type ExitCode = 0 | 1 | 2;
|
|
86
|
+
export type ExitCode = 0 | 1 | 2 | 130 | 143;
|
|
58
87
|
export interface InputTerminal {
|
|
59
88
|
isTTY: boolean;
|
|
60
89
|
}
|
|
@@ -63,6 +92,7 @@ export interface OutputTerminal extends InputTerminal {
|
|
|
63
92
|
rows: number | undefined;
|
|
64
93
|
}
|
|
65
94
|
export interface Host {
|
|
95
|
+
platform: string;
|
|
66
96
|
argv: string[];
|
|
67
97
|
cwd: string;
|
|
68
98
|
env: Record<string, string | undefined>;
|
|
@@ -76,7 +106,13 @@ export interface Host {
|
|
|
76
106
|
stderr: Writable;
|
|
77
107
|
}
|
|
78
108
|
export interface RunOptions {
|
|
109
|
+
rendering?: RenderingPolicy;
|
|
79
110
|
host?: Partial<Host>;
|
|
111
|
+
/**
|
|
112
|
+
* A caller-owned signal that cancels the run. Core subscribes to it at run entry and honors an
|
|
113
|
+
* abort at every phase boundary; it composes with an installed signals owner.
|
|
114
|
+
*/
|
|
115
|
+
signal?: AbortSignal;
|
|
80
116
|
}
|
|
81
117
|
/** The declaration one schema call validates, under the name and the scope it was declared in. */
|
|
82
118
|
export interface InputIdentity {
|
|
@@ -112,35 +148,173 @@ export type ValidationContext = {
|
|
|
112
148
|
passthrough: readonly string[];
|
|
113
149
|
supplied: SuppliedInputs;
|
|
114
150
|
};
|
|
151
|
+
/** Immutable authoring and measurement context for one view destination. */
|
|
152
|
+
export interface ViewContext {
|
|
153
|
+
readonly style: ContextualStyle;
|
|
154
|
+
readonly width: (text: string) => number;
|
|
155
|
+
}
|
|
156
|
+
/** A pure synchronous view turns one typed value into the marked text core resolves. */
|
|
157
|
+
export interface View<Data> {
|
|
158
|
+
render: (data: Readonly<Data>, context: ViewContext) => string;
|
|
159
|
+
/** A view has one shape; the row view of Results is the other. */
|
|
160
|
+
row?: never;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A row view renders a sequence one row at a time.
|
|
164
|
+
* `head` and `tail` open and close the sequence, and each defaults to the empty string.
|
|
165
|
+
* Every function is pure and synchronous and owns the newlines in the text it returns.
|
|
166
|
+
*/
|
|
167
|
+
export interface RowView<Row> {
|
|
168
|
+
row: (row: Readonly<Row>, index: number, context: ViewContext) => string;
|
|
169
|
+
head?: (context: ViewContext) => string;
|
|
170
|
+
tail?: (count: number, context: ViewContext) => string;
|
|
171
|
+
/** A row view has one shape; the whole view of Rendered output is the other. */
|
|
172
|
+
render?: never;
|
|
173
|
+
}
|
|
174
|
+
/** The views record of a value result: every entry renders the whole value. */
|
|
175
|
+
export type ResultViews<Value> = Readonly<Record<string, View<Value>>>;
|
|
176
|
+
/**
|
|
177
|
+
* The views record of a rows result: a whole view over the collected rows, which core buffers the
|
|
178
|
+
* sequence for, or a row view, which core feeds as the rows arrive.
|
|
179
|
+
*/
|
|
180
|
+
export type RowViews<Row> = Readonly<Record<string, View<readonly Row[]> | RowView<Row>>>;
|
|
181
|
+
/**
|
|
182
|
+
* One view a result names, with its data type erased, as the view registry erases a declared
|
|
183
|
+
* view's. The write site reads each function back through the key that resolved it.
|
|
184
|
+
*/
|
|
185
|
+
export type ResultView = View<never> | RowView<never>;
|
|
115
186
|
/**
|
|
116
|
-
* One
|
|
117
|
-
*
|
|
118
|
-
* holds no output handle. A throw or a non-string return is a renderer failure.
|
|
187
|
+
* One declared result as the write site reads it: the unit the action emits, the view names it
|
|
188
|
+
* declares in record order, and the key core renders when nothing selects another.
|
|
119
189
|
*/
|
|
120
|
-
export interface
|
|
121
|
-
|
|
190
|
+
export interface DeclaredResult {
|
|
191
|
+
default: string;
|
|
192
|
+
kind: 'value' | 'rows';
|
|
193
|
+
views: ReadonlyMap<string, ResultView>;
|
|
122
194
|
}
|
|
123
|
-
|
|
195
|
+
/** Phantom key. It brands the surface a lifecycle hook receives, so a forged value is not one. */
|
|
196
|
+
export declare const attachedCommand: unique symbol;
|
|
197
|
+
/**
|
|
198
|
+
* One Command as `onCommandAttach` receives it: the facts `inspect()` publishes, with their types
|
|
199
|
+
* erased, and the authoring calls a hook may make. Each call returns a new value whose facts hold
|
|
200
|
+
* what the call added, so a hook reads its own earlier calls back. The calls a hook cannot make are
|
|
201
|
+
* absent, because each of them changes what the action was compiled against or the graph's shape.
|
|
202
|
+
*/
|
|
203
|
+
export interface AttachedCommand {
|
|
204
|
+
readonly [attachedCommand]: true;
|
|
205
|
+
/** The Command's own name, and `null` for the root. */
|
|
206
|
+
readonly name: string | null;
|
|
207
|
+
readonly path: readonly string[];
|
|
208
|
+
readonly hasAction: boolean;
|
|
209
|
+
/** The declared argument names, in declaration order. */
|
|
210
|
+
readonly arguments: readonly string[];
|
|
211
|
+
/** The declared local option names, in declaration order. */
|
|
212
|
+
readonly options: readonly string[];
|
|
213
|
+
readonly result: ResultNode | null;
|
|
214
|
+
argument(name: string, config: ArgumentConfig): AttachedCommand;
|
|
215
|
+
option(name: string, config: OptionConfig): AttachedCommand;
|
|
216
|
+
views(replacements: Readonly<Record<string, ResultView>>, options?: {
|
|
217
|
+
default?: string;
|
|
218
|
+
}): AttachedCommand;
|
|
219
|
+
extend(...values: readonly ExtensionValue<'command'>[]): AttachedCommand;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The lifecycle hook core calls once per Command at graph build, in installation order, each
|
|
223
|
+
* receiving what the previous plugin's hook returned.
|
|
224
|
+
*/
|
|
225
|
+
export type CommandAttachHook = (command: AttachedCommand) => AttachedCommand;
|
|
226
|
+
/**
|
|
227
|
+
* What `out.results` accepts for one declared result. The declaration rides in the declared types
|
|
228
|
+
* as a closed discriminant, so a Command that declares none carries the neutral `unknown` and its
|
|
229
|
+
* `out.results` takes `never`. The parameter is never a union, so distribution reaches one member.
|
|
230
|
+
*/
|
|
231
|
+
export type ResultInput<Result> = Result extends {
|
|
232
|
+
kind: 'value';
|
|
233
|
+
value: infer Value;
|
|
234
|
+
} ? Value : Result extends {
|
|
235
|
+
kind: 'rows';
|
|
236
|
+
row: infer Row;
|
|
237
|
+
} ? Iterable<Row> | AsyncIterable<Row> : never;
|
|
238
|
+
/**
|
|
239
|
+
* The result an `out` carries where the declaration is not in hand. Its `results` accepts any
|
|
240
|
+
* value, because the authoring call already checked what the Command declares, and the channel
|
|
241
|
+
* core builds for an action carries that declaration at run time.
|
|
242
|
+
*/
|
|
243
|
+
export interface OpenResult {
|
|
244
|
+
kind: 'value';
|
|
245
|
+
value: unknown;
|
|
246
|
+
}
|
|
247
|
+
/** The record a `views()` call takes, which is the shape the carried result names. */
|
|
248
|
+
export type ResultViewsOf<Result> = Result extends {
|
|
249
|
+
kind: 'value';
|
|
250
|
+
value: infer Value;
|
|
251
|
+
} ? ResultViews<Value> : Result extends {
|
|
252
|
+
kind: 'rows';
|
|
253
|
+
row: infer Row;
|
|
254
|
+
} ? RowViews<Row> : never;
|
|
255
|
+
export interface Out<Result = unknown> {
|
|
124
256
|
print(message: string): Promise<void>;
|
|
125
257
|
info(message: string): Promise<void>;
|
|
126
258
|
success(message: string): Promise<void>;
|
|
127
259
|
warn(message: string): Promise<void>;
|
|
128
260
|
error(message: string): Promise<void>;
|
|
129
|
-
/** The neutral
|
|
130
|
-
render<Data>(data: Data,
|
|
261
|
+
/** The neutral view call: a rendered value has no purpose and no destination. */
|
|
262
|
+
render<Data>(data: Data, view: View<Data>): Promise<void>;
|
|
263
|
+
/** The same call over a sequence: core writes each row's text as the source yields it. */
|
|
264
|
+
render<Row>(rows: Iterable<Row> | AsyncIterable<Row>, view: RowView<Row>): Promise<void>;
|
|
265
|
+
/**
|
|
266
|
+
* The Command's own result, emitted once. Property syntax keeps the parameter contravariant, so
|
|
267
|
+
* a neutral `Out` never stands in for one that carries a declaration.
|
|
268
|
+
*/
|
|
269
|
+
results: (value: ResultInput<Result>) => Promise<void>;
|
|
131
270
|
fatal(message: string): never;
|
|
132
271
|
}
|
|
133
|
-
|
|
272
|
+
/**
|
|
273
|
+
* What the action's channel answers to: the routed path its diagnostics name, the result the
|
|
274
|
+
* routed Command declared, and the view this run selected. The declaration decides the
|
|
275
|
+
* destinations the channel carries, so the redirect is read from the graph once and never from the
|
|
276
|
+
* view a run selected; `view` decides the rendering alone, and is `null` when nothing selected one
|
|
277
|
+
* and the declaration's default stands.
|
|
278
|
+
*/
|
|
279
|
+
export interface ResultBinding {
|
|
280
|
+
path: readonly string[];
|
|
281
|
+
result: DeclaredResult | undefined;
|
|
282
|
+
view: string | null;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The routed Command's invocation after parsing and validation, which every middleware reads. The
|
|
286
|
+
* values are what the action receives, the output of each declaration's schema, for that Command's
|
|
287
|
+
* own arguments and local options; global and plugin option values are not here. The records are
|
|
288
|
+
* untyped and frozen, because a middleware runs ahead of every action and the graph carries no
|
|
289
|
+
* type for a value.
|
|
290
|
+
*/
|
|
291
|
+
export interface Request {
|
|
292
|
+
readonly args: Readonly<Record<string, unknown>>;
|
|
293
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
294
|
+
readonly passthrough: readonly string[];
|
|
295
|
+
}
|
|
296
|
+
/** The channel one action receives, with the emission the results lane holds it to. */
|
|
297
|
+
export interface ActionChannel {
|
|
298
|
+
out: Out<OpenResult>;
|
|
299
|
+
/** Whether the action emitted its result, which the missing rule reads after it returned. */
|
|
300
|
+
emitted: () => boolean;
|
|
301
|
+
/**
|
|
302
|
+
* Stops every sequence this channel still has pending. The action's own failure stays primary,
|
|
303
|
+
* so a sequence it never awaited is stopped rather than drained.
|
|
304
|
+
*/
|
|
305
|
+
stop: () => void;
|
|
306
|
+
}
|
|
307
|
+
export type StringOption = OptionSpelling & Presence & Multiplicity & Omission & Described & Listed & OptionExtensions & {
|
|
134
308
|
type: 'string';
|
|
135
309
|
polarity?: never;
|
|
136
310
|
validate?: StandardSchemaV1;
|
|
137
311
|
};
|
|
138
312
|
/** A variadic argument collects the remaining tokens, so it follows the multiple option rules. */
|
|
139
|
-
export type VariadicArgument = Presence & {
|
|
313
|
+
export type VariadicArgument = Presence & Described & ArgumentExtensions & {
|
|
140
314
|
variadic: true;
|
|
141
315
|
validate?: StandardSchemaV1;
|
|
142
316
|
};
|
|
143
|
-
export type ScalarArgument = Presence & Omission & {
|
|
317
|
+
export type ScalarArgument = Presence & Omission & Described & ArgumentExtensions & {
|
|
144
318
|
variadic?: false;
|
|
145
319
|
validate?: StandardSchemaV1;
|
|
146
320
|
};
|
|
@@ -198,7 +372,7 @@ export type ArgumentValue<Config extends ArgumentConfig> = Config extends {
|
|
|
198
372
|
} | {
|
|
199
373
|
validateOmitted: true;
|
|
200
374
|
} ? never : undefined);
|
|
201
|
-
export type BooleanOption = (OptionSpelling & {
|
|
375
|
+
export type BooleanOption = (OptionSpelling & Described & Listed & OptionExtensions & {
|
|
202
376
|
type: 'boolean';
|
|
203
377
|
validate?: never;
|
|
204
378
|
default?: never;
|
|
@@ -206,7 +380,7 @@ export type BooleanOption = (OptionSpelling & {
|
|
|
206
380
|
required?: never;
|
|
207
381
|
validateOmitted?: never;
|
|
208
382
|
polarity?: 'positive' | 'negative';
|
|
209
|
-
}) | {
|
|
383
|
+
}) | (Described & Listed & OptionExtensions & {
|
|
210
384
|
type: 'boolean';
|
|
211
385
|
validate?: never;
|
|
212
386
|
default?: never;
|
|
@@ -216,8 +390,24 @@ export type BooleanOption = (OptionSpelling & {
|
|
|
216
390
|
polarity: 'both';
|
|
217
391
|
short?: ShortAlias;
|
|
218
392
|
shortOnly?: false;
|
|
219
|
-
};
|
|
393
|
+
});
|
|
220
394
|
export type OptionConfig = StringOption | BooleanOption;
|
|
395
|
+
/**
|
|
396
|
+
* The parsing part of a string option config, which is all a plugin option declares. A plugin
|
|
397
|
+
* option carries no schema and no presence rule, because the pre-scan consumes it ahead of routing,
|
|
398
|
+
* where the validation context every schema is promised cannot exist. Its middleware interprets
|
|
399
|
+
* the value.
|
|
400
|
+
* A Boolean plugin option is an ordinary `BooleanOption`, which already declares none of them.
|
|
401
|
+
*/
|
|
402
|
+
export type PluginStringOption = OptionSpelling & Multiplicity & Described & Listed & OptionExtensions & {
|
|
403
|
+
type: 'string';
|
|
404
|
+
default?: string | string[];
|
|
405
|
+
polarity?: never;
|
|
406
|
+
required?: never;
|
|
407
|
+
validate?: never;
|
|
408
|
+
validateOmitted?: never;
|
|
409
|
+
};
|
|
410
|
+
export type PluginOptionConfig = PluginStringOption | BooleanOption;
|
|
221
411
|
export type OptionValue<Config extends OptionConfig> = Config extends StringOption ? Config extends {
|
|
222
412
|
multiple: true;
|
|
223
413
|
} ? ValidatedValue<Config, string[]> : ValidatedValue<Config, string> | (Config extends {
|
|
@@ -227,34 +417,40 @@ export type OptionValue<Config extends OptionConfig> = Config extends StringOpti
|
|
|
227
417
|
} | {
|
|
228
418
|
validateOmitted: true;
|
|
229
419
|
} ? never : undefined) : boolean;
|
|
230
|
-
export interface ActionContext<Args, Options = {}> {
|
|
420
|
+
export interface ActionContext<Args, Options = {}, Result = unknown> {
|
|
421
|
+
readonly style: ContextualStyle;
|
|
231
422
|
args: Args;
|
|
232
423
|
options: Options;
|
|
233
424
|
passthrough: string[];
|
|
234
|
-
out: Out
|
|
425
|
+
out: Out<Result>;
|
|
235
426
|
host: Host;
|
|
427
|
+
/** The run's cancellation signal, which a caller or an installed signals owner aborts. */
|
|
428
|
+
signal: AbortSignal;
|
|
236
429
|
}
|
|
237
|
-
export type Action<Args, Options = {}> = (context: ActionContext<Args, Options>) => unknown;
|
|
430
|
+
export type Action<Args, Options = {}, Result = unknown> = (context: ActionContext<Args, Options, Result>) => unknown;
|
|
238
431
|
/** Phantom key. It keeps the inferred declaration types exact and holds no runtime value. */
|
|
239
432
|
export declare const declaredTypes: unique symbol;
|
|
240
433
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* a return position, which makes them invariant: a child's globals must be the parent's own type,
|
|
244
|
-
* not a subset and not a superset, because one table serves every Command in the graph.
|
|
434
|
+
* Arguments and local options describe the action's own inputs. Globals are a requirement on the
|
|
435
|
+
* Receiving Application: a library that needs none can attach wherever its local keys are disjoint.
|
|
245
436
|
*/
|
|
246
|
-
export interface DeclaredTypes<Args, Options, Globals> {
|
|
437
|
+
export interface DeclaredTypes<Args, Options, Globals, Result = unknown> {
|
|
247
438
|
args: Args;
|
|
248
|
-
globals: (value: Globals) =>
|
|
439
|
+
globals: (value: Globals) => void;
|
|
249
440
|
options: Options;
|
|
441
|
+
/**
|
|
442
|
+
* The result the Command declares, or `unknown` where it declares none. A plain field keeps it
|
|
443
|
+
* covariant, so a child that carries one still satisfies a neutral `Command` annotation.
|
|
444
|
+
*/
|
|
445
|
+
result: Result;
|
|
250
446
|
}
|
|
251
447
|
/**
|
|
252
448
|
* The handler one declaration accepts. It reads the phantom types, not the `action()` call, so it
|
|
253
449
|
* holds on a fresh declaration, on a partly declared one, and on one that registered its action.
|
|
254
450
|
*/
|
|
255
451
|
export type ActionHandler<Declaration> = Declaration extends {
|
|
256
|
-
[declaredTypes]: DeclaredTypes<infer Args, infer Options, infer Globals>;
|
|
257
|
-
} ? Action<Args, Globals & Options> : never;
|
|
452
|
+
[declaredTypes]: DeclaredTypes<infer Args, infer Options, infer Globals, infer Result>;
|
|
453
|
+
} ? Action<Args, Globals & Options, Result> : never;
|
|
258
454
|
/** The args object an extracted handler receives for this declaration. */
|
|
259
455
|
export type ActionArgs<Declaration> = ActionArgument<Declaration> extends {
|
|
260
456
|
args: infer Args;
|