cli-api 0.1.2 → 0.2.1
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/README.md +25 -25
- package/dist/index.d.mts +439 -0
- package/dist/index.mjs +3 -0
- package/dist/interfaces-CfKTHP7y.mjs +510 -0
- package/dist/run-DEkBUX4b.mjs +1374 -0
- package/package.json +37 -37
- package/.hgignore +0 -12
- package/Makefile +0 -14
- package/babel.config.json +0 -33
- package/dist/cjs/index.js +0 -588
- package/dist/cjs/index.js.map +0 -1
- package/dist/es/index.mjs +0 -578
- package/dist/es/index.mjs.map +0 -1
- package/dist/types/app-help.d.ts +0 -3
- package/dist/types/commands/command-help.d.ts +0 -2
- package/dist/types/commands/version.d.ts +0 -2
- package/dist/types/constants.d.ts +0 -4
- package/dist/types/index.d.ts +0 -3
- package/dist/types/interfaces.d.ts +0 -79
- package/dist/types/options.d.ts +0 -7
- package/dist/types/print-command-help.d.ts +0 -2
- package/dist/types/run.d.ts +0 -2
- package/dist/types/utils.d.ts +0 -17
- package/rollup.config.js +0 -44
- package/tsconfig.json +0 -32
|
@@ -0,0 +1,1374 @@
|
|
|
1
|
+
import { a as getExecuteHandler, c as createChalk, i as OptType, n as Command, o as hasSubCommands, r as ExecutionContext, s as isExecutable } from "./interfaces-CfKTHP7y.mjs";
|
|
2
|
+
import Path from "path";
|
|
3
|
+
import stringWidth from "string-width";
|
|
4
|
+
import FileSys from "fs";
|
|
5
|
+
|
|
6
|
+
//#region src/constants.ts
|
|
7
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
8
|
+
const EMPTY_OBJECT = Object.freeze(Object.create({ __proto__: null }));
|
|
9
|
+
const TRUE_VALUES = new Set([
|
|
10
|
+
"y",
|
|
11
|
+
"yes",
|
|
12
|
+
"t",
|
|
13
|
+
"true",
|
|
14
|
+
"1",
|
|
15
|
+
"on"
|
|
16
|
+
]);
|
|
17
|
+
const FALSE_VALUES = new Set([
|
|
18
|
+
"n",
|
|
19
|
+
"no",
|
|
20
|
+
"f",
|
|
21
|
+
"false",
|
|
22
|
+
"0",
|
|
23
|
+
"off"
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/utils.ts
|
|
28
|
+
function print(str = "") {
|
|
29
|
+
return process.stdout.write(str);
|
|
30
|
+
}
|
|
31
|
+
function printLn(...args) {
|
|
32
|
+
console.log(...args);
|
|
33
|
+
}
|
|
34
|
+
function printErrLn(...args) {
|
|
35
|
+
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Semantic categories for user-facing CLI errors.
|
|
39
|
+
*/
|
|
40
|
+
let ErrorCategory = /* @__PURE__ */ function(ErrorCategory$1) {
|
|
41
|
+
ErrorCategory$1["InvalidArg"] = "invalid-arg";
|
|
42
|
+
ErrorCategory$1["Misconfig"] = "misconfig";
|
|
43
|
+
ErrorCategory$1["Internal"] = "internal";
|
|
44
|
+
return ErrorCategory$1;
|
|
45
|
+
}({});
|
|
46
|
+
const ERROR_PRESENTATION = {
|
|
47
|
+
[ErrorCategory.InvalidArg]: {
|
|
48
|
+
code: 2,
|
|
49
|
+
color: "#D73737"
|
|
50
|
+
},
|
|
51
|
+
[ErrorCategory.Misconfig]: {
|
|
52
|
+
code: 254,
|
|
53
|
+
color: "#B854D4"
|
|
54
|
+
},
|
|
55
|
+
[ErrorCategory.Internal]: {
|
|
56
|
+
code: 253,
|
|
57
|
+
color: "#6684E1"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function blockError(str, style, chalk) {
|
|
61
|
+
const lines = wrapText(str, Math.max(getTerminalWidth() - 4, 1));
|
|
62
|
+
const width = Math.max(...lines.map((l) => stringWidth(l)), 0) + 4;
|
|
63
|
+
const colorize = chalk.bgHex(ERROR_PRESENTATION[style].color).hex("#FEFBEC");
|
|
64
|
+
printErrLn(colorize(space(width)));
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const txt = ` ${line}`;
|
|
67
|
+
printErrLn(colorize(txt + space(width, txt)));
|
|
68
|
+
}
|
|
69
|
+
printErrLn(colorize(space(width)));
|
|
70
|
+
}
|
|
71
|
+
function inlineError(str) {
|
|
72
|
+
printErrLn(str);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a structured CLI error.
|
|
76
|
+
*
|
|
77
|
+
* @param message The error text that should be rendered.
|
|
78
|
+
* @param type The semantic error category used to determine presentation and exit code.
|
|
79
|
+
* @returns A structured CLI error object.
|
|
80
|
+
*/
|
|
81
|
+
function createError(message, type) {
|
|
82
|
+
return {
|
|
83
|
+
message,
|
|
84
|
+
type
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gets the process exit code associated with a given CLI error.
|
|
89
|
+
*
|
|
90
|
+
* @param error The structured CLI error to map to a process exit code.
|
|
91
|
+
* @returns The default exit code used for that error type.
|
|
92
|
+
*/
|
|
93
|
+
function getErrorExitCode(error) {
|
|
94
|
+
return ERROR_PRESENTATION[error.type].code;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Prints a user-facing CLI error block.
|
|
98
|
+
*
|
|
99
|
+
* @param error The structured CLI error to render.
|
|
100
|
+
* @returns Nothing.
|
|
101
|
+
*/
|
|
102
|
+
function printError(error, chalk) {
|
|
103
|
+
if (chalk.level > 0) {
|
|
104
|
+
blockError(error.message, error.type, chalk);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
inlineError(error.message);
|
|
108
|
+
}
|
|
109
|
+
function toArray(x) {
|
|
110
|
+
if (!x) return EMPTY_ARRAY;
|
|
111
|
+
if (Array.isArray(x)) return x;
|
|
112
|
+
return [x];
|
|
113
|
+
}
|
|
114
|
+
function resolve(x) {
|
|
115
|
+
return typeof x === "function" ? x() : x;
|
|
116
|
+
}
|
|
117
|
+
function toBool(str) {
|
|
118
|
+
if (typeof str === "boolean") return str;
|
|
119
|
+
str = String(str).trim().toLowerCase();
|
|
120
|
+
if (TRUE_VALUES.has(str)) return true;
|
|
121
|
+
if (FALSE_VALUES.has(str)) return false;
|
|
122
|
+
throw new Error(`Could not cast "${str}" to boolean`);
|
|
123
|
+
}
|
|
124
|
+
function space(len, str) {
|
|
125
|
+
if (str) len -= stringWidth(str);
|
|
126
|
+
return len > 0 ? " ".repeat(len) : "";
|
|
127
|
+
}
|
|
128
|
+
function getTerminalWidth() {
|
|
129
|
+
return process.stderr.columns && process.stderr.columns > 0 ? process.stderr.columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
130
|
+
}
|
|
131
|
+
function wrapText(text, width) {
|
|
132
|
+
if (width <= 0) return text.split("\n");
|
|
133
|
+
const wrappedLines = [];
|
|
134
|
+
for (const rawLine of text.split("\n")) {
|
|
135
|
+
if (rawLine.trim().length === 0) {
|
|
136
|
+
wrappedLines.push("");
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const words = rawLine.trim().split(/\s+/);
|
|
140
|
+
let currentLine = "";
|
|
141
|
+
for (const word of words) {
|
|
142
|
+
const candidate = currentLine.length === 0 ? word : `${currentLine} ${word}`;
|
|
143
|
+
if (currentLine.length > 0 && stringWidth(candidate) > width) {
|
|
144
|
+
wrappedLines.push(currentLine);
|
|
145
|
+
currentLine = word;
|
|
146
|
+
} else currentLine = candidate;
|
|
147
|
+
}
|
|
148
|
+
if (currentLine.length > 0) wrappedLines.push(currentLine);
|
|
149
|
+
}
|
|
150
|
+
return wrappedLines;
|
|
151
|
+
}
|
|
152
|
+
function getProcName(app) {
|
|
153
|
+
const bin = app._bin;
|
|
154
|
+
if (bin != null) return bin;
|
|
155
|
+
const relPath = Path.relative(process.cwd(), process.argv[1]);
|
|
156
|
+
return `${Path.basename(process.argv[0])} ${relPath.length < process.argv[1].length ? relPath : process.argv[1]}`;
|
|
157
|
+
}
|
|
158
|
+
function includes(needle, haystack) {
|
|
159
|
+
if (!haystack) return false;
|
|
160
|
+
if (Array.isArray(haystack)) return haystack.includes(needle);
|
|
161
|
+
return needle === haystack;
|
|
162
|
+
}
|
|
163
|
+
function statSync(path) {
|
|
164
|
+
try {
|
|
165
|
+
return FileSys.lstatSync(path);
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function sortBy(arr, cmp) {
|
|
171
|
+
const collator = new Intl.Collator();
|
|
172
|
+
const values = arr.map(cmp);
|
|
173
|
+
const keys = Array.from(arr.keys());
|
|
174
|
+
keys.sort((a, b) => collator.compare(values[a], values[b]));
|
|
175
|
+
return keys.map((i) => arr[i]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/options.ts
|
|
180
|
+
/**
|
|
181
|
+
* Error thrown when argument parsing encounters an unknown option name.
|
|
182
|
+
*
|
|
183
|
+
* @param option The unrecognized option token, including its leading dashes.
|
|
184
|
+
* @returns A parser error whose message matches the CLI's unknown-option wording.
|
|
185
|
+
*/
|
|
186
|
+
var UnknownOptionError = class extends Error {
|
|
187
|
+
option;
|
|
188
|
+
constructor(option, formattedOption = option) {
|
|
189
|
+
super(`option ${formattedOption} not recognized`);
|
|
190
|
+
this.option = option;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function getEnumValues(item) {
|
|
194
|
+
if (Array.isArray(item.type)) return item.type;
|
|
195
|
+
if (item.type === OptType.ENUM) return item.enumValues;
|
|
196
|
+
}
|
|
197
|
+
function getOptionImplicitValue(opt) {
|
|
198
|
+
if (opt.valueIfSet !== void 0) return resolve(opt.valueIfSet);
|
|
199
|
+
if (opt.type === OptType.BOOL) return true;
|
|
200
|
+
return !resolve(opt.defaultValue);
|
|
201
|
+
}
|
|
202
|
+
function getOptionNoPrefixValue(opt) {
|
|
203
|
+
if (opt.valueIfNoPrefix !== void 0) return resolve(opt.valueIfNoPrefix);
|
|
204
|
+
if (opt.type === OptType.BOOL) return false;
|
|
205
|
+
throw new Error(`\`${getOptName(opt)}\` option must define valueIfNoPrefix when noPrefix is enabled`);
|
|
206
|
+
}
|
|
207
|
+
function isRepeatable(value) {
|
|
208
|
+
return value === true || typeof value === "number";
|
|
209
|
+
}
|
|
210
|
+
function getMaxRepeatCount(value) {
|
|
211
|
+
if (value === true || value === false || value === void 0) return;
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
function getMinRequiredCount(value) {
|
|
215
|
+
if (value === true) return 1;
|
|
216
|
+
if (typeof value === "number") return value;
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
function getFixedPositionalCount(arg) {
|
|
220
|
+
const minRequired = getMinRequiredCount(arg.required);
|
|
221
|
+
if (isRepeatable(arg.repeatable)) {
|
|
222
|
+
const maxRepeatCount = getMaxRepeatCount(arg.repeatable);
|
|
223
|
+
if (maxRepeatCount !== void 0 && maxRepeatCount === minRequired) return minRequired;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
return minRequired > 0 ? 1 : void 0;
|
|
227
|
+
}
|
|
228
|
+
function getTrailingFixedRequiredCount(positonals, startIndex) {
|
|
229
|
+
let total = 0;
|
|
230
|
+
for (let i = startIndex; i < positonals.length; ++i) {
|
|
231
|
+
const fixedCount = getFixedPositionalCount(positonals[i]);
|
|
232
|
+
if (fixedCount === void 0 || fixedCount === 0) return;
|
|
233
|
+
total += fixedCount;
|
|
234
|
+
}
|
|
235
|
+
return total;
|
|
236
|
+
}
|
|
237
|
+
function describeItem(itemName) {
|
|
238
|
+
return itemName ? ` for ${itemName}` : "";
|
|
239
|
+
}
|
|
240
|
+
function formatToken(value, chalk) {
|
|
241
|
+
return chalk.level > 0 ? chalk.bold(value) : `\`${value}\``;
|
|
242
|
+
}
|
|
243
|
+
function formatItem(kind, value, chalk) {
|
|
244
|
+
return `${kind} ${formatToken(value, chalk)}`;
|
|
245
|
+
}
|
|
246
|
+
function formatPath(value, chalk) {
|
|
247
|
+
const fullPath = Path.resolve(value);
|
|
248
|
+
if (chalk.level === 0) return `"${fullPath}"`;
|
|
249
|
+
return chalk.underline(fullPath);
|
|
250
|
+
}
|
|
251
|
+
function describeOptionType(type, enumValues) {
|
|
252
|
+
if (Array.isArray(type) || type === OptType.ENUM) return `one of: ${(enumValues ?? (Array.isArray(type) ? type : [])).join(", ")}`;
|
|
253
|
+
switch (type) {
|
|
254
|
+
case OptType.BOOL: return "a boolean";
|
|
255
|
+
case OptType.INT: return "an integer";
|
|
256
|
+
case OptType.FLOAT: return "a number";
|
|
257
|
+
case OptType.STRING: return "a string";
|
|
258
|
+
case OptType.INPUT_FILE: return "a readable file path";
|
|
259
|
+
case OptType.INPUT_DIRECTORY: return "a readable directory path";
|
|
260
|
+
case OptType.OUTPUT_FILE: return "a writable file path";
|
|
261
|
+
case OptType.OUTPUT_DIRECTORY: return "a writable directory path";
|
|
262
|
+
case OptType.EMPTY_DIRECTORY: return "an empty directory path";
|
|
263
|
+
}
|
|
264
|
+
return "a valid value";
|
|
265
|
+
}
|
|
266
|
+
function ensureFiniteNumber(value, numericValue, type, itemName) {
|
|
267
|
+
if (Number.isNaN(numericValue) || !Number.isFinite(numericValue)) throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected ${describeOptionType(type)})`);
|
|
268
|
+
return numericValue;
|
|
269
|
+
}
|
|
270
|
+
function createPathError(kind, path, message, itemName) {
|
|
271
|
+
return /* @__PURE__ */ new Error(`${kind} ${path} ${message}${describeItem(itemName)}`);
|
|
272
|
+
}
|
|
273
|
+
function ensureInputDirectory(dir, chalk, itemName) {
|
|
274
|
+
const normalizedDir = Path.normalize(dir);
|
|
275
|
+
const fullPath = formatPath(normalizedDir, chalk);
|
|
276
|
+
const stat = statSync(normalizedDir);
|
|
277
|
+
if (!stat) throw createPathError("Directory", fullPath, "does not exist", itemName);
|
|
278
|
+
if (!stat.isDirectory()) throw createPathError("Directory", fullPath, "is not a directory", itemName);
|
|
279
|
+
try {
|
|
280
|
+
FileSys.accessSync(normalizedDir, FileSys.constants.R_OK | FileSys.constants.X_OK);
|
|
281
|
+
} catch {
|
|
282
|
+
throw createPathError("Directory", fullPath, "is not readable", itemName);
|
|
283
|
+
}
|
|
284
|
+
return normalizedDir;
|
|
285
|
+
}
|
|
286
|
+
function ensureOutputDirectory(dir, chalk, itemName) {
|
|
287
|
+
const normalizedDir = Path.normalize(dir);
|
|
288
|
+
const fullPath = formatPath(normalizedDir, chalk);
|
|
289
|
+
const stat = statSync(normalizedDir);
|
|
290
|
+
if (!stat) throw createPathError("Directory", fullPath, "does not exist", itemName);
|
|
291
|
+
if (!stat.isDirectory()) throw createPathError("Directory", fullPath, "is not a directory", itemName);
|
|
292
|
+
try {
|
|
293
|
+
FileSys.accessSync(normalizedDir, FileSys.constants.W_OK);
|
|
294
|
+
} catch {
|
|
295
|
+
throw createPathError("Directory", fullPath, "is not writable", itemName);
|
|
296
|
+
}
|
|
297
|
+
return normalizedDir;
|
|
298
|
+
}
|
|
299
|
+
function assertValidCount(name, value, { allowZero = false } = {}) {
|
|
300
|
+
if (!Number.isInteger(value) || value < 0 || !allowZero && value === 0) throw new Error(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer`);
|
|
301
|
+
}
|
|
302
|
+
function validatePositionalDefinitions(cmd) {
|
|
303
|
+
if (!cmd.positonals?.length) return;
|
|
304
|
+
let encounteredOptionalPositional = false;
|
|
305
|
+
for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
306
|
+
const arg = cmd.positonals[i];
|
|
307
|
+
const repeatable = isRepeatable(arg.repeatable);
|
|
308
|
+
const minRequired = getMinRequiredCount(arg.required);
|
|
309
|
+
const maxRepeatCount = getMaxRepeatCount(arg.repeatable);
|
|
310
|
+
if (typeof arg.required === "number") {
|
|
311
|
+
assertValidCount(`"${arg.name}" argument required count`, arg.required, { allowZero: true });
|
|
312
|
+
if (!repeatable) throw new Error(`"${arg.name}" argument cannot use a numeric required count unless it is repeatable`);
|
|
313
|
+
}
|
|
314
|
+
if (typeof arg.repeatable === "number") assertValidCount(`"${arg.name}" argument repeatable count`, arg.repeatable);
|
|
315
|
+
if (repeatable && i < cmd.positonals.length - 1) {
|
|
316
|
+
if (getTrailingFixedRequiredCount(cmd.positonals, i + 1) === void 0) throw new Error("Repeatable arguments can only be followed by required arguments with fixed counts");
|
|
317
|
+
}
|
|
318
|
+
if (maxRepeatCount !== void 0 && minRequired > maxRepeatCount) throw new Error(`"${arg.name}" argument requires at least ${minRequired} values but allows at most ${maxRepeatCount}`);
|
|
319
|
+
if (encounteredOptionalPositional && minRequired > 0) throw new Error("Required arguments cannot come after optional arguments");
|
|
320
|
+
if (minRequired === 0 && !repeatable) encounteredOptionalPositional = true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function pushRepeatableValue(target, value, itemName, maxCount, kind) {
|
|
324
|
+
if (maxCount !== void 0 && target.length >= maxCount) throw new Error(`"${itemName}" ${kind} allows at most ${maxCount} value${maxCount === 1 ? "" : "s"}`);
|
|
325
|
+
target.push(value);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Formats an option definition for display in help output.
|
|
329
|
+
*
|
|
330
|
+
* @param opt The option metadata to render.
|
|
331
|
+
* @returns A tuple containing the formatted flag label and its description text.
|
|
332
|
+
*/
|
|
333
|
+
function formatOption(opt, chalk) {
|
|
334
|
+
const aliases = [];
|
|
335
|
+
if (opt.alias) if (Array.isArray(opt.alias)) aliases.push(...opt.alias);
|
|
336
|
+
else aliases.push(opt.alias);
|
|
337
|
+
let flags = [...aliases.map((a) => chalk.green(a.length === 1 ? `-${a}` : `--${a}`)), chalk.green(`--${opt.name}`)].join(", ");
|
|
338
|
+
if (!opt.alias && opt.name.length > 1) flags = ` ${flags}`;
|
|
339
|
+
if (opt.type !== OptType.BOOL) {
|
|
340
|
+
const valuePlaceholder = chalk.magenta(getValuePlaceholder(opt));
|
|
341
|
+
flags += opt.valueNotRequired ? `${chalk.grey("[")}=${valuePlaceholder}${chalk.grey("]")}` : `=${valuePlaceholder}`;
|
|
342
|
+
}
|
|
343
|
+
if (opt.noPrefix) flags += `, ${chalk.green(`--no-${opt.name}`)}`;
|
|
344
|
+
let desc = opt.description ?? "";
|
|
345
|
+
let defaultValueText = opt.defaultValueText;
|
|
346
|
+
if (defaultValueText === void 0 && opt.defaultValue !== void 0) defaultValueText = JSON.stringify(resolve(opt.defaultValue));
|
|
347
|
+
if (defaultValueText !== void 0) desc += chalk.yellow(` [default: ${defaultValueText}]`);
|
|
348
|
+
const enumValues = getEnumValues(opt);
|
|
349
|
+
if (enumValues?.length) desc += ` [possible values: ${enumValues.join(", ")}]`;
|
|
350
|
+
return [flags, desc];
|
|
351
|
+
}
|
|
352
|
+
function getValuePlaceholder(opt) {
|
|
353
|
+
if (opt.valuePlaceholder !== void 0) return opt.valuePlaceholder;
|
|
354
|
+
const enumValues = getEnumValues(opt);
|
|
355
|
+
if (enumValues !== void 0) return enumValues.join("|").toUpperCase();
|
|
356
|
+
else if (opt.type == OptType.BOOL) return JSON.stringify(!resolve(opt.defaultValue));
|
|
357
|
+
else if (opt.type === OptType.INT || opt.type === OptType.FLOAT) return "#";
|
|
358
|
+
else if (opt.type === OptType.INPUT_FILE || opt.type === OptType.OUTPUT_FILE) return "FILE";
|
|
359
|
+
else if (opt.type === OptType.INPUT_DIRECTORY || opt.type === OptType.OUTPUT_DIRECTORY || opt.type === OptType.EMPTY_DIRECTORY) return "DIR";
|
|
360
|
+
else return opt.name.toUpperCase();
|
|
361
|
+
}
|
|
362
|
+
function sortOptions(options) {
|
|
363
|
+
return sortBy(options, (option) => option.name);
|
|
364
|
+
}
|
|
365
|
+
function getOptions(cmd) {
|
|
366
|
+
return sortOptions(toArray(cmd.options));
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Validates a command's static option and positional configuration before parsing argv.
|
|
370
|
+
*
|
|
371
|
+
* @param cmd The command definition to validate.
|
|
372
|
+
* @returns Nothing. Throws when the command configuration is internally inconsistent.
|
|
373
|
+
*/
|
|
374
|
+
function validateCommandConfig(cmd) {
|
|
375
|
+
validatePositionalDefinitions(cmd);
|
|
376
|
+
const seenOptionTokens = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const opt of getOptions(cmd)) {
|
|
378
|
+
if (typeof opt.repeatable === "number") assertValidCount(`\`${opt.name}\` option repeatable count`, opt.repeatable);
|
|
379
|
+
if (opt.noPrefix && !opt.valueNotRequired) throw new Error(`\`${getOptName(opt)}\` option cannot enable noPrefix unless valueNotRequired is enabled`);
|
|
380
|
+
if (opt.type === OptType.ENUM && !getEnumValues(opt)?.length) throw new Error(`\`${getOptName(opt)}\` option must define enumValues when using OptType.ENUM`);
|
|
381
|
+
if (opt.noPrefix && opt.type !== OptType.BOOL && opt.valueIfNoPrefix === void 0) throw new Error(`\`${getOptName(opt)}\` option must define valueIfNoPrefix when noPrefix is enabled`);
|
|
382
|
+
const tokens = new Set([opt.name]);
|
|
383
|
+
for (const alias of toArray(opt.alias)) tokens.add(alias);
|
|
384
|
+
if (opt.noPrefix) tokens.add(`no-${opt.name}`);
|
|
385
|
+
for (const token of tokens) {
|
|
386
|
+
const existing = seenOptionTokens.get(token);
|
|
387
|
+
if (existing !== void 0) {
|
|
388
|
+
const currentName = token.startsWith("no-") ? `--${token}` : getOptName(opt);
|
|
389
|
+
throw new Error(`Option token \`${currentName}\` collides with \`${existing}\``);
|
|
390
|
+
}
|
|
391
|
+
const displayName = token.startsWith("no-") ? `--${token}` : token.length === 1 ? `-${token}` : `--${token}`;
|
|
392
|
+
seenOptionTokens.set(token, displayName);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function assignPositionalArguments(positonals, rawArgs, opts, chalk) {
|
|
397
|
+
if (!positonals?.length) return [...rawArgs];
|
|
398
|
+
const args = [];
|
|
399
|
+
let rawArgIdx = 0;
|
|
400
|
+
for (let i = 0; i < positonals.length; ++i) {
|
|
401
|
+
const def = positonals[i];
|
|
402
|
+
const k = def.key ?? def.name;
|
|
403
|
+
if (isRepeatable(def.repeatable)) {
|
|
404
|
+
const trailingFixedRequiredCount = i < positonals.length - 1 ? getTrailingFixedRequiredCount(positonals, i + 1) ?? 0 : 0;
|
|
405
|
+
const valueCount = Math.max(0, rawArgs.length - rawArgIdx - trailingFixedRequiredCount);
|
|
406
|
+
const maxRepeatCount = getMaxRepeatCount(def.repeatable);
|
|
407
|
+
const arr = opts[k] ??= [];
|
|
408
|
+
if (maxRepeatCount !== void 0 && valueCount > maxRepeatCount) throw new Error(`"${def.name}" argument allows at most ${maxRepeatCount} value${maxRepeatCount === 1 ? "" : "s"}`);
|
|
409
|
+
for (let j = 0; j < valueCount; ++j) {
|
|
410
|
+
let value$1 = rawArgs[rawArgIdx++];
|
|
411
|
+
if (def.type != null) value$1 = coerceType(value$1, def.type, chalk, formatItem("argument", def.name, chalk), getEnumValues(def));
|
|
412
|
+
pushRepeatableValue(arr, value$1, def.name, maxRepeatCount, "argument");
|
|
413
|
+
args.push(value$1);
|
|
414
|
+
}
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (rawArgIdx >= rawArgs.length) continue;
|
|
418
|
+
let value = rawArgs[rawArgIdx++];
|
|
419
|
+
if (def.type != null) value = coerceType(value, def.type, chalk, formatItem("argument", def.name, chalk), getEnumValues(def));
|
|
420
|
+
opts[k] = value;
|
|
421
|
+
args.push(value);
|
|
422
|
+
}
|
|
423
|
+
if (rawArgIdx < rawArgs.length) args.push(...rawArgs.slice(rawArgIdx));
|
|
424
|
+
return args;
|
|
425
|
+
}
|
|
426
|
+
function parseArgs(cmd, argv, chalk, { skipRequiredOptions = false, skipRequiredPositionals = false } = {}) {
|
|
427
|
+
const opts = Object.create(null);
|
|
428
|
+
const rawArgs = [];
|
|
429
|
+
let parseFlags = true;
|
|
430
|
+
const allOptions = getOptions(cmd);
|
|
431
|
+
validateCommandConfig(cmd);
|
|
432
|
+
for (const opt of allOptions) if (isRepeatable(opt.repeatable)) {
|
|
433
|
+
const k = opt.key ?? opt.name;
|
|
434
|
+
if (opts[k] === void 0) opts[k] = [];
|
|
435
|
+
}
|
|
436
|
+
if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
437
|
+
const a = cmd.positonals[i];
|
|
438
|
+
if (isRepeatable(a.repeatable)) {
|
|
439
|
+
const k = a.key ?? a.name;
|
|
440
|
+
if (k && opts[k] === void 0) opts[k] = [];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const findOpt = (name) => {
|
|
444
|
+
const option = allOptions.find((o) => o.name === name || includes(name, o.alias));
|
|
445
|
+
if (option !== void 0) return {
|
|
446
|
+
opt: option,
|
|
447
|
+
negated: false
|
|
448
|
+
};
|
|
449
|
+
const negatedOption = allOptions.find((o) => o.noPrefix && `no-${o.name}` === name);
|
|
450
|
+
if (negatedOption !== void 0) return {
|
|
451
|
+
opt: negatedOption,
|
|
452
|
+
negated: true
|
|
453
|
+
};
|
|
454
|
+
};
|
|
455
|
+
const getUnknownShortOption = (cluster) => {
|
|
456
|
+
for (let j = 0; j < cluster.length; ++j) {
|
|
457
|
+
const ch = cluster[j];
|
|
458
|
+
if (!findOpt(ch)) return ch;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
for (let i = 0; i < argv.length; ++i) {
|
|
462
|
+
let token = argv[i];
|
|
463
|
+
if (parseFlags && token === "--") {
|
|
464
|
+
parseFlags = false;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (parseFlags && token.length >= 2 && token.startsWith("-")) {
|
|
468
|
+
let inlineValue;
|
|
469
|
+
if (token.startsWith("--")) {
|
|
470
|
+
if (token.includes("=")) {
|
|
471
|
+
const [left, right] = token.split("=", 2);
|
|
472
|
+
token = left;
|
|
473
|
+
inlineValue = right;
|
|
474
|
+
}
|
|
475
|
+
const name = token.slice(2);
|
|
476
|
+
const match = findOpt(name);
|
|
477
|
+
if (!match) throw new UnknownOptionError(`--${name}`, formatToken(`--${name}`, chalk));
|
|
478
|
+
const { opt, negated } = match;
|
|
479
|
+
if (negated && inlineValue !== void 0) throw new Error(`Option ${formatToken(`--no-${opt.name}`, chalk)} does not take a value`);
|
|
480
|
+
let value = inlineValue;
|
|
481
|
+
if (negated) value = getOptionNoPrefixValue(opt);
|
|
482
|
+
else if (value === void 0) if (opt.valueNotRequired) value = getOptionImplicitValue(opt);
|
|
483
|
+
else if (i < argv.length - 1) value = argv[++i];
|
|
484
|
+
else throw new Error(`Missing required value for option ${formatToken(token, chalk)}`);
|
|
485
|
+
if (opt.type != null) value = coerceType(value, opt.type, chalk, formatItem("option", token, chalk), getEnumValues(opt));
|
|
486
|
+
const k = opt.key ?? opt.name;
|
|
487
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
488
|
+
else opts[k] = value;
|
|
489
|
+
} else {
|
|
490
|
+
const clusterText = token.slice(1);
|
|
491
|
+
const equalIndex = clusterText.indexOf("=");
|
|
492
|
+
const cluster = equalIndex === -1 ? clusterText : clusterText.slice(0, equalIndex);
|
|
493
|
+
const hasInlineAssignment = equalIndex !== -1;
|
|
494
|
+
inlineValue = equalIndex === -1 ? void 0 : clusterText.slice(equalIndex + 1);
|
|
495
|
+
if (hasInlineAssignment) {
|
|
496
|
+
const unknownOption = getUnknownShortOption(cluster);
|
|
497
|
+
if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}`, formatToken(`-${unknownOption}`, chalk));
|
|
498
|
+
}
|
|
499
|
+
let j = 0;
|
|
500
|
+
while (j < cluster.length) {
|
|
501
|
+
const ch = cluster[j];
|
|
502
|
+
const match = findOpt(ch);
|
|
503
|
+
if (!match) throw new UnknownOptionError(`-${ch}`, formatToken(`-${ch}`, chalk));
|
|
504
|
+
const { opt } = match;
|
|
505
|
+
if (opt.valueNotRequired) {
|
|
506
|
+
const k$1 = opt.key ?? opt.name;
|
|
507
|
+
const v = getOptionImplicitValue(opt);
|
|
508
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k$1], v, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
509
|
+
else opts[k$1] = v;
|
|
510
|
+
j += 1;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
let value;
|
|
514
|
+
const remainder = cluster.slice(j + 1);
|
|
515
|
+
if (remainder.length && !hasInlineAssignment) value = remainder;
|
|
516
|
+
else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option ${formatToken(`-${ch}`, chalk)}`);
|
|
517
|
+
else if (inlineValue !== void 0 && remainder.length === 0) value = inlineValue;
|
|
518
|
+
else if (i < argv.length - 1) value = argv[++i];
|
|
519
|
+
else throw new Error(`Missing required value for option ${formatToken(`-${ch}`, chalk)}`);
|
|
520
|
+
if (opt.type != null) value = coerceType(value, opt.type, chalk, formatItem("option", `-${ch}`, chalk), getEnumValues(opt));
|
|
521
|
+
const k = opt.key ?? opt.name;
|
|
522
|
+
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
523
|
+
else opts[k] = value;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} else rawArgs.push(token);
|
|
528
|
+
}
|
|
529
|
+
const args = assignPositionalArguments(cmd.positonals, rawArgs, opts, chalk);
|
|
530
|
+
if (allOptions.length) for (const opt of allOptions) {
|
|
531
|
+
const k = opt.key ?? opt.name;
|
|
532
|
+
if (opts[k] === void 0) {
|
|
533
|
+
if (opt.defaultValue !== void 0) opts[k] = resolve(opt.defaultValue);
|
|
534
|
+
else if (opt.required && !skipRequiredOptions) throw new Error(`${formatToken(getOptName(opt), chalk)} option is required`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
538
|
+
const a = cmd.positonals[i];
|
|
539
|
+
const minRequired = getMinRequiredCount(a.required);
|
|
540
|
+
const k = a.key ?? a.name;
|
|
541
|
+
if (!isRepeatable(a.repeatable) && minRequired > 0 && opts[k] === void 0 && !skipRequiredPositionals) throw new Error(`${formatToken(a.name, chalk)} argument is required`);
|
|
542
|
+
if (isRepeatable(a.repeatable) && (opts[k]?.length ?? 0) < minRequired && !skipRequiredPositionals) throw new Error(`\`${a.name}\` argument requires at least ${minRequired} value${minRequired === 1 ? "" : "s"}`);
|
|
543
|
+
if (k && opts[k] === void 0 && a.defaultValue !== void 0) opts[k] = resolve(a.defaultValue);
|
|
544
|
+
}
|
|
545
|
+
return [args, opts];
|
|
546
|
+
}
|
|
547
|
+
function coerceType(value, type, chalk, itemName, enumValues) {
|
|
548
|
+
const normalizedEnumValue = () => {
|
|
549
|
+
const normalized = String(value).trim().toLowerCase();
|
|
550
|
+
const allowedValues = enumValues ?? (Array.isArray(type) ? type : void 0);
|
|
551
|
+
if (allowedValues !== void 0 && !allowedValues.includes(normalized)) throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected one of: ${allowedValues.join(", ")})`);
|
|
552
|
+
return normalized;
|
|
553
|
+
};
|
|
554
|
+
if (Array.isArray(type)) return normalizedEnumValue();
|
|
555
|
+
switch (type) {
|
|
556
|
+
case OptType.BOOL: try {
|
|
557
|
+
return toBool(value);
|
|
558
|
+
} catch {
|
|
559
|
+
throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected ${describeOptionType(type)})`);
|
|
560
|
+
}
|
|
561
|
+
case OptType.INT: return Math.trunc(ensureFiniteNumber(value, Number(value), type, itemName));
|
|
562
|
+
case OptType.FLOAT: return ensureFiniteNumber(value, Number(value), type, itemName);
|
|
563
|
+
case OptType.ENUM: return normalizedEnumValue();
|
|
564
|
+
case OptType.STRING: return String(value);
|
|
565
|
+
case OptType.INPUT_FILE: {
|
|
566
|
+
if (value === "-") return "/dev/stdin";
|
|
567
|
+
const file = Path.normalize(value);
|
|
568
|
+
const fullPath = formatPath(file, chalk);
|
|
569
|
+
const stat = statSync(file);
|
|
570
|
+
if (!stat) throw createPathError("File", fullPath, "does not exist", itemName);
|
|
571
|
+
if (!stat.isFile()) throw createPathError("File", fullPath, "is not a file", itemName);
|
|
572
|
+
try {
|
|
573
|
+
FileSys.accessSync(file, FileSys.constants.R_OK);
|
|
574
|
+
} catch {
|
|
575
|
+
throw createPathError("File", fullPath, "is not readable", itemName);
|
|
576
|
+
}
|
|
577
|
+
return file;
|
|
578
|
+
}
|
|
579
|
+
case OptType.INPUT_DIRECTORY: return ensureInputDirectory(value, chalk, itemName);
|
|
580
|
+
case OptType.OUTPUT_FILE: {
|
|
581
|
+
if (value === "-") return "/dev/stdout";
|
|
582
|
+
const file = Path.normalize(value);
|
|
583
|
+
const fullPath = formatPath(file, chalk);
|
|
584
|
+
const stat = statSync(file);
|
|
585
|
+
if (stat) {
|
|
586
|
+
if (!stat.isFile()) throw createPathError("File", fullPath, "is not a file", itemName);
|
|
587
|
+
try {
|
|
588
|
+
FileSys.accessSync(file, FileSys.constants.W_OK);
|
|
589
|
+
} catch {
|
|
590
|
+
throw createPathError("File", fullPath, "is not writable", itemName);
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
const parentDir = Path.dirname(file);
|
|
594
|
+
const fullParentDir = formatPath(parentDir, chalk);
|
|
595
|
+
const parentStat = statSync(parentDir);
|
|
596
|
+
if (!parentStat) throw createPathError("Directory", fullParentDir, "does not exist", itemName);
|
|
597
|
+
if (!parentStat.isDirectory()) throw createPathError("Directory", fullParentDir, "is not a directory", itemName);
|
|
598
|
+
try {
|
|
599
|
+
FileSys.accessSync(parentDir, FileSys.constants.W_OK);
|
|
600
|
+
} catch {
|
|
601
|
+
throw createPathError("Directory", fullParentDir, "is not writable", itemName);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return file;
|
|
605
|
+
}
|
|
606
|
+
case OptType.OUTPUT_DIRECTORY: return ensureOutputDirectory(value, chalk, itemName);
|
|
607
|
+
case OptType.EMPTY_DIRECTORY: {
|
|
608
|
+
const dir = Path.normalize(value);
|
|
609
|
+
const fullPath = formatPath(dir, chalk);
|
|
610
|
+
let files = [];
|
|
611
|
+
try {
|
|
612
|
+
files = FileSys.readdirSync(dir);
|
|
613
|
+
} catch (err) {
|
|
614
|
+
if (err?.code === "ENOENT") {
|
|
615
|
+
const parentDir = Path.dirname(dir);
|
|
616
|
+
const fullParentDir = formatPath(parentDir, chalk);
|
|
617
|
+
const parentStat = statSync(parentDir);
|
|
618
|
+
if (!parentStat) throw createPathError("Directory", fullParentDir, "does not exist", itemName);
|
|
619
|
+
if (!parentStat.isDirectory()) throw createPathError("Directory", fullParentDir, "is not a directory", itemName);
|
|
620
|
+
try {
|
|
621
|
+
FileSys.accessSync(parentDir, FileSys.constants.W_OK);
|
|
622
|
+
} catch {
|
|
623
|
+
throw createPathError("Directory", fullParentDir, "is not writable", itemName);
|
|
624
|
+
}
|
|
625
|
+
} else throw createPathError("Directory", fullPath, "is not readable", itemName);
|
|
626
|
+
}
|
|
627
|
+
if (files.length) throw createPathError("Directory", fullPath, "is not empty", itemName);
|
|
628
|
+
return dir;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return value;
|
|
632
|
+
}
|
|
633
|
+
function getOptName(opt) {
|
|
634
|
+
return (opt.name.length > 1 ? "--" : "-") + opt.name;
|
|
635
|
+
}
|
|
636
|
+
function findSubCommand(name, subCommands) {
|
|
637
|
+
const cmdName = String(name).trim().toLowerCase();
|
|
638
|
+
return subCommands.find((c) => c.name.toLowerCase() === cmdName || toArray(c.alias).some((alias) => alias.toLowerCase() === cmdName));
|
|
639
|
+
}
|
|
640
|
+
function getCommand(path, subCommands) {
|
|
641
|
+
if (!path.length) throw new Error("Command path is required.");
|
|
642
|
+
let current = subCommands;
|
|
643
|
+
let command;
|
|
644
|
+
const resolvedPath = [];
|
|
645
|
+
for (let i = 0; i < path.length; ++i) {
|
|
646
|
+
const segment = path[i];
|
|
647
|
+
const next = findSubCommand(segment, current);
|
|
648
|
+
if (next === void 0) throw new Error(`Command "${segment}" does not exist.`);
|
|
649
|
+
command = next;
|
|
650
|
+
resolvedPath.push(next.name);
|
|
651
|
+
if (i < path.length - 1) {
|
|
652
|
+
if (!hasSubCommands(next)) throw new Error(`Command "${resolvedPath.join(" ")}" does not have subCommands.`);
|
|
653
|
+
current = next.subCommands;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
command,
|
|
658
|
+
path: resolvedPath
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
//#endregion
|
|
663
|
+
//#region src/print-command-help.ts
|
|
664
|
+
function shouldWrapHelpEntry$1(label, description, labelWidth) {
|
|
665
|
+
if (!description) return false;
|
|
666
|
+
const terminalWidth = getTerminalWidth();
|
|
667
|
+
const inlineIndent = labelWidth + 4;
|
|
668
|
+
const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
|
|
669
|
+
return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
|
|
670
|
+
}
|
|
671
|
+
function printHelpEntry$1(label, description, labelWidth, forceWrap = false) {
|
|
672
|
+
print(` ${label}`);
|
|
673
|
+
if (!description) {
|
|
674
|
+
printLn();
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
if (!(forceWrap || shouldWrapHelpEntry$1(label, description, labelWidth))) {
|
|
678
|
+
printLn(`${space(labelWidth + 2, label)}${description}`);
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
printLn();
|
|
682
|
+
const terminalWidth = getTerminalWidth();
|
|
683
|
+
const descriptionIndent = " ".repeat(10);
|
|
684
|
+
const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
|
|
685
|
+
for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
function printOptionEntries$1(entries) {
|
|
689
|
+
const width = Math.max(...entries.map((line) => stringWidth(line[0])));
|
|
690
|
+
const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry$1(label, description, width));
|
|
691
|
+
entries.forEach(([label, description], index) => {
|
|
692
|
+
if (printHelpEntry$1(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
function getCommandLabel(context, path) {
|
|
696
|
+
const proc = context.chalk.cyan(getProcName(context.app));
|
|
697
|
+
if (!path.length) return proc;
|
|
698
|
+
return `${proc} ${path.join(" ")}`;
|
|
699
|
+
}
|
|
700
|
+
function formatUsageOption(opt, chalk) {
|
|
701
|
+
const optionName = chalk.green(getOptName(opt));
|
|
702
|
+
if (opt.type === OptType.BOOL) return optionName;
|
|
703
|
+
const valuePlaceholder = chalk.magenta(getValuePlaceholder(opt));
|
|
704
|
+
if (opt.valueNotRequired) return `${optionName}${chalk.grey("[")}=${valuePlaceholder}${chalk.grey("]")}`;
|
|
705
|
+
return `${optionName}=${valuePlaceholder}`;
|
|
706
|
+
}
|
|
707
|
+
function formatUsageArgument(arg, chalk) {
|
|
708
|
+
const argumentName = chalk.magenta(arg.repeatable ? `${arg.name}...` : arg.name);
|
|
709
|
+
return `${chalk.grey(arg.required ? "<" : "[")}${argumentName}${chalk.grey(arg.required ? ">" : "]")}`;
|
|
710
|
+
}
|
|
711
|
+
function printCommandHelp(context, cmd, path = []) {
|
|
712
|
+
const app = context.app;
|
|
713
|
+
const chalk = context.chalk;
|
|
714
|
+
if (cmd.description) {
|
|
715
|
+
printLn(cmd.description);
|
|
716
|
+
printLn();
|
|
717
|
+
}
|
|
718
|
+
if (cmd.longDescription) {
|
|
719
|
+
printLn(cmd.longDescription);
|
|
720
|
+
printLn();
|
|
721
|
+
}
|
|
722
|
+
if (cmd === app) {
|
|
723
|
+
const author = app._author;
|
|
724
|
+
if (author) {
|
|
725
|
+
printLn(`Author: ${author}`);
|
|
726
|
+
printLn();
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
printLn(chalk.yellow("Usage:"));
|
|
730
|
+
print(` ${getCommandLabel(context, path)}`);
|
|
731
|
+
if (hasSubCommands(cmd)) print(` ${chalk.gray("<")}command${chalk.gray(">")}`);
|
|
732
|
+
if (isExecutable(cmd)) {
|
|
733
|
+
const allOptions = getOptions(cmd);
|
|
734
|
+
if (allOptions.length) {
|
|
735
|
+
let otherOptions = 0;
|
|
736
|
+
for (const opt of allOptions) if (opt.required) print(` ${formatUsageOption(opt, chalk)}`);
|
|
737
|
+
else ++otherOptions;
|
|
738
|
+
if (otherOptions) print(` ${chalk.gray("[")}${chalk.magenta("--options")}${chalk.gray("]")}`);
|
|
739
|
+
}
|
|
740
|
+
if (cmd.positonals?.length) {
|
|
741
|
+
print(` ${chalk.grey("[")}--${chalk.grey("]")}`);
|
|
742
|
+
for (const arg of cmd.positonals) print(` ${formatUsageArgument(arg, chalk)}`);
|
|
743
|
+
}
|
|
744
|
+
} else if (!hasSubCommands(cmd)) print(` ${chalk.gray("[options] [arguments]")}`);
|
|
745
|
+
printLn();
|
|
746
|
+
if (isExecutable(cmd)) {
|
|
747
|
+
const allOptions = getOptions(cmd);
|
|
748
|
+
if (allOptions.length) {
|
|
749
|
+
printLn(chalk.yellow("\nOptions:"));
|
|
750
|
+
printOptionEntries$1(allOptions.map((option) => formatOption(option, chalk)));
|
|
751
|
+
}
|
|
752
|
+
if (cmd.positonals?.length) {
|
|
753
|
+
printLn(chalk.yellow("\nArguments:"));
|
|
754
|
+
const width = Math.max(...cmd.positonals.map((arg) => stringWidth(arg.name)));
|
|
755
|
+
for (const arg of cmd.positonals) printHelpEntry$1(chalk.green(arg.name), arg.description, width);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (hasSubCommands(cmd)) {
|
|
759
|
+
printLn();
|
|
760
|
+
printAvailableCommands(cmd.subCommands, "Sub-commands:", chalk);
|
|
761
|
+
}
|
|
762
|
+
const globalOptions = getGlobalOptions(app);
|
|
763
|
+
if (globalOptions.length) {
|
|
764
|
+
printLn();
|
|
765
|
+
printLn(chalk.yellow("Global Options:"));
|
|
766
|
+
printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
|
|
767
|
+
}
|
|
768
|
+
if (cmd.alias) {
|
|
769
|
+
const aliases = toArray(cmd.alias);
|
|
770
|
+
printLn(chalk.yellow(`\nAlias${aliases.length !== 1 ? "es" : ""}: `) + aliases.join(chalk.gray(", ")));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/builtins.ts
|
|
776
|
+
const DEFAULT_HELP_CONFIG = {
|
|
777
|
+
name: "help",
|
|
778
|
+
alias: "h"
|
|
779
|
+
};
|
|
780
|
+
const DEFAULT_VERSION_CONFIG = { name: "version" };
|
|
781
|
+
const DEFAULT_COLOR_CONFIG = { name: "color" };
|
|
782
|
+
function resolveBuiltinConfig(defaults, config) {
|
|
783
|
+
return {
|
|
784
|
+
...defaults,
|
|
785
|
+
...config ?? {}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
function getHelpConfig(app) {
|
|
789
|
+
return resolveBuiltinConfig(DEFAULT_HELP_CONFIG, app._helpConfig);
|
|
790
|
+
}
|
|
791
|
+
function getVersionConfig(app) {
|
|
792
|
+
return resolveBuiltinConfig(DEFAULT_VERSION_CONFIG, app._versionConfig);
|
|
793
|
+
}
|
|
794
|
+
function getColorConfig(app) {
|
|
795
|
+
return {
|
|
796
|
+
...DEFAULT_COLOR_CONFIG,
|
|
797
|
+
...app._colorConfig ?? {}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function createBuiltinOption(key, config, description) {
|
|
801
|
+
if (config.disableOption) return;
|
|
802
|
+
return {
|
|
803
|
+
name: config.name,
|
|
804
|
+
...config.alias !== void 0 ? { alias: config.alias } : {},
|
|
805
|
+
description,
|
|
806
|
+
key,
|
|
807
|
+
type: OptType.BOOL,
|
|
808
|
+
valueNotRequired: true,
|
|
809
|
+
valueIfSet: true
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function createHelpCommand(app) {
|
|
813
|
+
const config = getHelpConfig(app);
|
|
814
|
+
if (config.disableCommand) return;
|
|
815
|
+
const command = new Command(config.name).describe("Displays help for a command").arg("command", {
|
|
816
|
+
description: "The command path.",
|
|
817
|
+
repeatable: true
|
|
818
|
+
}).run(async ({ command: commandPath = [] }, context) => {
|
|
819
|
+
const rootCommands = getRootCommands(context.app);
|
|
820
|
+
if (commandPath.length) {
|
|
821
|
+
const { command: command$1, path } = getCommand(commandPath, rootCommands);
|
|
822
|
+
printCommandHelp(context, command$1, path);
|
|
823
|
+
} else if (hasSubCommands(context.app)) printHelp(context, rootCommands);
|
|
824
|
+
else printCommandHelp(context, context.app, []);
|
|
825
|
+
return 0;
|
|
826
|
+
});
|
|
827
|
+
const aliases = toArray(config.alias);
|
|
828
|
+
if (aliases.length) command.aliases(...aliases);
|
|
829
|
+
return command;
|
|
830
|
+
}
|
|
831
|
+
function createVersionCommand(app) {
|
|
832
|
+
const config = getVersionConfig(app);
|
|
833
|
+
if (config.disableCommand) return;
|
|
834
|
+
const command = new Command(config.name).describe("Displays current version").run(async (_, context) => {
|
|
835
|
+
printLn(context.app._version);
|
|
836
|
+
return 0;
|
|
837
|
+
});
|
|
838
|
+
const aliases = toArray(config.alias);
|
|
839
|
+
if (aliases.length) command.aliases(...aliases);
|
|
840
|
+
return command;
|
|
841
|
+
}
|
|
842
|
+
function getHelpOption(app) {
|
|
843
|
+
return createBuiltinOption("help", getHelpConfig(app), "Show help text");
|
|
844
|
+
}
|
|
845
|
+
function getVersionOption(app) {
|
|
846
|
+
return createBuiltinOption("version", getVersionConfig(app), "Show current version");
|
|
847
|
+
}
|
|
848
|
+
function getColorOption(app) {
|
|
849
|
+
const config = getColorConfig(app);
|
|
850
|
+
if (config.disableOption) return;
|
|
851
|
+
return {
|
|
852
|
+
name: config.name,
|
|
853
|
+
...config.alias !== void 0 ? { alias: config.alias } : {},
|
|
854
|
+
description: "Control ANSI color output.",
|
|
855
|
+
key: "color",
|
|
856
|
+
type: OptType.ENUM,
|
|
857
|
+
enumValues: [
|
|
858
|
+
"always",
|
|
859
|
+
"never",
|
|
860
|
+
"auto"
|
|
861
|
+
],
|
|
862
|
+
valuePlaceholder: "WHEN",
|
|
863
|
+
valueNotRequired: true,
|
|
864
|
+
valueIfSet: "always",
|
|
865
|
+
noPrefix: true,
|
|
866
|
+
valueIfNoPrefix: "never",
|
|
867
|
+
defaultValue: "auto"
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function getGlobalOptions(app) {
|
|
871
|
+
return sortOptions([...[
|
|
872
|
+
getHelpOption(app),
|
|
873
|
+
getVersionOption(app),
|
|
874
|
+
getColorOption(app)
|
|
875
|
+
].filter((value) => value !== void 0), ...app._globalOptions ?? []]);
|
|
876
|
+
}
|
|
877
|
+
function getRootCommands(app) {
|
|
878
|
+
return [...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [], ...[createVersionCommand(app), createHelpCommand(app)].filter((value) => value !== void 0)];
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/app-help.ts
|
|
883
|
+
function shouldWrapHelpEntry(label, description, labelWidth) {
|
|
884
|
+
if (!description) return false;
|
|
885
|
+
const terminalWidth = getTerminalWidth();
|
|
886
|
+
const inlineIndent = labelWidth + 4;
|
|
887
|
+
const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
|
|
888
|
+
return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
|
|
889
|
+
}
|
|
890
|
+
function printHelpEntry(label, description, labelWidth, forceWrap = false) {
|
|
891
|
+
print(` ${label}`);
|
|
892
|
+
if (!description) {
|
|
893
|
+
printLn();
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
if (!(forceWrap || shouldWrapHelpEntry(label, description, labelWidth))) {
|
|
897
|
+
printLn(`${space(labelWidth + 2, label)}${description}`);
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
printLn();
|
|
901
|
+
const terminalWidth = getTerminalWidth();
|
|
902
|
+
const descriptionIndent = " ".repeat(10);
|
|
903
|
+
const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
|
|
904
|
+
for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
907
|
+
function printOptionEntries(entries) {
|
|
908
|
+
const width = Math.max(...entries.map((line) => stringWidth(line[0])));
|
|
909
|
+
const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry(label, description, width));
|
|
910
|
+
entries.forEach(([label, description], index) => {
|
|
911
|
+
if (printHelpEntry(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function printHelp(context, commands) {
|
|
915
|
+
const app = context.app;
|
|
916
|
+
const chalk = context.chalk;
|
|
917
|
+
print(chalk.green(app.name));
|
|
918
|
+
const { _author: author, _version: version } = app;
|
|
919
|
+
if (version) print(` ver. ${chalk.yellow(version)}`);
|
|
920
|
+
if (author) print(` by ${chalk.cyan(author)}`);
|
|
921
|
+
printLn();
|
|
922
|
+
if (app.description) {
|
|
923
|
+
printLn();
|
|
924
|
+
printLn(app.description);
|
|
925
|
+
}
|
|
926
|
+
printLn();
|
|
927
|
+
printLn(chalk.yellow("Usage:"));
|
|
928
|
+
print(` ${chalk.cyan(getProcName(app))}`);
|
|
929
|
+
if (hasSubCommands(app)) print(` ${chalk.gray("[")}--GLOBAL-OPTIONS${chalk.gray("]")} ${chalk.gray("<")}COMMAND${chalk.gray(">")}`);
|
|
930
|
+
printLn("\n");
|
|
931
|
+
if (commands.length) printAvailableCommands(commands, "Commands:", chalk);
|
|
932
|
+
const globalOptions = getGlobalOptions(app);
|
|
933
|
+
if (globalOptions.length) {
|
|
934
|
+
printLn();
|
|
935
|
+
printLn(chalk.yellow("Global Options:"));
|
|
936
|
+
printOptionEntries(globalOptions.map((option) => formatOption(option, chalk)));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function printAvailableCommands(commands, title, chalk) {
|
|
940
|
+
if (!commands.length) return;
|
|
941
|
+
printLn(chalk.yellow(title));
|
|
942
|
+
const width = Math.max(...commands.map((c) => stringWidth(c.name))) + 2;
|
|
943
|
+
for (const cmd of commands) printHelpEntry(chalk.green(cmd.name), cmd.description, width);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region src/run.ts
|
|
948
|
+
function normalizeAppAsLeafCommand(app) {
|
|
949
|
+
const handler = app.handler;
|
|
950
|
+
if (handler === void 0) throw new Error("Command is not executable.");
|
|
951
|
+
return {
|
|
952
|
+
name: app.name,
|
|
953
|
+
...app.alias !== void 0 ? { alias: app.alias } : {},
|
|
954
|
+
...app.description !== void 0 ? { description: app.description } : {},
|
|
955
|
+
...app.longDescription !== void 0 ? { longDescription: app.longDescription } : {},
|
|
956
|
+
...app.options !== void 0 ? { options: [...app.options] } : {},
|
|
957
|
+
...app.positonals !== void 0 ? { positonals: [...app.positonals] } : {},
|
|
958
|
+
execute: handler
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function normalizeLeafCommand(cmd) {
|
|
962
|
+
const handler = getExecuteHandler(cmd);
|
|
963
|
+
if (handler === void 0) throw new Error("Command is not executable.");
|
|
964
|
+
return {
|
|
965
|
+
name: cmd.name,
|
|
966
|
+
...cmd.alias !== void 0 ? { alias: cmd.alias } : {},
|
|
967
|
+
...cmd.description !== void 0 ? { description: cmd.description } : {},
|
|
968
|
+
...cmd.longDescription !== void 0 ? { longDescription: cmd.longDescription } : {},
|
|
969
|
+
...cmd.options !== void 0 ? { options: [...cmd.options] } : {},
|
|
970
|
+
...cmd.positonals !== void 0 ? { positonals: [...cmd.positonals] } : {},
|
|
971
|
+
execute: handler
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function formatConfigError(error) {
|
|
975
|
+
return `Config Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
976
|
+
}
|
|
977
|
+
function mergeCommandOptions(app, cmd) {
|
|
978
|
+
const normalized = normalizeLeafCommand(cmd);
|
|
979
|
+
return {
|
|
980
|
+
...normalized,
|
|
981
|
+
options: [...getGlobalOptions(app), ...normalized.options ?? []]
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function createExecutionContext(app, opts, path = []) {
|
|
985
|
+
return new ExecutionContext(app, opts?.color ?? "auto", path);
|
|
986
|
+
}
|
|
987
|
+
function createGlobalParseCommand(app) {
|
|
988
|
+
return {
|
|
989
|
+
name: app.name,
|
|
990
|
+
options: getGlobalOptions(app),
|
|
991
|
+
positonals: [{
|
|
992
|
+
name: "argv",
|
|
993
|
+
key: "argv",
|
|
994
|
+
repeatable: true
|
|
995
|
+
}],
|
|
996
|
+
execute() {}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function getRequestedColorMode(app, argv) {
|
|
1000
|
+
const colorOption = getColorOption(app);
|
|
1001
|
+
if (colorOption === void 0) return "auto";
|
|
1002
|
+
let mode = "auto";
|
|
1003
|
+
const shortNames = /* @__PURE__ */ new Set();
|
|
1004
|
+
const longNames = new Set([colorOption.name, ...Array.isArray(colorOption.alias) ? colorOption.alias : colorOption.alias !== void 0 ? [colorOption.alias] : []]);
|
|
1005
|
+
if (colorOption.name.length === 1) shortNames.add(colorOption.name);
|
|
1006
|
+
for (const alias of longNames) if (alias.length === 1) shortNames.add(alias);
|
|
1007
|
+
for (const token of argv) {
|
|
1008
|
+
if (token === `--no-${colorOption.name}`) {
|
|
1009
|
+
mode = "never";
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (token.startsWith("--")) {
|
|
1013
|
+
const body = token.slice(2);
|
|
1014
|
+
const equalIndex = body.indexOf("=");
|
|
1015
|
+
const name = equalIndex === -1 ? body : body.slice(0, equalIndex);
|
|
1016
|
+
if (!longNames.has(name)) continue;
|
|
1017
|
+
if (equalIndex === -1) {
|
|
1018
|
+
mode = "always";
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const value = body.slice(equalIndex + 1);
|
|
1022
|
+
if (value === "always" || value === "never" || value === "auto") mode = value;
|
|
1023
|
+
else mode = "never";
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (token.startsWith("-") && !token.startsWith("--")) {
|
|
1027
|
+
const clusterText = token.slice(1);
|
|
1028
|
+
if ([...clusterText.includes("=") ? clusterText.slice(0, clusterText.indexOf("=")) : clusterText].some((ch) => shortNames.has(ch))) mode = "always";
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return mode;
|
|
1032
|
+
}
|
|
1033
|
+
function parseGlobalOptions(app, argv) {
|
|
1034
|
+
const colorMode = getRequestedColorMode(app, argv);
|
|
1035
|
+
try {
|
|
1036
|
+
const [, opts] = parseArgs(createGlobalParseCommand(app), extractGlobalOptionArgv(argv, getGlobalOptions(app)), createChalk(colorMode), {
|
|
1037
|
+
skipRequiredOptions: true,
|
|
1038
|
+
skipRequiredPositionals: true
|
|
1039
|
+
});
|
|
1040
|
+
return { opts };
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
if (err instanceof UnknownOptionError) return {};
|
|
1043
|
+
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
1044
|
+
return {
|
|
1045
|
+
opts: { color: colorMode },
|
|
1046
|
+
result: {
|
|
1047
|
+
code: getErrorExitCode(error),
|
|
1048
|
+
error
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function getOptionTokenConsumption(argv, index, rawOptions) {
|
|
1054
|
+
const token = argv[index];
|
|
1055
|
+
if (token === "--") return argv.length - index;
|
|
1056
|
+
if (!token.startsWith("-") || token === "-") return 0;
|
|
1057
|
+
const options = rawOptions;
|
|
1058
|
+
const matchLong = (name) => options.find((opt) => opt.name === name || opt.noPrefix && `no-${opt.name}` === name || opt.alias !== void 0 && (Array.isArray(opt.alias) ? opt.alias.includes(name) : opt.alias === name));
|
|
1059
|
+
if (token.startsWith("--")) {
|
|
1060
|
+
const [left] = token.split("=", 1);
|
|
1061
|
+
const option = matchLong(left.slice(2));
|
|
1062
|
+
if (option === void 0) return 0;
|
|
1063
|
+
if (left !== token || option.valueNotRequired) return 1;
|
|
1064
|
+
return index < argv.length - 1 ? 2 : 1;
|
|
1065
|
+
}
|
|
1066
|
+
const clusterText = token.slice(1);
|
|
1067
|
+
const equalIndex = clusterText.indexOf("=");
|
|
1068
|
+
const cluster = equalIndex === -1 ? clusterText : clusterText.slice(0, equalIndex);
|
|
1069
|
+
for (let i = 0; i < cluster.length; ++i) {
|
|
1070
|
+
const option = options.find((opt) => opt.name === cluster[i] || (Array.isArray(opt.alias) ? opt.alias.includes(cluster[i]) : opt.alias === cluster[i]));
|
|
1071
|
+
if (option === void 0) return 0;
|
|
1072
|
+
if (!option.valueNotRequired) {
|
|
1073
|
+
if (i < cluster.length - 1 || equalIndex !== -1) return 1;
|
|
1074
|
+
return index < argv.length - 1 ? 2 : 1;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return 1;
|
|
1078
|
+
}
|
|
1079
|
+
function extractGlobalOptionArgv(argv, globalOptions) {
|
|
1080
|
+
const extracted = [];
|
|
1081
|
+
for (let index = 0; index < argv.length;) {
|
|
1082
|
+
const consumed = getOptionTokenConsumption(argv, index, globalOptions);
|
|
1083
|
+
if (consumed > 0) {
|
|
1084
|
+
extracted.push(...argv.slice(index, index + consumed));
|
|
1085
|
+
index += consumed;
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
index += 1;
|
|
1089
|
+
}
|
|
1090
|
+
return extracted;
|
|
1091
|
+
}
|
|
1092
|
+
function resolveCommandWithGlobalOptions(argv, subCommands, globalOptions) {
|
|
1093
|
+
const path = [];
|
|
1094
|
+
const commandIndexes = /* @__PURE__ */ new Set();
|
|
1095
|
+
let command;
|
|
1096
|
+
let current = subCommands;
|
|
1097
|
+
let index = 0;
|
|
1098
|
+
while (index < argv.length) {
|
|
1099
|
+
const token = argv[index];
|
|
1100
|
+
const consumed = getOptionTokenConsumption(argv, index, globalOptions);
|
|
1101
|
+
if (consumed > 0) {
|
|
1102
|
+
index += consumed;
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const candidate = findSubCommand(token, current);
|
|
1106
|
+
if (candidate === void 0) break;
|
|
1107
|
+
command = candidate;
|
|
1108
|
+
commandIndexes.add(index);
|
|
1109
|
+
path.push(candidate.name);
|
|
1110
|
+
index += 1;
|
|
1111
|
+
if (!hasSubCommands(candidate)) break;
|
|
1112
|
+
current = candidate.subCommands;
|
|
1113
|
+
}
|
|
1114
|
+
return {
|
|
1115
|
+
command,
|
|
1116
|
+
path,
|
|
1117
|
+
remainingArgv: argv.filter((_, argvIndex) => !commandIndexes.has(argvIndex))
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function getFirstNonGlobalToken(argv, globalOptions) {
|
|
1121
|
+
for (let index = 0; index < argv.length;) {
|
|
1122
|
+
const consumed = getOptionTokenConsumption(argv, index, globalOptions);
|
|
1123
|
+
if (consumed > 0) {
|
|
1124
|
+
index += consumed;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
return argv[index];
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function unknownCommandResult(app, commandName, chalk) {
|
|
1131
|
+
const error = createError(`${getProcName(app)}: unknown command ${formatToken(commandName, chalk)}`, ErrorCategory.InvalidArg);
|
|
1132
|
+
return {
|
|
1133
|
+
code: getErrorExitCode(error),
|
|
1134
|
+
error
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function isOptionLikeToken(token) {
|
|
1138
|
+
return token.length >= 2 && token.startsWith("-");
|
|
1139
|
+
}
|
|
1140
|
+
function unknownOptionResult(app, optionToken, chalk) {
|
|
1141
|
+
const error = createError(`${getProcName(app)}: ${new UnknownOptionError(optionToken, formatToken(optionToken, chalk)).message}`, ErrorCategory.InvalidArg);
|
|
1142
|
+
return {
|
|
1143
|
+
code: getErrorExitCode(error),
|
|
1144
|
+
error
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function unknownTokenResult(app, token, chalk) {
|
|
1148
|
+
if (isOptionLikeToken(token)) return unknownOptionResult(app, token, chalk);
|
|
1149
|
+
return unknownCommandResult(app, token, chalk);
|
|
1150
|
+
}
|
|
1151
|
+
async function executeLeaf(app, cmd, rawArgs, path) {
|
|
1152
|
+
try {
|
|
1153
|
+
validateCommandConfig(mergeCommandOptions(app, cmd));
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
const error = createError(formatConfigError(err), ErrorCategory.Misconfig);
|
|
1156
|
+
return {
|
|
1157
|
+
result: {
|
|
1158
|
+
code: getErrorExitCode(error),
|
|
1159
|
+
error
|
|
1160
|
+
},
|
|
1161
|
+
context: createExecutionContext(app, void 0, path)
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
let opts;
|
|
1165
|
+
const globalParse = parseGlobalOptions(app, rawArgs);
|
|
1166
|
+
if (globalParse.result !== void 0) return {
|
|
1167
|
+
result: globalParse.result,
|
|
1168
|
+
context: createExecutionContext(app, globalParse.opts, path)
|
|
1169
|
+
};
|
|
1170
|
+
const parseChalk = createChalk(globalParse.opts?.color ?? "auto");
|
|
1171
|
+
try {
|
|
1172
|
+
const parseableCommand = mergeCommandOptions(app, cmd);
|
|
1173
|
+
const [, provisionalOpts] = parseArgs(parseableCommand, rawArgs, parseChalk, {
|
|
1174
|
+
skipRequiredOptions: true,
|
|
1175
|
+
skipRequiredPositionals: true
|
|
1176
|
+
});
|
|
1177
|
+
const provisionalContext = createExecutionContext(app, provisionalOpts, path);
|
|
1178
|
+
if (provisionalOpts.help) {
|
|
1179
|
+
if (cmd === app && !hasSubCommands(app)) {
|
|
1180
|
+
printHelp(provisionalContext, getRootCommands(app));
|
|
1181
|
+
return {
|
|
1182
|
+
result: { code: 0 },
|
|
1183
|
+
context: provisionalContext
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
printCommandHelp(provisionalContext, cmd, path);
|
|
1187
|
+
return {
|
|
1188
|
+
result: { code: 0 },
|
|
1189
|
+
context: provisionalContext
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
if (provisionalOpts.version) {
|
|
1193
|
+
printLn(app._version);
|
|
1194
|
+
return {
|
|
1195
|
+
result: { code: 0 },
|
|
1196
|
+
context: provisionalContext
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
[, opts] = parseArgs(parseableCommand, rawArgs, parseChalk);
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
if (err instanceof UnknownOptionError) {
|
|
1202
|
+
const error$1 = createError(`${getProcName(app)}: ${err.message}`, ErrorCategory.InvalidArg);
|
|
1203
|
+
return {
|
|
1204
|
+
result: {
|
|
1205
|
+
code: getErrorExitCode(error$1),
|
|
1206
|
+
error: error$1
|
|
1207
|
+
},
|
|
1208
|
+
context: createExecutionContext(app, globalParse.opts, path)
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
1212
|
+
return {
|
|
1213
|
+
result: {
|
|
1214
|
+
code: getErrorExitCode(error),
|
|
1215
|
+
error
|
|
1216
|
+
},
|
|
1217
|
+
context: createExecutionContext(app, globalParse.opts, path)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
const handler = getExecuteHandler(cmd);
|
|
1221
|
+
if (handler === void 0) {
|
|
1222
|
+
const error = createError("Command is not executable.", ErrorCategory.Internal);
|
|
1223
|
+
return {
|
|
1224
|
+
result: {
|
|
1225
|
+
code: getErrorExitCode(error),
|
|
1226
|
+
error
|
|
1227
|
+
},
|
|
1228
|
+
context: createExecutionContext(app, opts, path)
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
const context = createExecutionContext(app, opts, path);
|
|
1233
|
+
const code = await Promise.resolve(handler(opts, context));
|
|
1234
|
+
if (code === void 0) return {
|
|
1235
|
+
result: { code: null },
|
|
1236
|
+
context
|
|
1237
|
+
};
|
|
1238
|
+
return {
|
|
1239
|
+
result: { code },
|
|
1240
|
+
context
|
|
1241
|
+
};
|
|
1242
|
+
} catch (err) {
|
|
1243
|
+
const error = createError(String(err?.stack ?? err), ErrorCategory.Internal);
|
|
1244
|
+
return {
|
|
1245
|
+
result: {
|
|
1246
|
+
code: getErrorExitCode(error),
|
|
1247
|
+
error
|
|
1248
|
+
},
|
|
1249
|
+
context: createExecutionContext(app, opts, path)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
1254
|
+
const rootCommands = getRootCommands(app);
|
|
1255
|
+
const globalOptions = getGlobalOptions(app);
|
|
1256
|
+
const rootContext = createExecutionContext(app);
|
|
1257
|
+
if (argv.length === 0) {
|
|
1258
|
+
if (isExecutable(app)) return executeLeaf(app, normalizeAppAsLeafCommand(app), [], []);
|
|
1259
|
+
printHelp(rootContext, rootCommands);
|
|
1260
|
+
return {
|
|
1261
|
+
result: { code: 0 },
|
|
1262
|
+
context: rootContext
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
if (isExecutable(app) && !hasSubCommands(app)) {
|
|
1266
|
+
const globalParse = parseGlobalOptions(app, argv);
|
|
1267
|
+
if (globalParse.result !== void 0) return {
|
|
1268
|
+
result: globalParse.result,
|
|
1269
|
+
context: createExecutionContext(app, globalParse.opts)
|
|
1270
|
+
};
|
|
1271
|
+
if (globalParse.opts?.version) {
|
|
1272
|
+
printLn(app._version);
|
|
1273
|
+
return {
|
|
1274
|
+
result: { code: 0 },
|
|
1275
|
+
context: createExecutionContext(app, globalParse.opts)
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
if (globalParse.opts?.help) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1279
|
+
const builtin = findSubCommand(argv[0], rootCommands);
|
|
1280
|
+
if (builtin && isExecutable(builtin)) return executeLeaf(app, builtin, argv.slice(1), [builtin.name]);
|
|
1281
|
+
return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1282
|
+
}
|
|
1283
|
+
const resolved = resolveCommandWithGlobalOptions(argv, rootCommands, globalOptions);
|
|
1284
|
+
if (!resolved.command) {
|
|
1285
|
+
const globalParse = parseGlobalOptions(app, argv);
|
|
1286
|
+
if (globalParse.result !== void 0) return {
|
|
1287
|
+
result: globalParse.result,
|
|
1288
|
+
context: createExecutionContext(app, globalParse.opts)
|
|
1289
|
+
};
|
|
1290
|
+
const globalOpts = globalParse.opts ?? {};
|
|
1291
|
+
const context = createExecutionContext(app, globalOpts);
|
|
1292
|
+
if (globalOpts.version) {
|
|
1293
|
+
printLn(app._version);
|
|
1294
|
+
return {
|
|
1295
|
+
result: { code: 0 },
|
|
1296
|
+
context
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
const firstNonGlobalToken = getFirstNonGlobalToken(argv, globalOptions);
|
|
1300
|
+
if (firstNonGlobalToken === void 0) {
|
|
1301
|
+
if (globalOpts.help || !hasSubCommands(app)) {
|
|
1302
|
+
if (isExecutable(app)) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1303
|
+
printHelp(context, rootCommands);
|
|
1304
|
+
return {
|
|
1305
|
+
result: { code: 0 },
|
|
1306
|
+
context
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
printHelp(context, rootCommands);
|
|
1310
|
+
return {
|
|
1311
|
+
result: { code: 0 },
|
|
1312
|
+
context
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
result: unknownTokenResult(app, firstNonGlobalToken, context.chalk),
|
|
1317
|
+
context
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (hasSubCommands(resolved.command)) {
|
|
1321
|
+
const globalParse = parseGlobalOptions(app, resolved.remainingArgv);
|
|
1322
|
+
if (globalParse.result !== void 0) return {
|
|
1323
|
+
result: globalParse.result,
|
|
1324
|
+
context: createExecutionContext(app, globalParse.opts, resolved.path)
|
|
1325
|
+
};
|
|
1326
|
+
const branchOpts = globalParse.opts ?? {};
|
|
1327
|
+
const context = createExecutionContext(app, branchOpts, resolved.path);
|
|
1328
|
+
if (branchOpts.version) {
|
|
1329
|
+
printLn(app._version);
|
|
1330
|
+
return {
|
|
1331
|
+
result: { code: 0 },
|
|
1332
|
+
context
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
const firstNonGlobalToken = getFirstNonGlobalToken(resolved.remainingArgv, globalOptions);
|
|
1336
|
+
if (!resolved.remainingArgv.length || branchOpts.help && firstNonGlobalToken === void 0) {
|
|
1337
|
+
printCommandHelp(context, resolved.command, resolved.path);
|
|
1338
|
+
return {
|
|
1339
|
+
result: { code: 0 },
|
|
1340
|
+
context
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
result: unknownTokenResult(app, firstNonGlobalToken ?? resolved.remainingArgv[0], context.chalk),
|
|
1345
|
+
context
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
if (!isExecutable(resolved.command)) {
|
|
1349
|
+
const error = createError("Command is not executable.", ErrorCategory.Internal);
|
|
1350
|
+
return {
|
|
1351
|
+
result: {
|
|
1352
|
+
code: getErrorExitCode(error),
|
|
1353
|
+
error
|
|
1354
|
+
},
|
|
1355
|
+
context: createExecutionContext(app, void 0, resolved.path)
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return executeLeaf(app, resolved.command, resolved.remainingArgv, resolved.path);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Parses CLI arguments, prints any resulting error, and executes the matching command.
|
|
1362
|
+
*
|
|
1363
|
+
* @param app The app to execute.
|
|
1364
|
+
* @param argv The raw CLI arguments to parse. Defaults to `process.argv.slice(2)`.
|
|
1365
|
+
* @returns The process exit code for the CLI invocation.
|
|
1366
|
+
*/
|
|
1367
|
+
async function executeApp(app, argv = process.argv.slice(2)) {
|
|
1368
|
+
const { result, context } = await executeAppDetailed(app, argv);
|
|
1369
|
+
if (result.error !== void 0) printError(result.error, context.chalk);
|
|
1370
|
+
return result.code ?? 0;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
//#endregion
|
|
1374
|
+
export { executeApp };
|