@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.
@@ -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
- const name = optionName(token);
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
- for (const flag of locals.values()) {
93
- if (flag.required && !Object.hasOwn(values, flag.name)) throw new Error(`missing required option --${flag.name}`);
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
  }