@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/chain.js
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { prepareDispatch, routeInvocation } from './command.js';
|
|
2
|
+
import { InternalError, reasonOf, toFailure } from './errors.js';
|
|
3
|
+
import { inspectGraph } from './inspect.js';
|
|
4
|
+
import { booleanValue } from './options.js';
|
|
5
|
+
import { pluginSentence } from './plugin.js';
|
|
6
|
+
/**
|
|
7
|
+
* The default export a loader must resolve to. A loaded module is data core never declared, so the
|
|
8
|
+
* check is the one runtime fact that decides it: the export is callable. Core calls it with the
|
|
9
|
+
* context it owns and ignores whatever it returns.
|
|
10
|
+
*/
|
|
11
|
+
function isMiddlewareExport(value) {
|
|
12
|
+
return typeof value === 'function';
|
|
13
|
+
}
|
|
14
|
+
/** Whether one option name was supplied as a token, in any spelling a declaration accepts. */
|
|
15
|
+
function supplied(scan, name) {
|
|
16
|
+
return scan.strings.has(name) || scan.lists.has(name) || scan.booleans.has(name);
|
|
17
|
+
}
|
|
18
|
+
/** A collected value, or the declared array default, as this run's own copy. */
|
|
19
|
+
function collectedValue(collected, declared) {
|
|
20
|
+
if (collected) {
|
|
21
|
+
return [...collected];
|
|
22
|
+
}
|
|
23
|
+
return Array.isArray(declared) ? [...declared] : [];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One plugin's own option values for one run: what the pre-scan produced, or the declared default,
|
|
27
|
+
* filled without validation. A collected value and an array default are copied, so a middleware
|
|
28
|
+
* that writes to what it received changes neither the declaration nor the next run.
|
|
29
|
+
*/
|
|
30
|
+
function pluginValues(inputs, scan) {
|
|
31
|
+
const values = {};
|
|
32
|
+
for (const { config, name } of inputs) {
|
|
33
|
+
const declared = config.default;
|
|
34
|
+
if (config.type === 'boolean') {
|
|
35
|
+
values[name] = booleanValue(scan, name, config);
|
|
36
|
+
}
|
|
37
|
+
else if (config.multiple === true) {
|
|
38
|
+
values[name] = collectedValue(scan.lists.get(name), declared);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Build already proved that a string option without a schema declares a string default.
|
|
42
|
+
values[name] =
|
|
43
|
+
scan.strings.get(name) ?? (typeof declared === 'string' ? declared : undefined);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return values;
|
|
47
|
+
}
|
|
48
|
+
/** Whether one plugin's declared activation matched the tokens the pre-scan consumed. */
|
|
49
|
+
function activates(installed, scan) {
|
|
50
|
+
const { middleware } = installed;
|
|
51
|
+
if (!middleware) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return (middleware.activate === 'always' || middleware.activate.some((name) => supplied(scan, name)));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The chain for one invocation: each installed plugin whose activation matched, in installation
|
|
58
|
+
* order. Activation is read from the pre-scan, before any plugin code loads, so a plugin whose
|
|
59
|
+
* option was never supplied is not in the chain and its loader is never called.
|
|
60
|
+
*/
|
|
61
|
+
function activatedEntries(plugins, scan) {
|
|
62
|
+
return plugins
|
|
63
|
+
.filter((installed) => activates(installed, scan))
|
|
64
|
+
.map((installed) => ({
|
|
65
|
+
identity: installed.identity,
|
|
66
|
+
// Activation proved the middleware exists, so the empty loader is never the one core calls.
|
|
67
|
+
load: installed.middleware?.load ?? (() => undefined),
|
|
68
|
+
options: pluginValues(installed.inputs, scan),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The routed node inside the inspected graph, which routing already proved reachable. A missing
|
|
73
|
+
* segment means the two readings of one graph disagree, so the chain stops rather than hand a
|
|
74
|
+
* middleware the wrong Command.
|
|
75
|
+
*/
|
|
76
|
+
function nodeAt(graph, path) {
|
|
77
|
+
let node = graph.root;
|
|
78
|
+
for (const name of path) {
|
|
79
|
+
const child = node.children.find((entry) => entry.name === name);
|
|
80
|
+
if (!child) {
|
|
81
|
+
throw new InternalError(`The routed command "${path.join(' ')}" is not in the inspected graph.`, undefined);
|
|
82
|
+
}
|
|
83
|
+
node = child;
|
|
84
|
+
}
|
|
85
|
+
return node;
|
|
86
|
+
}
|
|
87
|
+
/** A downstream promise core awaits for its completion alone; its outcome was recorded already. */
|
|
88
|
+
async function quiet(pending) {
|
|
89
|
+
if (pending) {
|
|
90
|
+
try {
|
|
91
|
+
await pending;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// The rejection was recorded where it crossed the `next()` boundary.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The outcome one entry reports to its caller. `'cancelled'` wins over `'taken-over'`, so a later
|
|
100
|
+
* middleware that returned because it saw the abort reports as cancelled, the order the exit codes
|
|
101
|
+
* follow. An action that already ran still reports as dispatched.
|
|
102
|
+
*/
|
|
103
|
+
function reported(chain, outcome) {
|
|
104
|
+
return outcome === 'taken-over' && chain.cancelled() ? 'cancelled' : outcome;
|
|
105
|
+
}
|
|
106
|
+
/** A `next()` call that is no longer live: it dispatches nothing and rejects. */
|
|
107
|
+
function misuse(turn) {
|
|
108
|
+
const fault = new InternalError(`${pluginSentence(turn.entry.identity)} called next() ${turn.state.returned ? 'after its middleware returned' : 'twice'}.`, undefined);
|
|
109
|
+
turn.chain.report(fault);
|
|
110
|
+
const rejected = Promise.reject(fault);
|
|
111
|
+
void rejected.catch(() => undefined);
|
|
112
|
+
return rejected;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The `next` one middleware receives. It is live until that middleware's own result settles, so a
|
|
116
|
+
* second call, or a call after the middleware returned, rejects and continues nothing.
|
|
117
|
+
*/
|
|
118
|
+
function nextOf(turn) {
|
|
119
|
+
const { chain, state } = turn;
|
|
120
|
+
return () => {
|
|
121
|
+
if (state.returned || state.calls > 0) {
|
|
122
|
+
return misuse(turn);
|
|
123
|
+
}
|
|
124
|
+
state.calls += 1;
|
|
125
|
+
const pending = chain.step(turn.index + 1).then((outcome) => {
|
|
126
|
+
state.outcome = outcome;
|
|
127
|
+
state.settled = true;
|
|
128
|
+
return outcome;
|
|
129
|
+
}, (error) => {
|
|
130
|
+
state.rejection = { value: error };
|
|
131
|
+
state.settled = true;
|
|
132
|
+
chain.record(error);
|
|
133
|
+
throw error;
|
|
134
|
+
});
|
|
135
|
+
state.downstream = pending;
|
|
136
|
+
// Core awaits the downstream promise itself, so a middleware that never awaits `next()` still
|
|
137
|
+
// Holds the chain open and never ends the run with an unobserved rejection.
|
|
138
|
+
void pending.catch(() => undefined);
|
|
139
|
+
return pending;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** The middleware's own result as a value, so the decision below reads one shape. */
|
|
143
|
+
async function call(middleware, context) {
|
|
144
|
+
try {
|
|
145
|
+
await middleware(context);
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
return { value: error };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* What one entry reports to its caller once its own result has settled. A throw before `next()`
|
|
154
|
+
* settled, or without calling it, is this invocation's failure; a throw during unwinding is an
|
|
155
|
+
* internal error reported after the primary outcome, which keeps its own code.
|
|
156
|
+
*/
|
|
157
|
+
async function settle(turn, thrown) {
|
|
158
|
+
const { chain, state } = turn;
|
|
159
|
+
if (thrown) {
|
|
160
|
+
const propagated = state.rejection !== undefined && thrown.value === state.rejection.value;
|
|
161
|
+
if (propagated || !state.settled) {
|
|
162
|
+
await quiet(state.downstream);
|
|
163
|
+
throw thrown.value;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A fault core recorded where it was raised, such as a misused `next()`, reaches this point
|
|
167
|
+
* again when the middleware let it escape. It keeps the one report it already has.
|
|
168
|
+
*/
|
|
169
|
+
if (!chain.announced(thrown.value)) {
|
|
170
|
+
chain.report(new InternalError(reasonOf(thrown.value), thrown.value));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (state.calls === 0) {
|
|
174
|
+
return reported(chain, 'taken-over');
|
|
175
|
+
}
|
|
176
|
+
await quiet(state.downstream);
|
|
177
|
+
// A middleware that caught the rejection reports what the chain reached; the recorded failure
|
|
178
|
+
// Still decides the exit code.
|
|
179
|
+
return reported(chain, state.outcome ?? (chain.invoked() ? 'dispatched' : 'taken-over'));
|
|
180
|
+
}
|
|
181
|
+
/** The module one loader answers with, whether it throws where it is called or rejects later. */
|
|
182
|
+
async function loadModule(entry) {
|
|
183
|
+
try {
|
|
184
|
+
return await entry.load();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new InternalError(`Loading plugin "${entry.identity}" failed: ${reasonOf(error)}`, error);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** A plugin's module is loaded when the chain reaches it, never before. */
|
|
191
|
+
async function loadMiddleware(entry) {
|
|
192
|
+
const module = await loadModule(entry);
|
|
193
|
+
const handler = module !== null && typeof module === 'object' && 'default' in module
|
|
194
|
+
? module.default
|
|
195
|
+
: undefined;
|
|
196
|
+
if (!isMiddlewareExport(handler)) {
|
|
197
|
+
throw new InternalError(`Loading plugin "${entry.identity}" failed: the module exports no default middleware function.`, undefined);
|
|
198
|
+
}
|
|
199
|
+
return handler;
|
|
200
|
+
}
|
|
201
|
+
/** One entry's turn: its module loads here, when the chain reaches it and never before. */
|
|
202
|
+
async function runEntry(entry, index, chain) {
|
|
203
|
+
const middleware = await loadMiddleware(entry);
|
|
204
|
+
if (chain.cancelled()) {
|
|
205
|
+
/**
|
|
206
|
+
* A module import cannot be aborted, so a loader already in flight settles and core starts
|
|
207
|
+
* nothing with it: the middleware it resolved to is skipped.
|
|
208
|
+
*/
|
|
209
|
+
return 'cancelled';
|
|
210
|
+
}
|
|
211
|
+
const state = { calls: 0, returned: false, settled: false };
|
|
212
|
+
const turn = { chain, entry, index, state };
|
|
213
|
+
const thrown = await call(middleware, chain.context(entry, nextOf(turn)));
|
|
214
|
+
state.returned = true;
|
|
215
|
+
return settle(turn, thrown);
|
|
216
|
+
}
|
|
217
|
+
/** The whole chain, answering with the failure it raised when a middleware caught that failure. */
|
|
218
|
+
async function runChain(invocation, routed, entries) {
|
|
219
|
+
const run = { invoked: false, raised: undefined };
|
|
220
|
+
const terminal = async () => {
|
|
221
|
+
const dispatch = await prepareDispatch(invocation.graph, routed, invocation);
|
|
222
|
+
run.invoked = true;
|
|
223
|
+
await dispatch();
|
|
224
|
+
return 'dispatched';
|
|
225
|
+
};
|
|
226
|
+
const cancelled = () => invocation.signal.aborted;
|
|
227
|
+
if (entries.length === 0) {
|
|
228
|
+
if (!cancelled()) {
|
|
229
|
+
await terminal();
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
// The graph a middleware reads is the one `inspect()` returns, built once for the run.
|
|
234
|
+
const graph = inspectGraph(invocation.name, invocation.graph, invocation.facts);
|
|
235
|
+
const command = nodeAt(graph, routed.path);
|
|
236
|
+
// Every fault this chain has reported, so the same one raised again carries no second report.
|
|
237
|
+
const announced = new WeakSet();
|
|
238
|
+
const chain = {
|
|
239
|
+
announced: (value) => typeof value === 'object' && value !== null && announced.has(value),
|
|
240
|
+
cancelled,
|
|
241
|
+
context: (entry, next) => ({
|
|
242
|
+
command,
|
|
243
|
+
graph,
|
|
244
|
+
host: invocation.host,
|
|
245
|
+
next,
|
|
246
|
+
options: entry.options,
|
|
247
|
+
out: invocation.out,
|
|
248
|
+
signal: invocation.signal,
|
|
249
|
+
}),
|
|
250
|
+
invoked: () => run.invoked,
|
|
251
|
+
record: (error) => {
|
|
252
|
+
run.raised ??= toFailure(error);
|
|
253
|
+
},
|
|
254
|
+
report: (fault) => {
|
|
255
|
+
announced.add(fault);
|
|
256
|
+
invocation.report(fault);
|
|
257
|
+
},
|
|
258
|
+
step: (index) => {
|
|
259
|
+
if (cancelled()) {
|
|
260
|
+
/**
|
|
261
|
+
* Core starts nothing new after cancellation: a middleware the chain has not reached and
|
|
262
|
+
* an action not yet dispatched are skipped, and the entries already running unwind.
|
|
263
|
+
*/
|
|
264
|
+
return Promise.resolve('cancelled');
|
|
265
|
+
}
|
|
266
|
+
const entry = entries[index];
|
|
267
|
+
return entry ? runEntry(entry, index, chain) : terminal();
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
await chain.step(0);
|
|
271
|
+
return run.raised;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Runs one invocation: the global pre-scan, routing, the middleware chain, and the phases the chain
|
|
275
|
+
* terminates in. A middleware that returns without calling `next()` has taken over, so the
|
|
276
|
+
* remaining tokens are never parsed and nothing later in the chain runs.
|
|
277
|
+
*/
|
|
278
|
+
async function runInvocation(invocation) {
|
|
279
|
+
const routed = routeInvocation(invocation.graph, invocation.host.argv);
|
|
280
|
+
const entries = activatedEntries(invocation.plugins, routed.scan);
|
|
281
|
+
const raised = await runChain(invocation, routed, entries);
|
|
282
|
+
if (raised) {
|
|
283
|
+
// The chain resolved because a middleware caught the rejection. The failure it caught still
|
|
284
|
+
// Decides the exit code, the rule an action's caught output rejection already follows.
|
|
285
|
+
throw raised;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
export { runInvocation };
|
package/dist/command.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { DescriptorRegistry, ExtensionRecords, ExtensionValue } from './extension.js';
|
|
1
2
|
import type { BuiltGlobals, GlobalOptions } from './globals.js';
|
|
2
3
|
import { compileOptions } from './options.js';
|
|
4
|
+
import type { OptionValues } from './options.js';
|
|
5
|
+
import type { BuiltPlugin, PluginBuild } from './plugin.js';
|
|
3
6
|
import type { Action, ArgumentConfig, ArgumentValue, declaredTypes, DeclaredTypes, DefaultConstraint, GlobalNameConstraint, Host, MultipleConstraint, NameConstraint, OptionConfig, OptionValue, Out, ValidateOmittedConstraint } from './types.js';
|
|
4
7
|
import type { ArgumentInput, DefaultValues, InputDeclaration, OptionInput, ValidatedInputs } from './validation.js';
|
|
5
8
|
/** One positional slot: the declaration it fills and whether it takes the remaining tokens. */
|
|
@@ -12,10 +15,11 @@ export interface DispatchInput {
|
|
|
12
15
|
host: Host;
|
|
13
16
|
out: Out;
|
|
14
17
|
passthrough: string[];
|
|
18
|
+
signal: AbortSignal;
|
|
15
19
|
values: ValidatedInputs;
|
|
16
20
|
}
|
|
17
21
|
/**
|
|
18
|
-
* One child a bare token reaches, under its canonical name or one of its
|
|
22
|
+
* One child a bare token reaches, under its canonical name or one of its aliases.
|
|
19
23
|
* `name` repeats the key `children` holds, because `BuiltCommand.name` is `string | null` for the
|
|
20
24
|
* root and the routed path a child extends holds strings alone.
|
|
21
25
|
*/
|
|
@@ -26,13 +30,17 @@ export interface RoutedChild {
|
|
|
26
30
|
/**
|
|
27
31
|
* A group registers no action, so its `dispatch` is `undefined` and selection rejects it.
|
|
28
32
|
* `children` is keyed by canonical name, so every candidate list and every walk of the graph reads
|
|
29
|
-
* it, and `routes` adds the
|
|
33
|
+
* it, and `routes` adds the aliases, so routing alone resolves them.
|
|
30
34
|
*/
|
|
31
35
|
export interface BuiltCommand {
|
|
32
36
|
aliases: readonly string[];
|
|
33
37
|
arguments: readonly ArgumentSlot[];
|
|
34
38
|
children: ReadonlyMap<string, BuiltCommand>;
|
|
39
|
+
deprecated: string | undefined;
|
|
40
|
+
description: string | undefined;
|
|
35
41
|
dispatch: ((input: DispatchInput) => unknown) | undefined;
|
|
42
|
+
extensions: Readonly<Record<string, unknown>>;
|
|
43
|
+
hidden: boolean;
|
|
36
44
|
inputs: readonly InputDeclaration[];
|
|
37
45
|
name: string | null;
|
|
38
46
|
options: ReturnType<typeof compileOptions>;
|
|
@@ -81,6 +89,8 @@ interface AttachedCommand {
|
|
|
81
89
|
* owner is the parent whose attachment the walk meets first.
|
|
82
90
|
*/
|
|
83
91
|
interface BuildContext {
|
|
92
|
+
descriptors: DescriptorRegistry;
|
|
93
|
+
extensions: ExtensionRecords;
|
|
84
94
|
globals: BuiltGlobals;
|
|
85
95
|
owners: Map<AttachedCommand, string | null>;
|
|
86
96
|
}
|
|
@@ -97,13 +107,30 @@ export interface CommandState<Args, Options, Globals> {
|
|
|
97
107
|
options: Options;
|
|
98
108
|
};
|
|
99
109
|
children: readonly object[];
|
|
110
|
+
deprecated: unknown;
|
|
111
|
+
description: unknown;
|
|
112
|
+
extensions: unknown;
|
|
113
|
+
hidden: unknown;
|
|
100
114
|
globals: GlobalOptions<Globals> | undefined;
|
|
101
115
|
inputs: readonly InputDeclaration[];
|
|
102
116
|
late: readonly LateDeclaration[];
|
|
103
117
|
name: string | null;
|
|
118
|
+
options: unknown;
|
|
104
119
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
120
|
+
/**
|
|
121
|
+
* The state every declaration starts from. The unnamed root and each named Command share it.
|
|
122
|
+
* The declaration values arrive captured, because a later change to the options object the author
|
|
123
|
+
* passed changes nothing the declaration holds.
|
|
124
|
+
*/
|
|
125
|
+
export declare function freshState<Globals>(declaration: {
|
|
126
|
+
deprecated: unknown;
|
|
127
|
+
description: unknown;
|
|
128
|
+
extensions: unknown;
|
|
129
|
+
globals: GlobalOptions<Globals> | undefined;
|
|
130
|
+
hidden: unknown;
|
|
131
|
+
name: string | null;
|
|
132
|
+
options: unknown;
|
|
133
|
+
}): CommandState<{}, {}, Globals>;
|
|
107
134
|
/** The declared value joins `args` under its literal name, typed by its own config. */
|
|
108
135
|
export declare function declareArgument<Args, Options, Globals, Name extends string, Config extends ArgumentConfig>(state: CommandState<Args, Options, Globals>, input: ArgumentInput<Name, Config>): CommandState<Args & Record<Name, ArgumentValue<Config>>, Options, Globals>;
|
|
109
136
|
/** The declared value joins `options` under its literal name, typed by its own config. */
|
|
@@ -115,11 +142,16 @@ export declare function declareAction<Args, Options, Globals>(state: CommandStat
|
|
|
115
142
|
export declare function attachChild<Args, Options, Globals>(state: CommandState<Args, Options, Globals>, child: object): CommandState<Args, Options, Globals>;
|
|
116
143
|
/** Validates one declaration against the shared globals table and compiles it for dispatch. */
|
|
117
144
|
export declare function buildCommand<Args, Options, Globals>(state: CommandState<Args, Options, Globals>, context: BuildContext): BuiltCommand;
|
|
118
|
-
/**
|
|
119
|
-
export
|
|
145
|
+
/** One built graph: the shared globals table, the root Command, and the facts each node carries. */
|
|
146
|
+
export interface BuiltGraph {
|
|
147
|
+
extensions: ExtensionRecords;
|
|
120
148
|
globals: BuiltGlobals;
|
|
121
149
|
root: BuiltCommand;
|
|
122
|
-
}
|
|
150
|
+
}
|
|
151
|
+
/** The globals table and the owners record each compile once per invocation and the whole graph shares them. */
|
|
152
|
+
export declare function buildGraph<Args, Options, Globals>(root: CommandState<Args, Options, Globals>, install: PluginBuild & {
|
|
153
|
+
plugins: readonly BuiltPlugin[];
|
|
154
|
+
}): BuiltGraph;
|
|
123
155
|
export declare class CommandBuilder<Args, Options, Globals, State extends CommandMethod = CommandMethod> {
|
|
124
156
|
#private;
|
|
125
157
|
readonly [commandValue]: true;
|
|
@@ -129,8 +161,8 @@ export declare class CommandBuilder<Args, Options, Globals, State extends Comman
|
|
|
129
161
|
argument<const Name extends string, const Config extends ArgumentConfig>(name: Name, config: Config & NameConstraint<Name> & NoInfer<DefaultConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Command<Args & Record<Name, ArgumentValue<Config>>, Options, Globals, AfterArgument<State>>;
|
|
130
162
|
option<const Name extends string, const Config extends OptionConfig>(name: Name, config: Config & NameConstraint<Name> & GlobalNameConstraint<Name, Globals> & NoInfer<DefaultConstraint<Config>> & NoInfer<MultipleConstraint<Config>> & NoInfer<ValidateOmittedConstraint<Config>>): Command<Args, Options & Record<Name, OptionValue<Config>>, Globals, State>;
|
|
131
163
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
164
|
+
* Aliases are other bare tokens that route to this Command. They invalidate no call, and the
|
|
165
|
+
* tuple rest parameter rejects a call that names none.
|
|
134
166
|
*/
|
|
135
167
|
alias(...names: [string, ...string[]]): Command<Args, Options, Globals, State>;
|
|
136
168
|
/** A child arrives in any type state, because its own action is the call that finished it. */
|
|
@@ -147,11 +179,22 @@ export declare class CommandBuilder<Args, Options, Globals, State extends Comman
|
|
|
147
179
|
* calls, so `Command<A, O, G>` accepts a Command in any state, a finished one included.
|
|
148
180
|
*/
|
|
149
181
|
export type Command<Args = {}, Options = {}, Globals = {}, State extends CommandMethod = AfterAction> = Pick<CommandBuilder<Args, Options, Globals, State>, typeof commandValue | typeof declaredTypes | State>;
|
|
182
|
+
/**
|
|
183
|
+
* Everything a Command configures beside its declarations: the options value every Command in one
|
|
184
|
+
* application shares, and the core facts the declaration carries.
|
|
185
|
+
*/
|
|
186
|
+
export interface CommandOptions<Globals = {}> {
|
|
187
|
+
globals?: GlobalOptions<Globals>;
|
|
188
|
+
description?: string;
|
|
189
|
+
hidden?: boolean;
|
|
190
|
+
deprecated?: string;
|
|
191
|
+
extensions?: readonly ExtensionValue<'command'>[];
|
|
192
|
+
}
|
|
150
193
|
interface CommandConstructor {
|
|
151
194
|
new (name: string): Command<{}, {}, {}, CommandMethod>;
|
|
152
|
-
new <Globals>(name: string,
|
|
195
|
+
new <Globals = {}>(name: string, options: CommandOptions<Globals>): Command<{}, {}, Globals, CommandMethod>;
|
|
153
196
|
}
|
|
154
|
-
/** The public constructor
|
|
197
|
+
/** The public constructor takes a name and one options object, as the Application does. */
|
|
155
198
|
export declare const Command: CommandConstructor;
|
|
156
199
|
/** Every declaration in the graph, so defaults are validated before any token is read. */
|
|
157
200
|
export declare function collectInputs(command: BuiltCommand): InputDeclaration[];
|
|
@@ -161,16 +204,24 @@ export declare function route(root: BuiltCommand, tokens: readonly string[]): {
|
|
|
161
204
|
path: string[];
|
|
162
205
|
tokens: string[];
|
|
163
206
|
};
|
|
164
|
-
/**
|
|
165
|
-
export
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
/** One invocation after the pre-scan and routing, which the middleware chain runs on top of. */
|
|
208
|
+
export interface RoutedInvocation {
|
|
209
|
+
command: BuiltCommand;
|
|
210
|
+
path: readonly string[];
|
|
211
|
+
scan: OptionValues;
|
|
212
|
+
tokens: readonly string[];
|
|
213
|
+
}
|
|
214
|
+
/** Consumes the globals table, then routes the remaining bare tokens to a Command. */
|
|
215
|
+
export declare function routeInvocation(graph: BuiltGraph, argv: readonly string[]): RoutedInvocation;
|
|
216
|
+
/**
|
|
217
|
+
* The phases the middleware chain terminates in: the callable check, local parsing, validation, and
|
|
218
|
+
* the action. It answers with the call that dispatches, so the caller records that the action was
|
|
219
|
+
* invoked at the moment it invokes it and no earlier failure reads as a dispatch.
|
|
220
|
+
*/
|
|
221
|
+
export declare function prepareDispatch(graph: BuiltGraph, routed: RoutedInvocation, invocation: {
|
|
169
222
|
defaults: DefaultValues;
|
|
170
223
|
host: Host;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
values: ValidatedInputs;
|
|
175
|
-
}>;
|
|
224
|
+
out: Out;
|
|
225
|
+
signal: AbortSignal;
|
|
226
|
+
}): Promise<() => unknown>;
|
|
176
227
|
export {};
|