@loomcli/core 0.1.0 → 0.2.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 +20 -0
- package/dist/application.js +207 -75
- package/dist/chain.d.ts +49 -0
- package/dist/chain.js +288 -0
- package/dist/command.d.ts +72 -21
- package/dist/command.js +142 -28
- package/dist/errors.d.ts +10 -2
- package/dist/errors.js +33 -8
- package/dist/extension.d.ts +95 -0
- package/dist/extension.js +313 -0
- package/dist/facts.d.ts +39 -0
- package/dist/facts.js +95 -0
- package/dist/globals.d.ts +39 -7
- package/dist/globals.js +109 -12
- package/dist/index.d.ts +7 -1
- package/dist/index.js +2 -0
- package/dist/inspect.d.ts +44 -10
- package/dist/inspect.js +64 -21
- package/dist/options.d.ts +7 -0
- package/dist/options.js +9 -0
- package/dist/plugin.d.ts +116 -0
- package/dist/plugin.js +250 -0
- package/dist/signals.d.ts +52 -0
- package/dist/signals.js +85 -0
- package/dist/types.d.ts +55 -7
- package/dist/validation.d.ts +4 -2
- package/dist/validation.js +17 -15
- package/package.json +1 -1
package/dist/application.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AfterAction, AfterArgument, AfterCommand, Command, CommandMethod, CommandState } from './command.js';
|
|
2
2
|
import type { FailureRenderer } from './errors.js';
|
|
3
|
+
import type { ExtensionValue } from './extension.js';
|
|
3
4
|
import type { GlobalOptions } from './globals.js';
|
|
4
5
|
import type { CommandGraph } from './inspect.js';
|
|
6
|
+
import type { Plugin } from './plugin.js';
|
|
5
7
|
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint, MultipleConstraint, NameConstraint, ExitCode, OptionConfig, OptionValue, RunOptions, ValidateOmittedConstraint } from './types.js';
|
|
6
8
|
/**
|
|
7
9
|
* Every authoring call an Application can publish, beside `run()` and `name`, which always remain.
|
|
@@ -17,6 +19,10 @@ export type ApplicationMethod = Exclude<CommandMethod, 'alias'>;
|
|
|
17
19
|
export interface ApplicationOptions<Globals = {}> {
|
|
18
20
|
globals?: GlobalOptions<Globals>;
|
|
19
21
|
failures?: readonly FailureRenderer[];
|
|
22
|
+
plugins?: readonly Plugin[];
|
|
23
|
+
extensions?: readonly ExtensionValue<'command'>[];
|
|
24
|
+
description?: string;
|
|
25
|
+
version?: string;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* The Application holds the unnamed root's declaration state and applies the same transitions a
|
|
@@ -26,8 +32,10 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
26
32
|
#private;
|
|
27
33
|
readonly [declaredTypes]: DeclaredTypes<Args, Options, Globals>;
|
|
28
34
|
constructor(name: string, root: CommandState<Args, Options, Globals>, config: {
|
|
35
|
+
declared: DeclaredFacts;
|
|
29
36
|
failures: readonly FailureRenderer[];
|
|
30
37
|
options?: unknown;
|
|
38
|
+
plugins: readonly Plugin[];
|
|
31
39
|
});
|
|
32
40
|
get name(): string;
|
|
33
41
|
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Application<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State>>;
|
|
@@ -42,6 +50,13 @@ declare class ApplicationBuilder<Args, Options, Globals, State extends Applicati
|
|
|
42
50
|
* wrapper publishes the same runtime value in exactly that state.
|
|
43
51
|
*/
|
|
44
52
|
private derive;
|
|
53
|
+
/**
|
|
54
|
+
* Every rule that reads the declarations alone, in the order `run()` reads them: the options
|
|
55
|
+
* slot, the installed list, the application's failure registrations, each plugin's declarations,
|
|
56
|
+
* then the whole Command graph. The registry is published as soon as it is known, so a later
|
|
57
|
+
* declaration error still reaches the renderers the application registered for it.
|
|
58
|
+
*/
|
|
59
|
+
private prepare;
|
|
45
60
|
/**
|
|
46
61
|
* The built graph as plain, frozen data. It applies every rule `run()` applies without a schema,
|
|
47
62
|
* in the order `run()` applies them, and throws `DeclarationError` when one fails. Validating a
|
|
@@ -64,6 +79,11 @@ interface ApplicationConstructor {
|
|
|
64
79
|
new (name: string): Application<{}, {}, {}, ApplicationMethod>;
|
|
65
80
|
new <Globals = {}>(name: string, options: ApplicationOptions<Globals>): Application<{}, {}, Globals, ApplicationMethod>;
|
|
66
81
|
}
|
|
82
|
+
/** The same facts as the constructor captured them, before any rule has read them. */
|
|
83
|
+
interface DeclaredFacts {
|
|
84
|
+
description: unknown;
|
|
85
|
+
version: unknown;
|
|
86
|
+
}
|
|
67
87
|
/**
|
|
68
88
|
* The public constructor takes a name and one options object. The globals type narrows to the
|
|
69
89
|
* supplied value, and the failure renderers configure the application the way its commands do.
|
package/dist/application.js
CHANGED
|
@@ -1,12 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { runInvocation } from './chain.js';
|
|
2
|
+
import { attachChild, buildGraph, collectInputs, declareAction, declareArgument, declareOption, freshState, } from './command.js';
|
|
3
|
+
import { buildFailures, DeclarationError, describeFailure, InternalError, mergeFailures, reasonOf, toFailure, } from './errors.js';
|
|
4
|
+
import { checkDescription, checkNoListingFacts, checkVersion, isPlainObject } from './facts.js';
|
|
3
5
|
import { isGlobalOptions } from './globals.js';
|
|
4
6
|
import { captureHost } from './host.js';
|
|
5
7
|
import { inspectGraph } from './inspect.js';
|
|
6
8
|
import { Output, reportPlainly } from './output.js';
|
|
9
|
+
import { buildPlugins, installPlugins, ownedSignals, pluginSentence } from './plugin.js';
|
|
10
|
+
import { bracketRun, cancellationCode, isCancellationEcho } from './signals.js';
|
|
7
11
|
import { captureConfig, checkDeclarations, prepareInputs } from './validation.js';
|
|
8
12
|
/** The registry a failure is reported through when the application's own could not be built. */
|
|
9
13
|
const noRegistrations = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Whether one failure is the cancellation the run already reports, which core does not report a
|
|
16
|
+
* second time. Any other failure after cancellation is rendered as usual.
|
|
17
|
+
*/
|
|
18
|
+
function silenced(thrown, signal, cancelled) {
|
|
19
|
+
return cancelled !== undefined && isCancellationEcho(thrown, signal.reason);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The caller's own signal, read where it enters. A JavaScript caller reaches the slot with any
|
|
23
|
+
* value, and a value that is not an `AbortSignal` would otherwise escape as a raw TypeError.
|
|
24
|
+
*/
|
|
25
|
+
function checkSignal(signal) {
|
|
26
|
+
if (signal === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
if (!(signal instanceof AbortSignal)) {
|
|
30
|
+
throw new InternalError('run() received a signal that is not an AbortSignal. Supply the signal of an AbortController.', undefined);
|
|
31
|
+
}
|
|
32
|
+
return signal;
|
|
33
|
+
}
|
|
10
34
|
/**
|
|
11
35
|
* The Application holds the unnamed root's declaration state and applies the same transitions a
|
|
12
36
|
* Command does, so each declaration call has one typed implementation and no builder to recover.
|
|
@@ -15,14 +39,19 @@ class ApplicationBuilder {
|
|
|
15
39
|
#name;
|
|
16
40
|
#root;
|
|
17
41
|
#failures;
|
|
18
|
-
|
|
42
|
+
#plugins;
|
|
43
|
+
// The constructor's raw options argument, kept for the slot's own shape rules.
|
|
19
44
|
// The options-slot rules answer at the same point every other authoring fault does:
|
|
20
45
|
// `inspect()` and `run()`.
|
|
21
46
|
#options;
|
|
47
|
+
// The facts the constructor read out of that slot, unexamined until build.
|
|
48
|
+
#declared;
|
|
22
49
|
constructor(name, root, config) {
|
|
50
|
+
this.#declared = config.declared;
|
|
23
51
|
this.#failures = config.failures;
|
|
24
52
|
this.#name = name;
|
|
25
53
|
this.#options = config.options;
|
|
54
|
+
this.#plugins = config.plugins;
|
|
26
55
|
this.#root = root;
|
|
27
56
|
}
|
|
28
57
|
get name() {
|
|
@@ -59,10 +88,33 @@ class ApplicationBuilder {
|
|
|
59
88
|
*/
|
|
60
89
|
derive(root) {
|
|
61
90
|
return new ApplicationBuilder(this.#name, root, {
|
|
91
|
+
declared: this.#declared,
|
|
62
92
|
failures: this.#failures,
|
|
63
93
|
options: this.#options,
|
|
94
|
+
plugins: this.#plugins,
|
|
64
95
|
});
|
|
65
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Every rule that reads the declarations alone, in the order `run()` reads them: the options
|
|
99
|
+
* slot, the installed list, the application's failure registrations, each plugin's declarations,
|
|
100
|
+
* then the whole Command graph. The registry is published as soon as it is known, so a later
|
|
101
|
+
* declaration error still reaches the renderers the application registered for it.
|
|
102
|
+
*/
|
|
103
|
+
prepare(register) {
|
|
104
|
+
const facts = checkOptions(this.#options, this.#declared);
|
|
105
|
+
const installed = installPlugins(this.#plugins);
|
|
106
|
+
const application = buildFailures(this.#failures);
|
|
107
|
+
register(application);
|
|
108
|
+
const install = { descriptors: new Map(), extensions: new Map() };
|
|
109
|
+
const plugins = buildPlugins(installed, install);
|
|
110
|
+
register(mergeFailures([
|
|
111
|
+
application,
|
|
112
|
+
...plugins.map((entry) => buildFailures(entry.failures, pluginSentence(entry.identity))),
|
|
113
|
+
]));
|
|
114
|
+
const graph = buildGraph(this.#root, { ...install, plugins });
|
|
115
|
+
checkDeclarations([...graph.globals.inputs, ...collectInputs(graph.root)]);
|
|
116
|
+
return { facts, graph, plugins };
|
|
117
|
+
}
|
|
66
118
|
/**
|
|
67
119
|
* The built graph as plain, frozen data. It applies every rule `run()` applies without a schema,
|
|
68
120
|
* in the order `run()` applies them, and throws `DeclarationError` when one fails. Validating a
|
|
@@ -70,11 +122,8 @@ class ApplicationBuilder {
|
|
|
70
122
|
* Nothing is cached: each call builds the graph anew.
|
|
71
123
|
*/
|
|
72
124
|
inspect() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const graph = buildGraph(this.#root);
|
|
76
|
-
checkDeclarations([...graph.globals.inputs, ...collectInputs(graph.root)]);
|
|
77
|
-
return inspectGraph(this.#name, graph);
|
|
125
|
+
const built = this.prepare(() => undefined);
|
|
126
|
+
return inspectGraph(this.#name, built.graph, built.facts);
|
|
78
127
|
}
|
|
79
128
|
async run(options) {
|
|
80
129
|
let stderr = process.stderr;
|
|
@@ -83,91 +132,159 @@ class ApplicationBuilder {
|
|
|
83
132
|
let reportingFailed = false;
|
|
84
133
|
// A registry that could not be built reports through core's defaults, not through itself.
|
|
85
134
|
let registry = undefined;
|
|
135
|
+
// Faults a plugin raised beside the primary outcome, reported after it and never before it.
|
|
136
|
+
const faults = [];
|
|
137
|
+
// One private controller per run, subscribed to the caller's signal at run entry.
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
/**
|
|
140
|
+
* The bracket this run holds. It exists once the caller's own signal has been read, so a
|
|
141
|
+
* signal that is not an `AbortSignal` is reported through the failure path like any other.
|
|
142
|
+
*/
|
|
143
|
+
let signals = undefined;
|
|
144
|
+
// A cancelled run resolves its cancellation code whenever it ends after graph build.
|
|
145
|
+
// A declaration or internal failure raised before that ends the run with its own code instead.
|
|
146
|
+
let graphBuilt = false;
|
|
147
|
+
const cancellation = () => {
|
|
148
|
+
const reason = graphBuilt ? signals?.reason() : undefined;
|
|
149
|
+
return reason ? cancellationCode(reason) : undefined;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Every exit path of the run leaves through the removal below, the one place it is written,
|
|
153
|
+
* so no listener this run installed outlives it however the run ends.
|
|
154
|
+
*/
|
|
86
155
|
try {
|
|
87
|
-
const overrides = options?.host;
|
|
88
|
-
stderr = overrides?.stderr ?? stderr;
|
|
89
|
-
const host = captureHost(overrides, stderr);
|
|
90
|
-
output = new Output(host);
|
|
91
|
-
checkOptions(this.#options);
|
|
92
|
-
registry = buildFailures(this.#failures);
|
|
93
|
-
const graph = buildGraph(this.#root);
|
|
94
|
-
const inputs = { globals: graph.globals.inputs, locals: collectInputs(graph.root) };
|
|
95
|
-
const defaults = await prepareInputs(inputs, host);
|
|
96
|
-
const selected = await selectCommand(graph, { defaults, host });
|
|
97
|
-
await selected.dispatch({
|
|
98
|
-
host,
|
|
99
|
-
out: output.out,
|
|
100
|
-
passthrough: selected.passthrough,
|
|
101
|
-
values: selected.values,
|
|
102
|
-
});
|
|
103
|
-
// The fault check covers the same window the write accounting covers.
|
|
104
|
-
// A render failure an unawaited helper raised is still this invocation's failure.
|
|
105
|
-
await output.settle();
|
|
106
|
-
const fault = output.fault;
|
|
107
|
-
if (fault) {
|
|
108
|
-
// The action returned, so the renderer failure is this invocation's own failure.
|
|
109
|
-
throw new InternalError(`Rendering output failed: ${reasonOf(fault.cause)}`, fault.cause);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
156
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
157
|
+
const overrides = options?.host;
|
|
158
|
+
stderr = overrides?.stderr ?? stderr;
|
|
159
|
+
const host = captureHost(overrides, stderr);
|
|
160
|
+
output = new Output(host);
|
|
161
|
+
signals = bracketRun(controller, checkSignal(options?.signal));
|
|
162
|
+
const built = this.prepare((value) => {
|
|
163
|
+
registry = value;
|
|
164
|
+
});
|
|
165
|
+
const { graph } = built;
|
|
166
|
+
const inputs = { globals: graph.globals.inputs, locals: collectInputs(graph.root) };
|
|
167
|
+
const defaults = await prepareInputs(inputs, host);
|
|
168
|
+
graphBuilt = true;
|
|
169
|
+
if (!controller.signal.aborted) {
|
|
170
|
+
/**
|
|
171
|
+
* The listeners the validated signals owner claimed. A build failure installs none, and
|
|
172
|
+
* neither does a run the caller had already cancelled: it touches the process not at all.
|
|
173
|
+
*/
|
|
174
|
+
signals.install(ownedSignals(built.plugins));
|
|
175
|
+
await runInvocation({
|
|
176
|
+
defaults,
|
|
177
|
+
facts: built.facts,
|
|
178
|
+
graph,
|
|
179
|
+
host,
|
|
180
|
+
name: this.#name,
|
|
181
|
+
out: output.out,
|
|
182
|
+
plugins: built.plugins,
|
|
183
|
+
report: (fault) => faults.push(fault),
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// The fault check covers the same window the write accounting covers.
|
|
188
|
+
// A render failure an unawaited helper raised is still this invocation's failure.
|
|
189
|
+
await output.settle();
|
|
190
|
+
const fault = output.fault;
|
|
191
|
+
if (fault) {
|
|
192
|
+
// The action returned, so the renderer failure is this invocation's own failure.
|
|
193
|
+
throw new InternalError(`Rendering output failed: ${reasonOf(fault.cause)}`, fault.cause);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
try {
|
|
198
|
+
const failure = toFailure(error);
|
|
199
|
+
code = failure.exitCode;
|
|
200
|
+
output ??= new Output({ stderr, stdout: process.stdout });
|
|
201
|
+
const writes = await output.settle();
|
|
202
|
+
if (writes.kind === 'ok' && !silenced(error, controller.signal, cancellation())) {
|
|
203
|
+
const report = describeFailure(registry ?? noRegistrations, failure);
|
|
204
|
+
if (report.kind === 'rendered') {
|
|
205
|
+
// The renderer already owns every byte, trailing newline included: pass it through.
|
|
206
|
+
await output.report(report.text);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
code = 1;
|
|
210
|
+
// `report.text` is core's default text, which already ends in `\n`.
|
|
211
|
+
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
code = 1;
|
|
217
|
+
reportingFailed = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// A plugin's own fault is reported after the primary outcome and turns a would-be 0 into 1.
|
|
221
|
+
// The primary outcome keeps its code, the way a renderer failure leaves it alone.
|
|
222
|
+
// It is reported the way the primary failure is, so a registered renderer answers its class.
|
|
223
|
+
for (const fault of faults) {
|
|
224
|
+
if (!silenced(fault, controller.signal, cancellation())) {
|
|
225
|
+
code = code === 0 ? 1 : code;
|
|
226
|
+
try {
|
|
227
|
+
const report = describeFailure(registry ?? noRegistrations, fault);
|
|
228
|
+
if (report.kind === 'rendered') {
|
|
229
|
+
await output?.report(report.text);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
code = 1;
|
|
233
|
+
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
234
|
+
}
|
|
123
235
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// `report.text` is core's default text, which already ends in `\n`.
|
|
127
|
-
await reportPlainly(stderr, `${report.text}Internal error: Rendering the failure failed: ${report.reason}\n`);
|
|
236
|
+
catch {
|
|
237
|
+
reportingFailed = true;
|
|
128
238
|
}
|
|
129
239
|
}
|
|
130
240
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
241
|
+
if (output) {
|
|
242
|
+
const writes = await output.settle();
|
|
243
|
+
if (writes.kind === 'failed') {
|
|
244
|
+
code = 1;
|
|
245
|
+
reportingFailed = true;
|
|
246
|
+
}
|
|
247
|
+
output.dispose();
|
|
134
248
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const writes = await output.settle();
|
|
138
|
-
if (writes.kind === 'failed') {
|
|
139
|
-
code = 1;
|
|
140
|
-
reportingFailed = true;
|
|
249
|
+
if (reportingFailed) {
|
|
250
|
+
await reportPlainly(stderr, 'Internal error: Could not write invocation output.\n');
|
|
141
251
|
}
|
|
142
|
-
|
|
252
|
+
/**
|
|
253
|
+
* One rule orders every code: a cancelled run resolves its signal's code, and a broken
|
|
254
|
+
* failure renderer or destination in that run is reported as text without changing it. The
|
|
255
|
+
* signal decides the code whatever the action did afterward, so this reading comes last.
|
|
256
|
+
*/
|
|
257
|
+
code = cancellation() ?? code;
|
|
258
|
+
process.exitCode = code;
|
|
259
|
+
return code;
|
|
143
260
|
}
|
|
144
|
-
|
|
145
|
-
|
|
261
|
+
finally {
|
|
262
|
+
signals?.finish();
|
|
146
263
|
}
|
|
147
|
-
process.exitCode = code;
|
|
148
|
-
return code;
|
|
149
264
|
}
|
|
150
265
|
}
|
|
151
|
-
/** The options slot holds one object literal, so a declaration that carries state is not one. */
|
|
152
|
-
function isPlainObject(value) {
|
|
153
|
-
if (value === null || typeof value !== 'object') {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
const prototype = Object.getPrototypeOf(value);
|
|
157
|
-
return prototype === Object.prototype || prototype === null;
|
|
158
|
-
}
|
|
159
266
|
/**
|
|
160
267
|
* The second argument, read where it is supplied. The retired positional form declares its globals
|
|
161
268
|
* on a value that holds no `globals` key, so without this rule the globals vanish silently and the
|
|
162
|
-
* operator, not the author, meets the consequence as an unknown-option error.
|
|
269
|
+
* operator, not the author, meets the consequence as an unknown-option error. The slot's own shape
|
|
270
|
+
* settles first, because a slot that is not an options object carries no facts to report.
|
|
163
271
|
*/
|
|
164
|
-
function checkOptions(options) {
|
|
272
|
+
function checkOptions(options, declared) {
|
|
165
273
|
if (isGlobalOptions(options)) {
|
|
166
274
|
throw new DeclarationError('The Application takes an options object. Supply { globals } instead of a positional GlobalOptions value.');
|
|
167
275
|
}
|
|
168
|
-
if (options !== undefined
|
|
169
|
-
|
|
276
|
+
if (options !== undefined) {
|
|
277
|
+
if (!isPlainObject(options)) {
|
|
278
|
+
throw new DeclarationError('The Application options must be an object. Supply { globals, failures }.');
|
|
279
|
+
}
|
|
280
|
+
// The root is every page's entry point, so it carries neither listing fact.
|
|
281
|
+
// A key that may not be there is a fault of the slot, so it answers with the slot's shape.
|
|
282
|
+
checkNoListingFacts('The Application', options);
|
|
170
283
|
}
|
|
284
|
+
return {
|
|
285
|
+
description: checkDescription('The Application', declared.description),
|
|
286
|
+
version: checkVersion(declared.version),
|
|
287
|
+
};
|
|
171
288
|
}
|
|
172
289
|
/**
|
|
173
290
|
* The runtime class behind the public constructor. It is generic so that an instance's `Globals`
|
|
@@ -177,8 +294,23 @@ function checkOptions(options) {
|
|
|
177
294
|
class ApplicationDeclaration extends ApplicationBuilder {
|
|
178
295
|
constructor(name, options) {
|
|
179
296
|
// The options slot is read defensively, never inspected: an invalid value still yields
|
|
180
|
-
// `globals` and
|
|
181
|
-
|
|
297
|
+
// `globals`, `failures`, and the facts of some kind, and `checkOptions` reports it at build.
|
|
298
|
+
// The root's own slot and core facts stay empty, because the Application checks its own slot.
|
|
299
|
+
// Its diagnostics name the Application rather than the root Command.
|
|
300
|
+
super(name, freshState({
|
|
301
|
+
deprecated: undefined,
|
|
302
|
+
description: undefined,
|
|
303
|
+
extensions: options?.extensions,
|
|
304
|
+
globals: options?.globals,
|
|
305
|
+
hidden: undefined,
|
|
306
|
+
name: null,
|
|
307
|
+
options: undefined,
|
|
308
|
+
}), {
|
|
309
|
+
declared: { description: options?.description, version: options?.version },
|
|
310
|
+
failures: options?.failures ?? [],
|
|
311
|
+
options,
|
|
312
|
+
plugins: options?.plugins ?? [],
|
|
313
|
+
});
|
|
182
314
|
}
|
|
183
315
|
}
|
|
184
316
|
/**
|
package/dist/chain.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BuiltGraph } from './command.js';
|
|
2
|
+
import type { LoomError } from './errors.js';
|
|
3
|
+
import type { CommandGraph, CommandNode } from './inspect.js';
|
|
4
|
+
import type { BuiltPlugin, PluginOptions, PluginOptionValues } from './plugin.js';
|
|
5
|
+
import type { Host, Out } from './types.js';
|
|
6
|
+
import type { DefaultValues } from './validation.js';
|
|
7
|
+
/**
|
|
8
|
+
* What the rest of one chain did: the action ran, a later middleware took over by returning without
|
|
9
|
+
* calling its own `next()`, or the run was cancelled before the action ran.
|
|
10
|
+
*/
|
|
11
|
+
type ChainOutcome = 'cancelled' | 'dispatched' | 'taken-over';
|
|
12
|
+
/**
|
|
13
|
+
* What one middleware receives. `graph` is the frozen graph `inspect()` returns, built once for the
|
|
14
|
+
* run, and `command` is the routed node inside it. `options` holds this plugin's own option values
|
|
15
|
+
* and never another plugin's or the application's globals.
|
|
16
|
+
*/
|
|
17
|
+
interface MiddlewareContext<Options extends PluginOptions = PluginOptions> {
|
|
18
|
+
readonly options: PluginOptionValues<Options>;
|
|
19
|
+
readonly graph: CommandGraph;
|
|
20
|
+
readonly command: CommandNode;
|
|
21
|
+
readonly host: Host;
|
|
22
|
+
readonly out: Out;
|
|
23
|
+
readonly signal: AbortSignal;
|
|
24
|
+
readonly next: () => Promise<ChainOutcome>;
|
|
25
|
+
}
|
|
26
|
+
/** Everything one invocation needs after its graph is built and its defaults are validated. */
|
|
27
|
+
interface Invocation {
|
|
28
|
+
defaults: DefaultValues;
|
|
29
|
+
facts: {
|
|
30
|
+
description: string | undefined;
|
|
31
|
+
version: string;
|
|
32
|
+
};
|
|
33
|
+
graph: BuiltGraph;
|
|
34
|
+
host: Host;
|
|
35
|
+
name: string;
|
|
36
|
+
out: Out;
|
|
37
|
+
plugins: readonly BuiltPlugin[];
|
|
38
|
+
/** A fault reported after the primary outcome, which turns a would-be 0 into 1. */
|
|
39
|
+
report: (fault: LoomError) => void;
|
|
40
|
+
signal: AbortSignal;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs one invocation: the global pre-scan, routing, the middleware chain, and the phases the chain
|
|
44
|
+
* terminates in. A middleware that returns without calling `next()` has taken over, so the
|
|
45
|
+
* remaining tokens are never parsed and nothing later in the chain runs.
|
|
46
|
+
*/
|
|
47
|
+
declare function runInvocation(invocation: Invocation): Promise<void>;
|
|
48
|
+
export type { ChainOutcome, Invocation, MiddlewareContext };
|
|
49
|
+
export { runInvocation };
|