@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/plugin.d.ts
CHANGED
|
@@ -9,18 +9,21 @@ import type { ProcessSignal } from './signals.js';
|
|
|
9
9
|
import type { Palette } from './style-state.js';
|
|
10
10
|
import type { ContextualStyle, ThemeConstraint, ThemeMapping } from './style.js';
|
|
11
11
|
import type { Translation, TranslationContributor } from './translators.js';
|
|
12
|
-
import type { CommandAttachHook, Host, OptionValue, Out
|
|
12
|
+
import type { CommandAttachHook, GlobalOptionConfig, Host, OptionValue, Out } from './types.js';
|
|
13
13
|
import type { OptionInput } from './validation.js';
|
|
14
14
|
import type { ViewContribution, ViewSubject } from './view.js';
|
|
15
15
|
/**
|
|
16
|
-
* The declaration record a plugin contributes its options under
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The declaration record a plugin contributes its global options under, keyed by option name. Each
|
|
17
|
+
* entry takes the configuration `globalOption()` takes, so the config type publishes no presence
|
|
18
|
+
* rule, and `plugin()` repeats the rule for a JavaScript author.
|
|
19
|
+
*/
|
|
20
|
+
type PluginOptions = Readonly<Record<string, GlobalOptionConfig>>;
|
|
21
|
+
/**
|
|
22
|
+
* The values one plugin's options take, read through the same `OptionValue` an action's options
|
|
23
|
+
* are, so a validated option reads as its validator's output.
|
|
19
24
|
*/
|
|
20
|
-
type PluginOptions = Readonly<Record<string, PluginOptionConfig>>;
|
|
21
|
-
/** The values one plugin's own options take, read through the same rules an action's options are. */
|
|
22
25
|
type PluginOptionValues<Options extends PluginOptions> = {
|
|
23
|
-
readonly [Name in keyof Options]: OptionValue<Options[Name]>;
|
|
26
|
+
readonly [Name in keyof Options & string]: OptionValue<Options[Name]>;
|
|
24
27
|
};
|
|
25
28
|
/**
|
|
26
29
|
* The spelling that supplied each of one plugin's own options given as a token, such as `-h`,
|
|
@@ -48,6 +51,16 @@ declare class PluginDeclaration<Options extends PluginOptions, Theme extends The
|
|
|
48
51
|
type Plugin<Options extends PluginOptions = PluginOptions, Theme extends ThemeMapping = {}> = Pick<PluginDeclaration<Options, Theme>, typeof pluginOptions | typeof pluginTheme>;
|
|
49
52
|
/** The declared options of a plugin, or of the factory that returns one. */
|
|
50
53
|
type OptionsOf<Contributor> = Contributor extends Plugin<infer Options> ? Options : Contributor extends (...args: never[]) => Plugin<infer Options> ? Options : PluginOptions;
|
|
54
|
+
/**
|
|
55
|
+
* The option values an installed plugin tuple contributes to every action's options, each plugin's
|
|
56
|
+
* read through `PluginOptionValues`. The Application computes it once from the constructor's
|
|
57
|
+
* `plugins`. A plugin typed as the wide `Plugin` states no option names, and a list widened to
|
|
58
|
+
* `Plugin[]` states no plugins, so neither names an option.
|
|
59
|
+
*/
|
|
60
|
+
type InstalledOptionValues<Plugins extends readonly Plugin[]> = Plugins extends readonly [
|
|
61
|
+
infer First extends Plugin,
|
|
62
|
+
...infer Rest extends readonly Plugin[]
|
|
63
|
+
] ? (string extends keyof OptionsOf<First> ? {} : PluginOptionValues<OptionsOf<First>>) & InstalledOptionValues<Rest> : {};
|
|
51
64
|
/** A middleware reads its own plugin's options and either takes over or continues the chain. */
|
|
52
65
|
type Middleware<Contributor extends Plugin | ((...args: never[]) => Plugin)> = (context: MiddlewareContext<OptionsOf<Contributor>>) => Promise<void> | void;
|
|
53
66
|
/**
|
|
@@ -66,11 +79,12 @@ interface SourceContext<Options extends PluginOptions = PluginOptions> {
|
|
|
66
79
|
readonly style: ContextualStyle;
|
|
67
80
|
}
|
|
68
81
|
/**
|
|
69
|
-
* One answer: a value of the option's raw type, a string, a Boolean,
|
|
70
|
-
* multiple option, and the one-line label core
|
|
82
|
+
* One answer: a value of the option's raw type, a string, a Boolean, a whole number of 0 or more
|
|
83
|
+
* for a counted option, or a list of strings for a multiple option, and the one-line label core
|
|
84
|
+
* prints in a diagnostic about the value.
|
|
71
85
|
*/
|
|
72
86
|
interface SourceAnswer {
|
|
73
|
-
readonly value: string | boolean | readonly string[];
|
|
87
|
+
readonly value: string | boolean | number | readonly string[];
|
|
74
88
|
readonly label: string;
|
|
75
89
|
}
|
|
76
90
|
/**
|
|
@@ -182,17 +196,8 @@ interface BuiltPlugin {
|
|
|
182
196
|
}
|
|
183
197
|
/** The signals the one slot owner claimed, or none when no installed plugin claims the slot. */
|
|
184
198
|
declare function ownedSignals(plugins: readonly BuiltPlugin[]): readonly ProcessSignal[];
|
|
185
|
-
/** The value shape a plugin option takes, which is what `OptionValue` gives its declaration. */
|
|
186
|
-
type PluginValues = Record<string, string | string[] | boolean | undefined>;
|
|
187
|
-
/**
|
|
188
|
-
* One plugin's own option values for one run: what argv or an input source supplied, or the
|
|
189
|
-
* declared default, filled without validation. A collected value and an array default are copied,
|
|
190
|
-
* so a plugin that writes to what it received changes neither the declaration nor the next run.
|
|
191
|
-
* Entries become own keys even for a name such as `__proto__`, which assignment would not.
|
|
192
|
-
*/
|
|
193
|
-
declare function pluginValues(inputs: readonly OptionInput[], values: OptionValues): PluginValues;
|
|
194
199
|
/** One plugin's own spellings for one run, frozen, so no plugin writes what another reads. */
|
|
195
200
|
declare function pluginSpellings(inputs: readonly OptionInput[], values: OptionValues): Readonly<Record<string, string>>;
|
|
196
201
|
type ThemeOf<Contributor> = [Contributor] extends [never] ? {} : Contributor extends Plugin<PluginOptions, infer Theme> ? Theme : {};
|
|
197
|
-
export type { ThemeOf, BuiltPlugin, BuiltSource,
|
|
198
|
-
export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews,
|
|
202
|
+
export type { ThemeOf, BuiltPlugin, BuiltSource, InstalledOptionValues, SourceAnswer, SourceContext, SourceResolver, Middleware, OptionsOf, Plugin, PluginDefinition, PluginOptions, PluginOptionSpellings, PluginOptionValues, };
|
|
203
|
+
export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews, };
|
package/dist/plugin.js
CHANGED
|
@@ -5,13 +5,13 @@ import { attach, commandCode, commandNode } from './command.js';
|
|
|
5
5
|
import { elided, quoteString, spelled } from './diagnostic-text.js';
|
|
6
6
|
import { DeclarationError, InternalError, quoted, reasonOf } from './errors.js';
|
|
7
7
|
import { appliesTo, buildExtensions, isDescriptor, registerDescriptor } from './extension.js';
|
|
8
|
-
import { checkDeprecated, checkDescription, checkHidden,
|
|
9
|
-
import { boundOptions, pluginSites } from './globals.js';
|
|
8
|
+
import { checkDeprecated, checkDescription, checkHidden, partFinding, pluginOptionSite, slotSite, } from './facts.js';
|
|
9
|
+
import { boundOptions, checkNoPresenceRule, pluginSites } from './globals.js';
|
|
10
10
|
import { checkIdentity } from './identity.js';
|
|
11
11
|
import { coreViews } from './lanes.js';
|
|
12
|
-
import {
|
|
12
|
+
import { compileOptions } from './options.js';
|
|
13
13
|
import { copyOwnKeys, decidePlain, declaring, isPlainObject, shallowList, shallowRecord, } from './plain.js';
|
|
14
|
-
import { foreignValue, middlewareActivation, notAFunction, notAList, notAnObject, pluginInstalledTwice,
|
|
14
|
+
import { foreignValue, middlewareActivation, notAFunction, notAList, notAnObject, pluginInstalledTwice, signalClaimedTwice, slotTaken, sourceBinding, sourceBoundOwnOption, unknownSignal, } from './plugin-rules.js';
|
|
15
15
|
import { pluginLoaderFailed } from './rules.js';
|
|
16
16
|
import { isProcessSignal } from './signals.js';
|
|
17
17
|
import { buildTheme } from './theme.js';
|
|
@@ -208,10 +208,8 @@ function installPlugins(application, plugins) {
|
|
|
208
208
|
}
|
|
209
209
|
return { descriptors, plugins: installed };
|
|
210
210
|
}
|
|
211
|
-
/** The keys a plugin option may not declare, in the order its diagnostic names them. */
|
|
212
|
-
const forbidden = ['validate', 'validateOmitted', 'required'];
|
|
213
211
|
/**
|
|
214
|
-
* The rules a plugin option answers before every rule an ordinary declaration carries, read from
|
|
212
|
+
* The rules a plugin's option answers before every rule an ordinary declaration carries, read from
|
|
215
213
|
* the one copy of its config that `captureConfig` takes, which it answers with for every later read.
|
|
216
214
|
* `siteOf` places the option with the value its entry prints as: the config as declared for a value
|
|
217
215
|
* that is not one, elided for a config whose read threw, and the copy for every later rule.
|
|
@@ -236,14 +234,8 @@ function checkPluginOption(siteOf, declared) {
|
|
|
236
234
|
},
|
|
237
235
|
});
|
|
238
236
|
const site = siteOf(config);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
throw factFault(pluginOptionRule, site, {
|
|
242
|
-
correction: 'Remove it; a plugin option carries no validator or presence rule, and the middleware interprets the value.',
|
|
243
|
-
fact: rejected,
|
|
244
|
-
sentence: `${sentence} declares ${rejected}.`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
237
|
+
// A plugin's options are global options, so they answer the global option's presence rule.
|
|
238
|
+
checkNoPresenceRule(site, config);
|
|
247
239
|
checkDescription(site, config.description);
|
|
248
240
|
checkHidden(site, config.hidden);
|
|
249
241
|
checkDeprecated(site, config.deprecated);
|
|
@@ -293,7 +285,8 @@ function readOptions(identity, declared, build) {
|
|
|
293
285
|
}));
|
|
294
286
|
inputs.push(input);
|
|
295
287
|
}
|
|
296
|
-
// Two options of one plugin meet in the
|
|
288
|
+
// Two options of one plugin meet in the globals table every Command's table holds.
|
|
289
|
+
// They therefore share its rules.
|
|
297
290
|
const siteOf = pluginSites(identity, inputs);
|
|
298
291
|
compileOptions(inputs, { siteOf, subject: `plugin ${quoted(identity)}` });
|
|
299
292
|
claimVariables(boundOptions(inputs, (input) => ({
|
|
@@ -661,34 +654,6 @@ function readPlugin(named, definition) {
|
|
|
661
654
|
function ownedSignals(plugins) {
|
|
662
655
|
return plugins.find((entry) => entry.signals.length > 0)?.signals ?? [];
|
|
663
656
|
}
|
|
664
|
-
/** A collected value, or the declared array default, as this run's own copy. */
|
|
665
|
-
function collectedValue(collected, declared) {
|
|
666
|
-
if (collected) {
|
|
667
|
-
return [...collected];
|
|
668
|
-
}
|
|
669
|
-
return Array.isArray(declared) ? [...declared] : [];
|
|
670
|
-
}
|
|
671
|
-
/** One plugin option's value for one run: what a tier supplied, or the declared default. */
|
|
672
|
-
function pluginValue({ config, name }, values) {
|
|
673
|
-
const declared = config.default;
|
|
674
|
-
if (config.type === 'boolean') {
|
|
675
|
-
return booleanValue(values, name, config);
|
|
676
|
-
}
|
|
677
|
-
if (config.multiple === true) {
|
|
678
|
-
return collectedValue(values.lists.get(name), declared);
|
|
679
|
-
}
|
|
680
|
-
// Build already proved that a string option without a validator declares a string default.
|
|
681
|
-
return values.strings.get(name) ?? (typeof declared === 'string' ? declared : undefined);
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* One plugin's own option values for one run: what argv or an input source supplied, or the
|
|
685
|
-
* declared default, filled without validation. A collected value and an array default are copied,
|
|
686
|
-
* so a plugin that writes to what it received changes neither the declaration nor the next run.
|
|
687
|
-
* Entries become own keys even for a name such as `__proto__`, which assignment would not.
|
|
688
|
-
*/
|
|
689
|
-
function pluginValues(inputs, values) {
|
|
690
|
-
return Object.fromEntries(inputs.map((input) => [input.name, pluginValue(input, values)]));
|
|
691
|
-
}
|
|
692
657
|
/** One plugin's own spellings for one run, frozen, so no plugin writes what another reads. */
|
|
693
658
|
function pluginSpellings(inputs, values) {
|
|
694
659
|
// Entries become own keys even for a name such as `__proto__`, which assignment would not.
|
|
@@ -698,4 +663,4 @@ function pluginSpellings(inputs, values) {
|
|
|
698
663
|
});
|
|
699
664
|
return Object.freeze(Object.fromEntries(supplied));
|
|
700
665
|
}
|
|
701
|
-
export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews,
|
|
666
|
+
export { installPlugins, loadDefault, ownedSignals, plugin, pluginSentence, pluginSpellings, pluginViews, };
|
package/dist/sources.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { OptionValues } from './options.js';
|
|
|
5
5
|
import type { BuiltPlugin } from './plugin.js';
|
|
6
6
|
import type { ContextualStyle } from './style.js';
|
|
7
7
|
import type { Host, Out } from './types.js';
|
|
8
|
-
import type { OptionInput } from './validation.js';
|
|
8
|
+
import type { OptionInput, Provenance, Validation } from './validation.js';
|
|
9
9
|
/**
|
|
10
10
|
* One scope the stage fills: its options in declaration order, and the values argv supplied them.
|
|
11
11
|
* The stage writes each fill into `values`, so the caller hands it the run's own copy.
|
|
@@ -35,23 +35,32 @@ interface SourceStage {
|
|
|
35
35
|
signal: AbortSignal;
|
|
36
36
|
/** The contextual style an action receives, so a source escapes raw data before it warns. */
|
|
37
37
|
style: ContextualStyle;
|
|
38
|
+
/**
|
|
39
|
+
* Validates the listed options alone, from the values the stage has filled so far, with what the
|
|
40
|
+
* environment tier found about them, under the context every validator of the run reads. A
|
|
41
|
+
* configuration source's own options pass through it before the source loads.
|
|
42
|
+
*/
|
|
43
|
+
validate: (inputs: readonly OptionInput[], provenance: Provenance) => Promise<Validation>;
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
46
|
* What the stage found beside the values it filled. `labels` names where each filled option's value
|
|
41
|
-
* came from, by option name, and `rejected` names the variable of each Boolean
|
|
42
|
-
* is outside
|
|
47
|
+
* came from, by option name, and `rejected` names the variable of each Boolean or counted option
|
|
48
|
+
* whose value is outside its grammar. Both are internal to core's failure messages. `fault` is a configuration
|
|
43
49
|
* source's own fault, or the failure its resolver threw, which stops the stage and takes the place
|
|
44
|
-
* of every validation problem.
|
|
50
|
+
* of every validation problem. `validated` is the pass over the source's own options ahead of its
|
|
51
|
+
* call, whose values and problems the run's one validation pass reads.
|
|
45
52
|
*/
|
|
46
|
-
interface SourceOutcome {
|
|
53
|
+
interface SourceOutcome extends Provenance {
|
|
47
54
|
fault: LoomError | undefined;
|
|
48
|
-
|
|
49
|
-
rejected: ReadonlyMap<string, string>;
|
|
55
|
+
validated: Validation | undefined;
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
58
|
* The input-source stage: the environment, then the configuration source, for every option in
|
|
53
|
-
* scope that argv left unfilled. When no option is left to ask, the source never loads.
|
|
54
|
-
*
|
|
59
|
+
* scope that argv left unfilled. When no option is left to ask, the source never loads. The
|
|
60
|
+
* source's own options pass their validators before it loads, and when one is rejected the source
|
|
61
|
+
* is never called and the problem reports with every other validation problem, while each option
|
|
62
|
+
* the source would have been asked to fill reports no missing value. A source fault stops the
|
|
63
|
+
* stage and fills nothing more.
|
|
55
64
|
*/
|
|
56
65
|
declare function fillInputs(stage: SourceStage): Promise<SourceOutcome>;
|
|
57
66
|
export type { SourceOutcome, SourceStage, StageScope };
|
package/dist/sources.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { asSentence, foreignFailure, InternalError, LoomError, quoted, reasonOf, } from './errors.js';
|
|
2
2
|
import { isProseLine } from './facts.js';
|
|
3
|
+
import { frozenValues } from './globals.js';
|
|
3
4
|
import { isSupplied } from './options.js';
|
|
4
5
|
import { isPlainObject } from './plain.js';
|
|
5
|
-
import { loadDefault, pluginSentence
|
|
6
|
+
import { loadDefault, pluginSentence } from './plugin.js';
|
|
6
7
|
import { foreignThrow, foreignThrowCorrection, sourceAnswers, sourceAnswersCorrection, } from './rules.js';
|
|
7
8
|
/** The Boolean grammar a bound variable is read through: the whole value, case-insensitive. */
|
|
8
9
|
const truths = new Map([
|
|
@@ -11,10 +12,13 @@ const truths = new Map([
|
|
|
11
12
|
['false', false],
|
|
12
13
|
['true', true],
|
|
13
14
|
]);
|
|
15
|
+
/** The count grammar a bound variable is read through: the whole value, one or more ASCII digits. */
|
|
16
|
+
const countText = /^[0-9]+$/u;
|
|
14
17
|
/**
|
|
15
|
-
* Reads one option's bound variable. An empty variable is unset and falls through, a string
|
|
16
|
-
* receives the raw string,
|
|
17
|
-
* value states the option's value and not a spelling
|
|
18
|
+
* Reads one option's bound variable. An empty variable is unset and falls through, and a string
|
|
19
|
+
* option receives the raw string, never its implied value. A Boolean option reads the whole value
|
|
20
|
+
* through the grammar, so the value states the option's value and not a spelling, and a counted
|
|
21
|
+
* option reads the whole value as decimal digits.
|
|
18
22
|
*/
|
|
19
23
|
function readVariable(input, env) {
|
|
20
24
|
const variable = input.config.env;
|
|
@@ -25,6 +29,9 @@ function readVariable(input, env) {
|
|
|
25
29
|
if (input.config.type === 'string') {
|
|
26
30
|
return { kind: 'fill', value: raw };
|
|
27
31
|
}
|
|
32
|
+
if (input.config.type === 'count') {
|
|
33
|
+
return countText.test(raw) ? { kind: 'fill', value: Number(raw) } : { kind: 'rejected' };
|
|
34
|
+
}
|
|
28
35
|
const value = truths.get(raw.toLowerCase());
|
|
29
36
|
return value === undefined ? { kind: 'rejected' } : { kind: 'fill', value };
|
|
30
37
|
}
|
|
@@ -33,6 +40,9 @@ function fill(values, name, value) {
|
|
|
33
40
|
if (typeof value === 'boolean') {
|
|
34
41
|
values.booleans.set(name, value);
|
|
35
42
|
}
|
|
43
|
+
else if (typeof value === 'number') {
|
|
44
|
+
values.counts.set(name, value);
|
|
45
|
+
}
|
|
36
46
|
else if (typeof value === 'string') {
|
|
37
47
|
values.strings.set(name, value);
|
|
38
48
|
}
|
|
@@ -43,12 +53,13 @@ function fill(values, name, value) {
|
|
|
43
53
|
/** How a fault names the value an answer owed, by the shape the option takes. */
|
|
44
54
|
const owed = {
|
|
45
55
|
boolean: 'a Boolean',
|
|
56
|
+
count: 'a whole number of 0 or more',
|
|
46
57
|
list: 'an array of strings',
|
|
47
58
|
string: 'a string',
|
|
48
59
|
};
|
|
49
60
|
function rawKind(input) {
|
|
50
|
-
if (input.config.type
|
|
51
|
-
return
|
|
61
|
+
if (input.config.type !== 'string') {
|
|
62
|
+
return input.config.type;
|
|
52
63
|
}
|
|
53
64
|
return input.config.multiple === true ? 'list' : 'string';
|
|
54
65
|
}
|
|
@@ -78,6 +89,9 @@ function rawValue(kind, value) {
|
|
|
78
89
|
if (kind === 'boolean') {
|
|
79
90
|
return typeof value === 'boolean' ? value : undefined;
|
|
80
91
|
}
|
|
92
|
+
if (kind === 'count') {
|
|
93
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
94
|
+
}
|
|
81
95
|
return typeof value === 'string' ? value : undefined;
|
|
82
96
|
}
|
|
83
97
|
/**
|
|
@@ -150,7 +164,7 @@ function sourceFailure(sentence, error) {
|
|
|
150
164
|
* Core builds the context before the call, so a fault of its own is never the plugin's.
|
|
151
165
|
*/
|
|
152
166
|
async function askSource(stage, call) {
|
|
153
|
-
const { owner, requested } = call;
|
|
167
|
+
const { options, owner, requested } = call;
|
|
154
168
|
const resolver = await loadDefault(owner.identity, owner.source.load, {
|
|
155
169
|
guard: isResolverExport,
|
|
156
170
|
noun: 'source',
|
|
@@ -162,7 +176,8 @@ async function askSource(stage, call) {
|
|
|
162
176
|
const context = {
|
|
163
177
|
graph: stage.inspected(),
|
|
164
178
|
host: stage.host,
|
|
165
|
-
|
|
179
|
+
// Validation produced the record the plugin's option types describe.
|
|
180
|
+
options,
|
|
166
181
|
out: stage.out,
|
|
167
182
|
requests: requested.map(({ input, scope }) => stage.request(input, scope.global)),
|
|
168
183
|
style: stage.style,
|
|
@@ -193,8 +208,8 @@ async function askSource(stage, call) {
|
|
|
193
208
|
}
|
|
194
209
|
/**
|
|
195
210
|
* The environment tier: each option in scope that argv left unfilled reads its bound variable. A
|
|
196
|
-
* filled option is labeled with its variable, and a Boolean one outside
|
|
197
|
-
* as rejected, which fills nothing.
|
|
211
|
+
* filled option is labeled with its variable, and a Boolean or counted one outside its grammar is
|
|
212
|
+
* recorded as rejected, which fills nothing.
|
|
198
213
|
*/
|
|
199
214
|
function fillFromEnvironment(scopes, env) {
|
|
200
215
|
const labels = new Map();
|
|
@@ -228,8 +243,11 @@ function requestedOf(scopes, excluded, bound) {
|
|
|
228
243
|
}
|
|
229
244
|
/**
|
|
230
245
|
* The input-source stage: the environment, then the configuration source, for every option in
|
|
231
|
-
* scope that argv left unfilled. When no option is left to ask, the source never loads.
|
|
232
|
-
*
|
|
246
|
+
* scope that argv left unfilled. When no option is left to ask, the source never loads. The
|
|
247
|
+
* source's own options pass their validators before it loads, and when one is rejected the source
|
|
248
|
+
* is never called and the problem reports with every other validation problem, while each option
|
|
249
|
+
* the source would have been asked to fill reports no missing value. A source fault stops the
|
|
250
|
+
* stage and fills nothing more.
|
|
233
251
|
*/
|
|
234
252
|
async function fillInputs(stage) {
|
|
235
253
|
const scopes = stage.locals ? [stage.globals, stage.locals] : [stage.globals];
|
|
@@ -239,20 +257,32 @@ async function fillInputs(stage) {
|
|
|
239
257
|
? requestedOf(scopes, rejected, { binding: owner.source.binding, extensions: stage.extensions })
|
|
240
258
|
: [];
|
|
241
259
|
if (!owner || requested.length === 0) {
|
|
242
|
-
return { fault: undefined, labels, rejected };
|
|
260
|
+
return { fault: undefined, labels, rejected, validated: undefined };
|
|
243
261
|
}
|
|
262
|
+
let validated = undefined;
|
|
244
263
|
try {
|
|
245
|
-
|
|
264
|
+
validated = await stage.validate(owner.inputs, { labels, rejected });
|
|
265
|
+
// A rejected own option skips the source, and the options it would have filled report no omission.
|
|
266
|
+
if (validated.failure) {
|
|
267
|
+
const unanswered = new Set(requested.map(({ input }) => input));
|
|
268
|
+
return { fault: undefined, labels, rejected, unanswered, validated };
|
|
269
|
+
}
|
|
270
|
+
// A run cancelled while the source's own options were validated loads nothing.
|
|
271
|
+
if (stage.signal.aborted) {
|
|
272
|
+
return { fault: undefined, labels, rejected, validated };
|
|
273
|
+
}
|
|
274
|
+
const options = frozenValues(owner.inputs, validated.values);
|
|
275
|
+
for (const { label, target, value } of await askSource(stage, { options, owner, requested })) {
|
|
246
276
|
fill(target.scope.values, target.input.name, value);
|
|
247
277
|
labels.set(target.input.name, label);
|
|
248
278
|
}
|
|
249
|
-
return { fault: undefined, labels, rejected };
|
|
279
|
+
return { fault: undefined, labels, rejected, validated };
|
|
250
280
|
}
|
|
251
281
|
catch (error) {
|
|
252
282
|
// The resolver's own failure keeps its class, and so its code.
|
|
253
283
|
// Every other fault above is raised as an internal error, and anything else is wrapped the same way.
|
|
254
284
|
const fault = error instanceof LoomError ? error : foreignFailure(error);
|
|
255
|
-
return { fault, labels, rejected };
|
|
285
|
+
return { fault, labels, rejected, validated };
|
|
256
286
|
}
|
|
257
287
|
}
|
|
258
288
|
export { fillInputs };
|
package/dist/types.d.ts
CHANGED
|
@@ -8,12 +8,18 @@ import type { RenderingPolicy } from './rendering.js';
|
|
|
8
8
|
import type { ContextualStyle } from './style.js';
|
|
9
9
|
type LowercaseLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
|
|
10
10
|
type ShortAlias = LowercaseLetter | Uppercase<LowercaseLetter>;
|
|
11
|
+
/**
|
|
12
|
+
* The spellings an option declares beyond its long form. `aliases` adds a long spelling for each
|
|
13
|
+
* name, so `shortOnly`, which removes every long spelling, never declares one.
|
|
14
|
+
*/
|
|
11
15
|
type OptionSpelling = {
|
|
12
16
|
short?: ShortAlias;
|
|
13
17
|
shortOnly?: false;
|
|
18
|
+
aliases?: readonly string[];
|
|
14
19
|
} | {
|
|
15
20
|
short: ShortAlias;
|
|
16
21
|
shortOnly: true;
|
|
22
|
+
aliases?: never;
|
|
17
23
|
};
|
|
18
24
|
type Presence = {
|
|
19
25
|
required: true;
|
|
@@ -48,6 +54,24 @@ type Omission = {
|
|
|
48
54
|
} | {
|
|
49
55
|
validateOmitted?: false;
|
|
50
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* The presence rules a global option never declares, because its validation runs on every Command.
|
|
59
|
+
* `GlobalOptionConfig` and `GlobalOmissionConstraint` both read the rule from here.
|
|
60
|
+
*/
|
|
61
|
+
type PresenceRuleKey = 'required' | 'validateOmitted';
|
|
62
|
+
/** The fault each presence rule names when a global option declares it. */
|
|
63
|
+
interface PresenceRuleFaults {
|
|
64
|
+
required: {
|
|
65
|
+
'A global option declares no required; the Commands that read it check for it': never;
|
|
66
|
+
};
|
|
67
|
+
validateOmitted: {
|
|
68
|
+
'A global option declares no validateOmitted; its omission is plain absence': never;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** The first presence rule a config declares, in the order the faults name them. */
|
|
72
|
+
type DeclaredPresenceRule<Config> = {
|
|
73
|
+
[Key in PresenceRuleKey]: [Extract<Config, Record<Key, unknown>>] extends [never] ? never : Key;
|
|
74
|
+
}[PresenceRuleKey];
|
|
51
75
|
/**
|
|
52
76
|
* The one-line summary every projection reads. It is a core fact: optional, and a string that holds
|
|
53
77
|
* a character other than whitespace and no line terminator.
|
|
@@ -156,8 +180,11 @@ export interface InputIdentity {
|
|
|
156
180
|
export interface SuppliedInputs {
|
|
157
181
|
/** Raw positional values of the routed Command: one string, or the tokens of a variadic. */
|
|
158
182
|
args: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
159
|
-
/**
|
|
160
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Raw option values, globals and locals: a string, every occurrence, a Boolean presence, or the
|
|
185
|
+
* number of times a counted option was supplied.
|
|
186
|
+
*/
|
|
187
|
+
options: Readonly<Record<string, string | readonly string[] | boolean | number | undefined>>;
|
|
161
188
|
}
|
|
162
189
|
/**
|
|
163
190
|
* What core knows when it calls one schema. A default validates before any token is read, so its
|
|
@@ -317,7 +344,7 @@ export interface ResultBinding {
|
|
|
317
344
|
/**
|
|
318
345
|
* The routed Command's invocation after parsing and validation, which every middleware reads. The
|
|
319
346
|
* values are what the action receives, the output of each declaration's schema, for that Command's
|
|
320
|
-
* own arguments and local options; global
|
|
347
|
+
* own arguments and local options; global option values are not here. The records are
|
|
321
348
|
* untyped and frozen, because a middleware runs ahead of every action and the graph carries no
|
|
322
349
|
* type for a value.
|
|
323
350
|
*/
|
|
@@ -341,6 +368,8 @@ export type StringOption = OptionSpelling & Presence & Multiplicity & Omission &
|
|
|
341
368
|
type: 'string';
|
|
342
369
|
polarity?: never;
|
|
343
370
|
validate?: StandardSchemaV1;
|
|
371
|
+
/** The value a bare spelling supplies, so an explicit value is attached to the spelling. */
|
|
372
|
+
implied?: string;
|
|
344
373
|
};
|
|
345
374
|
/** A variadic argument collects the remaining tokens, so it follows the multiple option rules. */
|
|
346
375
|
export type VariadicArgument = Presence & Described & ArgumentExtensions & {
|
|
@@ -357,6 +386,19 @@ export type ValidatedValue<Config, Raw> = Config extends unknown ? 'validate' ex
|
|
|
357
386
|
export type DefaultConstraint<Config> = Config extends unknown ? Config & {
|
|
358
387
|
default?: 'validate' extends keyof Config ? PerValue<Config, SchemaInput<Config['validate']>> : RawValue<Config>;
|
|
359
388
|
} : never;
|
|
389
|
+
/**
|
|
390
|
+
* An implied value is the string an operator would otherwise attach, so it must be a string the
|
|
391
|
+
* validator's input type accepts, as a default must be. A multiple option's validator reads one
|
|
392
|
+
* value, so the implied value meets that one value's input type. A validator that declares no types
|
|
393
|
+
* infers `never`, so it states nothing to check against. The key names the fault, the way the
|
|
394
|
+
* other declaration constraints do, because an intersection with the literal would reduce the whole
|
|
395
|
+
* config to `never` and report every key.
|
|
396
|
+
*/
|
|
397
|
+
export type ImpliedConstraint<Config> = Config extends {
|
|
398
|
+
implied: infer Implied;
|
|
399
|
+
} ? 'validate' extends keyof Config ? [SchemaInput<Config['validate']>] extends [never] ? unknown : Implied extends SchemaInput<Config['validate']> ? unknown : {
|
|
400
|
+
'An implied value must be in the validator input type': Config['validate'];
|
|
401
|
+
} : unknown : unknown;
|
|
360
402
|
/**
|
|
361
403
|
* A multiple option or a variadic argument passes each value to its validator alone, so the
|
|
362
404
|
* declared validator must accept one `string`. The key names the fault, the way the other
|
|
@@ -404,19 +446,9 @@ export type ValidateOmittedConstraint<Config> = Config extends {
|
|
|
404
446
|
/**
|
|
405
447
|
* A global option declares no presence rule, so its omission is always plain absence. A union
|
|
406
448
|
* config fails when any member declares the key, and a wide `OptionConfig` passes, because its
|
|
407
|
-
* members only allow the key.
|
|
449
|
+
* members only allow the key. `required` is named first when a config declares both.
|
|
408
450
|
*/
|
|
409
|
-
export type GlobalOmissionConstraint<Config> = [
|
|
410
|
-
required: unknown;
|
|
411
|
-
}>] extends [
|
|
412
|
-
never
|
|
413
|
-
] ? [Extract<Config, {
|
|
414
|
-
validateOmitted: unknown;
|
|
415
|
-
}>] extends [never] ? unknown : {
|
|
416
|
-
'A global option declares no validateOmitted; its omission is plain absence': never;
|
|
417
|
-
} : {
|
|
418
|
-
'A global option declares no required; the Commands that read it check for it': never;
|
|
419
|
-
};
|
|
451
|
+
export type GlobalOmissionConstraint<Config> = [DeclaredPresenceRule<Config>] extends [never] ? unknown : PresenceRuleFaults['required' extends DeclaredPresenceRule<Config> ? 'required' : 'validateOmitted'];
|
|
420
452
|
export type ArgumentValue<Config extends ArgumentConfig> = Config extends {
|
|
421
453
|
variadic: true;
|
|
422
454
|
} ? ValidatedValue<Config, string[]> : ValidatedValue<Config, string> | (Config extends {
|
|
@@ -433,6 +465,7 @@ export type BooleanOption = (OptionSpelling & Described & Listed & EnvBinding &
|
|
|
433
465
|
multiple?: never;
|
|
434
466
|
required?: never;
|
|
435
467
|
validateOmitted?: never;
|
|
468
|
+
implied?: never;
|
|
436
469
|
polarity?: 'positive' | 'negative';
|
|
437
470
|
}) | (Described & Listed & EnvBinding & OptionExtensions & {
|
|
438
471
|
type: 'boolean';
|
|
@@ -441,27 +474,34 @@ export type BooleanOption = (OptionSpelling & Described & Listed & EnvBinding &
|
|
|
441
474
|
multiple?: never;
|
|
442
475
|
required?: never;
|
|
443
476
|
validateOmitted?: never;
|
|
477
|
+
implied?: never;
|
|
444
478
|
polarity: 'both';
|
|
445
479
|
short?: ShortAlias;
|
|
446
480
|
shortOnly?: false;
|
|
481
|
+
aliases?: readonly string[];
|
|
447
482
|
});
|
|
448
|
-
export type OptionConfig = StringOption | BooleanOption;
|
|
449
483
|
/**
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
* where the validation context every schema is promised cannot exist. Its middleware interprets
|
|
453
|
-
* the value.
|
|
454
|
-
* A Boolean plugin option is an ordinary `BooleanOption`, which already declares none of them.
|
|
484
|
+
* A counted option takes no value and reads how many times it was supplied, across every spelling,
|
|
485
|
+
* so it declares no validator, default, presence rule, collection, polarity, or implied value.
|
|
455
486
|
*/
|
|
456
|
-
export type
|
|
457
|
-
type: '
|
|
458
|
-
default?: string | string[];
|
|
459
|
-
polarity?: never;
|
|
460
|
-
required?: never;
|
|
487
|
+
export type CountOption = OptionSpelling & Described & Listed & EnvBinding & OptionExtensions & {
|
|
488
|
+
type: 'count';
|
|
461
489
|
validate?: never;
|
|
490
|
+
default?: never;
|
|
491
|
+
multiple?: never;
|
|
492
|
+
required?: never;
|
|
462
493
|
validateOmitted?: never;
|
|
494
|
+
polarity?: never;
|
|
495
|
+
implied?: never;
|
|
463
496
|
};
|
|
464
|
-
export type
|
|
497
|
+
export type OptionConfig = StringOption | BooleanOption | CountOption;
|
|
498
|
+
/**
|
|
499
|
+
* The configuration of a global option a plugin declares: everything `option()` takes except the
|
|
500
|
+
* presence rules, which a global option never declares, because its validation runs on every
|
|
501
|
+
* Command. `globalOption()` states the same rule through `GlobalOmissionConstraint`, which also
|
|
502
|
+
* accepts a config typed as the wide `OptionConfig`.
|
|
503
|
+
*/
|
|
504
|
+
export type GlobalOptionConfig = OptionConfig & Readonly<Partial<Record<PresenceRuleKey, never>>>;
|
|
465
505
|
export type OptionValue<Config extends OptionConfig> = Config extends StringOption ? Config extends {
|
|
466
506
|
multiple: true;
|
|
467
507
|
} ? ValidatedValue<Config, string[]> : ValidatedValue<Config, string> | (Config extends {
|
|
@@ -470,7 +510,7 @@ export type OptionValue<Config extends OptionConfig> = Config extends StringOpti
|
|
|
470
510
|
default: unknown;
|
|
471
511
|
} | {
|
|
472
512
|
validateOmitted: true;
|
|
473
|
-
} ? never : undefined) : boolean;
|
|
513
|
+
} ? never : undefined) : Config extends CountOption ? number : boolean;
|
|
474
514
|
/**
|
|
475
515
|
* What an action receives. `graph` is the frozen graph `inspect()` returns for this run, and
|
|
476
516
|
* `command` is the routed node inside it: the same two values the run's middleware receive.
|