@loomcli/core 0.4.0 → 0.5.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/LICENSE +21 -0
- package/dist/application.d.ts +44 -31
- package/dist/application.js +122 -110
- package/dist/bindings.d.ts +26 -0
- package/dist/bindings.js +45 -0
- package/dist/chain.d.ts +11 -6
- package/dist/chain.js +28 -81
- package/dist/command.d.ts +161 -84
- package/dist/command.js +650 -349
- package/dist/errors.d.ts +7 -2
- package/dist/errors.js +9 -1
- package/dist/extension.d.ts +3 -1
- package/dist/extension.js +8 -11
- package/dist/facts.d.ts +5 -0
- package/dist/facts.js +10 -3
- package/dist/globals.d.ts +45 -12
- package/dist/globals.js +74 -18
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/inspect.d.ts +25 -2
- package/dist/inspect.js +43 -5
- package/dist/locate.d.ts +41 -0
- package/dist/locate.js +121 -0
- package/dist/options.d.ts +73 -0
- package/dist/options.js +124 -27
- package/dist/output.d.ts +2 -0
- package/dist/output.js +5 -1
- package/dist/plugin.d.ts +107 -53
- package/dist/plugin.js +204 -66
- package/dist/sources.d.ts +56 -0
- package/dist/sources.js +249 -0
- package/dist/types.d.ts +65 -20
- package/dist/validation.d.ts +22 -8
- package/dist/validation.js +184 -77
- package/dist/view.d.ts +1 -1
- package/dist/view.js +2 -2
- package/package.json +3 -2
package/dist/validation.js
CHANGED
|
@@ -73,33 +73,44 @@ function spellingOf(input) {
|
|
|
73
73
|
return input.name;
|
|
74
74
|
}
|
|
75
75
|
const { config } = input;
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
if (config.shortOnly === true && config.short !== undefined) {
|
|
77
|
+
return `-${config.short}`;
|
|
78
|
+
}
|
|
79
|
+
// A negative-only Boolean option accepts its negative form alone.
|
|
80
|
+
return config.type === 'boolean' && config.polarity === 'negative'
|
|
81
|
+
? `--no-${input.name}`
|
|
78
82
|
: `--${input.name}`;
|
|
79
83
|
}
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
/**
|
|
85
|
+
* An input diagnostic names the declaration by kind and by the spelling that reaches it. A value an
|
|
86
|
+
* input source filled adds its source in parentheses, so the operator learns where it came from.
|
|
87
|
+
*/
|
|
88
|
+
function suppliedName(input, spelling, origin) {
|
|
89
|
+
if (input.kind === 'argument') {
|
|
90
|
+
return `Argument "${spelling}"`;
|
|
91
|
+
}
|
|
92
|
+
return origin === undefined ? `Option "${spelling}"` : `Option "${spelling}" (from ${origin})`;
|
|
83
93
|
}
|
|
84
94
|
/**
|
|
85
95
|
* The default sentence for an omitted required input. An argument and an option keep the wording
|
|
86
96
|
* each phase used before omission became one problem, and a collected input asks for one value
|
|
87
97
|
* more than a scalar does.
|
|
88
98
|
*/
|
|
89
|
-
function missingMessage(input, spelling,
|
|
99
|
+
function missingMessage(input, spelling, facts) {
|
|
100
|
+
const { collected, origin } = facts;
|
|
90
101
|
return input.kind === 'argument'
|
|
91
102
|
? `Argument "${spelling}" requires ${collected ? 'at least one value' : 'a value'}. Supply a value for "${spelling}".`
|
|
92
|
-
:
|
|
103
|
+
: `${suppliedName(input, spelling, origin)} is required. Supply ${collected ? 'at least one value' : 'a value'}.`;
|
|
93
104
|
}
|
|
94
105
|
/**
|
|
95
106
|
* A multiple option collects its occurrences and a variadic argument collects the remaining
|
|
96
|
-
* tokens, so either one carries
|
|
107
|
+
* tokens, so either one carries a `string[]` as its raw value and validates each value alone.
|
|
97
108
|
*/
|
|
98
109
|
function collects(input) {
|
|
99
110
|
return input.kind === 'option' ? input.config.multiple === true : input.config.variadic === true;
|
|
100
111
|
}
|
|
101
112
|
/**
|
|
102
|
-
* The declaration flag that sends an omitted value to its own
|
|
113
|
+
* The declaration flag that sends an omitted value to its own validator. Every declaration reads it
|
|
103
114
|
* here, and the declaration rules below reject it wherever another rule already decides absence.
|
|
104
115
|
*/
|
|
105
116
|
export function validatesOmission(input) {
|
|
@@ -114,16 +125,23 @@ function suppliedOption(options, name, collected) {
|
|
|
114
125
|
function copied(value) {
|
|
115
126
|
return Array.isArray(value) ? [...value] : value;
|
|
116
127
|
}
|
|
117
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Without a validator the raw shape is the declared default's only contract. With one, a default
|
|
130
|
+
* of several values must still be an array, because each of its values passes the validator.
|
|
131
|
+
*/
|
|
118
132
|
function holdsRawDefault(input) {
|
|
119
133
|
const value = input.config.default;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
if (!collects(input)) {
|
|
135
|
+
return input.config.validate !== undefined || typeof value === 'string';
|
|
136
|
+
}
|
|
137
|
+
return (Array.isArray(value) &&
|
|
138
|
+
(input.config.validate !== undefined ||
|
|
139
|
+
value.every((entry) => typeof entry === 'string')));
|
|
123
140
|
}
|
|
124
141
|
/**
|
|
125
|
-
* `validateOmitted: true` is the one way an omitted scalar reaches its
|
|
126
|
-
* that already decides absence rejects it, and the flag needs a
|
|
142
|
+
* `validateOmitted: true` is the one way an omitted scalar reaches its validator, so every other
|
|
143
|
+
* rule that already decides absence rejects it, and the flag needs a validator to receive the
|
|
144
|
+
* omission.
|
|
127
145
|
*/
|
|
128
146
|
function checkOmissionValidation(input, subject) {
|
|
129
147
|
const { config } = input;
|
|
@@ -134,10 +152,10 @@ function checkOmissionValidation(input, subject) {
|
|
|
134
152
|
throw new DeclarationError(`${subject} declares a default and validateOmitted. Remove one; the default already fills an omitted value.`);
|
|
135
153
|
}
|
|
136
154
|
if (collects(input)) {
|
|
137
|
-
throw new DeclarationError(`${subject}
|
|
155
|
+
throw new DeclarationError(`${subject} takes several values and declares validateOmitted. Remove validateOmitted; with no values the action receives an empty array and no validator runs.`);
|
|
138
156
|
}
|
|
139
157
|
if (config.validate === undefined) {
|
|
140
|
-
throw new DeclarationError(`${subject} declares validateOmitted without a
|
|
158
|
+
throw new DeclarationError(`${subject} declares validateOmitted without a validator. Add validate or remove validateOmitted.`);
|
|
141
159
|
}
|
|
142
160
|
}
|
|
143
161
|
function checkDeclaration(input, subject) {
|
|
@@ -169,15 +187,15 @@ function checkDeclaration(input, subject) {
|
|
|
169
187
|
if (validatesOmission(input)) {
|
|
170
188
|
checkOmissionValidation(input, subject);
|
|
171
189
|
}
|
|
172
|
-
const
|
|
173
|
-
if (
|
|
174
|
-
(
|
|
175
|
-
(typeof
|
|
176
|
-
!
|
|
177
|
-
|
|
178
|
-
typeof
|
|
179
|
-
typeof
|
|
180
|
-
throw new DeclarationError(`${subject} validate must be a Standard Schema v1 object. Supply a compatible
|
|
190
|
+
const validator = config.validate;
|
|
191
|
+
if (validator !== undefined &&
|
|
192
|
+
(validator === null ||
|
|
193
|
+
(typeof validator !== 'object' && typeof validator !== 'function') ||
|
|
194
|
+
!validator['~standard'] ||
|
|
195
|
+
validator['~standard'].version !== 1 ||
|
|
196
|
+
typeof validator['~standard'].vendor !== 'string' ||
|
|
197
|
+
typeof validator['~standard'].validate !== 'function')) {
|
|
198
|
+
throw new DeclarationError(`${subject} validate must be a Standard Schema v1 object. Supply a compatible validator.`);
|
|
181
199
|
}
|
|
182
200
|
}
|
|
183
201
|
function readIssue(issue) {
|
|
@@ -209,12 +227,12 @@ function readIssue(issue) {
|
|
|
209
227
|
* names the declaration. Returned issues belong to the value, so the caller names those.
|
|
210
228
|
*/
|
|
211
229
|
async function validate(input, raw, context) {
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
230
|
+
const validator = input.config.validate;
|
|
231
|
+
if (validator === undefined) {
|
|
214
232
|
return { value: raw };
|
|
215
233
|
}
|
|
216
234
|
try {
|
|
217
|
-
const result = await
|
|
235
|
+
const result = await validator['~standard'].validate(raw, schemaOptions(context));
|
|
218
236
|
if (result === null || typeof result !== 'object') {
|
|
219
237
|
throw new Error('The validator returned an invalid Standard Schema result.');
|
|
220
238
|
}
|
|
@@ -232,6 +250,42 @@ async function validate(input, raw, context) {
|
|
|
232
250
|
throw new DeclarationError(`${declaredName(input)} validator failed unexpectedly: ${reason} Fix the validator.`);
|
|
233
251
|
}
|
|
234
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* One validation of a declared value. A multiple option or a variadic argument passes each of its
|
|
255
|
+
* values through the validator in order, and each issue reads at its value's position before its
|
|
256
|
+
* own path, so the action receives the array of outputs. Every other input passes its value once.
|
|
257
|
+
* Each call reads a fresh context whose arrays are copies, so a write to them never reaches the
|
|
258
|
+
* next call. The host is the one captured object that every call and the action share.
|
|
259
|
+
*/
|
|
260
|
+
async function validateDeclared(input, raw, call) {
|
|
261
|
+
const { context, signal } = call;
|
|
262
|
+
if (!collects(input) || input.config.validate === undefined) {
|
|
263
|
+
return validate(input, raw, context());
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(raw)) {
|
|
266
|
+
// The parser, the input sources, and the declaration rules only ever supply an array here.
|
|
267
|
+
throw new TypeError(`${declaredName(input)} reached validation without an array of values.`);
|
|
268
|
+
}
|
|
269
|
+
const outputs = [];
|
|
270
|
+
const issues = [];
|
|
271
|
+
for (const [position, value] of raw.entries()) {
|
|
272
|
+
if (signal?.aborted) {
|
|
273
|
+
// A cancelled run starts no further call; the run resolves its cancellation code instead.
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
const result = await validate(input, value, context());
|
|
277
|
+
if (result.issues === undefined) {
|
|
278
|
+
outputs.push(result.value);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
issues.push(...reported(result.issues).map((issue) => ({
|
|
282
|
+
message: issue.message,
|
|
283
|
+
path: [position, ...(issue.path ?? [])],
|
|
284
|
+
})));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return issues.length === 0 ? { value: outputs } : { issues };
|
|
288
|
+
}
|
|
235
289
|
/**
|
|
236
290
|
* The dotted path an issue names inside a value, or `undefined` when the issue names the value
|
|
237
291
|
* itself. Core's default text and an application's own view read a position through this one
|
|
@@ -244,13 +298,13 @@ export function issuePath(issue) {
|
|
|
244
298
|
return path === undefined || path === '' ? undefined : path;
|
|
245
299
|
}
|
|
246
300
|
/**
|
|
247
|
-
* The issues one rejection reports. A
|
|
301
|
+
* The issues one rejection reports. A validator that returned none still rejected the value, so the
|
|
248
302
|
* placeholder stands in for its silence. Reporting takes this list once: the reported problem
|
|
249
303
|
* carries it and the default text is derived from it, so a view and core read the same issues.
|
|
250
304
|
*/
|
|
251
305
|
function reported(issues) {
|
|
252
306
|
return issues.length === 0
|
|
253
|
-
? [{ message: 'The
|
|
307
|
+
? [{ message: 'The validator rejected this value without an explanation.' }]
|
|
254
308
|
: issues;
|
|
255
309
|
}
|
|
256
310
|
function messages(subject, issues) {
|
|
@@ -259,58 +313,62 @@ function messages(subject, issues) {
|
|
|
259
313
|
return `${subject}${path === undefined ? '' : ` at ${path}`}: ${issue.message}`;
|
|
260
314
|
});
|
|
261
315
|
}
|
|
316
|
+
/** The fault a default of the wrong raw shape reports, by the shape its declaration expects. */
|
|
317
|
+
function defaultShapeFault(input, subject) {
|
|
318
|
+
if (!collects(input)) {
|
|
319
|
+
return `${subject} default must be a string without a validator. Supply a string default.`;
|
|
320
|
+
}
|
|
321
|
+
return input.config.validate === undefined
|
|
322
|
+
? `${subject} default must be an array of strings without a validator. Supply a string array default.`
|
|
323
|
+
: `${subject} default must be an array. Supply an array of values.`;
|
|
324
|
+
}
|
|
262
325
|
/** A declared `default: undefined` is a default, so presence is the key, never the value. */
|
|
263
326
|
function hasDefault(input) {
|
|
264
327
|
return Object.hasOwn(input.config, 'default');
|
|
265
328
|
}
|
|
266
329
|
/**
|
|
267
|
-
* Every declaration rule that reads the declaration alone. It is synchronous, so
|
|
268
|
-
*
|
|
269
|
-
* can be asynchronous, is left to `run()`. A
|
|
270
|
-
*
|
|
271
|
-
* declaration itself.
|
|
330
|
+
* Every declaration rule that reads the declaration alone. It is synchronous, so the call that
|
|
331
|
+
* declares an input applies it, and build applies it to an input a lifecycle hook declared; only
|
|
332
|
+
* validating a default through its validator, which can be asynchronous, is left to `run()`. A
|
|
333
|
+
* contributor that declares under its own name, such as a plugin, supplies the subject its
|
|
334
|
+
* diagnostics read with; every other caller is named by the declaration itself.
|
|
272
335
|
*/
|
|
273
336
|
export function checkDeclarations(inputs, named) {
|
|
274
337
|
for (const input of inputs) {
|
|
275
338
|
checkDeclaration(input, named ?? declaredName(input));
|
|
276
339
|
}
|
|
277
340
|
for (const input of inputs.filter((entry) => hasDefault(entry))) {
|
|
278
|
-
if (
|
|
341
|
+
if (!holdsRawDefault(input)) {
|
|
279
342
|
const subject = named ?? declaredName(input);
|
|
280
|
-
throw new DeclarationError(
|
|
281
|
-
? `${subject} default must be an array of strings without a schema. Supply a string array default.`
|
|
282
|
-
: `${subject} default must be a string without a schema. Supply a string default.`);
|
|
343
|
+
throw new DeclarationError(defaultShapeFault(input, subject));
|
|
283
344
|
}
|
|
284
345
|
}
|
|
285
346
|
}
|
|
286
347
|
/**
|
|
287
348
|
* Every declared default, validated before any token is read. The host is captured by then, so a
|
|
288
|
-
* default's
|
|
349
|
+
* default's validator reads the same Host its action will, under the `default` phase.
|
|
289
350
|
*/
|
|
290
351
|
export async function prepareInputs(inputs, host) {
|
|
291
352
|
const declarations = scoped(inputs);
|
|
292
|
-
checkDeclarations(declarations.map((entry) => entry.input));
|
|
293
353
|
const defaults = new Map();
|
|
294
354
|
for (const entry of declarations.filter(({ input }) => hasDefault(input))) {
|
|
295
355
|
const { input } = entry;
|
|
296
356
|
const subject = declaredName(input);
|
|
297
|
-
const result = await
|
|
298
|
-
host,
|
|
299
|
-
input: identityOf(entry),
|
|
300
|
-
phase: 'default',
|
|
357
|
+
const result = await validateDeclared(input, input.config.default, {
|
|
358
|
+
context: () => ({ host, input: identityOf(entry), phase: 'default' }),
|
|
301
359
|
});
|
|
302
360
|
if (result.issues !== undefined) {
|
|
303
|
-
throw new DeclarationError(`${subject} has an invalid default. Fix the default or its
|
|
361
|
+
throw new DeclarationError(`${subject} has an invalid default. Fix the default or its validator.\n${messages(subject, reported(result.issues)).join('\n')}`);
|
|
304
362
|
}
|
|
305
363
|
defaults.set(input, result.value);
|
|
306
364
|
}
|
|
307
365
|
return defaults;
|
|
308
366
|
}
|
|
309
367
|
/**
|
|
310
|
-
* An array default reaches the action as its own copy, so an action that mutates its
|
|
311
|
-
* rewrites neither the declaration nor the next invocation.
|
|
312
|
-
*
|
|
313
|
-
*
|
|
368
|
+
* An array default reaches the action as its own copy, so an action that mutates its array
|
|
369
|
+
* rewrites neither the declaration nor the next invocation. One prepared default serves every
|
|
370
|
+
* invocation of a run, and an unvalidated default is the declared array itself, so each read
|
|
371
|
+
* copies it. Every other output passes through unchanged.
|
|
314
372
|
*/
|
|
315
373
|
function freshDefault(value) {
|
|
316
374
|
return Array.isArray(value) ? [...value] : value;
|
|
@@ -339,50 +397,94 @@ function suppliedInputs(declarations, supplied) {
|
|
|
339
397
|
}
|
|
340
398
|
return { args, options };
|
|
341
399
|
}
|
|
400
|
+
/** The Boolean grammar's one issue, which a variable outside it reports. */
|
|
401
|
+
const grammarIssues = [{ message: 'Use true, false, 1, or 0.' }];
|
|
402
|
+
/**
|
|
403
|
+
* Every declaration in the order this phase reports its problems: the globals, then each plugin
|
|
404
|
+
* option whose variable is outside the grammar, then the routed Command's own declarations.
|
|
405
|
+
*/
|
|
406
|
+
function reportingOrder(invocation) {
|
|
407
|
+
const declarations = scoped(invocation.inputs);
|
|
408
|
+
const globals = declarations.filter((entry) => entry.global);
|
|
409
|
+
const rejected = invocation.plugins
|
|
410
|
+
.filter((input) => invocation.sources.rejected.has(input.name))
|
|
411
|
+
.map((input) => ({ global: true, input }));
|
|
412
|
+
return [...globals, ...rejected, ...declarations.filter((entry) => !entry.global)];
|
|
413
|
+
}
|
|
342
414
|
export async function validateValues(invocation) {
|
|
343
|
-
const { defaults, supplied } = invocation;
|
|
415
|
+
const { defaults, sources, supplied } = invocation;
|
|
344
416
|
const declarations = scoped(invocation.inputs);
|
|
417
|
+
/** Where a filled option's value came from, which its diagnostic names; argv names none. */
|
|
418
|
+
const originOf = (input) => input.kind === 'option' ? sources.labels.get(input.name) : undefined;
|
|
345
419
|
/**
|
|
346
|
-
* One reading of the tokens and the route, built anew for each
|
|
347
|
-
* tail, and every collected value are copies, so a
|
|
348
|
-
* the parser's collections, nor the tail the action receives, nor the next
|
|
349
|
-
* invocation.
|
|
420
|
+
* One reading of the tokens and the route, built anew for each validator call. The route, the
|
|
421
|
+
* tail, and every collected value are copies, so a validator that writes to them reaches neither
|
|
422
|
+
* the parser's collections, nor the tail the action receives, nor the next validator call of
|
|
423
|
+
* this invocation. Each copy is made on its first read and kept for the call, so a validator
|
|
424
|
+
* that never reads one never pays for it, however many values a list holds. The host is the
|
|
425
|
+
* captured object itself, the one the action receives.
|
|
350
426
|
*/
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
427
|
+
const contextOf = (entry) => {
|
|
428
|
+
const copies = {};
|
|
429
|
+
return {
|
|
430
|
+
get command() {
|
|
431
|
+
copies.command ??= [...invocation.command];
|
|
432
|
+
return copies.command;
|
|
433
|
+
},
|
|
434
|
+
host: invocation.host,
|
|
435
|
+
input: identityOf(entry),
|
|
436
|
+
get passthrough() {
|
|
437
|
+
copies.passthrough ??= [...invocation.passthrough];
|
|
438
|
+
return copies.passthrough;
|
|
439
|
+
},
|
|
440
|
+
phase: 'invocation',
|
|
441
|
+
get supplied() {
|
|
442
|
+
copies.supplied ??= suppliedInputs(declarations.map((declared) => declared.input), supplied);
|
|
443
|
+
return copies.supplied;
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
};
|
|
357
447
|
const values = new Map();
|
|
358
448
|
const lines = [];
|
|
359
449
|
const problems = [];
|
|
360
|
-
/** One
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
...facts(),
|
|
450
|
+
/** One rejected input, whatever rejected it, in the order this phase reaches it. */
|
|
451
|
+
const reject = (entry, issues, subject) => {
|
|
452
|
+
problems.push({
|
|
364
453
|
input: identityOf(entry),
|
|
365
|
-
|
|
454
|
+
issues,
|
|
455
|
+
reason: 'invalid',
|
|
456
|
+
spelling: spellingOf(entry.input),
|
|
457
|
+
});
|
|
458
|
+
lines.push(...messages(subject, issues));
|
|
459
|
+
};
|
|
460
|
+
/** One path for every value a validator reads, so a raw shape and its issues meet it once. */
|
|
461
|
+
const accept = async (entry, raw, spelling) => {
|
|
462
|
+
const result = await validateDeclared(entry.input, raw, {
|
|
463
|
+
context: () => contextOf(entry),
|
|
464
|
+
signal: invocation.signal,
|
|
366
465
|
});
|
|
367
466
|
if (result.issues === undefined) {
|
|
368
467
|
values.set(entry.input, result.value);
|
|
369
468
|
return;
|
|
370
469
|
}
|
|
371
|
-
|
|
372
|
-
problems.push({ input: identityOf(entry), issues, reason: 'invalid', spelling });
|
|
373
|
-
lines.push(...messages(suppliedName(entry.input, spelling), issues));
|
|
470
|
+
reject(entry, reported(result.issues), suppliedName(entry.input, spelling, originOf(entry.input)));
|
|
374
471
|
};
|
|
375
|
-
for (const entry of
|
|
472
|
+
for (const entry of reportingOrder(invocation)) {
|
|
376
473
|
if (invocation.signal.aborted) {
|
|
377
474
|
/**
|
|
378
|
-
* A cancelled run starts no further
|
|
475
|
+
* A cancelled run starts no further validator call. The one already in flight was awaited
|
|
379
476
|
* above, and whatever this phase collected is never raised, because the run resolves its
|
|
380
477
|
* cancellation code instead.
|
|
381
478
|
*/
|
|
382
479
|
break;
|
|
383
480
|
}
|
|
384
481
|
const { input } = entry;
|
|
385
|
-
|
|
482
|
+
const variable = sources.rejected.get(input.name);
|
|
483
|
+
if (input.kind === 'option' && variable !== undefined) {
|
|
484
|
+
// A Boolean variable outside the grammar filled nothing, so it is the option's problem.
|
|
485
|
+
reject(entry, grammarIssues, suppliedName(input, spellingOf(input), variable));
|
|
486
|
+
}
|
|
487
|
+
else if (input.kind === 'option' && input.config.type === 'boolean') {
|
|
386
488
|
values.set(input, booleanValue(supplied.options, input.name, input.config));
|
|
387
489
|
}
|
|
388
490
|
else {
|
|
@@ -396,14 +498,14 @@ export async function validateValues(invocation) {
|
|
|
396
498
|
// An omitted required argument arrives here too, so omission has one class.
|
|
397
499
|
// One aggregated diagnostic covers an omitted argument and an omitted option alike.
|
|
398
500
|
problems.push({ input: identityOf(entry), reason: 'missing', spelling });
|
|
399
|
-
lines.push(missingMessage(input, spelling, collected));
|
|
501
|
+
lines.push(missingMessage(input, spelling, { collected }));
|
|
400
502
|
}
|
|
401
503
|
else if (collected && !defaults.has(input)) {
|
|
402
|
-
// No occurrence
|
|
403
|
-
|
|
504
|
+
// No occurrence has no value to validate, so the action receives an empty array.
|
|
505
|
+
values.set(input, []);
|
|
404
506
|
}
|
|
405
507
|
else if (validatesOmission(input)) {
|
|
406
|
-
// The flag sends the omission itself to the
|
|
508
|
+
// The flag sends the omission itself to the validator.
|
|
407
509
|
// An absence rule reads the context a supplied value reads, and reports input issues.
|
|
408
510
|
await accept(entry, undefined, spelling);
|
|
409
511
|
}
|
|
@@ -411,6 +513,11 @@ export async function validateValues(invocation) {
|
|
|
411
513
|
values.set(input, freshDefault(defaults.get(input)));
|
|
412
514
|
}
|
|
413
515
|
}
|
|
516
|
+
else if (input.config.required && Array.isArray(raw) && raw.length === 0) {
|
|
517
|
+
// A filled list satisfies the at-least-one rule by its length, so an empty one is missing.
|
|
518
|
+
problems.push({ input: identityOf(entry), reason: 'missing', spelling });
|
|
519
|
+
lines.push(missingMessage(input, spelling, { collected, origin: originOf(input) }));
|
|
520
|
+
}
|
|
414
521
|
else {
|
|
415
522
|
await accept(entry, raw, spelling);
|
|
416
523
|
}
|
package/dist/view.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ declare function view<Data>(identity: string, definition: View<Data>): DeclaredV
|
|
|
78
78
|
declare function view<Row>(identity: string, definition: RowView<Row>): DeclaredRowView<Row>;
|
|
79
79
|
/**
|
|
80
80
|
* What one override replaces: a declared view, a failure class read as its prototype, or a value
|
|
81
|
-
* that is neither, which
|
|
81
|
+
* that is neither, which the list that holds it reports as its entry fault.
|
|
82
82
|
*/
|
|
83
83
|
type OverrideKey = {
|
|
84
84
|
kind: 'view';
|
package/dist/view.js
CHANGED
|
@@ -83,8 +83,8 @@ function override(key, replacement) {
|
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
85
|
* The key one failure-class override answers. A class is a function whose `prototype` is the
|
|
86
|
-
* object a thrown failure's chain holds, so anything else is no key at all and
|
|
87
|
-
*
|
|
86
|
+
* object a thrown failure's chain holds, so anything else is no key at all and the list that holds
|
|
87
|
+
* it reports it as its entry fault, rather than colliding with every other such value.
|
|
88
88
|
*/
|
|
89
89
|
function failureKey(key) {
|
|
90
90
|
if (typeof key !== 'function' || !('prototype' in key)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loomcli/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Loom CLI core package. Provides the scaffolding for creating new Loom CLI applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"url": "git+https://github.com/dbtlr/loomcli.git"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"LICENSE"
|
|
12
13
|
],
|
|
13
14
|
"type": "module",
|
|
14
15
|
"exports": {
|