@starci/cli 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/completions/_starci +145 -45
- package/completions/starci.bash +143 -43
- package/completions/starci.fish +161 -31
- package/completions/starci.ps1 +143 -43
- package/package.json +2 -2
- package/src/catalog.generated.mjs +1825 -2720
- package/src/help.mjs +7 -8
- package/src/main.mjs +141 -128
- package/src/runtime-install.generated.mjs +1 -1
- package/src/runtime-install.mjs +2 -2
- package/src/runtime-locate.mjs +13 -0
- package/src/runtime-target.mjs +41 -0
- package/src/shim.mjs +10 -2
- package/src/validate-args.mjs +96 -53
package/src/validate-args.mjs
CHANGED
|
@@ -31,6 +31,95 @@ const typedValue = (flag, raw) => {
|
|
|
31
31
|
return raw;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/** The values a list flag takes after its first one when the verb has no positional slot: every following token that is no option (`--knowledge a b c`), as the contracts write it. */
|
|
35
|
+
const listTail = (argv, index, flag, verb) => {
|
|
36
|
+
if (flag.type !== 'list' || (verb.positional ?? []).length > 0) return [];
|
|
37
|
+
const tail = [];
|
|
38
|
+
while (index + 2 + tail.length < argv.length && !argv[index + 2 + tail.length].startsWith('-')) tail.push(argv[index + 2 + tail.length]);
|
|
39
|
+
return tail;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** One `--name[=value]` token: type-check it into values/global, or pass it through to localArgs. */
|
|
43
|
+
const applyOption = (argv, index, token, { known, internal, values, global, localArgs, verb }) => {
|
|
44
|
+
const name = optionName(token);
|
|
45
|
+
const flag = known.get(name);
|
|
46
|
+
if (!flag) throw new Error(internal.has(name) ? `unknown option --${name}: it is a call of the runtime itself, not a CLI option; ${internal.get(name)}` : `unknown option --${name}`);
|
|
47
|
+
const { value: raw, consumed } = optionValue(argv, index, flag, known);
|
|
48
|
+
const value = typedValue(flag, raw);
|
|
49
|
+
const tail = consumed ? listTail(argv, index, flag, verb) : [];
|
|
50
|
+
if (flag.type === 'list') {
|
|
51
|
+
values[name] = [...(values[name] ?? []), value, ...tail];
|
|
52
|
+
} else if (Object.hasOwn(values, name)) {
|
|
53
|
+
throw new Error(`--${name} may be given only once`);
|
|
54
|
+
} else {
|
|
55
|
+
values[name] = value;
|
|
56
|
+
}
|
|
57
|
+
if (flag.global) { global[name] = values[name]; }
|
|
58
|
+
else {
|
|
59
|
+
localArgs.push(token);
|
|
60
|
+
if (consumed) localArgs.push(argv[index + consumed]);
|
|
61
|
+
// each further value of a list flag reaches the handler as a repeated option, the form every list-flag handler parses
|
|
62
|
+
for (const extra of tail) localArgs.push(token, extra);
|
|
63
|
+
}
|
|
64
|
+
return consumed + tail.length;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Every required local option must have been given. */
|
|
68
|
+
const requiredCheck = (locals, values) => {
|
|
69
|
+
for (const flag of locals.values()) {
|
|
70
|
+
if (flag.required && !Object.hasOwn(values, flag.name)) throw new Error(`missing required option --${flag.name}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** The values one positional slot holds: a variadic slot's list, a single value, or none. */
|
|
75
|
+
const candidatesOf = (supplied) => {
|
|
76
|
+
if (Array.isArray(supplied)) return supplied;
|
|
77
|
+
return supplied === undefined ? [] : [supplied];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** A slot with an enum accepts only its members. */
|
|
81
|
+
const checkEnum = (schema, candidates) => {
|
|
82
|
+
for (const candidate of candidates) {
|
|
83
|
+
if (schema.enum && !schema.enum.includes(candidate)) throw new Error(`${schema.name} expects one of: ${schema.enum.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Positional arity and enum checks; passthrough tokens fill free slots first. */
|
|
88
|
+
const checkPositionals = (verb, positionals, passthrough) => {
|
|
89
|
+
const positionalSchema = verb.positional ?? [];
|
|
90
|
+
const variadicAt = positionalSchema.findIndex((entry) => entry.variadic === true);
|
|
91
|
+
const maximum = variadicAt >= 0 ? Infinity : positionalSchema.length;
|
|
92
|
+
if (positionals.length > maximum) throw new Error(`too many positional arguments for ${verb.verb ?? 'command'}`);
|
|
93
|
+
positionals.push(...passthrough.slice(0, Math.max(0, maximum - positionals.length)));
|
|
94
|
+
for (const [index, schema] of positionalSchema.entries()) {
|
|
95
|
+
const supplied = variadicAt === index ? positionals.slice(index) : positionals[index];
|
|
96
|
+
const empty = Array.isArray(supplied) ? supplied.length === 0 : supplied === undefined;
|
|
97
|
+
if (schema.required && empty) throw new Error(`missing required positional ${schema.name}`);
|
|
98
|
+
checkEnum(schema, candidatesOf(supplied));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Edition availability, --cwd emptiness and --json support. */
|
|
103
|
+
const checkGlobals = (verb, global) => {
|
|
104
|
+
if (global.edition !== undefined && Array.isArray(verb.editions) && !verb.editions.includes(global.edition)) {
|
|
105
|
+
throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ` is not available in the ${global.edition} edition (editions: ${verb.editions.join(', ')})`);
|
|
106
|
+
}
|
|
107
|
+
if (global.cwd === '') throw new Error('--cwd needs a value');
|
|
108
|
+
if (global.json === true && verb.json === 'none') {
|
|
109
|
+
throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ' has no machine output');
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** args = supplied locals, else their declared defaults (a list flag's scalar default wraps in an array). */
|
|
114
|
+
const defaultArgs = (locals, values) => {
|
|
115
|
+
const args = {};
|
|
116
|
+
for (const flag of locals.values()) {
|
|
117
|
+
if (Object.hasOwn(values, flag.name)) args[flag.name] = values[flag.name];
|
|
118
|
+
else if (flag.default !== undefined) args[flag.name] = flag.type === 'list' && !Array.isArray(flag.default) ? [flag.default] : flag.default;
|
|
119
|
+
}
|
|
120
|
+
return args;
|
|
121
|
+
};
|
|
122
|
+
|
|
34
123
|
/**
|
|
35
124
|
* Validate the arguments after a catalog group and verb.
|
|
36
125
|
*
|
|
@@ -43,6 +132,8 @@ export function validateArgs(argv, verb, globalFlags = []) {
|
|
|
43
132
|
const globals = new Map(globalFlags.map((flag) => [flag.name, { ...flag, global: true }]));
|
|
44
133
|
const locals = new Map((verb.flags ?? []).map((flag) => [flag.name, flag]));
|
|
45
134
|
const known = new Map([...globals, ...locals]);
|
|
135
|
+
// Flags the runtime's own scripts take and the CLI refuses, each with the pointer a person is given instead.
|
|
136
|
+
const internal = new Map((verb.internalFlags ?? []).map((entry) => [entry.name, entry.pointer]));
|
|
46
137
|
const values = {};
|
|
47
138
|
const global = {};
|
|
48
139
|
const localArgs = [];
|
|
@@ -64,24 +155,7 @@ export function validateArgs(argv, verb, globalFlags = []) {
|
|
|
64
155
|
continue;
|
|
65
156
|
}
|
|
66
157
|
if (token.startsWith('--')) {
|
|
67
|
-
|
|
68
|
-
const flag = known.get(name);
|
|
69
|
-
if (!flag) throw new Error(`unknown option --${name}`);
|
|
70
|
-
const { value: raw, consumed } = optionValue(argv, index, flag, known);
|
|
71
|
-
const value = typedValue(flag, raw);
|
|
72
|
-
index += consumed;
|
|
73
|
-
if (flag.type === 'list') {
|
|
74
|
-
values[name] = [...(values[name] ?? []), value];
|
|
75
|
-
} else if (Object.hasOwn(values, name)) {
|
|
76
|
-
throw new Error(`--${name} may be given only once`);
|
|
77
|
-
} else {
|
|
78
|
-
values[name] = value;
|
|
79
|
-
}
|
|
80
|
-
if (flag.global) global[name] = values[name];
|
|
81
|
-
else {
|
|
82
|
-
localArgs.push(token);
|
|
83
|
-
if (consumed) localArgs.push(argv[index]);
|
|
84
|
-
}
|
|
158
|
+
index += applyOption(argv, index, token, { known, internal, values, global, localArgs, verb });
|
|
85
159
|
continue;
|
|
86
160
|
}
|
|
87
161
|
if (token.startsWith('-')) throw new Error(`unknown option ${token}`);
|
|
@@ -89,41 +163,10 @@ export function validateArgs(argv, verb, globalFlags = []) {
|
|
|
89
163
|
localArgs.push(token);
|
|
90
164
|
}
|
|
91
165
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const positionalSchema = verb.positional ?? [];
|
|
97
|
-
const variadicAt = positionalSchema.findIndex((entry) => entry.variadic === true);
|
|
98
|
-
const maximum = variadicAt >= 0 ? Infinity : positionalSchema.length;
|
|
99
|
-
if (positionals.length > maximum) throw new Error(`too many positional arguments for ${verb.verb ?? 'command'}`);
|
|
100
|
-
positionals.push(...passthrough.slice(0, Math.max(0, maximum - positionals.length)));
|
|
101
|
-
for (let index = 0; index < positionalSchema.length; index += 1) {
|
|
102
|
-
const schema = positionalSchema[index];
|
|
103
|
-
const supplied = variadicAt === index ? positionals.slice(index) : positionals[index];
|
|
104
|
-
const empty = Array.isArray(supplied) ? supplied.length === 0 : supplied === undefined;
|
|
105
|
-
if (schema.required && empty) throw new Error(`missing required positional ${schema.name}`);
|
|
106
|
-
const candidates = Array.isArray(supplied) ? supplied : supplied === undefined ? [] : [supplied];
|
|
107
|
-
for (const candidate of candidates) {
|
|
108
|
-
if (schema.enum && !schema.enum.includes(candidate)) {
|
|
109
|
-
throw new Error(`${schema.name} expects one of: ${schema.enum.join(', ')}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (global.edition !== undefined && Array.isArray(verb.editions) && !verb.editions.includes(global.edition)) {
|
|
115
|
-
throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ` is not available in the ${global.edition} edition (editions: ${verb.editions.join(', ')})`);
|
|
116
|
-
}
|
|
117
|
-
if (global.cwd === '') throw new Error('--cwd needs a value');
|
|
118
|
-
if (global.json === true && verb.json === 'none') {
|
|
119
|
-
throw new Error(`${verb.group ?? 'this command'} ${verb.verb ?? ''}`.trim() + ' has no machine output');
|
|
120
|
-
}
|
|
121
|
-
const args = {};
|
|
122
|
-
for (const flag of locals.values()) {
|
|
123
|
-
if (Object.hasOwn(values, flag.name)) args[flag.name] = values[flag.name];
|
|
124
|
-
else if (flag.default !== undefined) args[flag.name] = flag.type === 'list' && !Array.isArray(flag.default) ? [flag.default] : flag.default;
|
|
125
|
-
}
|
|
126
|
-
return { ok: true, values, args, global, localArgs, positionals };
|
|
166
|
+
requiredCheck(locals, values);
|
|
167
|
+
checkPositionals(verb, positionals, passthrough);
|
|
168
|
+
checkGlobals(verb, global);
|
|
169
|
+
return { ok: true, values, args: defaultArgs(locals, values), global, localArgs, positionals };
|
|
127
170
|
} catch (error) {
|
|
128
171
|
return { ok: false, code: 2, error: String(error?.message ?? error) };
|
|
129
172
|
}
|