@loomcli/core 0.4.0 → 0.6.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/LICENSE +21 -0
- package/dist/application.d.ts +60 -31
- package/dist/application.js +356 -149
- package/dist/bindings.d.ts +31 -0
- package/dist/bindings.js +64 -0
- package/dist/chain.d.ts +26 -12
- package/dist/chain.js +59 -92
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +212 -93
- package/dist/command.js +1224 -454
- package/dist/controls.d.ts +8 -0
- package/dist/controls.js +23 -0
- package/dist/defect.d.ts +18 -0
- package/dist/defect.js +272 -0
- package/dist/developer.d.ts +24 -0
- package/dist/developer.js +52 -0
- package/dist/diagnostic-text.d.ts +81 -0
- package/dist/diagnostic-text.js +283 -0
- package/dist/diagnostic.d.ts +11 -0
- package/dist/diagnostic.js +70 -0
- package/dist/errors.d.ts +115 -26
- package/dist/errors.js +333 -54
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +44 -9
- package/dist/extension.js +147 -65
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +108 -25
- package/dist/globals.d.ts +63 -22
- package/dist/globals.js +164 -39
- package/dist/hints.d.ts +79 -0
- package/dist/hints.js +247 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +43 -1
- package/dist/identity.d.ts +19 -0
- package/dist/identity.js +72 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +64 -0
- package/dist/input-rules.js +145 -0
- package/dist/inspect.d.ts +36 -5
- package/dist/inspect.js +125 -27
- package/dist/lanes.js +1 -1
- package/dist/locate.d.ts +41 -0
- package/dist/locate.js +121 -0
- package/dist/options.d.ts +96 -2
- package/dist/options.js +259 -71
- package/dist/output.d.ts +11 -2
- package/dist/output.js +23 -3
- package/dist/plain.d.ts +6 -0
- package/dist/plain.js +12 -0
- package/dist/plugin-rules.d.ts +62 -0
- package/dist/plugin-rules.js +155 -0
- package/dist/plugin.d.ts +121 -55
- package/dist/plugin.js +496 -126
- package/dist/prototypes.d.ts +7 -0
- package/dist/prototypes.js +29 -0
- package/dist/rendering.d.ts +6 -1
- package/dist/rendering.js +23 -5
- package/dist/rules.d.ts +51 -0
- package/dist/rules.js +115 -0
- package/dist/sequence.js +6 -1
- package/dist/sources.d.ts +58 -0
- package/dist/sources.js +258 -0
- package/dist/style-wire.js +1 -1
- package/dist/style.js +1 -1
- package/dist/theme.d.ts +4 -0
- package/dist/theme.js +25 -5
- package/dist/thenable.d.ts +15 -0
- package/dist/thenable.js +29 -0
- package/dist/translators.d.ts +69 -0
- package/dist/translators.js +253 -0
- package/dist/types.d.ts +76 -21
- package/dist/validation.d.ts +65 -10
- package/dist/validation.js +314 -108
- package/dist/view.d.ts +49 -15
- package/dist/view.js +157 -79
- package/package.json +3 -2
package/dist/output.js
CHANGED
|
@@ -145,16 +145,21 @@ export class Output {
|
|
|
145
145
|
signal;
|
|
146
146
|
destinations = new Map();
|
|
147
147
|
renderFault = undefined;
|
|
148
|
+
// Every value a view failed with.
|
|
149
|
+
// A caller that lets one propagate never offers it to a translator.
|
|
150
|
+
viewFaults = new WeakSet();
|
|
148
151
|
// Every fault the output path raised beside its calls, reported after the primary outcome.
|
|
149
152
|
// A source a sequence stopped on and a results-lane fault are both such faults.
|
|
150
153
|
stops = [];
|
|
151
|
-
// The
|
|
154
|
+
// The path routing walked, which an incomplete sequence names, updated as each name routes.
|
|
152
155
|
route = [];
|
|
153
156
|
/**
|
|
154
157
|
* The channel every caller writes through. It is typed with the result left open, because the
|
|
155
158
|
* declaration a call answers to is checked where the action was authored.
|
|
156
159
|
*/
|
|
157
160
|
out;
|
|
161
|
+
/** The channel a configuration source receives: `out` with the results call naming a source. */
|
|
162
|
+
sourceOut;
|
|
158
163
|
palette = new Map();
|
|
159
164
|
policy = {};
|
|
160
165
|
// The contributors this invocation resolves a declared view through, published once they build.
|
|
@@ -179,6 +184,8 @@ export class Output {
|
|
|
179
184
|
success: (message) => this.emit('success', message, 'stderr'),
|
|
180
185
|
warn: (message) => this.emit('warn', message, 'stderr'),
|
|
181
186
|
};
|
|
187
|
+
// A configuration source writes through the same channel, and its results call names it.
|
|
188
|
+
this.sourceOut = { ...this.out, results: () => this.resultFault('source') };
|
|
182
189
|
}
|
|
183
190
|
/**
|
|
184
191
|
* The channel one action receives. On a Command that declares a result nothing the action writes
|
|
@@ -238,7 +245,7 @@ export class Output {
|
|
|
238
245
|
});
|
|
239
246
|
}
|
|
240
247
|
if (typeof view.row === 'function') {
|
|
241
|
-
//
|
|
248
|
+
// The result() call rejects a row view on a value result, so reaching one here is core's own fault.
|
|
242
249
|
return this.renderFailed(new Error(`The view "${selected}" renders rows, not a value.`));
|
|
243
250
|
}
|
|
244
251
|
return this.rendered(() => resolveView(bare, view)(erased(value), this.context('stdout')));
|
|
@@ -259,7 +266,7 @@ export class Output {
|
|
|
259
266
|
useViews(registry) {
|
|
260
267
|
this.registry = registry;
|
|
261
268
|
}
|
|
262
|
-
/** The
|
|
269
|
+
/** The path routing walked, updated as each name routes, which an incomplete sequence names. */
|
|
263
270
|
useRoute(path) {
|
|
264
271
|
this.route = path;
|
|
265
272
|
}
|
|
@@ -391,10 +398,23 @@ export class Output {
|
|
|
391
398
|
*/
|
|
392
399
|
renderFailed(cause) {
|
|
393
400
|
this.renderFault ??= { cause };
|
|
401
|
+
if ((typeof cause === 'object' || typeof cause === 'function') && cause !== null) {
|
|
402
|
+
this.viewFaults.add(cause);
|
|
403
|
+
}
|
|
394
404
|
const rejection = Promise.reject(cause);
|
|
395
405
|
void rejection.catch(() => undefined);
|
|
396
406
|
return rejection;
|
|
397
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Whether one value is what a view or a message check failed with during this invocation. A
|
|
410
|
+
* broken view is a defect in the code that broke the view contract, so an action or a source that
|
|
411
|
+
* lets its rejection propagate reports it as that defect.
|
|
412
|
+
*/
|
|
413
|
+
raisedByView(value) {
|
|
414
|
+
return ((typeof value === 'object' || typeof value === 'function') &&
|
|
415
|
+
value !== null &&
|
|
416
|
+
this.viewFaults.has(value));
|
|
417
|
+
}
|
|
398
418
|
/** What a view failed with during this invocation, if one did. */
|
|
399
419
|
get fault() {
|
|
400
420
|
return this.renderFault;
|
package/dist/plain.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A structural value core reads as plain data: an object literal, and never a declaration that
|
|
3
|
+
* carries state of its own. The options slots read it to reject a value that is not an options
|
|
4
|
+
* object, and inspection reads it to copy a declared value faithfully.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
package/dist/plain.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A structural value core reads as plain data: an object literal, and never a declaration that
|
|
3
|
+
* carries state of its own. The options slots read it to reject a value that is not an options
|
|
4
|
+
* object, and inspection reads it to copy a declared value faithfully.
|
|
5
|
+
*/
|
|
6
|
+
export function isPlainObject(value) {
|
|
7
|
+
if (value === null || typeof value !== 'object') {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const prototype = Object.getPrototypeOf(value);
|
|
11
|
+
return prototype === Object.prototype || prototype === null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** A slot that holds a list, such as `plugins` or `commands`, holding a value of another kind. */
|
|
2
|
+
declare const notAList: import("./diagnostic-text.js").DiagnosticRule;
|
|
3
|
+
/**
|
|
4
|
+
* A declaration core reads by its keys, such as a plugin's middleware or a constructor's options,
|
|
5
|
+
* that is not an object.
|
|
6
|
+
*/
|
|
7
|
+
declare const notAnObject: import("./diagnostic-text.js").DiagnosticRule;
|
|
8
|
+
/** A list entry that its factory did not build, such as a hand-made plugin or translation. */
|
|
9
|
+
declare const foreignValue: import("./diagnostic-text.js").DiagnosticRule;
|
|
10
|
+
/** One plugin identity installed twice. */
|
|
11
|
+
declare const pluginInstalledTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
12
|
+
/** A second plugin claiming the theme, the signals, or the configuration source slot. */
|
|
13
|
+
declare const slotTaken: import("./diagnostic-text.js").DiagnosticRule;
|
|
14
|
+
/** A plugin, extension, or view identity outside the identity grammar. */
|
|
15
|
+
declare const invalidIdentity: import("./diagnostic-text.js").DiagnosticRule;
|
|
16
|
+
/** A validator or a presence rule on a plugin option. */
|
|
17
|
+
declare const pluginOptionRule: import("./diagnostic-text.js").DiagnosticRule;
|
|
18
|
+
/** A middleware activation that is missing, empty, or names an option the plugin lacks. */
|
|
19
|
+
declare const middlewareActivation: import("./diagnostic-text.js").DiagnosticRule;
|
|
20
|
+
/** A loader, a hook, or a translator that core cannot call. */
|
|
21
|
+
declare const notAFunction: import("./diagnostic-text.js").DiagnosticRule;
|
|
22
|
+
/** A claimed signal outside SIGINT and SIGTERM. */
|
|
23
|
+
declare const unknownSignal: import("./diagnostic-text.js").DiagnosticRule;
|
|
24
|
+
/** One signal claimed twice by one plugin. */
|
|
25
|
+
declare const signalClaimedTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
26
|
+
/** A configuration source binding that is not one of the plugin's option extensions. */
|
|
27
|
+
declare const sourceBinding: import("./diagnostic-text.js").DiagnosticRule;
|
|
28
|
+
/** A plugin option that carries its own plugin's source binding. */
|
|
29
|
+
declare const sourceBoundOwnOption: import("./diagnostic-text.js").DiagnosticRule;
|
|
30
|
+
/** Two distinct objects under one extension or declared-view identity. */
|
|
31
|
+
declare const twoPackageCopies: import("./diagnostic-text.js").DiagnosticRule;
|
|
32
|
+
/** A descriptor with no Standard Schema. */
|
|
33
|
+
declare const extensionWithoutSchema: import("./diagnostic-text.js").DiagnosticRule;
|
|
34
|
+
/** An extension value on a declaration its descriptor does not target. */
|
|
35
|
+
declare const extensionTarget: import("./diagnostic-text.js").DiagnosticRule;
|
|
36
|
+
/** Two values of one extension in one layer. */
|
|
37
|
+
declare const extensionValueTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
38
|
+
/** An extension value its schema rejects, by an issue or by a throw. */
|
|
39
|
+
declare const invalidExtensionValue: import("./diagnostic-text.js").DiagnosticRule;
|
|
40
|
+
/** An extension schema that answers with a promise. */
|
|
41
|
+
declare const asyncExtensionSchema: import("./diagnostic-text.js").DiagnosticRule;
|
|
42
|
+
/** An extension output that is not plain data. */
|
|
43
|
+
declare const extensionOutput: import("./diagnostic-text.js").DiagnosticRule;
|
|
44
|
+
/** An override whose key is neither a declared view nor a failure class. */
|
|
45
|
+
declare const overrideKey: import("./diagnostic-text.js").DiagnosticRule;
|
|
46
|
+
/** One key overridden twice inside one contributor. */
|
|
47
|
+
declare const overrideTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
48
|
+
/** A `translate()` key that is not a class, or that is a failure class. */
|
|
49
|
+
declare const translationKey: import("./diagnostic-text.js").DiagnosticRule;
|
|
50
|
+
/** An `onCommandAttach` hook that threw or returned a value that is not the attached Command. */
|
|
51
|
+
declare const brokenAttachHook: import("./diagnostic-text.js").DiagnosticRule;
|
|
52
|
+
/** The retired `globals` or `failures` Application option. */
|
|
53
|
+
declare const retiredApplicationOption: import("./diagnostic-text.js").DiagnosticRule;
|
|
54
|
+
/** A packet that is not an object, or whose build is neither value. */
|
|
55
|
+
declare const invalidPacket: import("./diagnostic-text.js").DiagnosticRule;
|
|
56
|
+
/** A rendering policy that is not an object, or holds a setting outside its closed set. */
|
|
57
|
+
declare const renderingPolicyRule: import("./diagnostic-text.js").DiagnosticRule;
|
|
58
|
+
/** A plugin theme that is not a mapping of names to unapplied concrete style chains. */
|
|
59
|
+
declare const themeMapping: import("./diagnostic-text.js").DiagnosticRule;
|
|
60
|
+
/** A theme name that a built-in style member already holds. */
|
|
61
|
+
declare const themeNameTaken: import("./diagnostic-text.js").DiagnosticRule;
|
|
62
|
+
export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, pluginOptionRule, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { registerRule } from './diagnostic-text.js';
|
|
2
|
+
/*
|
|
3
|
+
* Core's rules for the declaration faults of plugins, extensions, views, translators, lifecycle
|
|
4
|
+
* hooks, and the Application options that install them. Each is declared once here and shared by
|
|
5
|
+
* every site that raises it, as a plugin's rules are.
|
|
6
|
+
*/
|
|
7
|
+
/** A slot that holds a list, such as `plugins` or `commands`, holding a value of another kind. */
|
|
8
|
+
const notAList = registerRule('@loomcli/core/not-a-list', {
|
|
9
|
+
explanation: 'Core reads plugins, commands, extensions, views, translators, and signals each as a list, in order. A value of any other kind has no entries to read.',
|
|
10
|
+
headline: 'Not a list',
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* A declaration core reads by its keys, such as a plugin's middleware or a constructor's options,
|
|
14
|
+
* that is not an object.
|
|
15
|
+
*/
|
|
16
|
+
const notAnObject = registerRule('@loomcli/core/not-an-object', {
|
|
17
|
+
explanation: "Core reads the options of a Command and of the Application, a plugin's definition, its options record, each of its option declarations, its middleware, its source, and the config of an argument or option by their keys. A value of any other kind has no keys to read.",
|
|
18
|
+
headline: 'Not an object',
|
|
19
|
+
});
|
|
20
|
+
/** A list entry that its factory did not build, such as a hand-made plugin or translation. */
|
|
21
|
+
const foreignValue = registerRule('@loomcli/core/foreign-value', {
|
|
22
|
+
explanation: 'Core reads a plugin, an extension, an extension value, a declared view, a view override, and a translation through facts its factory recorded when it built the value. Any other value carries none, even one of the same shape.',
|
|
23
|
+
headline: 'Value not from its factory',
|
|
24
|
+
});
|
|
25
|
+
/** One plugin identity installed twice. */
|
|
26
|
+
const pluginInstalledTwice = registerRule('@loomcli/core/plugin-installed-twice', {
|
|
27
|
+
explanation: 'Core keys each plugin by its identity, and a plugin contributes its options, middleware, hooks, and views once, so a second installation would contribute each of them again.',
|
|
28
|
+
headline: 'Plugin installed twice',
|
|
29
|
+
});
|
|
30
|
+
/** A second plugin claiming the theme, the signals, or the configuration source slot. */
|
|
31
|
+
const slotTaken = registerRule('@loomcli/core/slot-taken', {
|
|
32
|
+
explanation: 'A slot is a position exactly one plugin claims: the theme, the process signals, and the configuration source. A second claim would leave two plugins answering where core asks one.',
|
|
33
|
+
headline: 'Slot already claimed',
|
|
34
|
+
});
|
|
35
|
+
/** A plugin, extension, or view identity outside the identity grammar. */
|
|
36
|
+
const invalidIdentity = registerRule('@loomcli/core/invalid-identity', {
|
|
37
|
+
explanation: 'An identity keys what a plugin, an extension, or a view contributes, names it in every diagnostic, and prefixes the identities of the rules its package declares, so it is a package name as npm spells one, scoped or not, then any subpath segments, each after a / and each of lowercase letters and digits in words joined by single hyphens.',
|
|
38
|
+
headline: 'Invalid identity',
|
|
39
|
+
});
|
|
40
|
+
/** A validator or a presence rule on a plugin option. */
|
|
41
|
+
const pluginOptionRule = registerRule('@loomcli/core/plugin-option-rule', {
|
|
42
|
+
explanation: "A plugin's middleware interprets its own options' values, so a plugin option declares how it parses and nothing more: no validator and no presence rule.",
|
|
43
|
+
headline: 'Rule on a plugin option',
|
|
44
|
+
});
|
|
45
|
+
/** A middleware activation that is missing, empty, or names an option the plugin lacks. */
|
|
46
|
+
const middlewareActivation = registerRule('@loomcli/core/middleware-activation', {
|
|
47
|
+
explanation: "Activation decides when core loads a plugin's middleware: on every run with 'always', or only when an invocation supplies one of the plugin's own options the list names, so a middleware no invocation needs costs it nothing.",
|
|
48
|
+
headline: 'Invalid middleware activation',
|
|
49
|
+
});
|
|
50
|
+
/** A loader, a hook, or a translator that core cannot call. */
|
|
51
|
+
const notAFunction = registerRule('@loomcli/core/not-a-function', {
|
|
52
|
+
explanation: 'Core calls each of these values at a point of its own: load when a run first reaches a middleware or a source, onCommandAttach at graph build, onFailure when a failure renders, and a translator when a foreign throw reaches it. A value core cannot call leaves it nothing to run.',
|
|
53
|
+
headline: 'Not a function',
|
|
54
|
+
});
|
|
55
|
+
/** A claimed signal outside SIGINT and SIGTERM. */
|
|
56
|
+
const unknownSignal = registerRule('@loomcli/core/unknown-signal', {
|
|
57
|
+
explanation: 'Core installs listeners for SIGINT and SIGTERM alone, the two signals that ask a command-line program to stop, so a plugin claims one of those.',
|
|
58
|
+
headline: 'Unknown signal',
|
|
59
|
+
});
|
|
60
|
+
/** One signal claimed twice by one plugin. */
|
|
61
|
+
const signalClaimedTwice = registerRule('@loomcli/core/signal-claimed-twice', {
|
|
62
|
+
explanation: 'Core installs one listener for each signal a plugin claims, and a second listener on one signal would take the forced path on the first signal the run receives.',
|
|
63
|
+
headline: 'Signal claimed twice',
|
|
64
|
+
});
|
|
65
|
+
/** A configuration source binding that is not one of the plugin's option extensions. */
|
|
66
|
+
const sourceBinding = registerRule('@loomcli/core/source-binding', {
|
|
67
|
+
explanation: 'A configuration source answers the options that carry its binding, an extension the plugin lists under extensions that applies to options. Core asks the source about those options without knowing what the binding means.',
|
|
68
|
+
headline: 'Invalid source binding',
|
|
69
|
+
});
|
|
70
|
+
/** A plugin option that carries its own plugin's source binding. */
|
|
71
|
+
const sourceBoundOwnOption = registerRule('@loomcli/core/source-bound-own-option', {
|
|
72
|
+
explanation: "A plugin's own options resolve before its configuration source loads, because the source reads them, so none of them can take a value from that source.",
|
|
73
|
+
headline: 'Source bound to its own option',
|
|
74
|
+
});
|
|
75
|
+
/** Two distinct objects under one extension or declared-view identity. */
|
|
76
|
+
const twoPackageCopies = registerRule('@loomcli/core/two-package-copies', {
|
|
77
|
+
explanation: 'Core keys each extension and each declared view by its identity and compares it by reference. Two distinct objects under one identity mean two copies of the package that defines it are installed, and a value one copy made cannot be read through the other.',
|
|
78
|
+
headline: 'Two copies of one package',
|
|
79
|
+
});
|
|
80
|
+
/** A descriptor with no Standard Schema. */
|
|
81
|
+
const extensionWithoutSchema = registerRule('@loomcli/core/extension-without-schema', {
|
|
82
|
+
explanation: "Core validates each extension value against its descriptor's Standard Schema at the call that carries it, so a descriptor with no schema leaves the value unchecked.",
|
|
83
|
+
headline: 'Extension without a schema',
|
|
84
|
+
});
|
|
85
|
+
/** An extension value on a declaration its descriptor does not target. */
|
|
86
|
+
const extensionTarget = registerRule('@loomcli/core/extension-target', {
|
|
87
|
+
explanation: 'An extension is defined for one target, Commands, options, or arguments, and a typed read takes that kind of node alone, so a value on another kind would never be read.',
|
|
88
|
+
headline: 'Extension on the wrong target',
|
|
89
|
+
});
|
|
90
|
+
/** Two values of one extension in one layer. */
|
|
91
|
+
const extensionValueTwice = registerRule('@loomcli/core/extension-value-twice', {
|
|
92
|
+
explanation: 'One extensions list or one extend() call sets each extension once, collecting or not, so two values of one extension leave it unclear which the author meant.',
|
|
93
|
+
headline: 'Extension value twice',
|
|
94
|
+
});
|
|
95
|
+
/** An extension value its schema rejects, by an issue or by a throw. */
|
|
96
|
+
const invalidExtensionValue = registerRule('@loomcli/core/invalid-extension-value', {
|
|
97
|
+
explanation: "An extension value passes its descriptor's schema at the call that carries it, so every plugin that reads it reads a value the schema accepted.",
|
|
98
|
+
headline: 'Invalid extension value',
|
|
99
|
+
});
|
|
100
|
+
/** An extension schema that answers with a promise. */
|
|
101
|
+
const asyncExtensionSchema = registerRule('@loomcli/core/async-extension-schema', {
|
|
102
|
+
explanation: 'Core validates extension values synchronously while it builds the declaration, so a schema that answers with a promise leaves it no verdict to read.',
|
|
103
|
+
headline: 'Asynchronous extension schema',
|
|
104
|
+
});
|
|
105
|
+
/** An extension output that is not plain data. */
|
|
106
|
+
const extensionOutput = registerRule('@loomcli/core/extension-output', {
|
|
107
|
+
explanation: "Every projection, the manifest included, reads an extension's output as frozen plain data: strings, finite numbers, Booleans, null, arrays, and plain objects.",
|
|
108
|
+
headline: 'Extension output not plain data',
|
|
109
|
+
});
|
|
110
|
+
/** An override whose key is neither a declared view nor a failure class. */
|
|
111
|
+
const overrideKey = registerRule('@loomcli/core/override-key', {
|
|
112
|
+
explanation: 'An override replaces the view of a declared view or of a failure class, so its key is one of them.',
|
|
113
|
+
headline: 'Invalid override key',
|
|
114
|
+
});
|
|
115
|
+
/** One key overridden twice inside one contributor. */
|
|
116
|
+
const overrideTwice = registerRule('@loomcli/core/override-twice', {
|
|
117
|
+
explanation: 'Within one contributor, one key answers to one override, so two leave it unclear which the author meant. The same key overridden by two contributors resolves to the first installed.',
|
|
118
|
+
headline: 'Key overridden twice',
|
|
119
|
+
});
|
|
120
|
+
/** A `translate()` key that is not a class, or that is a failure class. */
|
|
121
|
+
const translationKey = registerRule('@loomcli/core/translation-key', {
|
|
122
|
+
explanation: 'A translation keys on the foreign error class it replaces. Core offers a translator only a thrown value no failure class made, so the key is a class and never a failure class.',
|
|
123
|
+
headline: 'Invalid translation key',
|
|
124
|
+
});
|
|
125
|
+
/** An `onCommandAttach` hook that threw or returned a value that is not the attached Command. */
|
|
126
|
+
const brokenAttachHook = registerRule('@loomcli/core/broken-attach-hook', {
|
|
127
|
+
explanation: "onCommandAttach receives each Command's declaration at graph build and returns it, or a value derived from it, synchronously and without throwing. Core builds the Command the hook returns, so a throw or any other value leaves it nothing to build.",
|
|
128
|
+
headline: 'Broken attach hook',
|
|
129
|
+
});
|
|
130
|
+
/** The retired `globals` or `failures` Application option. */
|
|
131
|
+
const retiredApplicationOption = registerRule('@loomcli/core/retired-application-option', {
|
|
132
|
+
explanation: 'The Application no longer reads globals or failures. A global option is declared with globalOption(), so its type reaches every action, and a failure view is an override under views.',
|
|
133
|
+
headline: 'Retired Application option',
|
|
134
|
+
});
|
|
135
|
+
/** A packet that is not an object, or whose build is neither value. */
|
|
136
|
+
const invalidPacket = registerRule('@loomcli/core/invalid-packet', {
|
|
137
|
+
explanation: 'The packet says whether the application was built for development, which decides whether a defect shows the author its Developer Diagnostic or the operator one generic message. Its build reads development or distributed.',
|
|
138
|
+
headline: 'Invalid packet',
|
|
139
|
+
});
|
|
140
|
+
/** A rendering policy that is not an object, or holds a setting outside its closed set. */
|
|
141
|
+
const renderingPolicyRule = registerRule('@loomcli/core/rendering-policy', {
|
|
142
|
+
explanation: 'The rendering policy decides whether output carries color, modifiers, hyperlinks, and terminal controls. color, modifiers, and hyperlinks each read auto, always, or never, and terminalControls reads strip or preserve.',
|
|
143
|
+
headline: 'Invalid rendering policy',
|
|
144
|
+
});
|
|
145
|
+
/** A plugin theme that is not a mapping of names to unapplied concrete style chains. */
|
|
146
|
+
const themeMapping = registerRule('@loomcli/core/theme-mapping', {
|
|
147
|
+
explanation: "A plugin's theme maps each name to an unapplied chain of concrete styles. A semantic token reads the theme itself, so a chain that holds one has no concrete style to resolve to.",
|
|
148
|
+
headline: 'Invalid theme mapping',
|
|
149
|
+
});
|
|
150
|
+
/** A theme name that a built-in style member already holds. */
|
|
151
|
+
const themeNameTaken = registerRule('@loomcli/core/theme-name-taken', {
|
|
152
|
+
explanation: 'Each theme name becomes a member of the style object beside the built-in members, so a name a built-in already holds would hide it.',
|
|
153
|
+
headline: 'Theme name taken',
|
|
154
|
+
});
|
|
155
|
+
export { asyncExtensionSchema, brokenAttachHook, extensionOutput, extensionTarget, extensionValueTwice, extensionWithoutSchema, foreignValue, invalidExtensionValue, invalidIdentity, invalidPacket, middlewareActivation, notAFunction, notAList, notAnObject, overrideKey, overrideTwice, pluginInstalledTwice, pluginOptionRule, renderingPolicyRule, retiredApplicationOption, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, themeMapping, themeNameTaken, translationKey, twoPackageCopies, unknownSignal, };
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,46 +1,36 @@
|
|
|
1
1
|
import type { MiddlewareContext } from './chain.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AttachedChild, Command } from './command.js';
|
|
3
|
+
import type { AdmittedDescriptor, AnyExtension, DescriptorRegistry } from './extension.js';
|
|
4
|
+
import type { InputRecords } from './globals.js';
|
|
5
|
+
import type { FailureHook } from './hints.js';
|
|
6
|
+
import type { CommandGraph, OptionNode } from './inspect.js';
|
|
7
|
+
import type { OptionValues } from './options.js';
|
|
3
8
|
import type { ProcessSignal } from './signals.js';
|
|
4
9
|
import type { Palette } from './style-state.js';
|
|
5
|
-
import type { ThemeConstraint, ThemeMapping } from './style.js';
|
|
6
|
-
import type {
|
|
10
|
+
import type { ContextualStyle, ThemeConstraint, ThemeMapping } from './style.js';
|
|
11
|
+
import type { Translation, TranslationContributor } from './translators.js';
|
|
12
|
+
import type { CommandAttachHook, Host, OptionValue, Out, PluginOptionConfig } from './types.js';
|
|
7
13
|
import type { OptionInput } from './validation.js';
|
|
8
|
-
import type { ViewContribution } from './view.js';
|
|
14
|
+
import type { ViewContribution, ViewSubject } from './view.js';
|
|
9
15
|
/**
|
|
10
16
|
* The declaration record a plugin contributes its options under: the parsing part of an option
|
|
11
17
|
* config, keyed by option name. A plugin option carries no schema and no presence rule, so the
|
|
12
|
-
* config type publishes neither, and
|
|
18
|
+
* config type publishes neither, and `plugin()` repeats the rule for a JavaScript author.
|
|
13
19
|
*/
|
|
14
20
|
type PluginOptions = Readonly<Record<string, PluginOptionConfig>>;
|
|
15
21
|
/** The values one plugin's own options take, read through the same rules an action's options are. */
|
|
16
22
|
type PluginOptionValues<Options extends PluginOptions> = {
|
|
17
23
|
readonly [Name in keyof Options]: OptionValue<Options[Name]>;
|
|
18
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* The spelling that supplied each of one plugin's own options given as a token, such as `-h`,
|
|
27
|
+
* `--help`, or `--no-total`. An option filled by an input source, defaulted, or not supplied has
|
|
28
|
+
* no entry.
|
|
29
|
+
*/
|
|
30
|
+
type PluginOptionSpellings<Options extends PluginOptions> = Readonly<Partial<Record<keyof Options & string, string>>>;
|
|
19
31
|
/** Phantom key. It carries a plugin's declared options in a read position and holds no value. */
|
|
20
32
|
declare const pluginOptions: unique symbol;
|
|
21
33
|
declare const pluginTheme: unique symbol;
|
|
22
|
-
/**
|
|
23
|
-
* One plugin's declarations as the registry holds them, with the generic parts erased. Build reads
|
|
24
|
-
* every one of them defensively, because a JavaScript author reaches the same slots, so the erased
|
|
25
|
-
* shape is what the rules below read and no declaration is claimed to be well formed here.
|
|
26
|
-
*/
|
|
27
|
-
interface DeclaredPlugin {
|
|
28
|
-
theme?: unknown;
|
|
29
|
-
options?: PluginOptions;
|
|
30
|
-
middleware?: {
|
|
31
|
-
activate?: unknown;
|
|
32
|
-
load?: unknown;
|
|
33
|
-
};
|
|
34
|
-
onCommandAttach?: unknown;
|
|
35
|
-
extensions?: readonly AnyExtension[];
|
|
36
|
-
views?: unknown;
|
|
37
|
-
signals?: unknown;
|
|
38
|
-
}
|
|
39
|
-
/** The declarations behind one plugin value, read by this package alone. */
|
|
40
|
-
interface PluginNode {
|
|
41
|
-
definition: DeclaredPlugin;
|
|
42
|
-
identity: unknown;
|
|
43
|
-
}
|
|
44
34
|
/**
|
|
45
35
|
* The runtime value `plugin()` returns. `Options` appears in a read position alone, which makes it
|
|
46
36
|
* covariant: a `plugins` list holds plugins with different options the way `views` holds
|
|
@@ -49,7 +39,7 @@ interface PluginNode {
|
|
|
49
39
|
declare class PluginDeclaration<Options extends PluginOptions, Theme extends ThemeMapping> {
|
|
50
40
|
readonly [pluginTheme]: Theme;
|
|
51
41
|
readonly [pluginOptions]: () => Options;
|
|
52
|
-
constructor(node:
|
|
42
|
+
constructor(node: BuiltPlugin);
|
|
53
43
|
}
|
|
54
44
|
/**
|
|
55
45
|
* One plugin, as the opaque value `plugin()` returns. The declarations behind it stay private to
|
|
@@ -61,8 +51,39 @@ type OptionsOf<Contributor> = Contributor extends Plugin<infer Options> ? Option
|
|
|
61
51
|
/** A middleware reads its own plugin's options and either takes over or continues the chain. */
|
|
62
52
|
type Middleware<Contributor extends Plugin | ((...args: never[]) => Plugin)> = (context: MiddlewareContext<OptionsOf<Contributor>>) => Promise<void> | void;
|
|
63
53
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
54
|
+
* What a configuration source receives: the host, its own plugin's option values, resolved from
|
|
55
|
+
* argv, the environment, and their defaults, the `OptionNode` of every option core asks about, and
|
|
56
|
+
* the ordinary channels a middleware and an action already read. Each request is a node inside
|
|
57
|
+
* `graph`, the graph `inspect()` returns for the run. `out` is the channel a middleware receives,
|
|
58
|
+
* and `style` the contextual style an action receives, so a source warns and escapes as they do.
|
|
59
|
+
*/
|
|
60
|
+
interface SourceContext<Options extends PluginOptions = PluginOptions> {
|
|
61
|
+
readonly host: Host;
|
|
62
|
+
readonly options: PluginOptionValues<Options>;
|
|
63
|
+
readonly requests: readonly OptionNode[];
|
|
64
|
+
readonly graph: CommandGraph;
|
|
65
|
+
readonly out: Out;
|
|
66
|
+
readonly style: ContextualStyle;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* One answer: a value of the option's raw type, a string, a Boolean, or a list of strings for a
|
|
70
|
+
* multiple option, and the one-line label core prints in a diagnostic about the value.
|
|
71
|
+
*/
|
|
72
|
+
interface SourceAnswer {
|
|
73
|
+
readonly value: string | boolean | readonly string[];
|
|
74
|
+
readonly label: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A configuration source answers the requested options by declared name. A requested option with
|
|
78
|
+
* no key in the record has no answer and falls through to its default.
|
|
79
|
+
*/
|
|
80
|
+
type SourceResolver<Contributor extends Plugin | ((...args: never[]) => Plugin)> = (context: SourceContext<OptionsOf<Contributor>>) => Promise<Readonly<Record<string, SourceAnswer>>>;
|
|
81
|
+
/**
|
|
82
|
+
* Everything a plugin declares. `plugin()` checks every rule the definition carries on its own, and
|
|
83
|
+
* creating and installing the value runs none of its code: `onCommandAttach` runs at graph build,
|
|
84
|
+
* `onFailure` runs when `run()` renders a failure, the middleware runs inside an invocation, the
|
|
85
|
+
* configuration source runs in the input-source stage when an unfilled option carries its binding,
|
|
86
|
+
* and a translator runs when a foreign throw its key matches leaves the application's work.
|
|
66
87
|
*/
|
|
67
88
|
interface PluginDefinition<Options extends PluginOptions = PluginOptions, Theme extends ThemeMapping = ThemeMapping> {
|
|
68
89
|
theme?: Theme & ThemeConstraint<Theme>;
|
|
@@ -74,59 +95,104 @@ interface PluginDefinition<Options extends PluginOptions = PluginOptions, Theme
|
|
|
74
95
|
}>;
|
|
75
96
|
};
|
|
76
97
|
onCommandAttach?: CommandAttachHook;
|
|
98
|
+
onFailure?: FailureHook;
|
|
77
99
|
extensions?: readonly AnyExtension[];
|
|
78
100
|
views?: readonly ViewContribution[];
|
|
101
|
+
translators?: readonly Translation[];
|
|
79
102
|
signals?: readonly ('SIGINT' | 'SIGTERM')[];
|
|
103
|
+
source?: {
|
|
104
|
+
binding: AnyExtension & {
|
|
105
|
+
readonly target: 'option';
|
|
106
|
+
};
|
|
107
|
+
load: () => Promise<{
|
|
108
|
+
default: SourceResolver<Plugin<Options>>;
|
|
109
|
+
}>;
|
|
110
|
+
};
|
|
111
|
+
commands?: readonly Command<unknown, unknown>[];
|
|
80
112
|
}
|
|
81
113
|
/**
|
|
82
114
|
* One plugin: an identity and the contributions it carries. Creating and installing the value runs
|
|
83
|
-
* none of its code:
|
|
84
|
-
*
|
|
115
|
+
* none of its code: `onCommandAttach` runs at graph build, `onFailure` runs when `run()` renders a
|
|
116
|
+
* failure, and the middleware runs inside an invocation, so an installed plugin an invocation never
|
|
117
|
+
* reaches costs that invocation its hooks alone. Every rule
|
|
118
|
+
* that one definition carries on its own throws here, before the value exists.
|
|
85
119
|
*/
|
|
86
120
|
declare function plugin<Options extends PluginOptions = {}, const Theme extends ThemeMapping = {}>(identity: string, definition: PluginDefinition<Options, Theme>): Plugin<NoInfer<Options>, NoInfer<Theme>>;
|
|
87
|
-
/** One installed plugin, with the declarations build reads out of it in installation order. */
|
|
88
|
-
interface InstalledPlugin {
|
|
89
|
-
declaration: DeclaredPlugin;
|
|
90
|
-
identity: string;
|
|
91
|
-
}
|
|
92
121
|
/** How every plugin diagnostic names one plugin at the start of a sentence. */
|
|
93
122
|
declare function pluginSentence(identity: string): string;
|
|
123
|
+
/** The subject a plugin's `views` list reports under, and the call that declared it. */
|
|
124
|
+
declare function pluginViews(identity: string): ViewSubject;
|
|
125
|
+
/** What the installed list resolves to: the plugins in order, and every descriptor they define. */
|
|
126
|
+
interface InstalledPlugins {
|
|
127
|
+
descriptors: DescriptorRegistry;
|
|
128
|
+
plugins: readonly BuiltPlugin[];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The installed list in composition order, with every rule that reads two plugins together: an
|
|
132
|
+
* identity installed twice, a second claim on the theme slot, the signals slot, or the
|
|
133
|
+
* configuration source, and two distinct descriptors under one identity. The slot is read
|
|
134
|
+
* defensively, because a JavaScript author reaches it with any value. Each plugin's own rules
|
|
135
|
+
* already ran at its `plugin()` call.
|
|
136
|
+
*/
|
|
137
|
+
declare function installPlugins(application: string, plugins: unknown): InstalledPlugins;
|
|
94
138
|
/**
|
|
95
|
-
* The
|
|
96
|
-
*
|
|
97
|
-
*
|
|
139
|
+
* The default export one plugin loader resolves to, checked by the guard its caller supplies. A
|
|
140
|
+
* loader that throws where it is called and one that rejects later are one failure, and a module
|
|
141
|
+
* without the export names the kind of function it owed, such as `middleware` or `source`.
|
|
98
142
|
*/
|
|
99
|
-
declare function
|
|
143
|
+
declare function loadDefault<Export>(identity: string, load: () => unknown, owed: {
|
|
144
|
+
guard: (value: unknown) => value is Export;
|
|
145
|
+
noun: string;
|
|
146
|
+
}): Promise<Export>;
|
|
100
147
|
/** One plugin's declared middleware: what wakes it, and the loader that fetches its module. */
|
|
101
148
|
interface BuiltMiddleware {
|
|
102
149
|
activate: 'always' | readonly string[];
|
|
103
150
|
load: () => unknown;
|
|
104
151
|
}
|
|
105
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* One plugin's configuration source: the identity of the binding that marks an option as
|
|
154
|
+
* configuration-bound, and the loader that fetches the resolver's module.
|
|
155
|
+
*/
|
|
156
|
+
interface BuiltSource {
|
|
157
|
+
binding: string;
|
|
158
|
+
load: () => unknown;
|
|
159
|
+
}
|
|
160
|
+
/** One plugin's declarations, read once at its `plugin()` call. */
|
|
106
161
|
interface BuiltPlugin {
|
|
107
162
|
theme: Palette | undefined;
|
|
108
163
|
/** The hook core calls once per Command at graph build, or nothing where none is declared. */
|
|
109
164
|
onCommandAttach: CommandAttachHook | undefined;
|
|
165
|
+
/** The hook core calls for each failure `run()` renders after graph build, or nothing. */
|
|
166
|
+
onFailure: FailureHook | undefined;
|
|
110
167
|
/** The plugin's own `views` slot, read once the validated theme is in place. */
|
|
111
168
|
views: unknown;
|
|
169
|
+
/** The translations the plugin registers, which resolve after the application's. */
|
|
170
|
+
translators: TranslationContributor;
|
|
112
171
|
identity: string;
|
|
113
172
|
inputs: readonly OptionInput[];
|
|
114
173
|
middleware: BuiltMiddleware | undefined;
|
|
115
174
|
signals: readonly ProcessSignal[];
|
|
175
|
+
source: BuiltSource | undefined;
|
|
176
|
+
/** The Commands the plugin attaches to the root, in list order. */
|
|
177
|
+
commands: readonly AttachedChild[];
|
|
178
|
+
/** Every descriptor the plugin defines or its options' values name, by identity. */
|
|
179
|
+
descriptors: ReadonlyMap<string, AdmittedDescriptor>;
|
|
180
|
+
/** The extension record each of the plugin's own options carries. */
|
|
181
|
+
records: InputRecords;
|
|
116
182
|
}
|
|
117
|
-
/** The shared registers one build fills while it reads each plugin's contributions. */
|
|
118
|
-
interface PluginBuild {
|
|
119
|
-
descriptors: DescriptorRegistry;
|
|
120
|
-
extensions: ExtensionRecords;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Every installed plugin's declarations, in installation order. A plugin's own extensions register
|
|
124
|
-
* before any declaration carries a value, so a duplicated package copy is reported from the list
|
|
125
|
-
* that installed it.
|
|
126
|
-
*/
|
|
127
|
-
declare function buildPlugins(installed: readonly InstalledPlugin[], build: PluginBuild): readonly BuiltPlugin[];
|
|
128
183
|
/** The signals the one slot owner claimed, or none when no installed plugin claims the slot. */
|
|
129
184
|
declare function ownedSignals(plugins: readonly BuiltPlugin[]): readonly ProcessSignal[];
|
|
185
|
+
/** The value shape a plugin option takes, which is what `OptionValue` gives its declaration. */
|
|
186
|
+
type PluginValues = Record<string, string | string[] | boolean | undefined>;
|
|
187
|
+
/**
|
|
188
|
+
* One plugin's own option values for one run: what argv or an input source supplied, or the
|
|
189
|
+
* declared default, filled without validation. A collected value and an array default are copied,
|
|
190
|
+
* so a plugin that writes to what it received changes neither the declaration nor the next run.
|
|
191
|
+
* Entries become own keys even for a name such as `__proto__`, which assignment would not.
|
|
192
|
+
*/
|
|
193
|
+
declare function pluginValues(inputs: readonly OptionInput[], values: OptionValues): PluginValues;
|
|
194
|
+
/** One plugin's own spellings for one run, frozen, so no plugin writes what another reads. */
|
|
195
|
+
declare function pluginSpellings(inputs: readonly OptionInput[], values: OptionValues): Readonly<Record<string, string>>;
|
|
130
196
|
type ThemeOf<Contributor> = [Contributor] extends [never] ? {} : Contributor extends Plugin<PluginOptions, infer Theme> ? Theme : {};
|
|
131
|
-
export type { ThemeOf, BuiltPlugin,
|
|
132
|
-
export {
|
|
197
|
+
export type { ThemeOf, BuiltPlugin, BuiltSource, PluginValues, SourceAnswer, SourceContext, SourceResolver, Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, };
|
|
198
|
+
export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews, pluginValues, };
|