@loomcli/core 0.7.0 → 0.8.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 +11 -6
- package/dist/application.js +11 -5
- package/dist/chain.d.ts +28 -17
- package/dist/chain.js +11 -10
- package/dist/command-rules.d.ts +1 -1
- package/dist/command-rules.js +2 -2
- package/dist/command.d.ts +31 -47
- package/dist/command.js +136 -170
- package/dist/errors.d.ts +22 -21
- package/dist/errors.js +38 -18
- package/dist/globals.d.ts +48 -22
- package/dist/globals.js +63 -22
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/input-rules.d.ts +16 -2
- package/dist/input-rules.js +40 -5
- package/dist/inspect.d.ts +35 -11
- package/dist/inspect.js +69 -66
- package/dist/locate.js +52 -60
- package/dist/options.d.ts +94 -83
- package/dist/options.js +298 -260
- package/dist/parse.d.ts +162 -0
- package/dist/parse.js +601 -0
- package/dist/plugin-rules.d.ts +2 -4
- package/dist/plugin-rules.js +3 -8
- package/dist/plugin.d.ts +26 -21
- package/dist/plugin.js +10 -45
- package/dist/sources.d.ts +18 -9
- package/dist/sources.js +46 -16
- package/dist/types.d.ts +68 -28
- package/dist/validation.d.ts +84 -31
- package/dist/validation.js +222 -110
- package/package.json +1 -1
package/dist/command.js
CHANGED
|
@@ -3,17 +3,18 @@ import { checkEnvBinding, checkNoArgumentBinding } from './bindings.js';
|
|
|
3
3
|
import { captureDeclaration, unreadableArgument } from './capture.js';
|
|
4
4
|
import { aliasWithoutNames, argumentDeclaredTwice, argumentsBesideChildren, commandAttachedTwice, commandGlobals, commandWithoutAction, declaredAfterAction, declaredName, groupOption, multipleActions, multipleResults, nestingDepth, notACommand, optionalArgumentLast, portableName, repeatedAlias, resultWithoutAction, resultWithoutViews, rowViewOnValue, siblingNameTaken, unknownDefaultView, variadicArgumentLast, viewName, viewShape, viewsWithoutResult, } from './command-rules.js';
|
|
5
5
|
import { elided, quoteString, spelled } from './diagnostic-text.js';
|
|
6
|
-
import { asSentence, commandSentence, commandSubject, DeclarationError, NonCallableCommandError, quoted, ResultError, toFailure,
|
|
6
|
+
import { asSentence, commandSentence, commandSubject, DeclarationError, NonCallableCommandError, quoted, ResultError, toFailure, reasonOf, } from './errors.js';
|
|
7
7
|
import { buildExtensions, extendStore, publishStore, registerDescriptor, storeCommandLayers, validateLayer, } from './extension.js';
|
|
8
8
|
import { checkDeprecated, checkDescription, checkHidden, checkNoListingFacts, declarerNote, siteFinding, } from './facts.js';
|
|
9
|
-
import { buildGlobals, checkLocalOptions } from './globals.js';
|
|
9
|
+
import { buildGlobals, checkLocalOptions, frozenValues } from './globals.js';
|
|
10
10
|
import { nameSharedAcrossKinds, optionDeclaredTwice, spellingTaken } from './input-rules.js';
|
|
11
11
|
import { graphMismatch, nodeAt, resultNode } from './inspect.js';
|
|
12
|
-
import { checkOptionName, compileOptions, copyValues,
|
|
12
|
+
import { checkOptionName, compileOptions, copyValues, declaredNameCorrection, emptyValues, isDeclaredName, mergeValues, repeatedAliasText, spellingMark, tableEntries, } from './options.js';
|
|
13
|
+
import { argumentSlot, candidatesOf } from './parse.js';
|
|
13
14
|
import { declaring, isPlainObject, shallowList, snapshot } from './plain.js';
|
|
14
15
|
import { brokenAttachHook, notAnObject } from './plugin-rules.js';
|
|
15
16
|
import { fillInputs } from './sources.js';
|
|
16
|
-
import { captureInputConfig, configUnread, checkDeclarations, declaringSite, inputPlace, validateValues, } from './validation.js';
|
|
17
|
+
import { captureInputConfig, configUnread, checkDeclarations, declaringSite, inputPlace, rejectsGlobal, validateValues, } from './validation.js';
|
|
17
18
|
/**
|
|
18
19
|
* The deepest level below the root a Command may sit at, and the word its diagnostic spells it with.
|
|
19
20
|
* A child of the root sits at level 1. Raising the cap relaxes a rule and breaks no application.
|
|
@@ -105,10 +106,6 @@ function checkCommandOptions(name, options) {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
|
-
/** One name rule for an argument, option, or view name: a bare token the parser can read. */
|
|
109
|
-
function isDeclaredName(name) {
|
|
110
|
-
return typeof name === 'string' && Boolean(name) && !name.startsWith('-') && !/[\s=]/u.test(name);
|
|
111
|
-
}
|
|
112
109
|
/**
|
|
113
110
|
* The portable name rule, for every name an operator types as a command at a shell prompt: the
|
|
114
111
|
* application name, every Command name, and every alias. The characters are the POSIX portable
|
|
@@ -234,11 +231,11 @@ function checkOpen(state, late) {
|
|
|
234
231
|
const inputRemedy = 'Declare arguments and options before action().';
|
|
235
232
|
/** Where one input was declared: the call on the Command at `path` that declared it. */
|
|
236
233
|
function inputSite(name, path, input) {
|
|
237
|
-
return declaringSite(input, inputPlace(input,
|
|
234
|
+
return declaringSite(input, inputPlace(input, path), `${commandSentence(name)} ${input.kind} ${quoted(input.name)}`);
|
|
238
235
|
}
|
|
239
236
|
/** The finding for the `argument()` or `option()` call that declared one input, marking its name. */
|
|
240
237
|
function inputFinding(path, input, note) {
|
|
241
|
-
const site = declaringSite(input, inputPlace(input,
|
|
238
|
+
const site = declaringSite(input, inputPlace(input, path));
|
|
242
239
|
return siteFinding(site, site.named, note);
|
|
243
240
|
}
|
|
244
241
|
/**
|
|
@@ -246,7 +243,7 @@ function inputFinding(path, input, note) {
|
|
|
246
243
|
* name is judged, so it prints elided.
|
|
247
244
|
*/
|
|
248
245
|
function nameFinding(path, input) {
|
|
249
|
-
const site = configUnread(declaringSite(input, inputPlace(input,
|
|
246
|
+
const site = configUnread(declaringSite(input, inputPlace(input, path)));
|
|
250
247
|
return siteFinding(site, site.named);
|
|
251
248
|
}
|
|
252
249
|
/** The scope one Command's own options compile under: its subject, and each option's call. */
|
|
@@ -324,7 +321,7 @@ function checkArgumentName(command, declared, input) {
|
|
|
324
321
|
const { path } = command;
|
|
325
322
|
if (!isDeclaredName(input.name)) {
|
|
326
323
|
throw new DeclarationError(declaredName, {
|
|
327
|
-
correction:
|
|
324
|
+
correction: declaredNameCorrection,
|
|
328
325
|
findings: [nameFinding(path, input)],
|
|
329
326
|
sentence: `${commandSentence(command.name)} declares an argument named ${quoted(input.name)}.`,
|
|
330
327
|
});
|
|
@@ -439,18 +436,17 @@ export function declareAlias(state, names) {
|
|
|
439
436
|
const aliases = [...state.aliases];
|
|
440
437
|
for (const [index, alias] of names.entries()) {
|
|
441
438
|
const repeated = { arguments: names, call: 'alias', mark: String(index), path };
|
|
439
|
+
const text = repeatedAliasText(commandSentence(name), alias);
|
|
442
440
|
if (alias === name) {
|
|
443
441
|
throw new DeclarationError(repeatedAlias, {
|
|
444
|
-
|
|
442
|
+
...text.own,
|
|
445
443
|
findings: [{ ...repeated, note: 'its own name' }],
|
|
446
|
-
sentence: `${commandSentence(name)} declares alias "${alias}", which is its own name.`,
|
|
447
444
|
});
|
|
448
445
|
}
|
|
449
446
|
if (aliases.includes(alias)) {
|
|
450
447
|
throw new DeclarationError(repeatedAlias, {
|
|
451
|
-
|
|
448
|
+
...text.twice,
|
|
452
449
|
findings: [{ ...repeated, note: 'already an alias' }],
|
|
453
|
-
sentence: `${commandSentence(name)} declares alias "${alias}" twice.`,
|
|
454
450
|
});
|
|
455
451
|
}
|
|
456
452
|
aliases.push(alias);
|
|
@@ -727,7 +723,7 @@ function attachChild(state, child) {
|
|
|
727
723
|
/**
|
|
728
724
|
* Walks one subtree joining an Application, once, for the rules only the Application can judge: one
|
|
729
725
|
* Command value reached through two paths, two distinct descriptors under one identity, a local
|
|
730
|
-
* option that meets a global
|
|
726
|
+
* option that meets a global option's key, spelling, or variable, and the nesting cap
|
|
731
727
|
* measured from the root. A claim is by node identity, and the name serves the diagnostic alone.
|
|
732
728
|
* At a cap of two a named parent's `command()` already rejects every deeper tree, so the depth check
|
|
733
729
|
* here fires only once the cap rises: attach reads one level, and only this walk knows each level.
|
|
@@ -1293,13 +1289,13 @@ function readSpellings(table, claimed, placeOf) {
|
|
|
1293
1289
|
for (const [spelling, option] of table) {
|
|
1294
1290
|
if (!claimed.has(spelling)) {
|
|
1295
1291
|
const form = longs.get(option.name) ?? spelling;
|
|
1296
|
-
claimed.set(spelling, { finding: placeOf(option.name, option
|
|
1292
|
+
claimed.set(spelling, { finding: placeOf(option.name, option), form });
|
|
1297
1293
|
}
|
|
1298
1294
|
}
|
|
1299
1295
|
}
|
|
1300
1296
|
/** The place one input's spelling sits: the key of its call that yields the spelling. */
|
|
1301
|
-
function spellingPlace(site,
|
|
1302
|
-
return siteFinding(site, spellingMark(site,
|
|
1297
|
+
function spellingPlace(site, origin, note) {
|
|
1298
|
+
return siteFinding(site, spellingMark(site, origin), note);
|
|
1303
1299
|
}
|
|
1304
1300
|
/** One hook-declared option's spellings, against every spelling the table already claims. */
|
|
1305
1301
|
function checkAttachedSpelling(declared, named) {
|
|
@@ -1314,14 +1310,14 @@ function checkAttachedSpelling(declared, named) {
|
|
|
1314
1310
|
throw new DeclarationError(spellingTaken, {
|
|
1315
1311
|
correction: 'Change one of the two spellings or omit the plugin.',
|
|
1316
1312
|
findings: [
|
|
1317
|
-
spellingPlace(site, option
|
|
1313
|
+
spellingPlace(site, option, declarerNote(identity)),
|
|
1318
1314
|
...(used.finding === undefined ? [] : [used.finding]),
|
|
1319
1315
|
],
|
|
1320
1316
|
sentence: `Plugin ${quoted(identity)} declares option ${quoted(input.name)} with spelling ${quoted(spelling)} on ${subject}, which ${quoted(used.form)} already uses.`,
|
|
1321
1317
|
});
|
|
1322
1318
|
}
|
|
1323
1319
|
}
|
|
1324
|
-
readSpellings(table, claimed, (_name,
|
|
1320
|
+
readSpellings(table, claimed, (_name, origin) => spellingPlace(site, origin, declarerNote(identity)));
|
|
1325
1321
|
}
|
|
1326
1322
|
/** The declarations of one kind, keyed by name, the first of each name winning. */
|
|
1327
1323
|
function byName(inputs) {
|
|
@@ -1335,7 +1331,7 @@ function byName(inputs) {
|
|
|
1335
1331
|
}
|
|
1336
1332
|
/** Where the globals table's options declared their spellings, a global or a plugin's option. */
|
|
1337
1333
|
function tableSpellings(globals) {
|
|
1338
|
-
return (name,
|
|
1334
|
+
return (name, origin) => {
|
|
1339
1335
|
const entry = globals.names.get(name);
|
|
1340
1336
|
if (!entry) {
|
|
1341
1337
|
return undefined;
|
|
@@ -1344,7 +1340,7 @@ function tableSpellings(globals) {
|
|
|
1344
1340
|
const note = owner.kind === 'plugin'
|
|
1345
1341
|
? `an option of plugin ${quoted(owner.identity)}`
|
|
1346
1342
|
: `the global option ${quoted(name)}`;
|
|
1347
|
-
return spellingPlace(site,
|
|
1343
|
+
return spellingPlace(site, origin, note);
|
|
1348
1344
|
};
|
|
1349
1345
|
}
|
|
1350
1346
|
/**
|
|
@@ -1371,11 +1367,11 @@ function checkAttachedInputs(declared, attached, place) {
|
|
|
1371
1367
|
};
|
|
1372
1368
|
const claimed = new Map();
|
|
1373
1369
|
readSpellings(globals.options, claimed, tableSpellings(globals));
|
|
1374
|
-
readSpellings(compileOptions(options, scope), claimed, (name,
|
|
1370
|
+
readSpellings(compileOptions(options, scope), claimed, (name, origin) => {
|
|
1375
1371
|
const local = locals.get(name);
|
|
1376
1372
|
return local === undefined || local.kind !== 'option'
|
|
1377
1373
|
? undefined
|
|
1378
|
-
: spellingPlace(scope.siteOf(local),
|
|
1374
|
+
: spellingPlace(scope.siteOf(local), origin, `the local option ${quoted(name)}`);
|
|
1379
1375
|
});
|
|
1380
1376
|
for (const entry of attached) {
|
|
1381
1377
|
const { identity, input } = entry;
|
|
@@ -1404,7 +1400,7 @@ function bindDispatch(state, action, globals) {
|
|
|
1404
1400
|
// The graph erases the binder's generic relationship.
|
|
1405
1401
|
// It holds because attachment checks the global output requirement.
|
|
1406
1402
|
// The call or the attach that met both options rejected a collision between a global and a local.
|
|
1407
|
-
// This binder returns the
|
|
1403
|
+
// This binder returns the validated value of every global option, the plugins' included.
|
|
1408
1404
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
1409
1405
|
const globalOptions = globals.bind(values);
|
|
1410
1406
|
return action({
|
|
@@ -1499,6 +1495,7 @@ export function buildCommand(state, context) {
|
|
|
1499
1495
|
checkArgumentPlacement(place, slots[0], state.children[0]);
|
|
1500
1496
|
const result = checkFinished(hooked, hasAction, { path: context.path });
|
|
1501
1497
|
const options = checkLocalOptions(optionsOf(hooked.inputs), globals, localScope(name, context.path));
|
|
1498
|
+
const table = new Map([...tableEntries(options, false), ...globals.table]);
|
|
1502
1499
|
// A hook's erased calls answer the declaration rules an authored call answers at the call.
|
|
1503
1500
|
checkDeclarations(hookInputs.map(({ input }) => ({ input, site: inputSite(name, context.path, input) })));
|
|
1504
1501
|
const children = new Map();
|
|
@@ -1525,9 +1522,9 @@ export function buildCommand(state, context) {
|
|
|
1525
1522
|
hidden: facts.hidden,
|
|
1526
1523
|
inputs: hooked.inputs,
|
|
1527
1524
|
name,
|
|
1528
|
-
options,
|
|
1529
1525
|
result,
|
|
1530
1526
|
routes,
|
|
1527
|
+
table,
|
|
1531
1528
|
};
|
|
1532
1529
|
}
|
|
1533
1530
|
/**
|
|
@@ -1637,17 +1634,15 @@ export function collectInputs(command) {
|
|
|
1637
1634
|
];
|
|
1638
1635
|
}
|
|
1639
1636
|
/**
|
|
1640
|
-
* Where every input of one graph was declared: each global option at its `globalOption()` call
|
|
1641
|
-
* and each Command's own inputs at their calls on the Command,
|
|
1637
|
+
* Where every input of one graph was declared: each global option at its `globalOption()` call or
|
|
1638
|
+
* in its plugin's `options` record, and each Command's own inputs at their calls on the Command,
|
|
1639
|
+
* under its path from the root.
|
|
1642
1640
|
*/
|
|
1643
1641
|
export function inputPlaces(graph) {
|
|
1644
|
-
const places = new Map();
|
|
1645
|
-
for (const input of graph.globals.inputs) {
|
|
1646
|
-
places.set(input, inputPlace(input, { global: true, path: [] }));
|
|
1647
|
-
}
|
|
1642
|
+
const places = new Map(graph.globals.sites);
|
|
1648
1643
|
const walk = (command, path) => {
|
|
1649
1644
|
for (const input of command.inputs) {
|
|
1650
|
-
places.set(input,
|
|
1645
|
+
places.set(input, declaringSite(input, inputPlace(input, path)));
|
|
1651
1646
|
}
|
|
1652
1647
|
for (const [name, child] of command.children) {
|
|
1653
1648
|
walk(child, [...path, name]);
|
|
@@ -1656,97 +1651,36 @@ export function inputPlaces(graph) {
|
|
|
1656
1651
|
walk(graph.root, []);
|
|
1657
1652
|
return places;
|
|
1658
1653
|
}
|
|
1659
|
-
/**
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
/**
|
|
1671
|
-
* Whether a token names one of the Command's children: the Command has children and the token is
|
|
1672
|
-
* no option token. Routing and `locate` read a bare word through this rule.
|
|
1673
|
-
*/
|
|
1674
|
-
export function readsAsChild(command, token) {
|
|
1675
|
-
return command.children.size > 0 && !isOptionToken(token);
|
|
1676
|
-
}
|
|
1677
|
-
/**
|
|
1678
|
-
* Bare tokens, names or aliases, select children until a Command has none; a hyphen commits.
|
|
1679
|
-
* `walked` receives the path after each name routes, so an unknown Command leaves its caller
|
|
1680
|
-
* holding the partial path walked before it.
|
|
1681
|
-
*/
|
|
1682
|
-
export function route(root, tokens, walked) {
|
|
1683
|
-
let command = root;
|
|
1684
|
-
const path = [];
|
|
1685
|
-
let index = 0;
|
|
1686
|
-
for (let token = tokens[index]; token !== undefined; token = tokens[index]) {
|
|
1687
|
-
if (!readsAsChild(command, token)) {
|
|
1688
|
-
break;
|
|
1689
|
-
}
|
|
1690
|
-
const child = command.routes.get(token);
|
|
1691
|
-
if (!child) {
|
|
1692
|
-
throw new UnknownCommandError(token, candidatesOf(command));
|
|
1693
|
-
}
|
|
1694
|
-
// An alias routes like the canonical name, and the path it walks reports that name alone.
|
|
1695
|
-
command = child.command;
|
|
1696
|
-
path.push(child.name);
|
|
1697
|
-
walked?.(Object.freeze([...path]));
|
|
1698
|
-
index += 1;
|
|
1654
|
+
/** One positional word bound to its slot: a variadic slot collects it, any other holds it. */
|
|
1655
|
+
function bindWord(values, slot, word) {
|
|
1656
|
+
const bound = values.get(slot.input);
|
|
1657
|
+
if (!slot.variadic) {
|
|
1658
|
+
values.set(slot.input, word);
|
|
1659
|
+
}
|
|
1660
|
+
else if (Array.isArray(bound)) {
|
|
1661
|
+
bound.push(word);
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
values.set(slot.input, [word]);
|
|
1699
1665
|
}
|
|
1700
|
-
return { command, path, tokens: tokens.slice(index) };
|
|
1701
1666
|
}
|
|
1702
1667
|
/**
|
|
1703
|
-
* The
|
|
1668
|
+
* The words each positional slot received. Parsing already held the first positional no slot
|
|
1669
|
+
* accepts, so every positional here has a slot. An omitted required argument binds nothing and
|
|
1704
1670
|
* reports as a missing input in the validation phase, so omission has one class whether the input
|
|
1705
|
-
* is an argument or an option.
|
|
1671
|
+
* is an argument or an option. An empty variadic tail binds nothing, so validation reads it as
|
|
1672
|
+
* `[]` or reports the omission.
|
|
1706
1673
|
*/
|
|
1707
|
-
function bindArguments(command,
|
|
1674
|
+
function bindArguments(command, positionals) {
|
|
1708
1675
|
const values = new Map();
|
|
1709
|
-
for (const [position,
|
|
1676
|
+
for (const [position, word] of positionals.entries()) {
|
|
1710
1677
|
const slot = argumentSlot(command.arguments, position);
|
|
1711
|
-
if (
|
|
1712
|
-
|
|
1713
|
-
}
|
|
1714
|
-
// An empty variadic tail binds nothing, so validation reads it as `[]` or reports the omission.
|
|
1715
|
-
const bound = values.get(slot.input);
|
|
1716
|
-
if (!slot.variadic) {
|
|
1717
|
-
values.set(slot.input, token);
|
|
1718
|
-
}
|
|
1719
|
-
else if (Array.isArray(bound)) {
|
|
1720
|
-
bound.push(token);
|
|
1721
|
-
}
|
|
1722
|
-
else {
|
|
1723
|
-
values.set(slot.input, [token]);
|
|
1678
|
+
if (slot) {
|
|
1679
|
+
bindWord(values, slot, word);
|
|
1724
1680
|
}
|
|
1725
1681
|
}
|
|
1726
1682
|
return values;
|
|
1727
1683
|
}
|
|
1728
|
-
/**
|
|
1729
|
-
* The slot the positional at one index fills: the slot at that index, else a variadic last slot,
|
|
1730
|
-
* which accepts every later positional, else none. Binding and `locate` read positions through it.
|
|
1731
|
-
*/
|
|
1732
|
-
export function argumentSlot(slots, position) {
|
|
1733
|
-
const last = slots.at(-1);
|
|
1734
|
-
return slots[position] ?? (last?.variadic ? last : undefined);
|
|
1735
|
-
}
|
|
1736
|
-
/**
|
|
1737
|
-
* Consumes the globals table, then routes the remaining bare tokens to a Command. `walked`
|
|
1738
|
-
* receives the path as routing extends it, the partial path of an unknown Command included.
|
|
1739
|
-
*/
|
|
1740
|
-
export function routeInvocation(graph, argv, walked) {
|
|
1741
|
-
const scan = extractGlobals(graph.globals.options, [...argv]);
|
|
1742
|
-
const routed = route(graph.root, scan.rest, walked);
|
|
1743
|
-
return {
|
|
1744
|
-
command: routed.command,
|
|
1745
|
-
path: routed.path,
|
|
1746
|
-
scan: scan.values,
|
|
1747
|
-
tokens: routed.tokens,
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
1684
|
/**
|
|
1751
1685
|
* The frozen records one middleware reads: the routed Command's own inputs, and the tail. Every
|
|
1752
1686
|
* value is a plain-data copy frozen to every depth, so a middleware that reaches into a list or a
|
|
@@ -1768,30 +1702,25 @@ function requestOf(command, values, passthrough) {
|
|
|
1768
1702
|
});
|
|
1769
1703
|
}
|
|
1770
1704
|
/**
|
|
1771
|
-
* The
|
|
1772
|
-
* missing-subcommand error
|
|
1773
|
-
* fault holds the same way.
|
|
1705
|
+
* The held structural fault, then the callable check. A group answers no invocation of its own, so
|
|
1706
|
+
* it holds the missing-subcommand error, which ranks after every structural fault.
|
|
1774
1707
|
*/
|
|
1775
1708
|
function parseLocal(routed) {
|
|
1776
|
-
const { command, path } = routed;
|
|
1709
|
+
const { command, fault, path } = routed;
|
|
1777
1710
|
const { dispatch } = command;
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
throw new NonCallableCommandError(path, candidatesOf(command));
|
|
1781
|
-
}
|
|
1782
|
-
const parsed = parseInputs(command.options, routed.tokens);
|
|
1783
|
-
const args = bindArguments(command, path, parsed.positionals);
|
|
1784
|
-
return {
|
|
1785
|
-
args,
|
|
1786
|
-
dispatch,
|
|
1787
|
-
kind: 'parsed',
|
|
1788
|
-
options: parsed.options,
|
|
1789
|
-
passthrough: parsed.passthrough,
|
|
1790
|
-
};
|
|
1711
|
+
if (fault) {
|
|
1712
|
+
return { fault, kind: 'held' };
|
|
1791
1713
|
}
|
|
1792
|
-
|
|
1793
|
-
return { fault:
|
|
1714
|
+
if (!dispatch) {
|
|
1715
|
+
return { fault: new NonCallableCommandError(path, candidatesOf(command)), kind: 'held' };
|
|
1794
1716
|
}
|
|
1717
|
+
return {
|
|
1718
|
+
args: bindArguments(command, routed.positionals),
|
|
1719
|
+
dispatch,
|
|
1720
|
+
kind: 'parsed',
|
|
1721
|
+
options: routed.values.locals,
|
|
1722
|
+
passthrough: routed.passthrough,
|
|
1723
|
+
};
|
|
1795
1724
|
}
|
|
1796
1725
|
/**
|
|
1797
1726
|
* The node of one option a configuration source is asked about: in the graph's globals, or among
|
|
@@ -1806,21 +1735,24 @@ function requestNode(graph, path, place) {
|
|
|
1806
1735
|
}
|
|
1807
1736
|
return node;
|
|
1808
1737
|
}
|
|
1738
|
+
/** The passthrough tail a validator reads: the routed Command's, or none while a fault is held. */
|
|
1739
|
+
function tailOf(local) {
|
|
1740
|
+
return local.kind === 'parsed' ? local.passthrough : [];
|
|
1741
|
+
}
|
|
1809
1742
|
/**
|
|
1810
|
-
* The input-source stage over this run's own copies of the parsed values. The global
|
|
1811
|
-
*
|
|
1812
|
-
*
|
|
1743
|
+
* The input-source stage over this run's own copies of the parsed values. The global options fill
|
|
1744
|
+
* whatever was held, and a faulted occurrence supplied nothing, so a source may fill its option;
|
|
1745
|
+
* the routed Command's own options fill only when nothing is held, because the request is `null`
|
|
1746
|
+
* otherwise. A configuration source's own options pass their validators before the source is
|
|
1747
|
+
* called, as a pass over those options alone.
|
|
1813
1748
|
*/
|
|
1814
|
-
async function fillScope(
|
|
1815
|
-
const
|
|
1749
|
+
async function fillScope(preparation, local) {
|
|
1750
|
+
const { graph, invocation, routed } = preparation;
|
|
1751
|
+
const globals = copyValues(routed.values.globals);
|
|
1816
1752
|
const locals = copyValues(local.kind === 'parsed' ? local.options : emptyValues());
|
|
1817
1753
|
const sources = await fillInputs({
|
|
1818
1754
|
extensions: graph.extensions,
|
|
1819
|
-
globals: {
|
|
1820
|
-
global: true,
|
|
1821
|
-
inputs: [...graph.globals.inputs, ...graph.globals.plugins.flatMap((entry) => entry.inputs)],
|
|
1822
|
-
values: globals,
|
|
1823
|
-
},
|
|
1755
|
+
globals: { global: true, inputs: graph.globals.inputs, values: globals },
|
|
1824
1756
|
host: invocation.host,
|
|
1825
1757
|
inspected: invocation.inspected,
|
|
1826
1758
|
locals: local.kind === 'parsed'
|
|
@@ -1832,28 +1764,52 @@ async function fillScope({ graph, invocation, routed }, local) {
|
|
|
1832
1764
|
request: (input, global) => requestNode(invocation.inspected(), routed.path, { global, name: input.name }),
|
|
1833
1765
|
signal: invocation.signal,
|
|
1834
1766
|
style: invocation.style,
|
|
1767
|
+
validate: (inputs, provenance) => validateInvocation(preparation, {
|
|
1768
|
+
local,
|
|
1769
|
+
locals,
|
|
1770
|
+
only: new Set(inputs),
|
|
1771
|
+
sources: provenance,
|
|
1772
|
+
values: globals,
|
|
1773
|
+
}),
|
|
1835
1774
|
});
|
|
1836
1775
|
return { globals, locals, sources };
|
|
1837
1776
|
}
|
|
1838
1777
|
/**
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1778
|
+
* A validation pass over the values the input-source stage has filled: every global option
|
|
1779
|
+
* whatever was held, and the routed Command's own declarations only when nothing is held and the
|
|
1780
|
+
* Command is not a group. `only` narrows the declarations a pass validates, as the pass over a
|
|
1781
|
+
* configuration source's own options does ahead of its call, while every validator reads the same
|
|
1782
|
+
* context. The run's one pass reads that earlier pass's values and problems rather than validating
|
|
1783
|
+
* them again.
|
|
1842
1784
|
*/
|
|
1843
|
-
|
|
1844
|
-
const {
|
|
1845
|
-
const
|
|
1846
|
-
|
|
1847
|
-
command: path,
|
|
1848
|
-
|
|
1785
|
+
function validateInvocation({ graph, invocation, routed }, filled) {
|
|
1786
|
+
const { local, only, sources } = filled;
|
|
1787
|
+
const parsed = local.kind === 'parsed';
|
|
1788
|
+
return validateValues({
|
|
1789
|
+
command: routed.path,
|
|
1790
|
+
declaredValues: invocation.declaredValues,
|
|
1849
1791
|
host: invocation.host,
|
|
1850
|
-
inputs: { globals: graph.globals.inputs, locals: command.inputs },
|
|
1851
|
-
passthrough: local
|
|
1852
|
-
|
|
1792
|
+
inputs: { globals: graph.globals.inputs, locals: parsed ? routed.command.inputs : [] },
|
|
1793
|
+
passthrough: tailOf(local),
|
|
1794
|
+
places: invocation.places,
|
|
1853
1795
|
signal: invocation.signal,
|
|
1854
|
-
sources
|
|
1855
|
-
supplied: {
|
|
1796
|
+
sources,
|
|
1797
|
+
supplied: {
|
|
1798
|
+
args: parsed ? local.args : new Map(),
|
|
1799
|
+
options: parsed ? mergeValues(filled.values, filled.locals) : filled.values,
|
|
1800
|
+
},
|
|
1801
|
+
table: routed.command.table,
|
|
1802
|
+
...(only ? { only } : {}),
|
|
1803
|
+
...(sources.validated ? { prior: sources.validated } : {}),
|
|
1856
1804
|
});
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* The dispatch a clean validation prepares. It answers with the call that dispatches, so the caller
|
|
1808
|
+
* records that the action was invoked at the moment it invokes it and no earlier failure reads as a
|
|
1809
|
+
* dispatch.
|
|
1810
|
+
*/
|
|
1811
|
+
function readyDispatch({ invocation, routed }, local, values) {
|
|
1812
|
+
const { command, path } = routed;
|
|
1857
1813
|
return {
|
|
1858
1814
|
dispatch: async (view) => {
|
|
1859
1815
|
// The channel is built at the boundary, because the view a middleware selected is read there.
|
|
@@ -1893,37 +1849,47 @@ async function readyDispatch({ graph, invocation, routed }, filled) {
|
|
|
1893
1849
|
/**
|
|
1894
1850
|
* Prepares one dispatch ahead of the middleware chain and holds whatever fault it found, so a
|
|
1895
1851
|
* middleware reads the request before the action runs and a takeover never observes the fault.
|
|
1896
|
-
* The phases run in order:
|
|
1897
|
-
*
|
|
1852
|
+
* The phases run in order: the structural fault parsing held, a group's missing subcommand, the
|
|
1853
|
+
* input-source stage, and validation. Either of the first two outranks a configuration source's
|
|
1854
|
+
* fault, and a source's fault takes the place of every validation problem. Validation checks the
|
|
1855
|
+
* global options whatever was held, so a middleware reads them after a local fault; a structural
|
|
1856
|
+
* fault on a global option, a rejected global option, a source's fault, or a validator's own fault
|
|
1857
|
+
* leaves `options` `null`.
|
|
1898
1858
|
*/
|
|
1899
1859
|
export async function prepareDispatch(graph, routed, invocation) {
|
|
1900
1860
|
const result = routed.command.result;
|
|
1901
1861
|
const local = parseLocal(routed);
|
|
1902
1862
|
const preparation = { graph, invocation, routed };
|
|
1903
1863
|
const { globals, locals, sources } = await fillScope(preparation, local);
|
|
1904
|
-
const held = (fault) => ({
|
|
1905
|
-
fault,
|
|
1864
|
+
const held = (fault, options) => ({
|
|
1865
|
+
fault: local.kind === 'held' ? local.fault : fault,
|
|
1906
1866
|
globals,
|
|
1907
1867
|
kind: 'held',
|
|
1868
|
+
options,
|
|
1908
1869
|
request: null,
|
|
1909
1870
|
result,
|
|
1910
1871
|
});
|
|
1911
|
-
if (local.kind === 'held') {
|
|
1912
|
-
return held(local.fault);
|
|
1913
|
-
}
|
|
1914
1872
|
if (sources.fault) {
|
|
1915
|
-
return held(sources.fault);
|
|
1873
|
+
return held(sources.fault, null);
|
|
1916
1874
|
}
|
|
1917
1875
|
try {
|
|
1918
|
-
const
|
|
1876
|
+
const validation = await validateInvocation(preparation, {
|
|
1919
1877
|
local,
|
|
1878
|
+
locals,
|
|
1920
1879
|
sources,
|
|
1921
|
-
values:
|
|
1880
|
+
values: globals,
|
|
1922
1881
|
});
|
|
1923
|
-
|
|
1882
|
+
const options = routed.globalFault || rejectsGlobal(validation)
|
|
1883
|
+
? null
|
|
1884
|
+
: frozenValues(graph.globals.inputs, validation.values);
|
|
1885
|
+
if (local.kind === 'held' || validation.failure) {
|
|
1886
|
+
return held(validation.failure, options);
|
|
1887
|
+
}
|
|
1888
|
+
const ready = readyDispatch(preparation, local, validation.values);
|
|
1889
|
+
return { ...ready, globals, kind: 'ready', options, result };
|
|
1924
1890
|
}
|
|
1925
1891
|
catch (error) {
|
|
1926
1892
|
// The fault is held as a failure, so a throw from reading a validator's output is never offered.
|
|
1927
|
-
return held(toFailure(error));
|
|
1893
|
+
return held(toFailure(error), null);
|
|
1928
1894
|
}
|
|
1929
1895
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -10,20 +10,6 @@ import type { InputIdentity } from './types.js';
|
|
|
10
10
|
* keeps the raw value.
|
|
11
11
|
*/
|
|
12
12
|
export declare function quoted(value: unknown): string;
|
|
13
|
-
/**
|
|
14
|
-
* The two short-group faults. A value option that is not last in its group names that option's
|
|
15
|
-
* spelling; a group that mixes scopes names only the two letters that disagree, because the rest
|
|
16
|
-
* of the group may hold an inline value. `token` keeps the whole group as the reported fact.
|
|
17
|
-
*/
|
|
18
|
-
type ShortGroupFault = {
|
|
19
|
-
reason: 'value-position';
|
|
20
|
-
token: string;
|
|
21
|
-
} | {
|
|
22
|
-
reason: 'mixed-scope';
|
|
23
|
-
token: string;
|
|
24
|
-
global: string;
|
|
25
|
-
other: string;
|
|
26
|
-
};
|
|
27
13
|
/**
|
|
28
14
|
* The one message a distributed build shows an operator for a defect or a declaration fault: the
|
|
29
15
|
* application name and a fixed phrase, with no reason, class name, code, or path.
|
|
@@ -123,24 +109,39 @@ export declare class UnknownOptionError extends UsageError {
|
|
|
123
109
|
readonly spelling: string;
|
|
124
110
|
constructor(spelling: string);
|
|
125
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Where a missing value should have been: the words ran out, or the next word was an option word or
|
|
114
|
+
* the bare `--`, which is never a separate value, so the sentence names the attached form too.
|
|
115
|
+
*/
|
|
116
|
+
type MissingValueForm = 'attached' | 'separate';
|
|
126
117
|
export declare class MissingValueError extends UsageError {
|
|
127
118
|
readonly spelling: string;
|
|
128
|
-
constructor(spelling: string);
|
|
119
|
+
constructor(spelling: string, form?: MissingValueForm);
|
|
129
120
|
}
|
|
130
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* A Boolean or counted spelling takes no value, so the token carried one the declaration cannot
|
|
123
|
+
* accept. A counted option's sentence tells the operator to repeat the spelling instead, and `kind`
|
|
124
|
+
* chooses the sentence alone.
|
|
125
|
+
*/
|
|
131
126
|
export declare class UnexpectedValueError extends UsageError {
|
|
132
127
|
readonly spelling: string;
|
|
133
128
|
readonly value: string;
|
|
134
|
-
constructor(spelling: string, value: string);
|
|
129
|
+
constructor(spelling: string, value: string, kind?: 'boolean' | 'count');
|
|
135
130
|
}
|
|
136
131
|
export declare class RepeatedOptionError extends UsageError {
|
|
137
132
|
readonly spelling: string;
|
|
138
133
|
constructor(spelling: string);
|
|
139
134
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
/**
|
|
136
|
+
* An option word the Command routing reached does not declare, while a visible Command below it
|
|
137
|
+
* does, such as a Command's own option typed before its name, or a parent's own option the routed
|
|
138
|
+
* Command declares with another value class. `commands` holds each such Command's path from the
|
|
139
|
+
* root, in authoring order.
|
|
140
|
+
*/
|
|
141
|
+
export declare class MisplacedOptionError extends UsageError {
|
|
142
|
+
readonly spelling: string;
|
|
143
|
+
readonly commands: readonly (readonly string[])[];
|
|
144
|
+
constructor(spelling: string, commands: readonly (readonly string[])[]);
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
146
147
|
* Exit 1: the declaration is wrong, so the author reads its Developer Diagnostic. A rule and the
|