cli-api 0.2.0 → 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.
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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
2
|
import Path from "path";
|
|
3
3
|
import stringWidth from "string-width";
|
|
4
4
|
import FileSys from "fs";
|
|
@@ -31,6 +31,9 @@ function print(str = "") {
|
|
|
31
31
|
function printLn(...args) {
|
|
32
32
|
console.log(...args);
|
|
33
33
|
}
|
|
34
|
+
function printErrLn(...args) {
|
|
35
|
+
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
36
|
+
}
|
|
34
37
|
/**
|
|
35
38
|
* Semantic categories for user-facing CLI errors.
|
|
36
39
|
*/
|
|
@@ -55,15 +58,18 @@ const ERROR_PRESENTATION = {
|
|
|
55
58
|
}
|
|
56
59
|
};
|
|
57
60
|
function blockError(str, style, chalk) {
|
|
58
|
-
const lines = str.
|
|
59
|
-
const width = Math.max(...lines.map((l) => stringWidth(l))) + 4;
|
|
61
|
+
const lines = wrapText(str, Math.max(getTerminalWidth() - 4, 1));
|
|
62
|
+
const width = Math.max(...lines.map((l) => stringWidth(l)), 0) + 4;
|
|
60
63
|
const colorize = chalk.bgHex(ERROR_PRESENTATION[style].color).hex("#FEFBEC");
|
|
61
|
-
|
|
64
|
+
printErrLn(colorize(space(width)));
|
|
62
65
|
for (const line of lines) {
|
|
63
66
|
const txt = ` ${line}`;
|
|
64
|
-
|
|
67
|
+
printErrLn(colorize(txt + space(width, txt)));
|
|
65
68
|
}
|
|
66
|
-
|
|
69
|
+
printErrLn(colorize(space(width)));
|
|
70
|
+
}
|
|
71
|
+
function inlineError(str) {
|
|
72
|
+
printErrLn(str);
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
* Creates a structured CLI error.
|
|
@@ -94,7 +100,11 @@ function getErrorExitCode(error) {
|
|
|
94
100
|
* @returns Nothing.
|
|
95
101
|
*/
|
|
96
102
|
function printError(error, chalk) {
|
|
97
|
-
|
|
103
|
+
if (chalk.level > 0) {
|
|
104
|
+
blockError(error.message, error.type, chalk);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
inlineError(error.message);
|
|
98
108
|
}
|
|
99
109
|
function toArray(x) {
|
|
100
110
|
if (!x) return EMPTY_ARRAY;
|
|
@@ -116,7 +126,7 @@ function space(len, str) {
|
|
|
116
126
|
return len > 0 ? " ".repeat(len) : "";
|
|
117
127
|
}
|
|
118
128
|
function getTerminalWidth() {
|
|
119
|
-
return process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
129
|
+
return process.stderr.columns && process.stderr.columns > 0 ? process.stderr.columns : process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
120
130
|
}
|
|
121
131
|
function wrapText(text, width) {
|
|
122
132
|
if (width <= 0) return text.split("\n");
|
|
@@ -165,12 +175,6 @@ function sortBy(arr, cmp) {
|
|
|
165
175
|
return keys.map((i) => arr[i]);
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
//#endregion
|
|
169
|
-
//#region src/commands/version.ts
|
|
170
|
-
const versionCommand = new Command("version").describe("Displays current version").run(async (_, __, context) => {
|
|
171
|
-
printLn(context.app._version);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
178
|
//#endregion
|
|
175
179
|
//#region src/options.ts
|
|
176
180
|
/**
|
|
@@ -181,8 +185,8 @@ const versionCommand = new Command("version").describe("Displays current version
|
|
|
181
185
|
*/
|
|
182
186
|
var UnknownOptionError = class extends Error {
|
|
183
187
|
option;
|
|
184
|
-
constructor(option) {
|
|
185
|
-
super(`option ${
|
|
188
|
+
constructor(option, formattedOption = option) {
|
|
189
|
+
super(`option ${formattedOption} not recognized`);
|
|
186
190
|
this.option = option;
|
|
187
191
|
}
|
|
188
192
|
};
|
|
@@ -212,6 +216,86 @@ function getMinRequiredCount(value) {
|
|
|
212
216
|
if (typeof value === "number") return value;
|
|
213
217
|
return 0;
|
|
214
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
|
+
}
|
|
215
299
|
function assertValidCount(name, value, { allowZero = false } = {}) {
|
|
216
300
|
if (!Number.isInteger(value) || value < 0 || !allowZero && value === 0) throw new Error(`${name} must be ${allowZero ? "a non-negative" : "a positive"} integer`);
|
|
217
301
|
}
|
|
@@ -228,10 +312,12 @@ function validatePositionalDefinitions(cmd) {
|
|
|
228
312
|
if (!repeatable) throw new Error(`"${arg.name}" argument cannot use a numeric required count unless it is repeatable`);
|
|
229
313
|
}
|
|
230
314
|
if (typeof arg.repeatable === "number") assertValidCount(`"${arg.name}" argument repeatable count`, arg.repeatable);
|
|
231
|
-
if (repeatable && i < cmd.positonals.length - 1)
|
|
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
|
+
}
|
|
232
318
|
if (maxRepeatCount !== void 0 && minRequired > maxRepeatCount) throw new Error(`"${arg.name}" argument requires at least ${minRequired} values but allows at most ${maxRepeatCount}`);
|
|
233
319
|
if (encounteredOptionalPositional && minRequired > 0) throw new Error("Required arguments cannot come after optional arguments");
|
|
234
|
-
if (minRequired === 0) encounteredOptionalPositional = true;
|
|
320
|
+
if (minRequired === 0 && !repeatable) encounteredOptionalPositional = true;
|
|
235
321
|
}
|
|
236
322
|
}
|
|
237
323
|
function pushRepeatableValue(target, value, itemName, maxCount, kind) {
|
|
@@ -307,9 +393,39 @@ function validateCommandConfig(cmd) {
|
|
|
307
393
|
}
|
|
308
394
|
}
|
|
309
395
|
}
|
|
310
|
-
function
|
|
396
|
+
function assignPositionalArguments(positonals, rawArgs, opts, chalk) {
|
|
397
|
+
if (!positonals?.length) return [...rawArgs];
|
|
311
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 } = {}) {
|
|
312
427
|
const opts = Object.create(null);
|
|
428
|
+
const rawArgs = [];
|
|
313
429
|
let parseFlags = true;
|
|
314
430
|
const allOptions = getOptions(cmd);
|
|
315
431
|
validateCommandConfig(cmd);
|
|
@@ -342,7 +458,6 @@ function parseArgs(cmd, argv) {
|
|
|
342
458
|
if (!findOpt(ch)) return ch;
|
|
343
459
|
}
|
|
344
460
|
};
|
|
345
|
-
let argIdx = 0;
|
|
346
461
|
for (let i = 0; i < argv.length; ++i) {
|
|
347
462
|
let token = argv[i];
|
|
348
463
|
if (parseFlags && token === "--") {
|
|
@@ -359,15 +474,15 @@ function parseArgs(cmd, argv) {
|
|
|
359
474
|
}
|
|
360
475
|
const name = token.slice(2);
|
|
361
476
|
const match = findOpt(name);
|
|
362
|
-
if (!match) throw new UnknownOptionError(`--${name}
|
|
477
|
+
if (!match) throw new UnknownOptionError(`--${name}`, formatToken(`--${name}`, chalk));
|
|
363
478
|
const { opt, negated } = match;
|
|
364
|
-
if (negated && inlineValue !== void 0) throw new Error(`Option
|
|
479
|
+
if (negated && inlineValue !== void 0) throw new Error(`Option ${formatToken(`--no-${opt.name}`, chalk)} does not take a value`);
|
|
365
480
|
let value = inlineValue;
|
|
366
481
|
if (negated) value = getOptionNoPrefixValue(opt);
|
|
367
482
|
else if (value === void 0) if (opt.valueNotRequired) value = getOptionImplicitValue(opt);
|
|
368
483
|
else if (i < argv.length - 1) value = argv[++i];
|
|
369
|
-
else throw new Error(`Missing required value for option
|
|
370
|
-
if (opt.type != null) value = coerceType(value, opt.type,
|
|
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));
|
|
371
486
|
const k = opt.key ?? opt.name;
|
|
372
487
|
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
373
488
|
else opts[k] = value;
|
|
@@ -379,13 +494,13 @@ function parseArgs(cmd, argv) {
|
|
|
379
494
|
inlineValue = equalIndex === -1 ? void 0 : clusterText.slice(equalIndex + 1);
|
|
380
495
|
if (hasInlineAssignment) {
|
|
381
496
|
const unknownOption = getUnknownShortOption(cluster);
|
|
382
|
-
if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}
|
|
497
|
+
if (unknownOption !== void 0) throw new UnknownOptionError(`-${unknownOption}`, formatToken(`-${unknownOption}`, chalk));
|
|
383
498
|
}
|
|
384
499
|
let j = 0;
|
|
385
500
|
while (j < cluster.length) {
|
|
386
501
|
const ch = cluster[j];
|
|
387
502
|
const match = findOpt(ch);
|
|
388
|
-
if (!match) throw new UnknownOptionError(`-${ch}
|
|
503
|
+
if (!match) throw new UnknownOptionError(`-${ch}`, formatToken(`-${ch}`, chalk));
|
|
389
504
|
const { opt } = match;
|
|
390
505
|
if (opt.valueNotRequired) {
|
|
391
506
|
const k$1 = opt.key ?? opt.name;
|
|
@@ -398,124 +513,118 @@ function parseArgs(cmd, argv) {
|
|
|
398
513
|
let value;
|
|
399
514
|
const remainder = cluster.slice(j + 1);
|
|
400
515
|
if (remainder.length && !hasInlineAssignment) value = remainder;
|
|
401
|
-
else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option
|
|
516
|
+
else if (remainder.length && hasInlineAssignment) throw new Error(`Missing required value for option ${formatToken(`-${ch}`, chalk)}`);
|
|
402
517
|
else if (inlineValue !== void 0 && remainder.length === 0) value = inlineValue;
|
|
403
518
|
else if (i < argv.length - 1) value = argv[++i];
|
|
404
|
-
else throw new Error(`Missing required value for option
|
|
405
|
-
if (opt.type != null) value = coerceType(value, opt.type,
|
|
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));
|
|
406
521
|
const k = opt.key ?? opt.name;
|
|
407
522
|
if (isRepeatable(opt.repeatable)) pushRepeatableValue(opts[k], value, opt.name, getMaxRepeatCount(opt.repeatable), "option");
|
|
408
523
|
else opts[k] = value;
|
|
409
524
|
break;
|
|
410
525
|
}
|
|
411
526
|
}
|
|
412
|
-
} else
|
|
413
|
-
let value = token;
|
|
414
|
-
const def = cmd.positonals?.[argIdx];
|
|
415
|
-
if (def) {
|
|
416
|
-
if (def.type != null) value = coerceType(value, def.type, `argument \`${def.name}\``, getEnumValues(def));
|
|
417
|
-
const k = def.key ?? def.name;
|
|
418
|
-
if (isRepeatable(def.repeatable)) {
|
|
419
|
-
const arr = opts[k] ??= [];
|
|
420
|
-
pushRepeatableValue(arr, value, def.name, getMaxRepeatCount(def.repeatable), "argument");
|
|
421
|
-
for (i = i + 1; i < argv.length; ++i) {
|
|
422
|
-
let v = argv[i];
|
|
423
|
-
if (parseFlags && v === "--") {
|
|
424
|
-
parseFlags = false;
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
if (parseFlags && v.startsWith("-")) {
|
|
428
|
-
i -= 1;
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
if (def.type != null) v = coerceType(v, def.type, `argument \`${def.name}\``, getEnumValues(def));
|
|
432
|
-
pushRepeatableValue(arr, v, def.name, getMaxRepeatCount(def.repeatable), "argument");
|
|
433
|
-
}
|
|
434
|
-
argIdx = isRepeatable(def.repeatable) && cmd.positonals ? cmd.positonals.length : argIdx;
|
|
435
|
-
args.push(...arr);
|
|
436
|
-
continue;
|
|
437
|
-
} else opts[k] = value;
|
|
438
|
-
}
|
|
439
|
-
args.push(value);
|
|
440
|
-
++argIdx;
|
|
441
|
-
}
|
|
527
|
+
} else rawArgs.push(token);
|
|
442
528
|
}
|
|
529
|
+
const args = assignPositionalArguments(cmd.positonals, rawArgs, opts, chalk);
|
|
443
530
|
if (allOptions.length) for (const opt of allOptions) {
|
|
444
531
|
const k = opt.key ?? opt.name;
|
|
445
532
|
if (opts[k] === void 0) {
|
|
446
533
|
if (opt.defaultValue !== void 0) opts[k] = resolve(opt.defaultValue);
|
|
447
|
-
else if (opt.required) throw new Error(
|
|
534
|
+
else if (opt.required && !skipRequiredOptions) throw new Error(`${formatToken(getOptName(opt), chalk)} option is required`);
|
|
448
535
|
}
|
|
449
536
|
}
|
|
450
537
|
if (cmd.positonals?.length) for (let i = 0; i < cmd.positonals.length; ++i) {
|
|
451
538
|
const a = cmd.positonals[i];
|
|
452
539
|
const minRequired = getMinRequiredCount(a.required);
|
|
453
|
-
if (minRequired > 0 && argIdx <= i && !isRepeatable(a.repeatable)) throw new Error(`\`${a.name}\` argument is required`);
|
|
454
540
|
const k = a.key ?? a.name;
|
|
455
|
-
if (isRepeatable(a.repeatable) &&
|
|
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"}`);
|
|
456
543
|
if (k && opts[k] === void 0 && a.defaultValue !== void 0) opts[k] = resolve(a.defaultValue);
|
|
457
544
|
}
|
|
458
545
|
return [args, opts];
|
|
459
546
|
}
|
|
460
|
-
function coerceType(value, type, itemName, enumValues) {
|
|
547
|
+
function coerceType(value, type, chalk, itemName, enumValues) {
|
|
461
548
|
const normalizedEnumValue = () => {
|
|
462
549
|
const normalized = String(value).trim().toLowerCase();
|
|
463
550
|
const allowedValues = enumValues ?? (Array.isArray(type) ? type : void 0);
|
|
464
|
-
if (allowedValues !== void 0 && !allowedValues.includes(normalized)) {
|
|
465
|
-
const itemText = itemName ? ` for ${itemName}` : "";
|
|
466
|
-
throw new Error(`Invalid value "${value}"${itemText} (expected one of: ${allowedValues.join(", ")})`);
|
|
467
|
-
}
|
|
551
|
+
if (allowedValues !== void 0 && !allowedValues.includes(normalized)) throw new Error(`Invalid value "${value}"${describeItem(itemName)} (expected one of: ${allowedValues.join(", ")})`);
|
|
468
552
|
return normalized;
|
|
469
553
|
};
|
|
470
554
|
if (Array.isArray(type)) return normalizedEnumValue();
|
|
471
555
|
switch (type) {
|
|
472
|
-
case OptType.BOOL:
|
|
473
|
-
|
|
474
|
-
|
|
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);
|
|
475
563
|
case OptType.ENUM: return normalizedEnumValue();
|
|
476
564
|
case OptType.STRING: return String(value);
|
|
477
565
|
case OptType.INPUT_FILE: {
|
|
478
566
|
if (value === "-") return "/dev/stdin";
|
|
479
567
|
const file = Path.normalize(value);
|
|
480
|
-
const fullPath =
|
|
568
|
+
const fullPath = formatPath(file, chalk);
|
|
481
569
|
const stat = statSync(file);
|
|
482
|
-
if (!stat) throw
|
|
483
|
-
if (!stat.isFile()) throw
|
|
570
|
+
if (!stat) throw createPathError("File", fullPath, "does not exist", itemName);
|
|
571
|
+
if (!stat.isFile()) throw createPathError("File", fullPath, "is not a file", itemName);
|
|
484
572
|
try {
|
|
485
573
|
FileSys.accessSync(file, FileSys.constants.R_OK);
|
|
486
|
-
} catch
|
|
487
|
-
throw
|
|
574
|
+
} catch {
|
|
575
|
+
throw createPathError("File", fullPath, "is not readable", itemName);
|
|
488
576
|
}
|
|
489
577
|
return file;
|
|
490
578
|
}
|
|
491
|
-
case OptType.INPUT_DIRECTORY:
|
|
492
|
-
const dir = Path.normalize(value);
|
|
493
|
-
FileSys.accessSync(dir, FileSys.constants.X_OK);
|
|
494
|
-
return dir;
|
|
495
|
-
}
|
|
579
|
+
case OptType.INPUT_DIRECTORY: return ensureInputDirectory(value, chalk, itemName);
|
|
496
580
|
case OptType.OUTPUT_FILE: {
|
|
497
581
|
if (value === "-") return "/dev/stdout";
|
|
498
582
|
const file = Path.normalize(value);
|
|
583
|
+
const fullPath = formatPath(file, chalk);
|
|
499
584
|
const stat = statSync(file);
|
|
500
585
|
if (stat) {
|
|
501
|
-
if (!stat.isFile()) throw
|
|
502
|
-
|
|
503
|
-
|
|
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
|
+
}
|
|
504
604
|
return file;
|
|
505
605
|
}
|
|
506
|
-
case OptType.OUTPUT_DIRECTORY:
|
|
507
|
-
FileSys.accessSync(value, FileSys.constants.W_OK);
|
|
508
|
-
return Path.normalize(value);
|
|
606
|
+
case OptType.OUTPUT_DIRECTORY: return ensureOutputDirectory(value, chalk, itemName);
|
|
509
607
|
case OptType.EMPTY_DIRECTORY: {
|
|
510
608
|
const dir = Path.normalize(value);
|
|
609
|
+
const fullPath = formatPath(dir, chalk);
|
|
511
610
|
let files = [];
|
|
512
611
|
try {
|
|
513
612
|
files = FileSys.readdirSync(dir);
|
|
514
613
|
} catch (err) {
|
|
515
|
-
if (err?.code === "ENOENT")
|
|
516
|
-
|
|
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);
|
|
517
626
|
}
|
|
518
|
-
if (files.length) throw
|
|
627
|
+
if (files.length) throw createPathError("Directory", fullPath, "is not empty", itemName);
|
|
519
628
|
return dir;
|
|
520
629
|
}
|
|
521
630
|
}
|
|
@@ -525,8 +634,8 @@ function getOptName(opt) {
|
|
|
525
634
|
return (opt.name.length > 1 ? "--" : "-") + opt.name;
|
|
526
635
|
}
|
|
527
636
|
function findSubCommand(name, subCommands) {
|
|
528
|
-
const cmdName = String(name).trim().
|
|
529
|
-
return subCommands.find((c) => c.name === cmdName ||
|
|
637
|
+
const cmdName = String(name).trim().toLowerCase();
|
|
638
|
+
return subCommands.find((c) => c.name.toLowerCase() === cmdName || toArray(c.alias).some((alias) => alias.toLowerCase() === cmdName));
|
|
530
639
|
}
|
|
531
640
|
function getCommand(path, subCommands) {
|
|
532
641
|
if (!path.length) throw new Error("Command path is required.");
|
|
@@ -551,41 +660,7 @@ function getCommand(path, subCommands) {
|
|
|
551
660
|
}
|
|
552
661
|
|
|
553
662
|
//#endregion
|
|
554
|
-
//#region src/
|
|
555
|
-
const HELP_OPTION = {
|
|
556
|
-
name: "help",
|
|
557
|
-
alias: "h",
|
|
558
|
-
description: "Show help text",
|
|
559
|
-
type: OptType.BOOL,
|
|
560
|
-
valueNotRequired: true,
|
|
561
|
-
valueIfSet: true
|
|
562
|
-
};
|
|
563
|
-
const COLOR_OPTION = {
|
|
564
|
-
name: "color",
|
|
565
|
-
description: "Control ANSI color output.",
|
|
566
|
-
type: OptType.ENUM,
|
|
567
|
-
enumValues: [
|
|
568
|
-
"always",
|
|
569
|
-
"never",
|
|
570
|
-
"auto"
|
|
571
|
-
],
|
|
572
|
-
valuePlaceholder: "WHEN",
|
|
573
|
-
valueNotRequired: true,
|
|
574
|
-
valueIfSet: "always",
|
|
575
|
-
noPrefix: true,
|
|
576
|
-
valueIfNoPrefix: "never",
|
|
577
|
-
defaultValue: "auto"
|
|
578
|
-
};
|
|
579
|
-
function getGlobalOptions(app) {
|
|
580
|
-
return sortOptions([
|
|
581
|
-
HELP_OPTION,
|
|
582
|
-
COLOR_OPTION,
|
|
583
|
-
...app._globalOptions ?? []
|
|
584
|
-
]);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
//#endregion
|
|
588
|
-
//#region src/app-help.ts
|
|
663
|
+
//#region src/print-command-help.ts
|
|
589
664
|
function shouldWrapHelpEntry$1(label, description, labelWidth) {
|
|
590
665
|
if (!description) return false;
|
|
591
666
|
const terminalWidth = getTerminalWidth();
|
|
@@ -617,71 +692,6 @@ function printOptionEntries$1(entries) {
|
|
|
617
692
|
if (printHelpEntry$1(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
618
693
|
});
|
|
619
694
|
}
|
|
620
|
-
function printHelp(context, commands) {
|
|
621
|
-
const app = context.app;
|
|
622
|
-
const chalk = context.chalk;
|
|
623
|
-
print(chalk.green(app.name));
|
|
624
|
-
const { _author: author, _version: version } = app;
|
|
625
|
-
if (version) print(` ver. ${chalk.yellow(version)}`);
|
|
626
|
-
if (author) print(` by ${chalk.cyan(author)}`);
|
|
627
|
-
printLn();
|
|
628
|
-
if (app.description) {
|
|
629
|
-
printLn();
|
|
630
|
-
printLn(app.description);
|
|
631
|
-
}
|
|
632
|
-
printLn();
|
|
633
|
-
printLn(chalk.yellow("Usage:"));
|
|
634
|
-
print(` ${chalk.cyan(getProcName(app))}`);
|
|
635
|
-
if (hasSubCommands(app)) print(` ${chalk.gray("[")}--GLOBAL-OPTIONS${chalk.gray("]")} ${chalk.gray("<")}COMMAND${chalk.gray(">")}`);
|
|
636
|
-
printLn("\n");
|
|
637
|
-
if (commands.length) printAvailableCommands(commands, "Commands:", chalk);
|
|
638
|
-
const globalOptions = getGlobalOptions(app);
|
|
639
|
-
if (globalOptions.length) {
|
|
640
|
-
printLn();
|
|
641
|
-
printLn(chalk.yellow("Global Options:"));
|
|
642
|
-
printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
function printAvailableCommands(commands, title, chalk) {
|
|
646
|
-
if (!commands.length) return;
|
|
647
|
-
printLn(chalk.yellow(title));
|
|
648
|
-
const width = Math.max(...commands.map((c) => stringWidth(c.name))) + 2;
|
|
649
|
-
for (const cmd of commands) printHelpEntry$1(chalk.green(cmd.name), cmd.description, width);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
//#endregion
|
|
653
|
-
//#region src/print-command-help.ts
|
|
654
|
-
function shouldWrapHelpEntry(label, description, labelWidth) {
|
|
655
|
-
if (!description) return false;
|
|
656
|
-
const terminalWidth = getTerminalWidth();
|
|
657
|
-
const inlineIndent = labelWidth + 4;
|
|
658
|
-
const inlineDescriptionLines = wrapText(description, Math.max(terminalWidth - inlineIndent, 1));
|
|
659
|
-
return description.includes("\n") || inlineDescriptionLines.length > 1 || labelWidth + 4 + stringWidth(description) > terminalWidth;
|
|
660
|
-
}
|
|
661
|
-
function printHelpEntry(label, description, labelWidth, forceWrap = false) {
|
|
662
|
-
print(` ${label}`);
|
|
663
|
-
if (!description) {
|
|
664
|
-
printLn();
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
if (!(forceWrap || shouldWrapHelpEntry(label, description, labelWidth))) {
|
|
668
|
-
printLn(`${space(labelWidth + 2, label)}${description}`);
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
671
|
-
printLn();
|
|
672
|
-
const terminalWidth = getTerminalWidth();
|
|
673
|
-
const descriptionIndent = " ".repeat(10);
|
|
674
|
-
const wrappedDescription = wrapText(description, Math.max(terminalWidth - descriptionIndent.length, 1));
|
|
675
|
-
for (const line of wrappedDescription) printLn(line.length ? `${descriptionIndent}${line}` : "");
|
|
676
|
-
return true;
|
|
677
|
-
}
|
|
678
|
-
function printOptionEntries(entries) {
|
|
679
|
-
const width = Math.max(...entries.map((line) => stringWidth(line[0])));
|
|
680
|
-
const forceWrap = entries.some(([label, description]) => shouldWrapHelpEntry(label, description, width));
|
|
681
|
-
entries.forEach(([label, description], index) => {
|
|
682
|
-
if (printHelpEntry(label, description, width, forceWrap) && index < entries.length - 1) printLn();
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
695
|
function getCommandLabel(context, path) {
|
|
686
696
|
const proc = context.chalk.cyan(getProcName(context.app));
|
|
687
697
|
if (!path.length) return proc;
|
|
@@ -737,12 +747,12 @@ function printCommandHelp(context, cmd, path = []) {
|
|
|
737
747
|
const allOptions = getOptions(cmd);
|
|
738
748
|
if (allOptions.length) {
|
|
739
749
|
printLn(chalk.yellow("\nOptions:"));
|
|
740
|
-
printOptionEntries(allOptions.map((option) => formatOption(option, chalk)));
|
|
750
|
+
printOptionEntries$1(allOptions.map((option) => formatOption(option, chalk)));
|
|
741
751
|
}
|
|
742
752
|
if (cmd.positonals?.length) {
|
|
743
753
|
printLn(chalk.yellow("\nArguments:"));
|
|
744
754
|
const width = Math.max(...cmd.positonals.map((arg) => stringWidth(arg.name)));
|
|
745
|
-
for (const arg of cmd.positonals) printHelpEntry(chalk.green(arg.name), arg.description, width);
|
|
755
|
+
for (const arg of cmd.positonals) printHelpEntry$1(chalk.green(arg.name), arg.description, width);
|
|
746
756
|
}
|
|
747
757
|
}
|
|
748
758
|
if (hasSubCommands(cmd)) {
|
|
@@ -753,7 +763,7 @@ function printCommandHelp(context, cmd, path = []) {
|
|
|
753
763
|
if (globalOptions.length) {
|
|
754
764
|
printLn();
|
|
755
765
|
printLn(chalk.yellow("Global Options:"));
|
|
756
|
-
printOptionEntries(globalOptions.map((option) => formatOption(option, chalk)));
|
|
766
|
+
printOptionEntries$1(globalOptions.map((option) => formatOption(option, chalk)));
|
|
757
767
|
}
|
|
758
768
|
if (cmd.alias) {
|
|
759
769
|
const aliases = toArray(cmd.alias);
|
|
@@ -762,23 +772,176 @@ function printCommandHelp(context, cmd, path = []) {
|
|
|
762
772
|
}
|
|
763
773
|
|
|
764
774
|
//#endregion
|
|
765
|
-
//#region src/
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
}
|
|
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) {
|
|
770
915
|
const app = context.app;
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
+
}
|
|
782
945
|
|
|
783
946
|
//#endregion
|
|
784
947
|
//#region src/run.ts
|
|
@@ -818,29 +981,6 @@ function mergeCommandOptions(app, cmd) {
|
|
|
818
981
|
options: [...getGlobalOptions(app), ...normalized.options ?? []]
|
|
819
982
|
};
|
|
820
983
|
}
|
|
821
|
-
function isVersionFlag(arg) {
|
|
822
|
-
return arg === "--version";
|
|
823
|
-
}
|
|
824
|
-
function getHelpValidationCommand(cmd) {
|
|
825
|
-
return {
|
|
826
|
-
...cmd,
|
|
827
|
-
options: cmd.options?.map((opt) => ({
|
|
828
|
-
...opt,
|
|
829
|
-
required: false
|
|
830
|
-
})),
|
|
831
|
-
positonals: cmd.positonals?.map((arg) => ({
|
|
832
|
-
...arg,
|
|
833
|
-
required: false
|
|
834
|
-
}))
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
function getRootCommands(app) {
|
|
838
|
-
return [
|
|
839
|
-
...app.subCommands !== void 0 ? sortBy(app.subCommands, (c) => c.name) : [],
|
|
840
|
-
versionCommand,
|
|
841
|
-
helpCommand
|
|
842
|
-
];
|
|
843
|
-
}
|
|
844
984
|
function createExecutionContext(app, opts, path = []) {
|
|
845
985
|
return new ExecutionContext(app, opts?.color ?? "auto", path);
|
|
846
986
|
}
|
|
@@ -856,17 +996,58 @@ function createGlobalParseCommand(app) {
|
|
|
856
996
|
execute() {}
|
|
857
997
|
};
|
|
858
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
|
+
}
|
|
859
1033
|
function parseGlobalOptions(app, argv) {
|
|
1034
|
+
const colorMode = getRequestedColorMode(app, argv);
|
|
860
1035
|
try {
|
|
861
|
-
const [, opts] = parseArgs(
|
|
1036
|
+
const [, opts] = parseArgs(createGlobalParseCommand(app), extractGlobalOptionArgv(argv, getGlobalOptions(app)), createChalk(colorMode), {
|
|
1037
|
+
skipRequiredOptions: true,
|
|
1038
|
+
skipRequiredPositionals: true
|
|
1039
|
+
});
|
|
862
1040
|
return { opts };
|
|
863
1041
|
} catch (err) {
|
|
864
1042
|
if (err instanceof UnknownOptionError) return {};
|
|
865
1043
|
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
866
|
-
return {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1044
|
+
return {
|
|
1045
|
+
opts: { color: colorMode },
|
|
1046
|
+
result: {
|
|
1047
|
+
code: getErrorExitCode(error),
|
|
1048
|
+
error
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
870
1051
|
}
|
|
871
1052
|
}
|
|
872
1053
|
function getOptionTokenConsumption(argv, index, rawOptions) {
|
|
@@ -895,6 +1076,19 @@ function getOptionTokenConsumption(argv, index, rawOptions) {
|
|
|
895
1076
|
}
|
|
896
1077
|
return 1;
|
|
897
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
|
+
}
|
|
898
1092
|
function resolveCommandWithGlobalOptions(argv, subCommands, globalOptions) {
|
|
899
1093
|
const path = [];
|
|
900
1094
|
const commandIndexes = /* @__PURE__ */ new Set();
|
|
@@ -933,13 +1127,27 @@ function getFirstNonGlobalToken(argv, globalOptions) {
|
|
|
933
1127
|
return argv[index];
|
|
934
1128
|
}
|
|
935
1129
|
}
|
|
936
|
-
function unknownCommandResult(app, commandName) {
|
|
937
|
-
const error = createError(`${getProcName(app)}: unknown command
|
|
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);
|
|
938
1142
|
return {
|
|
939
1143
|
code: getErrorExitCode(error),
|
|
940
1144
|
error
|
|
941
1145
|
};
|
|
942
1146
|
}
|
|
1147
|
+
function unknownTokenResult(app, token, chalk) {
|
|
1148
|
+
if (isOptionLikeToken(token)) return unknownOptionResult(app, token, chalk);
|
|
1149
|
+
return unknownCommandResult(app, token, chalk);
|
|
1150
|
+
}
|
|
943
1151
|
async function executeLeaf(app, cmd, rawArgs, path) {
|
|
944
1152
|
try {
|
|
945
1153
|
validateCommandConfig(mergeCommandOptions(app, cmd));
|
|
@@ -953,11 +1161,19 @@ async function executeLeaf(app, cmd, rawArgs, path) {
|
|
|
953
1161
|
context: createExecutionContext(app, void 0, path)
|
|
954
1162
|
};
|
|
955
1163
|
}
|
|
956
|
-
let args;
|
|
957
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");
|
|
958
1171
|
try {
|
|
959
1172
|
const parseableCommand = mergeCommandOptions(app, cmd);
|
|
960
|
-
const [, provisionalOpts] = parseArgs(
|
|
1173
|
+
const [, provisionalOpts] = parseArgs(parseableCommand, rawArgs, parseChalk, {
|
|
1174
|
+
skipRequiredOptions: true,
|
|
1175
|
+
skipRequiredPositionals: true
|
|
1176
|
+
});
|
|
961
1177
|
const provisionalContext = createExecutionContext(app, provisionalOpts, path);
|
|
962
1178
|
if (provisionalOpts.help) {
|
|
963
1179
|
if (cmd === app && !hasSubCommands(app)) {
|
|
@@ -973,7 +1189,14 @@ async function executeLeaf(app, cmd, rawArgs, path) {
|
|
|
973
1189
|
context: provisionalContext
|
|
974
1190
|
};
|
|
975
1191
|
}
|
|
976
|
-
|
|
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);
|
|
977
1200
|
} catch (err) {
|
|
978
1201
|
if (err instanceof UnknownOptionError) {
|
|
979
1202
|
const error$1 = createError(`${getProcName(app)}: ${err.message}`, ErrorCategory.InvalidArg);
|
|
@@ -982,7 +1205,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
|
|
|
982
1205
|
code: getErrorExitCode(error$1),
|
|
983
1206
|
error: error$1
|
|
984
1207
|
},
|
|
985
|
-
context: createExecutionContext(app,
|
|
1208
|
+
context: createExecutionContext(app, globalParse.opts, path)
|
|
986
1209
|
};
|
|
987
1210
|
}
|
|
988
1211
|
const error = createError(err instanceof Error ? err.message : String(err), ErrorCategory.InvalidArg);
|
|
@@ -991,7 +1214,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
|
|
|
991
1214
|
code: getErrorExitCode(error),
|
|
992
1215
|
error
|
|
993
1216
|
},
|
|
994
|
-
context: createExecutionContext(app,
|
|
1217
|
+
context: createExecutionContext(app, globalParse.opts, path)
|
|
995
1218
|
};
|
|
996
1219
|
}
|
|
997
1220
|
const handler = getExecuteHandler(cmd);
|
|
@@ -1007,7 +1230,7 @@ async function executeLeaf(app, cmd, rawArgs, path) {
|
|
|
1007
1230
|
}
|
|
1008
1231
|
try {
|
|
1009
1232
|
const context = createExecutionContext(app, opts, path);
|
|
1010
|
-
const code = await Promise.resolve(handler(opts,
|
|
1233
|
+
const code = await Promise.resolve(handler(opts, context));
|
|
1011
1234
|
if (code === void 0) return {
|
|
1012
1235
|
result: { code: null },
|
|
1013
1236
|
context
|
|
@@ -1040,18 +1263,18 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
|
1040
1263
|
};
|
|
1041
1264
|
}
|
|
1042
1265
|
if (isExecutable(app) && !hasSubCommands(app)) {
|
|
1043
|
-
|
|
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) {
|
|
1044
1272
|
printLn(app._version);
|
|
1045
1273
|
return {
|
|
1046
1274
|
result: { code: 0 },
|
|
1047
|
-
context:
|
|
1275
|
+
context: createExecutionContext(app, globalParse.opts)
|
|
1048
1276
|
};
|
|
1049
1277
|
}
|
|
1050
|
-
const globalParse = parseGlobalOptions(app, argv);
|
|
1051
|
-
if (globalParse.result !== void 0) return {
|
|
1052
|
-
result: globalParse.result,
|
|
1053
|
-
context: rootContext
|
|
1054
|
-
};
|
|
1055
1278
|
if (globalParse.opts?.help) return executeLeaf(app, normalizeAppAsLeafCommand(app), argv, []);
|
|
1056
1279
|
const builtin = findSubCommand(argv[0], rootCommands);
|
|
1057
1280
|
if (builtin && isExecutable(builtin)) return executeLeaf(app, builtin, argv.slice(1), [builtin.name]);
|
|
@@ -1062,10 +1285,17 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
|
1062
1285
|
const globalParse = parseGlobalOptions(app, argv);
|
|
1063
1286
|
if (globalParse.result !== void 0) return {
|
|
1064
1287
|
result: globalParse.result,
|
|
1065
|
-
context:
|
|
1288
|
+
context: createExecutionContext(app, globalParse.opts)
|
|
1066
1289
|
};
|
|
1067
1290
|
const globalOpts = globalParse.opts ?? {};
|
|
1068
1291
|
const context = createExecutionContext(app, globalOpts);
|
|
1292
|
+
if (globalOpts.version) {
|
|
1293
|
+
printLn(app._version);
|
|
1294
|
+
return {
|
|
1295
|
+
result: { code: 0 },
|
|
1296
|
+
context
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1069
1299
|
const firstNonGlobalToken = getFirstNonGlobalToken(argv, globalOptions);
|
|
1070
1300
|
if (firstNonGlobalToken === void 0) {
|
|
1071
1301
|
if (globalOpts.help || !hasSubCommands(app)) {
|
|
@@ -1083,7 +1313,7 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
|
1083
1313
|
};
|
|
1084
1314
|
}
|
|
1085
1315
|
return {
|
|
1086
|
-
result:
|
|
1316
|
+
result: unknownTokenResult(app, firstNonGlobalToken, context.chalk),
|
|
1087
1317
|
context
|
|
1088
1318
|
};
|
|
1089
1319
|
}
|
|
@@ -1091,10 +1321,17 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
|
1091
1321
|
const globalParse = parseGlobalOptions(app, resolved.remainingArgv);
|
|
1092
1322
|
if (globalParse.result !== void 0) return {
|
|
1093
1323
|
result: globalParse.result,
|
|
1094
|
-
context:
|
|
1324
|
+
context: createExecutionContext(app, globalParse.opts, resolved.path)
|
|
1095
1325
|
};
|
|
1096
1326
|
const branchOpts = globalParse.opts ?? {};
|
|
1097
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
|
+
}
|
|
1098
1335
|
const firstNonGlobalToken = getFirstNonGlobalToken(resolved.remainingArgv, globalOptions);
|
|
1099
1336
|
if (!resolved.remainingArgv.length || branchOpts.help && firstNonGlobalToken === void 0) {
|
|
1100
1337
|
printCommandHelp(context, resolved.command, resolved.path);
|
|
@@ -1104,7 +1341,7 @@ async function executeAppDetailed(app, argv = process.argv.slice(2)) {
|
|
|
1104
1341
|
};
|
|
1105
1342
|
}
|
|
1106
1343
|
return {
|
|
1107
|
-
result:
|
|
1344
|
+
result: unknownTokenResult(app, firstNonGlobalToken ?? resolved.remainingArgv[0], context.chalk),
|
|
1108
1345
|
context
|
|
1109
1346
|
};
|
|
1110
1347
|
}
|