@loomcli/core 0.5.0 → 0.7.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/NOTICE +34 -0
- package/dist/application.d.ts +18 -2
- package/dist/application.js +302 -75
- package/dist/bindings.d.ts +15 -10
- package/dist/bindings.js +34 -15
- package/dist/capture.d.ts +65 -0
- package/dist/capture.js +99 -0
- package/dist/chain.d.ts +15 -6
- package/dist/chain.js +40 -20
- package/dist/command-rules.d.ts +55 -0
- package/dist/command-rules.js +142 -0
- package/dist/command.d.ts +65 -23
- package/dist/command.js +734 -236
- 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 +108 -24
- package/dist/errors.js +324 -53
- package/dist/exit-codes.d.ts +45 -0
- package/dist/exit-codes.js +46 -0
- package/dist/extension.d.ts +41 -8
- package/dist/extension.js +142 -57
- package/dist/facts.d.ts +71 -11
- package/dist/facts.js +106 -23
- package/dist/globals.d.ts +29 -21
- package/dist/globals.js +117 -46
- package/dist/glyphs.generated.js +1 -1
- 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 +12 -2
- package/dist/index.js +6 -0
- package/dist/input-rules.d.ts +68 -0
- package/dist/input-rules.js +152 -0
- package/dist/inspect.d.ts +12 -12
- package/dist/inspect.js +92 -48
- package/dist/lanes.js +1 -1
- package/dist/locate.js +4 -4
- package/dist/options.d.ts +30 -2
- package/dist/options.js +143 -46
- package/dist/output.d.ts +9 -2
- package/dist/output.js +18 -2
- package/dist/plain.d.ts +56 -0
- package/dist/plain.js +238 -0
- package/dist/plugin-rules.d.ts +68 -0
- package/dist/plugin-rules.js +164 -0
- package/dist/plugin-settings.d.ts +18 -0
- package/dist/plugin-settings.js +38 -0
- package/dist/plugin.d.ts +27 -15
- package/dist/plugin.js +429 -144
- 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 +6 -4
- package/dist/sources.js +25 -16
- package/dist/style-layout.js +2 -2
- package/dist/style-width.d.ts +13 -0
- package/dist/style-width.js +170 -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 +13 -3
- package/dist/unicode.generated.d.ts +27 -0
- package/dist/unicode.generated.js +1036 -0
- package/dist/validation.d.ts +69 -7
- package/dist/validation.js +211 -67
- package/dist/view.d.ts +61 -22
- package/dist/view.js +168 -81
- package/licenses/unicode-LICENSE.txt +41 -0
- package/licenses/uucode-LICENSE.md +35 -0
- package/package.json +9 -5
package/dist/bindings.js
CHANGED
|
@@ -1,45 +1,64 @@
|
|
|
1
|
-
import { DeclarationError } from './errors.js';
|
|
1
|
+
import { DeclarationError, quoted } from './errors.js';
|
|
2
|
+
import { factFault, siteFinding } from './facts.js';
|
|
3
|
+
import { envName, envOnArgument, envOnMultiple, variableBoundTwice } from './input-rules.js';
|
|
2
4
|
/** A variable name: a letter or an underscore, then letters, digits, or underscores. */
|
|
3
5
|
const variableName = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
4
6
|
/**
|
|
5
7
|
* The environment binding one option declares, or `undefined` when it declares none. A multiple
|
|
6
8
|
* option takes its list from the configuration source, so it cannot bind, and a bound name must be
|
|
7
|
-
* a variable name.
|
|
9
|
+
* a variable name. The site names the declaration and marks its `env`.
|
|
8
10
|
*/
|
|
9
|
-
export function checkEnvBinding(
|
|
11
|
+
export function checkEnvBinding(site, config) {
|
|
10
12
|
const env = config.env;
|
|
11
13
|
if (env === undefined) {
|
|
12
14
|
return undefined;
|
|
13
15
|
}
|
|
14
16
|
if (config.multiple === true) {
|
|
15
|
-
throw
|
|
17
|
+
throw factFault(envOnMultiple, site, {
|
|
18
|
+
correction: 'Remove env; a list comes from the configuration source.',
|
|
19
|
+
fact: 'env',
|
|
20
|
+
sentence: `${site.subject} is a multiple option and declares env.`,
|
|
21
|
+
});
|
|
16
22
|
}
|
|
17
23
|
if (typeof env !== 'string' || !variableName.test(env)) {
|
|
18
|
-
const
|
|
19
|
-
throw
|
|
24
|
+
const named = typeof env === 'string' ? ` ${quoted(env)}` : '';
|
|
25
|
+
throw factFault(envName, site, {
|
|
26
|
+
correction: 'Use a letter or an underscore, then letters, digits, or underscores.',
|
|
27
|
+
fact: 'env',
|
|
28
|
+
sentence: `${site.subject} env${named} is not a variable name.`,
|
|
29
|
+
});
|
|
20
30
|
}
|
|
21
31
|
return env;
|
|
22
32
|
}
|
|
23
33
|
/** An argument is identified by its place among bare tokens, so no variable can stand in for it. */
|
|
24
|
-
export function checkNoArgumentBinding(
|
|
34
|
+
export function checkNoArgumentBinding(site, config) {
|
|
25
35
|
if ('env' in config) {
|
|
26
|
-
throw
|
|
36
|
+
throw factFault(envOnArgument, site, {
|
|
37
|
+
correction: 'Remove it.',
|
|
38
|
+
fact: 'env',
|
|
39
|
+
sentence: `${site.subject} declares env, which applies to options alone.`,
|
|
40
|
+
});
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
|
-
* The variables one scope binds, each to the
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
44
|
+
* The variables one scope binds, each to the option that binds it. Within one invocation's scope a
|
|
45
|
+
* variable binds one option, so a second binder is a declaration error that names the first
|
|
46
|
+
* binder, in scope order, and then the second. `held` is what an enclosing scope already binds,
|
|
47
|
+
* such as the globals table under a Command's own options.
|
|
34
48
|
*/
|
|
35
49
|
export function claimVariables(bound, held = new Map()) {
|
|
36
50
|
const claimed = new Map(held);
|
|
37
|
-
for (const
|
|
51
|
+
for (const binder of bound) {
|
|
52
|
+
const { phrase, variable } = binder;
|
|
38
53
|
const owner = claimed.get(variable);
|
|
39
54
|
if (owner !== undefined) {
|
|
40
|
-
throw new DeclarationError(
|
|
55
|
+
throw new DeclarationError(variableBoundTwice, {
|
|
56
|
+
correction: 'Bind each variable to one option.',
|
|
57
|
+
findings: [owner, binder].map(({ site }) => siteFinding(site, `${site.at}.env`)),
|
|
58
|
+
sentence: `Variable ${quoted(variable)} is bound by ${owner.phrase} and ${phrase}.`,
|
|
59
|
+
});
|
|
41
60
|
}
|
|
42
|
-
claimed.set(variable,
|
|
61
|
+
claimed.set(variable, binder);
|
|
43
62
|
}
|
|
44
63
|
return claimed;
|
|
45
64
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Finding } from './diagnostic-text.js';
|
|
2
|
+
import { DeclarationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* One declaring call's read of the object an author passed it, under way. It remembers the slot it
|
|
5
|
+
* is reading, so a read that throws names the slot it threw in, or no slot when the object itself
|
|
6
|
+
* threw.
|
|
7
|
+
*/
|
|
8
|
+
declare class DeclarationRead {
|
|
9
|
+
/** The top-level key being read, or `undefined` while the object itself is read. */
|
|
10
|
+
slot: string | undefined;
|
|
11
|
+
/** The copy of every own string key of one object, each read under its own slot. */
|
|
12
|
+
record(declared: object): Record<string, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* Replaces the value one key of a copy holds with `copy` of it, read under that key's slot. An
|
|
15
|
+
* absent key stays absent, so a finding prints the copy with the keys the author wrote.
|
|
16
|
+
*/
|
|
17
|
+
nested(captured: Record<string, unknown>, key: string, copy: (value: unknown) => unknown): void;
|
|
18
|
+
}
|
|
19
|
+
/** The two faults one declaring call raises when it cannot capture what it was given. */
|
|
20
|
+
interface CaptureFaults {
|
|
21
|
+
/** The value is not a plain object, so it has no keys for core to read. */
|
|
22
|
+
readonly notAnObject: () => DeclarationError;
|
|
23
|
+
/** A read threw the value it receives, in the top-level slot it names, or in the object itself. */
|
|
24
|
+
readonly unreadable: (thrown: unknown, slot: string | undefined) => DeclarationError;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Authoring's one read of an object a declaring call receives, inside one try: the verdict on its
|
|
28
|
+
* prototype, the copy of every own string key, and the copies `nested` takes of the parts it holds,
|
|
29
|
+
* each judged plain once by `decidePlain`. Every later check, stored value, and finding reads that
|
|
30
|
+
* copy and never the author's object again, so a getter runs once and a read that threw is never
|
|
31
|
+
* repeated. A value that is not a plain object is the not-an-object fault, and a read that throws,
|
|
32
|
+
* from a getter or a proxy trap, is the unreadable fault, named by the slot it threw in.
|
|
33
|
+
*/
|
|
34
|
+
declare function captureDeclaration<Declared>(declared: Declared, nested: (copy: Record<string, unknown>, read: DeclarationRead) => void, faults: CaptureFaults): Declared & Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* What a finding prints for a declaration whose read threw: the slot it threw in, elided, under the
|
|
37
|
+
* keys that lead to it, or the whole declaration elided. A read that threw is never repeated.
|
|
38
|
+
*/
|
|
39
|
+
declare function elidedRead(slot: string | undefined): {
|
|
40
|
+
keys: readonly string[];
|
|
41
|
+
shown: unknown;
|
|
42
|
+
};
|
|
43
|
+
/** What one unreadable declaration is: an input's config, a plugin's definition, or an options object. */
|
|
44
|
+
type Unreadable = 'config' | 'definition' | 'options';
|
|
45
|
+
/**
|
|
46
|
+
* The fault for a declaration whose read threw, named by `subject` and marked by `findings`. The
|
|
47
|
+
* thrown value's reason ends the sentence and the value itself is the fault's cause.
|
|
48
|
+
*/
|
|
49
|
+
declare function unreadableFault(report: {
|
|
50
|
+
readonly declared: Unreadable;
|
|
51
|
+
readonly findings: readonly Finding[];
|
|
52
|
+
readonly subject: string;
|
|
53
|
+
}, thrown: unknown): DeclarationError;
|
|
54
|
+
/**
|
|
55
|
+
* The unreadable fault of the object a `plugin()` call or a constructor receives as its second
|
|
56
|
+
* argument. Its finding marks the top-level slot whose read threw, or the whole argument when the
|
|
57
|
+
* object itself threw, and prints that part elided.
|
|
58
|
+
*/
|
|
59
|
+
declare function unreadableArgument(declaration: {
|
|
60
|
+
readonly call: string;
|
|
61
|
+
readonly named: unknown;
|
|
62
|
+
readonly subject: string;
|
|
63
|
+
}, declared: 'definition' | 'options'): (thrown: unknown, slot: string | undefined) => DeclarationError;
|
|
64
|
+
export type { CaptureFaults };
|
|
65
|
+
export { captureDeclaration, elidedRead, unreadableArgument, unreadableFault };
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { elided, spelled } from './diagnostic-text.js';
|
|
2
|
+
import { asSentence, DeclarationError, reasonOf } from './errors.js';
|
|
3
|
+
import { copyOwnKeys, decidePlain } from './plain.js';
|
|
4
|
+
import { unreadableDeclaration } from './plugin-rules.js';
|
|
5
|
+
/**
|
|
6
|
+
* One declaring call's read of the object an author passed it, under way. It remembers the slot it
|
|
7
|
+
* is reading, so a read that throws names the slot it threw in, or no slot when the object itself
|
|
8
|
+
* threw.
|
|
9
|
+
*/
|
|
10
|
+
class DeclarationRead {
|
|
11
|
+
/** The top-level key being read, or `undefined` while the object itself is read. */
|
|
12
|
+
slot = undefined;
|
|
13
|
+
/** The copy of every own string key of one object, each read under its own slot. */
|
|
14
|
+
record(declared) {
|
|
15
|
+
const copy = copyOwnKeys(declared, (key) => {
|
|
16
|
+
this.slot = key;
|
|
17
|
+
});
|
|
18
|
+
this.slot = undefined;
|
|
19
|
+
return copy;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Replaces the value one key of a copy holds with `copy` of it, read under that key's slot. An
|
|
23
|
+
* absent key stays absent, so a finding prints the copy with the keys the author wrote.
|
|
24
|
+
*/
|
|
25
|
+
nested(captured, key, copy) {
|
|
26
|
+
if (Object.hasOwn(captured, key)) {
|
|
27
|
+
this.slot = key;
|
|
28
|
+
captured[key] = copy(captured[key]);
|
|
29
|
+
this.slot = undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Authoring's one read of an object a declaring call receives, inside one try: the verdict on its
|
|
35
|
+
* prototype, the copy of every own string key, and the copies `nested` takes of the parts it holds,
|
|
36
|
+
* each judged plain once by `decidePlain`. Every later check, stored value, and finding reads that
|
|
37
|
+
* copy and never the author's object again, so a getter runs once and a read that threw is never
|
|
38
|
+
* repeated. A value that is not a plain object is the not-an-object fault, and a read that throws,
|
|
39
|
+
* from a getter or a proxy trap, is the unreadable fault, named by the slot it threw in.
|
|
40
|
+
*/
|
|
41
|
+
function captureDeclaration(declared, nested, faults) {
|
|
42
|
+
const read = new DeclarationRead();
|
|
43
|
+
let copy = undefined;
|
|
44
|
+
try {
|
|
45
|
+
if (decidePlain(declared)) {
|
|
46
|
+
copy = read.record(declared);
|
|
47
|
+
nested(copy, read);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw faults.unreadable(error, read.slot);
|
|
52
|
+
}
|
|
53
|
+
if (copy === undefined) {
|
|
54
|
+
throw faults.notAnObject();
|
|
55
|
+
}
|
|
56
|
+
// Last resort: no typed path exists.
|
|
57
|
+
// The copy is built key by key, so the compiler types it as a record of unknown values.
|
|
58
|
+
// A spread would keep the declared type, but it reads enumerable keys alone and names no slot.
|
|
59
|
+
// It holds because the copy holds every own string key of the declared plain object.
|
|
60
|
+
// Each holds the value read from it once, or that value's copy of the same kind.
|
|
61
|
+
// No declaration type declares a symbol key, and none reads its prototype.
|
|
62
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
63
|
+
return copy;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What a finding prints for a declaration whose read threw: the slot it threw in, elided, under the
|
|
67
|
+
* keys that lead to it, or the whole declaration elided. A read that threw is never repeated.
|
|
68
|
+
*/
|
|
69
|
+
function elidedRead(slot) {
|
|
70
|
+
return slot === undefined
|
|
71
|
+
? { keys: [], shown: spelled(elided) }
|
|
72
|
+
: { keys: [slot], shown: Object.fromEntries([[slot, spelled(elided)]]) };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The fault for a declaration whose read threw, named by `subject` and marked by `findings`. The
|
|
76
|
+
* thrown value's reason ends the sentence and the value itself is the fault's cause.
|
|
77
|
+
*/
|
|
78
|
+
function unreadableFault(report, thrown) {
|
|
79
|
+
const { declared, findings, subject } = report;
|
|
80
|
+
return new DeclarationError(unreadableDeclaration, {
|
|
81
|
+
correction: `Declare the ${declared} as a plain object literal whose properties read without throwing.`,
|
|
82
|
+
findings,
|
|
83
|
+
sentence: `${subject} ${declared} could not be read: ${asSentence(reasonOf(thrown))}`,
|
|
84
|
+
}, { cause: thrown });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The unreadable fault of the object a `plugin()` call or a constructor receives as its second
|
|
88
|
+
* argument. Its finding marks the top-level slot whose read threw, or the whole argument when the
|
|
89
|
+
* object itself threw, and prints that part elided.
|
|
90
|
+
*/
|
|
91
|
+
function unreadableArgument(declaration, declared) {
|
|
92
|
+
const { call, named, subject } = declaration;
|
|
93
|
+
return (thrown, slot) => {
|
|
94
|
+
const { keys, shown } = elidedRead(slot);
|
|
95
|
+
const finding = { arguments: [named, shown], call, mark: ['1', ...keys].join('.') };
|
|
96
|
+
return unreadableFault({ declared, findings: [finding], subject }, thrown);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export { captureDeclaration, elidedRead, unreadableArgument, unreadableFault };
|
package/dist/chain.d.ts
CHANGED
|
@@ -40,13 +40,19 @@ interface Invocation {
|
|
|
40
40
|
/** The action's own channel, built from the routed Command's declaration when it dispatches. */
|
|
41
41
|
channel: (binding: ResultBinding) => ActionChannel;
|
|
42
42
|
defaults: DefaultValues;
|
|
43
|
-
facts: {
|
|
44
|
-
description: string | undefined;
|
|
45
|
-
version: string;
|
|
46
|
-
};
|
|
47
43
|
graph: BuiltGraph;
|
|
48
44
|
host: Host;
|
|
49
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The graph `inspect()` returns, built at most once for the run. A configuration source reads
|
|
47
|
+
* its requests from it, the chain reads it after the source, and an `onFailure` hook reads it too.
|
|
48
|
+
*/
|
|
49
|
+
inspected: () => CommandGraph;
|
|
50
|
+
/**
|
|
51
|
+
* Offers one throw from the application's work to the translators, and answers with the failure
|
|
52
|
+
* that replaces it, or `undefined` when none does. The chain offers a throw where it leaves the
|
|
53
|
+
* chain, and a configuration source's throw is offered where the resolver's call settles.
|
|
54
|
+
*/
|
|
55
|
+
offer: (thrown: unknown) => LoomError | undefined;
|
|
50
56
|
/**
|
|
51
57
|
* The invocation's own channel. A middleware reads it as the neutral `Out`, and the action
|
|
52
58
|
* receives the channel the results lane builds for the Command that was routed.
|
|
@@ -57,7 +63,10 @@ interface Invocation {
|
|
|
57
63
|
plugins: readonly BuiltPlugin[];
|
|
58
64
|
/** A fault reported after the primary outcome, which turns a would-be 0 into 1. */
|
|
59
65
|
report: (fault: LoomError) => void;
|
|
60
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* The path routing walked, published as each name routes, which output names in its own line
|
|
68
|
+
* and a failure view reads. An unknown Command leaves the partial path published.
|
|
69
|
+
*/
|
|
61
70
|
route: (path: readonly string[]) => void;
|
|
62
71
|
signal: AbortSignal;
|
|
63
72
|
}
|
package/dist/chain.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { prepareDispatch, routeInvocation } from './command.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { foreignFailure, InternalError, routedSubject } from './errors.js';
|
|
3
|
+
import { nodeAt } from './inspect.js';
|
|
4
4
|
import { isSupplied } from './options.js';
|
|
5
5
|
import { loadDefault, pluginSentence, pluginSpellings, pluginValues } from './plugin.js';
|
|
6
|
+
import { nextMisuse, viewSelection, viewSelectionCorrection } from './rules.js';
|
|
6
7
|
/**
|
|
7
8
|
* The view one run selects, which is one value whichever middleware wrote it. The assignment is
|
|
8
9
|
* kept as it arrived, because a JavaScript caller reaches the setter with any value and the check
|
|
@@ -53,13 +54,13 @@ class ViewSelection {
|
|
|
53
54
|
const plugin = pluginSentence(assigned.identity);
|
|
54
55
|
const { name } = assigned;
|
|
55
56
|
if (!this.#result) {
|
|
56
|
-
throw
|
|
57
|
+
throw selectionFault(`${plugin} selected view "${String(name)}" on ${routedSubject(path)}, which declares no result.`);
|
|
57
58
|
}
|
|
58
59
|
if (typeof name !== 'string') {
|
|
59
|
-
throw
|
|
60
|
+
throw selectionFault(`${plugin} selected a view that is not a string on ${routedSubject(path)}.`);
|
|
60
61
|
}
|
|
61
62
|
if (!this.#result.views.has(name)) {
|
|
62
|
-
throw
|
|
63
|
+
throw selectionFault(`${plugin} selected view "${name}", which ${routedSubject(path)} does not name.`);
|
|
63
64
|
}
|
|
64
65
|
return name;
|
|
65
66
|
}
|
|
@@ -118,9 +119,21 @@ async function quiet(pending) {
|
|
|
118
119
|
function reported(chain, outcome) {
|
|
119
120
|
return outcome === 'taken-over' && chain.cancelled() ? 'cancelled' : outcome;
|
|
120
121
|
}
|
|
122
|
+
/** The defect a view selection the routed Command cannot render reports. */
|
|
123
|
+
function selectionFault(sentence) {
|
|
124
|
+
return new InternalError(viewSelection, {
|
|
125
|
+
cause: undefined,
|
|
126
|
+
correction: viewSelectionCorrection,
|
|
127
|
+
sentence,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
121
130
|
/** A `next()` call that is no longer live: it dispatches nothing and rejects. */
|
|
122
131
|
function misuse(turn) {
|
|
123
|
-
const fault = new InternalError(
|
|
132
|
+
const fault = new InternalError(nextMisuse, {
|
|
133
|
+
cause: undefined,
|
|
134
|
+
correction: 'Call next() once, and await it before the middleware returns.',
|
|
135
|
+
sentence: `${pluginSentence(turn.entry.identity)} called next() ${turn.state.returned ? 'after its middleware returned' : 'twice'}.`,
|
|
136
|
+
});
|
|
124
137
|
turn.chain.report(fault);
|
|
125
138
|
const rejected = Promise.reject(fault);
|
|
126
139
|
void rejected.catch(() => undefined);
|
|
@@ -183,7 +196,7 @@ async function settle(turn, thrown) {
|
|
|
183
196
|
* again when the middleware let it escape. It keeps the one report it already has.
|
|
184
197
|
*/
|
|
185
198
|
if (!chain.announced(thrown.value)) {
|
|
186
|
-
chain.report(
|
|
199
|
+
chain.report(foreignFailure(thrown.value));
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
if (state.calls === 0) {
|
|
@@ -217,10 +230,16 @@ async function runEntry(entry, index, chain) {
|
|
|
217
230
|
state.returned = true;
|
|
218
231
|
return settle(turn, thrown);
|
|
219
232
|
}
|
|
220
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* The whole chain, answering with the value it raised when a middleware caught that value. The
|
|
235
|
+
* value is kept as it was thrown, so it is offered to the translators once, where it leaves.
|
|
236
|
+
*/
|
|
221
237
|
async function runChain(invocation, routed, prepared) {
|
|
222
238
|
const entries = activatedEntries(invocation.plugins, prepared.globals);
|
|
223
|
-
const run = {
|
|
239
|
+
const run = {
|
|
240
|
+
invoked: false,
|
|
241
|
+
raised: undefined,
|
|
242
|
+
};
|
|
224
243
|
const selection = new ViewSelection(prepared.result);
|
|
225
244
|
/**
|
|
226
245
|
* The dispatch boundary: the point the chain reaches when its last middleware continues. Core
|
|
@@ -271,7 +290,7 @@ async function runChain(invocation, routed, prepared) {
|
|
|
271
290
|
}),
|
|
272
291
|
invoked: () => run.invoked,
|
|
273
292
|
record: (error) => {
|
|
274
|
-
run.raised ??=
|
|
293
|
+
run.raised ??= { value: error };
|
|
275
294
|
},
|
|
276
295
|
report: (fault) => {
|
|
277
296
|
announced.add(fault);
|
|
@@ -300,20 +319,21 @@ async function runChain(invocation, routed, prepared) {
|
|
|
300
319
|
* raised and nothing later in the chain runs.
|
|
301
320
|
*/
|
|
302
321
|
async function runInvocation(invocation) {
|
|
303
|
-
const routed = routeInvocation(invocation.graph, invocation.host.argv);
|
|
304
|
-
invocation.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
const routed = routeInvocation(invocation.graph, invocation.host.argv, invocation.route);
|
|
323
|
+
const prepared = await prepareDispatch(invocation.graph, routed, invocation);
|
|
324
|
+
let raised = undefined;
|
|
325
|
+
try {
|
|
326
|
+
raised = await runChain(invocation, routed, prepared);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
// A throw leaves the chain here, so a middleware that awaited next() saw it raw.
|
|
330
|
+
throw invocation.offer(error) ?? error;
|
|
331
|
+
}
|
|
312
332
|
if (raised) {
|
|
313
333
|
// The chain resolved because a middleware caught the rejection.
|
|
314
334
|
// The failure it caught still decides the exit code.
|
|
315
335
|
// That is the rule an action's caught output rejection already follows.
|
|
316
|
-
throw raised;
|
|
336
|
+
throw invocation.offer(raised.value) ?? raised.value;
|
|
317
337
|
}
|
|
318
338
|
}
|
|
319
339
|
export { runInvocation };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** An application name, a Command name, or an alias outside the portable name rule. */
|
|
2
|
+
declare const portableName: import("./diagnostic-text.js").DiagnosticRule;
|
|
3
|
+
/** An argument name outside the declared-name rule. */
|
|
4
|
+
declare const declaredName: import("./diagnostic-text.js").DiagnosticRule;
|
|
5
|
+
/** Globals declared on a named Command's options. */
|
|
6
|
+
declare const commandGlobals: import("./diagnostic-text.js").DiagnosticRule;
|
|
7
|
+
/** A declaration call a Command's own `action()` already closed. */
|
|
8
|
+
declare const declaredAfterAction: import("./diagnostic-text.js").DiagnosticRule;
|
|
9
|
+
/** A second `action()` on one Command. */
|
|
10
|
+
declare const multipleActions: import("./diagnostic-text.js").DiagnosticRule;
|
|
11
|
+
/** One Command that declares arguments and attaches children. */
|
|
12
|
+
declare const argumentsBesideChildren: import("./diagnostic-text.js").DiagnosticRule;
|
|
13
|
+
/** Two arguments with one name on one Command. */
|
|
14
|
+
declare const argumentDeclaredTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
15
|
+
/** A variadic argument that another argument follows. */
|
|
16
|
+
declare const variadicArgumentLast: import("./diagnostic-text.js").DiagnosticRule;
|
|
17
|
+
/** An optional argument that another argument follows. */
|
|
18
|
+
declare const optionalArgumentLast: import("./diagnostic-text.js").DiagnosticRule;
|
|
19
|
+
/** An `alias()` call that names no alias. */
|
|
20
|
+
declare const aliasWithoutNames: import("./diagnostic-text.js").DiagnosticRule;
|
|
21
|
+
/** An alias that repeats its own Command's name or another of its aliases. */
|
|
22
|
+
declare const repeatedAlias: import("./diagnostic-text.js").DiagnosticRule;
|
|
23
|
+
/** A child whose name or alias repeats a sibling's name or alias. */
|
|
24
|
+
declare const siblingNameTaken: import("./diagnostic-text.js").DiagnosticRule;
|
|
25
|
+
/** A child that would sit more than two levels below the root. */
|
|
26
|
+
declare const nestingDepth: import("./diagnostic-text.js").DiagnosticRule;
|
|
27
|
+
/** A value passed to `command()` that is not a Command. */
|
|
28
|
+
declare const notACommand: import("./diagnostic-text.js").DiagnosticRule;
|
|
29
|
+
/** One Command value attached at two places. */
|
|
30
|
+
declare const commandAttachedTwice: import("./diagnostic-text.js").DiagnosticRule;
|
|
31
|
+
/** A Command with neither an action nor children. */
|
|
32
|
+
declare const commandWithoutAction: import("./diagnostic-text.js").DiagnosticRule;
|
|
33
|
+
/** A group, a Command with no action, that declares a local option. */
|
|
34
|
+
declare const groupOption: import("./diagnostic-text.js").DiagnosticRule;
|
|
35
|
+
/** A second `result()` or `rows()` on one Command. */
|
|
36
|
+
declare const multipleResults: import("./diagnostic-text.js").DiagnosticRule;
|
|
37
|
+
/** A `views()` call on a Command that declares no result. */
|
|
38
|
+
declare const viewsWithoutResult: import("./diagnostic-text.js").DiagnosticRule;
|
|
39
|
+
/** A views entry that is not exactly one view. */
|
|
40
|
+
declare const viewShape: import("./diagnostic-text.js").DiagnosticRule;
|
|
41
|
+
/** A row view under a result declared with `result()`. */
|
|
42
|
+
declare const rowViewOnValue: import("./diagnostic-text.js").DiagnosticRule;
|
|
43
|
+
/** A view name outside the declared-name rule, or one that is integer-like. */
|
|
44
|
+
declare const viewName: import("./diagnostic-text.js").DiagnosticRule;
|
|
45
|
+
/** A result on a Command with no action. */
|
|
46
|
+
declare const resultWithoutAction: import("./diagnostic-text.js").DiagnosticRule;
|
|
47
|
+
/** A result whose merged views record holds no view. */
|
|
48
|
+
declare const resultWithoutViews: import("./diagnostic-text.js").DiagnosticRule;
|
|
49
|
+
/** A default view that the merged views record does not hold. */
|
|
50
|
+
declare const unknownDefaultView: import("./diagnostic-text.js").DiagnosticRule;
|
|
51
|
+
/** A description, a deprecated message, or an Application version that is not one line of prose. */
|
|
52
|
+
declare const notOneLine: import("./diagnostic-text.js").DiagnosticRule;
|
|
53
|
+
/** `hidden` or `deprecated` on the Application or on an argument. */
|
|
54
|
+
declare const misplacedListingFact: import("./diagnostic-text.js").DiagnosticRule;
|
|
55
|
+
export { aliasWithoutNames, argumentDeclaredTwice, argumentsBesideChildren, commandAttachedTwice, commandGlobals, commandWithoutAction, declaredAfterAction, declaredName, groupOption, misplacedListingFact, multipleActions, multipleResults, nestingDepth, notOneLine, notACommand, optionalArgumentLast, portableName, repeatedAlias, resultWithoutAction, resultWithoutViews, rowViewOnValue, siblingNameTaken, unknownDefaultView, variadicArgumentLast, viewName, viewShape, viewsWithoutResult, };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { registerRule } from './diagnostic-text.js';
|
|
2
|
+
/*
|
|
3
|
+
* Core's rules for the declaration faults of Commands and their names, aliases, nesting, children,
|
|
4
|
+
* actions, results, and core facts. Each is declared once here and shared by every site that raises
|
|
5
|
+
* it, as a plugin's rules are.
|
|
6
|
+
*/
|
|
7
|
+
/** An application name, a Command name, or an alias outside the portable name rule. */
|
|
8
|
+
const portableName = registerRule('@loomcli/core/portable-name', {
|
|
9
|
+
explanation: 'An operator types the application name, each Command name, and each alias as a command at a shell prompt. A character outside the POSIX portable filename set needs quoting there, a leading "-" reads as an option, and a leading "." names a file a shell hides.',
|
|
10
|
+
headline: 'Name not portable',
|
|
11
|
+
});
|
|
12
|
+
/** An argument name outside the declared-name rule. */
|
|
13
|
+
const declaredName = registerRule('@loomcli/core/declared-name', {
|
|
14
|
+
explanation: 'An action reads each argument and option under its name, and help and diagnostics print it. A leading "-" reads as an option, and whitespace or "=" splits the name where the parser reads it.',
|
|
15
|
+
headline: 'Invalid declared name',
|
|
16
|
+
});
|
|
17
|
+
/** Globals declared on a named Command's options. */
|
|
18
|
+
const commandGlobals = registerRule('@loomcli/core/command-globals', {
|
|
19
|
+
explanation: "Global options belong to the Application, which declares them with globalOption() and hands their values to every action. A named Command reads their types from the Application's registered environment.",
|
|
20
|
+
headline: 'Globals on a named Command',
|
|
21
|
+
});
|
|
22
|
+
/** A declaration call a Command's own `action()` already closed. */
|
|
23
|
+
const declaredAfterAction = registerRule('@loomcli/core/declared-after-action', {
|
|
24
|
+
explanation: "action() finishes a Command's declaration: the handler's types read every argument, option, alias, result, and child declared before it, so a later call would declare what the handler never sees.",
|
|
25
|
+
headline: 'Declared after the action',
|
|
26
|
+
});
|
|
27
|
+
/** A second `action()` on one Command. */
|
|
28
|
+
const multipleActions = registerRule('@loomcli/core/multiple-actions', {
|
|
29
|
+
explanation: 'Routing runs one handler for the Command it selects, so a second action would leave one of the two unreachable.',
|
|
30
|
+
headline: 'Second action',
|
|
31
|
+
});
|
|
32
|
+
/** One Command that declares arguments and attaches children. */
|
|
33
|
+
const argumentsBesideChildren = registerRule('@loomcli/core/arguments-beside-children', {
|
|
34
|
+
explanation: "A Command's first bare token either names a child or fills an argument, so a Command that holds both cannot tell which one an operator meant.",
|
|
35
|
+
headline: 'Arguments beside children',
|
|
36
|
+
});
|
|
37
|
+
/** Two arguments with one name on one Command. */
|
|
38
|
+
const argumentDeclaredTwice = registerRule('@loomcli/core/argument-declared-twice', {
|
|
39
|
+
explanation: 'An action reads each argument under its name, so two arguments with one name leave one of them unreadable.',
|
|
40
|
+
headline: 'Argument declared twice',
|
|
41
|
+
});
|
|
42
|
+
/** A variadic argument that another argument follows. */
|
|
43
|
+
const variadicArgumentLast = registerRule('@loomcli/core/variadic-argument-last', {
|
|
44
|
+
explanation: 'A variadic argument takes every positional token that remains, so an argument after it would never receive one.',
|
|
45
|
+
headline: 'Variadic argument not last',
|
|
46
|
+
});
|
|
47
|
+
/** An optional argument that another argument follows. */
|
|
48
|
+
const optionalArgumentLast = registerRule('@loomcli/core/optional-argument-last', {
|
|
49
|
+
explanation: 'Positional tokens fill the arguments in order, and an operator leaves out an optional argument from the end of the line. An argument after an optional one would take the token the optional one was meant to receive.',
|
|
50
|
+
headline: 'Optional argument not last',
|
|
51
|
+
});
|
|
52
|
+
/** An `alias()` call that names no alias. */
|
|
53
|
+
const aliasWithoutNames = registerRule('@loomcli/core/alias-without-names', {
|
|
54
|
+
explanation: 'alias() adds each name it receives to the names that route to its Command, so a call with none adds nothing.',
|
|
55
|
+
headline: 'Alias with no names',
|
|
56
|
+
});
|
|
57
|
+
/** An alias that repeats its own Command's name or another of its aliases. */
|
|
58
|
+
const repeatedAlias = registerRule('@loomcli/core/repeated-alias', {
|
|
59
|
+
explanation: 'A Command answers to its name and to each of its aliases, so an alias that repeats one of them routes nothing new.',
|
|
60
|
+
headline: 'Alias repeats a name',
|
|
61
|
+
});
|
|
62
|
+
/** A child whose name or alias repeats a sibling's name or alias. */
|
|
63
|
+
const siblingNameTaken = registerRule('@loomcli/core/sibling-name-taken', {
|
|
64
|
+
explanation: 'Every canonical name and alias under one parent routes one token to one child, so a name that two siblings share cannot route.',
|
|
65
|
+
headline: 'Name taken by a sibling',
|
|
66
|
+
});
|
|
67
|
+
/** A child that would sit more than two levels below the root. */
|
|
68
|
+
const nestingDepth = registerRule('@loomcli/core/nesting-depth', {
|
|
69
|
+
explanation: 'Each level of nesting adds a token an operator types before a Command runs. Loom keeps every Command at most two levels below the root, so every invocation stays short enough to remember.',
|
|
70
|
+
headline: 'Commands nested too deep',
|
|
71
|
+
});
|
|
72
|
+
/** A value passed to `command()` that is not a Command. */
|
|
73
|
+
const notACommand = registerRule('@loomcli/core/not-a-command', {
|
|
74
|
+
explanation: 'A Command value carries the declaration that routing, parsing, and help read. Any other value carries none.',
|
|
75
|
+
headline: 'Not a Command',
|
|
76
|
+
});
|
|
77
|
+
/** One Command value attached at two places. */
|
|
78
|
+
const commandAttachedTwice = registerRule('@loomcli/core/command-attached-twice', {
|
|
79
|
+
explanation: 'A Command value sits at one place in the tree, where its path, its help page, and its parent read it, so one value attached at two places would have two paths.',
|
|
80
|
+
headline: 'Command attached twice',
|
|
81
|
+
});
|
|
82
|
+
/** A Command with neither an action nor children. */
|
|
83
|
+
const commandWithoutAction = registerRule('@loomcli/core/command-without-action', {
|
|
84
|
+
explanation: "Routing ends at a Command that runs its action, or passes on to one of a group's children. A Command with neither leaves an invocation that reaches it nothing to run.",
|
|
85
|
+
headline: 'Nothing to run',
|
|
86
|
+
});
|
|
87
|
+
/** A group, a Command with no action, that declares a local option. */
|
|
88
|
+
const groupOption = registerRule('@loomcli/core/group-option', {
|
|
89
|
+
explanation: 'A Command with no action is a group, which passes an invocation on to one of its children. A local option is never inherited, so no action reads an option a group declares.',
|
|
90
|
+
headline: 'Option on a group',
|
|
91
|
+
});
|
|
92
|
+
/** A second `result()` or `rows()` on one Command. */
|
|
93
|
+
const multipleResults = registerRule('@loomcli/core/multiple-results', {
|
|
94
|
+
explanation: "A Command's action emits one result through out.results(), declared once, as a value with result() or as rows with rows(), so its consumers read one declaration.",
|
|
95
|
+
headline: 'Second result',
|
|
96
|
+
});
|
|
97
|
+
/** A `views()` call on a Command that declares no result. */
|
|
98
|
+
const viewsWithoutResult = registerRule('@loomcli/core/views-without-result', {
|
|
99
|
+
explanation: 'views() reshapes the views a declared result renders through. A Command that declares no result emits nothing for a view to render.',
|
|
100
|
+
headline: 'Views with no result',
|
|
101
|
+
});
|
|
102
|
+
/** A views entry that is not exactly one view. */
|
|
103
|
+
const viewShape = registerRule('@loomcli/core/view-shape', {
|
|
104
|
+
explanation: 'A views entry is a view with render, which receives the whole result, or a row view with row, which receives one row at a time. Core reads which function it holds to decide how to feed it.',
|
|
105
|
+
headline: 'Not one view',
|
|
106
|
+
});
|
|
107
|
+
/** A row view under a result declared with `result()`. */
|
|
108
|
+
const rowViewOnValue = registerRule('@loomcli/core/row-view-on-value', {
|
|
109
|
+
explanation: 'A row view renders one row at a time, which only a result declared with rows() emits. A value result arrives whole, so a view with render reads it.',
|
|
110
|
+
headline: 'Row view on a value result',
|
|
111
|
+
});
|
|
112
|
+
/** A view name outside the declared-name rule, or one that is integer-like. */
|
|
113
|
+
const viewName = registerRule('@loomcli/core/view-name', {
|
|
114
|
+
explanation: 'An operator and a middleware select a view by its name, so it is a bare token. It is not integer-like either, because an object moves such a key ahead of every other and the views lose the order they were declared in.',
|
|
115
|
+
headline: 'Invalid view name',
|
|
116
|
+
});
|
|
117
|
+
/** A result on a Command with no action. */
|
|
118
|
+
const resultWithoutAction = registerRule('@loomcli/core/result-without-action', {
|
|
119
|
+
explanation: 'A declared result is a promise the action keeps by emitting through out.results(). A Command with no action has nothing to keep it.',
|
|
120
|
+
headline: 'Result with no action',
|
|
121
|
+
});
|
|
122
|
+
/** A result whose merged views record holds no view. */
|
|
123
|
+
const resultWithoutViews = registerRule('@loomcli/core/result-without-views', {
|
|
124
|
+
explanation: 'A result prints through one of its views: the default one, or the one an operator or a middleware selects. A result with none has no way to print.',
|
|
125
|
+
headline: 'Result with no views',
|
|
126
|
+
});
|
|
127
|
+
/** A default view that the merged views record does not hold. */
|
|
128
|
+
const unknownDefaultView = registerRule('@loomcli/core/unknown-default-view', {
|
|
129
|
+
explanation: 'The default view renders a result when nothing selects another, so it names one of the views the result declares.',
|
|
130
|
+
headline: 'Default view not declared',
|
|
131
|
+
});
|
|
132
|
+
/** A description, a deprecated message, or an Application version that is not one line of prose. */
|
|
133
|
+
const notOneLine = registerRule('@loomcli/core/not-one-line', {
|
|
134
|
+
explanation: 'Help, --version, the manifest, and every other listing print a description, a deprecated message, and a version on one line beside what each names, so each holds prose and no line break. An operator or an agent follows a deprecated message to the replacement, so a bare true names none, and a version is a string as the package manifest spells it.',
|
|
135
|
+
headline: 'Text not one line',
|
|
136
|
+
});
|
|
137
|
+
/** `hidden` or `deprecated` on the Application or on an argument. */
|
|
138
|
+
const misplacedListingFact = registerRule('@loomcli/core/misplaced-listing-fact', {
|
|
139
|
+
explanation: 'hidden and deprecated keep a named Command or an option off a listing, or mark it retired. The root is the entry point of every page, and an argument cannot leave the grammar it sits in, so neither carries them.',
|
|
140
|
+
headline: 'Listing fact out of place',
|
|
141
|
+
});
|
|
142
|
+
export { aliasWithoutNames, argumentDeclaredTwice, argumentsBesideChildren, commandAttachedTwice, commandGlobals, commandWithoutAction, declaredAfterAction, declaredName, groupOption, misplacedListingFact, multipleActions, multipleResults, nestingDepth, notOneLine, notACommand, optionalArgumentLast, portableName, repeatedAlias, resultWithoutAction, resultWithoutViews, rowViewOnValue, siblingNameTaken, unknownDefaultView, variadicArgumentLast, viewName, viewShape, viewsWithoutResult, };
|