toolcraft 0.0.17 → 0.0.18
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/dist/cli.d.ts +2 -0
- package/dist/cli.js +833 -124
- package/dist/error-report.d.ts +39 -0
- package/dist/error-report.js +330 -0
- package/dist/human-in-loop/approval-tasks.js +11 -8
- package/dist/human-in-loop/approvals-commands.js +21 -20
- package/dist/human-in-loop/default-provider.js +5 -3
- package/dist/human-in-loop/runner.js +45 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +55 -35
- package/dist/json-schema-converter.d.ts +1 -0
- package/dist/json-schema-converter.js +102 -52
- package/dist/mcp-proxy.d.ts +1 -0
- package/dist/mcp-proxy.js +13 -6
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +131 -55
- package/dist/sdk.d.ts +4 -2
- package/dist/sdk.js +132 -48
- package/dist/source-snippet.d.ts +8 -0
- package/dist/source-snippet.js +42 -0
- package/dist/stack-trim.d.ts +4 -0
- package/dist/stack-trim.js +70 -0
- package/dist/suggest.d.ts +4 -0
- package/dist/suggest.js +46 -0
- package/dist/user-error.d.ts +3 -0
- package/dist/user-error.js +7 -1
- package/dist/validation-errors.d.ts +5 -0
- package/dist/validation-errors.js +18 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +8 -1
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/index.js +3 -0
- package/node_modules/@poe-code/design-system/package.json +1 -0
- package/package.json +6 -2
package/dist/cli.js
CHANGED
|
@@ -4,12 +4,27 @@ import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Opti
|
|
|
4
4
|
import { cancel, confirm, createLogger, formatCommandList, formatOptionList, getTheme, helpFormatterPlain, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text } from "@poe-code/design-system";
|
|
5
5
|
import { ApprovalDeclinedError, UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
|
|
6
6
|
import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
|
|
7
|
+
import { writeErrorReport } from "./error-report.js";
|
|
7
8
|
import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
|
|
8
9
|
import { resolveMcpProxies } from "./mcp-proxy.js";
|
|
9
10
|
import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
|
|
10
11
|
import { findEntrypointPackageMetadata } from "./package-metadata.js";
|
|
11
12
|
import { renderResult } from "./renderer.js";
|
|
12
|
-
|
|
13
|
+
import { renderSourceSnippet } from "./source-snippet.js";
|
|
14
|
+
import { enableSourceMaps, formatDebugStack } from "./stack-trim.js";
|
|
15
|
+
import { suggest } from "./suggest.js";
|
|
16
|
+
import { throwValidationErrors } from "./validation-errors.js";
|
|
17
|
+
const RESERVED_SERVICE_NAMES = new Set([
|
|
18
|
+
"params",
|
|
19
|
+
"secrets",
|
|
20
|
+
"fetch",
|
|
21
|
+
"fs",
|
|
22
|
+
"env",
|
|
23
|
+
"progress",
|
|
24
|
+
"runtimeOptions",
|
|
25
|
+
"root"
|
|
26
|
+
]);
|
|
27
|
+
const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
|
|
13
28
|
const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
|
|
14
29
|
function inferProgramName(argv) {
|
|
15
30
|
const entrypoint = argv[1];
|
|
@@ -368,12 +383,14 @@ function parseBooleanText(value, label) {
|
|
|
368
383
|
if (normalized === "false") {
|
|
369
384
|
return false;
|
|
370
385
|
}
|
|
371
|
-
throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false.`);
|
|
386
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false, got ${describeReceived(value)}.`);
|
|
372
387
|
}
|
|
373
388
|
function parseEnumValue(value, values, label) {
|
|
374
389
|
const match = values.find((candidate) => String(candidate) === value);
|
|
375
390
|
if (match === undefined) {
|
|
376
|
-
|
|
391
|
+
const suggestions = suggest(value, values.map((candidate) => String(candidate)));
|
|
392
|
+
const suggestionLine = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
|
|
393
|
+
throw new InvalidArgumentError(`Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`);
|
|
377
394
|
}
|
|
378
395
|
return match;
|
|
379
396
|
}
|
|
@@ -393,9 +410,114 @@ function parseJsonText(value, label) {
|
|
|
393
410
|
try {
|
|
394
411
|
return JSON.parse(value);
|
|
395
412
|
}
|
|
396
|
-
catch {
|
|
397
|
-
throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON.`);
|
|
413
|
+
catch (error) {
|
|
414
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected valid JSON, got ${describeReceived(value)} (parser: ${getErrorMessage(error)}).`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function describeReceived(value) {
|
|
418
|
+
if (value === null)
|
|
419
|
+
return "null";
|
|
420
|
+
if (value === undefined)
|
|
421
|
+
return "missing";
|
|
422
|
+
if (Array.isArray(value))
|
|
423
|
+
return `array(${value.length})`;
|
|
424
|
+
if (typeof value === "object")
|
|
425
|
+
return "object";
|
|
426
|
+
if (typeof value === "string") {
|
|
427
|
+
const s = value.length > 40 ? `${value.slice(0, 40)}…` : value;
|
|
428
|
+
return `${JSON.stringify(s)}`;
|
|
429
|
+
}
|
|
430
|
+
return JSON.stringify(value);
|
|
431
|
+
}
|
|
432
|
+
function getErrorMessage(error) {
|
|
433
|
+
return error instanceof Error ? error.message : String(error);
|
|
434
|
+
}
|
|
435
|
+
function formatJsonParseUserErrorMessage(label, filePath, source, error, options) {
|
|
436
|
+
const location = getJsonParseErrorLocation(error, source);
|
|
437
|
+
const message = getErrorMessage(error);
|
|
438
|
+
const positionText = location === null ? "" : ` at line ${location.line} column ${location.column}`;
|
|
439
|
+
const formattedPath = options.quotePath ? `"${filePath}"` : filePath;
|
|
440
|
+
const snippet = location === null
|
|
441
|
+
? ""
|
|
442
|
+
: `\n${renderSourceSnippet({
|
|
443
|
+
source,
|
|
444
|
+
line: location.line,
|
|
445
|
+
column: location.column,
|
|
446
|
+
filePath
|
|
447
|
+
})}`;
|
|
448
|
+
return `${label} ${formattedPath} is not valid JSON: ${message}${positionText}.${snippet}`;
|
|
449
|
+
}
|
|
450
|
+
function getJsonParseErrorLocation(error, source) {
|
|
451
|
+
const causeLocation = getJsonParseCauseLocation(error);
|
|
452
|
+
if (causeLocation !== null) {
|
|
453
|
+
return causeLocation;
|
|
454
|
+
}
|
|
455
|
+
const directPosition = getNumericProperty(error, "position");
|
|
456
|
+
if (directPosition !== null) {
|
|
457
|
+
return getSourceOffsetLocation(source, directPosition);
|
|
458
|
+
}
|
|
459
|
+
const messagePosition = getJsonParseMessagePosition(getErrorMessage(error));
|
|
460
|
+
if (messagePosition !== null) {
|
|
461
|
+
return getSourceOffsetLocation(source, messagePosition);
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
function getJsonParseCauseLocation(error) {
|
|
466
|
+
if (typeof error !== "object" || error === null || !("cause" in error)) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
const cause = error.cause;
|
|
470
|
+
const line = getNumericProperty(cause, "line");
|
|
471
|
+
const column = getNumericProperty(cause, "column") ?? getNumericProperty(cause, "col");
|
|
472
|
+
if (line === null || column === null) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
return { line, column };
|
|
476
|
+
}
|
|
477
|
+
function getNumericProperty(value, key) {
|
|
478
|
+
if (typeof value !== "object" || value === null || !(key in value)) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
const propertyValue = value[key];
|
|
482
|
+
return typeof propertyValue === "number" && Number.isFinite(propertyValue)
|
|
483
|
+
? propertyValue
|
|
484
|
+
: null;
|
|
485
|
+
}
|
|
486
|
+
function getJsonParseMessagePosition(message) {
|
|
487
|
+
const marker = " at position ";
|
|
488
|
+
const markerIndex = message.indexOf(marker);
|
|
489
|
+
if (markerIndex === -1) {
|
|
490
|
+
return null;
|
|
398
491
|
}
|
|
492
|
+
const startIndex = markerIndex + marker.length;
|
|
493
|
+
let endIndex = startIndex;
|
|
494
|
+
while (endIndex < message.length && isAsciiDigit(message[endIndex] ?? "")) {
|
|
495
|
+
endIndex += 1;
|
|
496
|
+
}
|
|
497
|
+
if (endIndex === startIndex) {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
return Number.parseInt(message.slice(startIndex, endIndex), 10);
|
|
501
|
+
}
|
|
502
|
+
function getSourceOffsetLocation(source, offset) {
|
|
503
|
+
let line = 1;
|
|
504
|
+
let column = 1;
|
|
505
|
+
const boundedOffset = Math.max(0, Math.floor(offset));
|
|
506
|
+
for (let index = 0; index < boundedOffset && index < source.length; index += 1) {
|
|
507
|
+
if (source[index] === "\n") {
|
|
508
|
+
line += 1;
|
|
509
|
+
column = 1;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
column += 1;
|
|
513
|
+
}
|
|
514
|
+
return { line, column };
|
|
515
|
+
}
|
|
516
|
+
function isAsciiDigit(value) {
|
|
517
|
+
return value >= "0" && value <= "9";
|
|
518
|
+
}
|
|
519
|
+
function formatAvailableList(values) {
|
|
520
|
+
return `Available: ${[...values].sort().join(", ")}.`;
|
|
399
521
|
}
|
|
400
522
|
function normalizeCommanderOptionValue(value) {
|
|
401
523
|
return value === NULL_OPTION_VALUE ? null : value;
|
|
@@ -410,7 +532,7 @@ function parseScalarValue(value, schema, label) {
|
|
|
410
532
|
case "number": {
|
|
411
533
|
const parsed = Number(value);
|
|
412
534
|
if (!isValidNumberSchemaValue(parsed, schema)) {
|
|
413
|
-
throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}.`);
|
|
535
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}, got ${describeReceived(value)}.`);
|
|
414
536
|
}
|
|
415
537
|
return parsed;
|
|
416
538
|
}
|
|
@@ -419,7 +541,7 @@ function parseScalarValue(value, schema, label) {
|
|
|
419
541
|
case "enum":
|
|
420
542
|
return parseEnumValue(value, schema.values, label);
|
|
421
543
|
}
|
|
422
|
-
throw new UserError(
|
|
544
|
+
throw new UserError(`Unsupported CLI schema kind. ${formatAvailableList(["boolean", "enum", "number", "string"])}`);
|
|
423
545
|
}
|
|
424
546
|
function splitArrayInput(value) {
|
|
425
547
|
const items = [];
|
|
@@ -455,7 +577,6 @@ function parseArrayValue(value, schema, label) {
|
|
|
455
577
|
function createOption(field, globalLongOptionFlags) {
|
|
456
578
|
const flags = formatOptionFlags(field, globalLongOptionFlags);
|
|
457
579
|
const collidesWithGlobalFlag = globalLongOptionFlags.has(field.optionFlag);
|
|
458
|
-
const commanderValue = (value) => (value === null ? NULL_OPTION_VALUE : value);
|
|
459
580
|
if (field.schema.kind === "boolean") {
|
|
460
581
|
if (collidesWithGlobalFlag) {
|
|
461
582
|
return [createCommanderOption(flags, field.description, field)];
|
|
@@ -463,9 +584,7 @@ function createOption(field, globalLongOptionFlags) {
|
|
|
463
584
|
const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
|
|
464
585
|
mainOption.preset(true);
|
|
465
586
|
// Commander v14 passes the preset value through argParser too, so guard with typeof check
|
|
466
|
-
mainOption.argParser((value) => typeof value === "boolean"
|
|
467
|
-
? value
|
|
468
|
-
: commanderValue(parseBooleanText(value, field.displayPath)));
|
|
587
|
+
mainOption.argParser((value) => (typeof value === "boolean" ? value : value));
|
|
469
588
|
return [
|
|
470
589
|
mainOption,
|
|
471
590
|
createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field)
|
|
@@ -473,29 +592,16 @@ function createOption(field, globalLongOptionFlags) {
|
|
|
473
592
|
}
|
|
474
593
|
if (field.schema.kind === "array") {
|
|
475
594
|
return [
|
|
476
|
-
createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) =>
|
|
477
|
-
const parsed = parseArrayValue(value, field.schema, field.displayPath);
|
|
478
|
-
if (parsed === null) {
|
|
479
|
-
return NULL_OPTION_VALUE;
|
|
480
|
-
}
|
|
481
|
-
return [...(previous ?? []), ...parsed];
|
|
482
|
-
})
|
|
595
|
+
createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [...previous, value])
|
|
483
596
|
];
|
|
484
597
|
}
|
|
485
598
|
if (field.schema.kind === "json") {
|
|
486
|
-
return [
|
|
487
|
-
createCommanderOption(`${flags} <json>`, field.description, field).argParser((value) => commanderValue(parseJsonText(value, field.displayPath)))
|
|
488
|
-
];
|
|
599
|
+
return [createCommanderOption(`${flags} <json>`, field.description, field)];
|
|
489
600
|
}
|
|
490
601
|
const option = createCommanderOption(`${flags} <value>`, field.description, field);
|
|
491
|
-
if (field.schema.kind === "enum" &&
|
|
492
|
-
field.schema.values.every((value) => typeof value === "string")) {
|
|
493
|
-
option.choices([...field.schema.values]);
|
|
494
|
-
}
|
|
495
|
-
option.argParser((value) => commanderValue(parseScalarValue(value, field.schema, field.displayPath)));
|
|
496
602
|
return [option];
|
|
497
603
|
}
|
|
498
|
-
const ALWAYS_GLOBAL_LONG_OPTION_FLAGS = ["--yes", "--output", "--debug"];
|
|
604
|
+
const ALWAYS_GLOBAL_LONG_OPTION_FLAGS = ["--yes", "--output", "--debug", "--verbose"];
|
|
499
605
|
function getGlobalLongOptionFlags(presetsEnabled) {
|
|
500
606
|
return new Set(presetsEnabled
|
|
501
607
|
? ["--preset", ...ALWAYS_GLOBAL_LONG_OPTION_FLAGS]
|
|
@@ -850,9 +956,6 @@ function formatGlobalOptionRows(ctx) {
|
|
|
850
956
|
}, {
|
|
851
957
|
flags: "--output <format>",
|
|
852
958
|
description: "Output format: rich, md, json."
|
|
853
|
-
}, {
|
|
854
|
-
flags: "--debug",
|
|
855
|
-
description: "Print stack traces for unexpected errors."
|
|
856
959
|
});
|
|
857
960
|
if (ctx.showVersion) {
|
|
858
961
|
rows.push({
|
|
@@ -871,6 +974,9 @@ function collectSchemaGlobalFieldRows(group, scope, casing, globalLongOptionFlag
|
|
|
871
974
|
if (field.global !== true) {
|
|
872
975
|
continue;
|
|
873
976
|
}
|
|
977
|
+
if (globalLongOptionFlags.has(field.optionFlag)) {
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
874
980
|
const dedupeKey = `${field.optionFlag}|${field.shortFlag ?? ""}`;
|
|
875
981
|
if (seen.has(dedupeKey)) {
|
|
876
982
|
continue;
|
|
@@ -1083,9 +1189,24 @@ function addGlobalOptions(command, presetsEnabled) {
|
|
|
1083
1189
|
if (value === "markdown") {
|
|
1084
1190
|
return "md";
|
|
1085
1191
|
}
|
|
1086
|
-
throw new InvalidArgumentError(
|
|
1192
|
+
throw new InvalidArgumentError(formatInvalidEnumMessage("--output", value, ["rich", "md", "markdown", "json"], {
|
|
1193
|
+
candidates: ["rich", "markdown", "json"],
|
|
1194
|
+
threshold: 3
|
|
1195
|
+
}));
|
|
1087
1196
|
})
|
|
1088
|
-
.
|
|
1197
|
+
.addOption(new Option("--debug [mode]", "Print stack traces for unexpected errors.")
|
|
1198
|
+
.preset("trim")
|
|
1199
|
+
.argParser(parseDebugStackMode))
|
|
1200
|
+
.option("--verbose", "Print detailed runtime diagnostics.");
|
|
1201
|
+
}
|
|
1202
|
+
function parseDebugStackMode(value) {
|
|
1203
|
+
if (value === true || value === "trim") {
|
|
1204
|
+
return "trim";
|
|
1205
|
+
}
|
|
1206
|
+
if (value === "raw") {
|
|
1207
|
+
return "raw";
|
|
1208
|
+
}
|
|
1209
|
+
throw new InvalidArgumentError(formatInvalidEnumMessage("--debug", String(value), ["raw"], { candidates: ["raw"] }));
|
|
1089
1210
|
}
|
|
1090
1211
|
function setNestedValue(target, path, value) {
|
|
1091
1212
|
let cursor = target;
|
|
@@ -1283,7 +1404,7 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
|
|
|
1283
1404
|
break;
|
|
1284
1405
|
}
|
|
1285
1406
|
}
|
|
1286
|
-
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}.`);
|
|
1407
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}, got ${describeReceived(value)}.`);
|
|
1287
1408
|
}
|
|
1288
1409
|
function validatePresetFieldValue(value, field, presetPath) {
|
|
1289
1410
|
if (field.schema.kind === "json") {
|
|
@@ -1297,7 +1418,7 @@ function validatePresetFieldValue(value, field, presetPath) {
|
|
|
1297
1418
|
throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
|
|
1298
1419
|
}
|
|
1299
1420
|
if (!Array.isArray(value)) {
|
|
1300
|
-
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array.`);
|
|
1421
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array, got ${describeReceived(value)}.`);
|
|
1301
1422
|
}
|
|
1302
1423
|
return value.map((item) => validatePresetScalarValue(item, itemSchema, field.displayPath, presetPath));
|
|
1303
1424
|
}
|
|
@@ -1319,8 +1440,10 @@ async function loadPresetValues(fields, presetPath) {
|
|
|
1319
1440
|
try {
|
|
1320
1441
|
parsedPreset = JSON.parse(rawPreset);
|
|
1321
1442
|
}
|
|
1322
|
-
catch {
|
|
1323
|
-
throw new UserError(
|
|
1443
|
+
catch (error) {
|
|
1444
|
+
throw new UserError(formatJsonParseUserErrorMessage("Preset file", presetPath, rawPreset, error, {
|
|
1445
|
+
quotePath: true
|
|
1446
|
+
}), { cause: error });
|
|
1324
1447
|
}
|
|
1325
1448
|
if (!isPlainObject(parsedPreset)) {
|
|
1326
1449
|
throw new UserError(`Preset file "${presetPath}" must contain a JSON object.`);
|
|
@@ -1340,7 +1463,7 @@ async function loadPresetValues(fields, presetPath) {
|
|
|
1340
1463
|
throw new UserError(`Preset file "${presetPath}" contains unknown parameter "${displayPath}".`);
|
|
1341
1464
|
}
|
|
1342
1465
|
if (!isPlainObject(value)) {
|
|
1343
|
-
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${displayPath}". Expected an object.`);
|
|
1466
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`);
|
|
1344
1467
|
}
|
|
1345
1468
|
visitObject(value, nextPath);
|
|
1346
1469
|
}
|
|
@@ -1548,7 +1671,7 @@ function resolveFixturePath(commandPath) {
|
|
|
1548
1671
|
const parsed = path.parse(commandPath);
|
|
1549
1672
|
return path.join(parsed.dir, `${parsed.name}.fixture.json`);
|
|
1550
1673
|
}
|
|
1551
|
-
function selectFixtureScenario(scenarios, selector) {
|
|
1674
|
+
function selectFixtureScenario(scenarios, selector, fixturePath) {
|
|
1552
1675
|
if (isNumericFixtureSelector(selector)) {
|
|
1553
1676
|
const index = Number(selector) - 1;
|
|
1554
1677
|
const scenario = scenarios[index];
|
|
@@ -1559,7 +1682,13 @@ function selectFixtureScenario(scenarios, selector) {
|
|
|
1559
1682
|
}
|
|
1560
1683
|
const scenario = scenarios.find((entry) => entry.name === selector);
|
|
1561
1684
|
if (scenario === undefined) {
|
|
1562
|
-
|
|
1685
|
+
const names = scenarios
|
|
1686
|
+
.map((entry) => entry.name)
|
|
1687
|
+
.filter((name) => typeof name === "string" && name.length > 0);
|
|
1688
|
+
const available = names.length === 0
|
|
1689
|
+
? `No fixtures are declared in ${fixturePath}.`
|
|
1690
|
+
: formatAvailableList(names);
|
|
1691
|
+
throw new UserError(`Fixture scenario "${selector}" was not found. ${available}`);
|
|
1563
1692
|
}
|
|
1564
1693
|
return scenario;
|
|
1565
1694
|
}
|
|
@@ -1582,13 +1711,15 @@ async function loadFixtureScenario(command, selector) {
|
|
|
1582
1711
|
try {
|
|
1583
1712
|
parsed = JSON.parse(rawFixture);
|
|
1584
1713
|
}
|
|
1585
|
-
catch {
|
|
1586
|
-
throw new UserError(
|
|
1714
|
+
catch (error) {
|
|
1715
|
+
throw new UserError(formatJsonParseUserErrorMessage("Fixture file", fixturePath, rawFixture, error, {
|
|
1716
|
+
quotePath: false
|
|
1717
|
+
}), { cause: error });
|
|
1587
1718
|
}
|
|
1588
1719
|
if (!Array.isArray(parsed)) {
|
|
1589
1720
|
throw new UserError(`Fixture file ${fixturePath} must contain a JSON array of scenarios.`);
|
|
1590
1721
|
}
|
|
1591
|
-
return selectFixtureScenario(parsed, selector);
|
|
1722
|
+
return selectFixtureScenario(parsed, selector, fixturePath);
|
|
1592
1723
|
}
|
|
1593
1724
|
function resolveFixtureSecrets(command) {
|
|
1594
1725
|
return Object.fromEntries(Object.keys(command.secrets).map((name) => [name, "fixture-secret"]));
|
|
@@ -1662,7 +1793,7 @@ function renderApprovalDeclined(error) {
|
|
|
1662
1793
|
function validateServices(services) {
|
|
1663
1794
|
for (const name of Object.keys(services)) {
|
|
1664
1795
|
if (RESERVED_SERVICE_NAMES.has(name)) {
|
|
1665
|
-
throw new Error(`Service name "${name}" is reserved. Choose a different name
|
|
1796
|
+
throw new Error(`Service name "${name}" is reserved. Choose a different name. ${RESERVED_SERVICE_NAMES_MESSAGE}`);
|
|
1666
1797
|
}
|
|
1667
1798
|
}
|
|
1668
1799
|
}
|
|
@@ -1683,6 +1814,38 @@ function parseFieldInputValue(value, schema, label) {
|
|
|
1683
1814
|
}
|
|
1684
1815
|
return parseScalarValue(value, schema, label);
|
|
1685
1816
|
}
|
|
1817
|
+
function parseOptionFieldValue(field, value, errors) {
|
|
1818
|
+
try {
|
|
1819
|
+
if (value === null) {
|
|
1820
|
+
return { ok: true, value };
|
|
1821
|
+
}
|
|
1822
|
+
if (field.schema.kind === "array" && Array.isArray(value)) {
|
|
1823
|
+
const parsedValues = [];
|
|
1824
|
+
for (const item of value) {
|
|
1825
|
+
const parsed = parseArrayValue(String(item), field.schema, field.displayPath);
|
|
1826
|
+
if (parsed === null) {
|
|
1827
|
+
return { ok: true, value: null };
|
|
1828
|
+
}
|
|
1829
|
+
parsedValues.push(...parsed);
|
|
1830
|
+
}
|
|
1831
|
+
return { ok: true, value: parsedValues };
|
|
1832
|
+
}
|
|
1833
|
+
if (typeof value !== "string") {
|
|
1834
|
+
return { ok: true, value };
|
|
1835
|
+
}
|
|
1836
|
+
return { ok: true, value: parseFieldInputValue(value, field.schema, field.displayPath) };
|
|
1837
|
+
}
|
|
1838
|
+
catch (error) {
|
|
1839
|
+
if (error instanceof UserError || error instanceof InvalidArgumentError) {
|
|
1840
|
+
errors.push({
|
|
1841
|
+
path: field.displayPath,
|
|
1842
|
+
message: error.message
|
|
1843
|
+
});
|
|
1844
|
+
return { ok: false };
|
|
1845
|
+
}
|
|
1846
|
+
throw error;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1686
1849
|
function consumeFieldValue(args, index, schema, label, inlineValue) {
|
|
1687
1850
|
if (schema.kind === "boolean") {
|
|
1688
1851
|
if (inlineValue !== undefined) {
|
|
@@ -1746,8 +1909,9 @@ function consumeFieldValue(args, index, schema, label, inlineValue) {
|
|
|
1746
1909
|
value: parseFieldInputValue(next, schema, label)
|
|
1747
1910
|
};
|
|
1748
1911
|
}
|
|
1749
|
-
function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displayPath = []) {
|
|
1912
|
+
function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displayPath = [], displayPathPrefix = "") {
|
|
1750
1913
|
const unwrappedSchema = unwrapOptional(schema);
|
|
1914
|
+
const kind = formatCliSchemaKind(unwrappedSchema.kind);
|
|
1751
1915
|
if (rawSegments.length === 0) {
|
|
1752
1916
|
if (unwrappedSchema.kind === "json" ||
|
|
1753
1917
|
unwrappedSchema.kind === "array" ||
|
|
@@ -1761,7 +1925,7 @@ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displa
|
|
|
1761
1925
|
schema: unwrappedSchema
|
|
1762
1926
|
};
|
|
1763
1927
|
}
|
|
1764
|
-
throw new UserError(
|
|
1928
|
+
throw new UserError(formatUnsupportedDynamicSchemaMessage(kind, qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))));
|
|
1765
1929
|
}
|
|
1766
1930
|
switch (unwrappedSchema.kind) {
|
|
1767
1931
|
case "object": {
|
|
@@ -1770,30 +1934,32 @@ function resolveDynamicLeaf(schema, rawSegments, casing, outputPath = [], displa
|
|
|
1770
1934
|
if (formatSegment(key, casing) !== head) {
|
|
1771
1935
|
continue;
|
|
1772
1936
|
}
|
|
1773
|
-
return resolveDynamicLeaf(childSchema, rest, casing, [...outputPath, key], [...displayPath, key]);
|
|
1937
|
+
return resolveDynamicLeaf(childSchema, rest, casing, [...outputPath, key], [...displayPath, key], displayPathPrefix);
|
|
1774
1938
|
}
|
|
1775
|
-
throw new UserError(`Unknown parameter "${[...displayPath, head].join(".")}"
|
|
1939
|
+
throw new UserError(`Unknown parameter "${qualifyDisplayPath(displayPathPrefix, [...displayPath, head].join("."))}". ${formatAvailableList(Object.keys(unwrappedSchema.shape).map((key) => qualifyDisplayPath(displayPathPrefix, toDisplayPath([...displayPath, formatSegment(key, casing)]))))}`);
|
|
1776
1940
|
}
|
|
1777
1941
|
case "record": {
|
|
1778
1942
|
const [head, ...rest] = rawSegments;
|
|
1779
|
-
return resolveDynamicLeaf(unwrappedSchema.value, rest, casing, [...outputPath, head ?? ""], [...displayPath, head ?? ""]);
|
|
1943
|
+
return resolveDynamicLeaf(unwrappedSchema.value, rest, casing, [...outputPath, head ?? ""], [...displayPath, head ?? ""], displayPathPrefix);
|
|
1780
1944
|
}
|
|
1781
1945
|
case "array": {
|
|
1782
1946
|
const itemSchema = unwrapOptional(unwrappedSchema.item);
|
|
1783
1947
|
if (itemSchema.kind !== "object") {
|
|
1784
|
-
throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use object items.`);
|
|
1948
|
+
throw new UserError(`Array parameter "${qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))}" must use object items.`);
|
|
1785
1949
|
}
|
|
1786
1950
|
const [head, ...rest] = rawSegments;
|
|
1787
1951
|
if (head === undefined || !isNumericFixtureSelector(head)) {
|
|
1788
|
-
throw new UserError(`Array parameter "${toDisplayPath(displayPath)}" must use numeric indices.`);
|
|
1952
|
+
throw new UserError(`Array parameter "${qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))}" must use numeric indices.`);
|
|
1789
1953
|
}
|
|
1790
|
-
return resolveDynamicLeaf(itemSchema, rest, casing, [...outputPath, head], [...displayPath, head]);
|
|
1954
|
+
return resolveDynamicLeaf(itemSchema, rest, casing, [...outputPath, head], [...displayPath, head], displayPathPrefix);
|
|
1791
1955
|
}
|
|
1792
1956
|
default:
|
|
1793
|
-
throw new UserError(`Unknown parameter "${[...displayPath, ...rawSegments].join(".")}"
|
|
1957
|
+
throw new UserError(`Unknown parameter "${qualifyDisplayPath(displayPathPrefix, [...displayPath, ...rawSegments].join("."))}". ${formatAvailableList(displayPath.length === 0
|
|
1958
|
+
? []
|
|
1959
|
+
: [qualifyDisplayPath(displayPathPrefix, toDisplayPath(displayPath))])}`);
|
|
1794
1960
|
}
|
|
1795
1961
|
}
|
|
1796
|
-
function finalizeDynamicValue(schema, value, displayPath) {
|
|
1962
|
+
function finalizeDynamicValue(schema, value, displayPath, errors) {
|
|
1797
1963
|
const unwrappedSchema = unwrapOptional(schema);
|
|
1798
1964
|
if (value === undefined) {
|
|
1799
1965
|
return undefined;
|
|
@@ -1814,23 +1980,39 @@ function finalizeDynamicValue(schema, value, displayPath) {
|
|
|
1814
1980
|
return value;
|
|
1815
1981
|
}
|
|
1816
1982
|
if (!isPlainObject(value)) {
|
|
1817
|
-
|
|
1983
|
+
errors.push({
|
|
1984
|
+
path: displayPath,
|
|
1985
|
+
message: `Invalid value for "${displayPath}". Expected indexed object entries, got ${describeReceived(value)}.`
|
|
1986
|
+
});
|
|
1987
|
+
return value;
|
|
1818
1988
|
}
|
|
1819
1989
|
const entries = Object.entries(value);
|
|
1820
1990
|
const indices = entries.map(([key]) => Number(key)).sort((left, right) => left - right);
|
|
1821
1991
|
if (indices.some((index) => !Number.isInteger(index) || index < 0)) {
|
|
1822
|
-
|
|
1992
|
+
errors.push({
|
|
1993
|
+
path: displayPath,
|
|
1994
|
+
message: `Array parameter "${displayPath}" must use numeric indices.`
|
|
1995
|
+
});
|
|
1996
|
+
return value;
|
|
1823
1997
|
}
|
|
1824
1998
|
for (let index = 0; index < indices.length; index += 1) {
|
|
1825
1999
|
if (indices[index] !== index) {
|
|
1826
|
-
|
|
2000
|
+
errors.push({
|
|
2001
|
+
path: displayPath,
|
|
2002
|
+
message: `Array parameter "${displayPath}" must use contiguous indices starting at 0.`
|
|
2003
|
+
});
|
|
2004
|
+
return value;
|
|
1827
2005
|
}
|
|
1828
2006
|
}
|
|
1829
|
-
return indices.map((index) => finalizeDynamicValue(unwrappedSchema.item, value[String(index)], `${displayPath}.${index}
|
|
2007
|
+
return indices.map((index) => finalizeDynamicValue(unwrappedSchema.item, value[String(index)], `${displayPath}.${index}`, errors));
|
|
1830
2008
|
}
|
|
1831
2009
|
case "object": {
|
|
1832
2010
|
if (!isPlainObject(value)) {
|
|
1833
|
-
|
|
2011
|
+
errors.push({
|
|
2012
|
+
path: displayPath,
|
|
2013
|
+
message: `Invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`
|
|
2014
|
+
});
|
|
2015
|
+
return value;
|
|
1834
2016
|
}
|
|
1835
2017
|
const result = {};
|
|
1836
2018
|
for (const [key, rawChildSchema] of Object.entries(unwrappedSchema.shape)) {
|
|
@@ -1845,26 +2027,53 @@ function finalizeDynamicValue(schema, value, displayPath) {
|
|
|
1845
2027
|
if (rawChildSchema.kind === "optional") {
|
|
1846
2028
|
continue;
|
|
1847
2029
|
}
|
|
1848
|
-
|
|
2030
|
+
errors.push({
|
|
2031
|
+
path: childDisplayPath,
|
|
2032
|
+
message: `Missing required parameter "${childDisplayPath}".`
|
|
2033
|
+
});
|
|
2034
|
+
continue;
|
|
1849
2035
|
}
|
|
1850
|
-
result[key] = finalizeDynamicValue(rawChildSchema, childValue, childDisplayPath);
|
|
2036
|
+
result[key] = finalizeDynamicValue(rawChildSchema, childValue, childDisplayPath, errors);
|
|
1851
2037
|
}
|
|
1852
2038
|
return result;
|
|
1853
2039
|
}
|
|
1854
2040
|
case "record": {
|
|
1855
2041
|
if (!isPlainObject(value)) {
|
|
1856
|
-
|
|
2042
|
+
errors.push({
|
|
2043
|
+
path: displayPath,
|
|
2044
|
+
message: `Invalid value for "${displayPath}". Expected an object, got ${describeReceived(value)}.`
|
|
2045
|
+
});
|
|
2046
|
+
return value;
|
|
1857
2047
|
}
|
|
1858
2048
|
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
|
|
1859
2049
|
key,
|
|
1860
|
-
finalizeDynamicValue(unwrappedSchema.value, entryValue, displayPath.length === 0 ? key : `${displayPath}.${key}
|
|
2050
|
+
finalizeDynamicValue(unwrappedSchema.value, entryValue, displayPath.length === 0 ? key : `${displayPath}.${key}`, errors)
|
|
1861
2051
|
]));
|
|
1862
2052
|
}
|
|
1863
2053
|
default:
|
|
1864
|
-
|
|
2054
|
+
errors.push({
|
|
2055
|
+
path: displayPath,
|
|
2056
|
+
message: formatUnsupportedDynamicSchemaMessage(formatCliSchemaKind(unwrappedSchema.kind), displayPath)
|
|
2057
|
+
});
|
|
2058
|
+
return value;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
function formatCliSchemaKind(kind) {
|
|
2062
|
+
return kind === "oneOf" ? "oneof" : kind;
|
|
2063
|
+
}
|
|
2064
|
+
function formatUnsupportedDynamicSchemaMessage(kind, displayPath) {
|
|
2065
|
+
return `Unsupported parameter type "${kind}" for "${displayPath}". Supported types: string, number, integer, boolean, array, object, enum, oneof.`;
|
|
2066
|
+
}
|
|
2067
|
+
function qualifyDisplayPath(prefix, displayPath) {
|
|
2068
|
+
if (prefix.length === 0) {
|
|
2069
|
+
return displayPath;
|
|
2070
|
+
}
|
|
2071
|
+
if (displayPath.length === 0) {
|
|
2072
|
+
return prefix;
|
|
1865
2073
|
}
|
|
2074
|
+
return `${prefix}.${displayPath}`;
|
|
1866
2075
|
}
|
|
1867
|
-
function parseDynamicValues(dynamicFields, rawArgv, casing) {
|
|
2076
|
+
function parseDynamicValues(dynamicFields, rawArgv, casing, errors) {
|
|
1868
2077
|
const rawValues = new Map();
|
|
1869
2078
|
const providedFieldIds = new Set();
|
|
1870
2079
|
const sortedFields = [...dynamicFields].sort((left, right) => right.optionPath.length - left.optionPath.length);
|
|
@@ -1885,11 +2094,11 @@ function parseDynamicValues(dynamicFields, rawArgv, casing) {
|
|
|
1885
2094
|
optionPath.every((segment, segmentIndex) => flagPath[segmentIndex] === segment));
|
|
1886
2095
|
});
|
|
1887
2096
|
if (match === undefined) {
|
|
1888
|
-
throw new UserError(`Unknown parameter "${flagName}"
|
|
2097
|
+
throw new UserError(`Unknown parameter "${flagName}". ${formatAvailableList(dynamicFields.map((field) => field.optionPathDisplay))}`);
|
|
1889
2098
|
}
|
|
1890
2099
|
const optionPath = match.optionPath.map((segment) => formatSegment(segment, casing));
|
|
1891
2100
|
const remainder = flagPath.slice(optionPath.length);
|
|
1892
|
-
const leaf = resolveDynamicLeaf(match.schema, remainder, casing);
|
|
2101
|
+
const leaf = resolveDynamicLeaf(match.schema, remainder, casing, [], [], match.displayPath);
|
|
1893
2102
|
const rawStore = rawValues.get(match.id) ?? {};
|
|
1894
2103
|
const label = `${match.displayPath}.${leaf.displayPath}`.replace(/^\./u, "");
|
|
1895
2104
|
const parsed = negated && leaf.schema.kind === "boolean"
|
|
@@ -1909,13 +2118,23 @@ function parseDynamicValues(dynamicFields, rawArgv, casing) {
|
|
|
1909
2118
|
.filter((field) => rawValues.has(field.id))
|
|
1910
2119
|
.map((field) => [
|
|
1911
2120
|
field.id,
|
|
1912
|
-
finalizeDynamicValue(field.schema.kind === "record" ? field.schema : field.schema, rawValues.get(field.id), field.displayPath)
|
|
2121
|
+
finalizeDynamicValue(field.schema.kind === "record" ? field.schema : field.schema, rawValues.get(field.id), field.displayPath, errors)
|
|
1913
2122
|
]))
|
|
1914
2123
|
};
|
|
1915
2124
|
}
|
|
1916
|
-
async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt) {
|
|
2125
|
+
async function enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, providedDynamicFieldIds, providedFieldIds, shouldPrompt, errors) {
|
|
1917
2126
|
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
|
1918
2127
|
const dynamicFieldById = new Map(dynamicFields.map((field) => [field.id, field]));
|
|
2128
|
+
const getAvailableBranchParameters = (branch) => [
|
|
2129
|
+
...branch.fieldIds
|
|
2130
|
+
.map((fieldId) => fieldById.get(fieldId))
|
|
2131
|
+
.filter((field) => field !== undefined && field.synthetic !== true)
|
|
2132
|
+
.map((field) => field.displayPath),
|
|
2133
|
+
...branch.dynamicFieldIds
|
|
2134
|
+
.map((fieldId) => dynamicFieldById.get(fieldId))
|
|
2135
|
+
.filter((field) => field !== undefined)
|
|
2136
|
+
.map((field) => field.optionPathDisplay)
|
|
2137
|
+
];
|
|
1919
2138
|
for (const variant of variants) {
|
|
1920
2139
|
let selectedBranchId = resolvedFieldValues.get(variant.controlFieldId);
|
|
1921
2140
|
if (selectedBranchId === undefined && shouldPrompt) {
|
|
@@ -1932,12 +2151,21 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
|
|
|
1932
2151
|
if (variant.optional) {
|
|
1933
2152
|
continue;
|
|
1934
2153
|
}
|
|
1935
|
-
|
|
2154
|
+
errors.push({
|
|
2155
|
+
path: variant.controlDisplayPath,
|
|
2156
|
+
message: `Missing required parameter "${variant.controlDisplayPath}".`
|
|
2157
|
+
});
|
|
2158
|
+
continue;
|
|
1936
2159
|
}
|
|
1937
2160
|
const selectedBranch = variant.branches.find((branch) => branch.branchId === selectedBranchId);
|
|
1938
2161
|
if (selectedBranch === undefined) {
|
|
1939
|
-
|
|
2162
|
+
errors.push({
|
|
2163
|
+
path: variant.controlDisplayPath,
|
|
2164
|
+
message: `Invalid value for "${variant.controlDisplayPath}". Expected one of: ${variant.branches.map((branch) => branch.branchId).join(", ")}, got ${describeReceived(selectedBranchId)}.`
|
|
2165
|
+
});
|
|
2166
|
+
continue;
|
|
1940
2167
|
}
|
|
2168
|
+
let hasInvalidBranchParameter = false;
|
|
1941
2169
|
for (const branch of variant.branches) {
|
|
1942
2170
|
if (branch.branchId === selectedBranch.branchId) {
|
|
1943
2171
|
continue;
|
|
@@ -1946,17 +2174,28 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
|
|
|
1946
2174
|
if (invalidFieldId !== undefined) {
|
|
1947
2175
|
const field = fieldById.get(invalidFieldId);
|
|
1948
2176
|
if (field !== undefined) {
|
|
1949
|
-
|
|
2177
|
+
errors.push({
|
|
2178
|
+
path: field.displayPath,
|
|
2179
|
+
message: `Unknown parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
|
|
2180
|
+
});
|
|
2181
|
+
hasInvalidBranchParameter = true;
|
|
1950
2182
|
}
|
|
1951
2183
|
}
|
|
1952
2184
|
const invalidDynamicFieldId = branch.dynamicFieldIds.find((fieldId) => providedDynamicFieldIds.has(fieldId));
|
|
1953
2185
|
if (invalidDynamicFieldId !== undefined) {
|
|
1954
2186
|
const field = dynamicFieldById.get(invalidDynamicFieldId);
|
|
1955
2187
|
if (field !== undefined) {
|
|
1956
|
-
|
|
2188
|
+
errors.push({
|
|
2189
|
+
path: field.displayPath,
|
|
2190
|
+
message: `Unknown parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
|
|
2191
|
+
});
|
|
2192
|
+
hasInvalidBranchParameter = true;
|
|
1957
2193
|
}
|
|
1958
2194
|
}
|
|
1959
2195
|
}
|
|
2196
|
+
if (hasInvalidBranchParameter) {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
1960
2199
|
for (const fieldId of selectedBranch.requiredFieldIds) {
|
|
1961
2200
|
const field = fieldById.get(fieldId);
|
|
1962
2201
|
if (field === undefined || field.synthetic) {
|
|
@@ -1972,7 +2211,10 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
|
|
|
1972
2211
|
providedFieldIds.add(field.id);
|
|
1973
2212
|
continue;
|
|
1974
2213
|
}
|
|
1975
|
-
|
|
2214
|
+
errors.push({
|
|
2215
|
+
path: field.displayPath,
|
|
2216
|
+
message: `Missing required parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
|
|
2217
|
+
});
|
|
1976
2218
|
}
|
|
1977
2219
|
for (const fieldId of selectedBranch.requiredDynamicFieldIds) {
|
|
1978
2220
|
const field = dynamicFieldById.get(fieldId);
|
|
@@ -1982,7 +2224,10 @@ async function enforceVariantConstraints(params, fields, dynamicFields, variants
|
|
|
1982
2224
|
if (getNestedValue(params, field.path) !== undefined) {
|
|
1983
2225
|
continue;
|
|
1984
2226
|
}
|
|
1985
|
-
|
|
2227
|
+
errors.push({
|
|
2228
|
+
path: field.displayPath,
|
|
2229
|
+
message: `Missing required parameter "${field.displayPath}" for ${variant.controlDisplayPath}="${selectedBranch.branchId}". ${formatAvailableList(getAvailableBranchParameters(selectedBranch))}`
|
|
2230
|
+
});
|
|
1986
2231
|
}
|
|
1987
2232
|
}
|
|
1988
2233
|
}
|
|
@@ -1993,6 +2238,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
|
|
|
1993
2238
|
: {};
|
|
1994
2239
|
const providedFieldIds = new Set();
|
|
1995
2240
|
const resolvedFieldValues = new Map();
|
|
2241
|
+
const errors = [];
|
|
1996
2242
|
for (const field of fields) {
|
|
1997
2243
|
let value;
|
|
1998
2244
|
let source;
|
|
@@ -2026,11 +2272,25 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
|
|
|
2026
2272
|
value = normalizeCommanderOptionValue(optionValues[field.optionAttribute]);
|
|
2027
2273
|
source = "option";
|
|
2028
2274
|
}
|
|
2275
|
+
if (value === undefined &&
|
|
2276
|
+
field.optionFlag === "--verbose" &&
|
|
2277
|
+
Object.prototype.hasOwnProperty.call(optionValues, field.optionAttribute) &&
|
|
2278
|
+
hasFieldValue(optionValues[field.optionAttribute])) {
|
|
2279
|
+
value = normalizeCommanderOptionValue(optionValues[field.optionAttribute]);
|
|
2280
|
+
source = "option";
|
|
2281
|
+
}
|
|
2029
2282
|
if (value === undefined &&
|
|
2030
2283
|
Object.prototype.hasOwnProperty.call(presetValues, field.optionAttribute)) {
|
|
2031
2284
|
value = presetValues[field.optionAttribute];
|
|
2032
2285
|
source = "preset";
|
|
2033
2286
|
}
|
|
2287
|
+
if (source === "option") {
|
|
2288
|
+
const parsed = parseOptionFieldValue(field, value, errors);
|
|
2289
|
+
if (!parsed.ok) {
|
|
2290
|
+
continue;
|
|
2291
|
+
}
|
|
2292
|
+
value = parsed.value;
|
|
2293
|
+
}
|
|
2034
2294
|
if (value === undefined && shouldPrompt && !field.optional) {
|
|
2035
2295
|
value = await promptForField(field);
|
|
2036
2296
|
source = "prompt";
|
|
@@ -2043,7 +2303,11 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
|
|
|
2043
2303
|
if (field.optional) {
|
|
2044
2304
|
continue;
|
|
2045
2305
|
}
|
|
2046
|
-
|
|
2306
|
+
errors.push({
|
|
2307
|
+
path: field.displayPath,
|
|
2308
|
+
message: `Missing required parameter "${field.displayPath}".`
|
|
2309
|
+
});
|
|
2310
|
+
continue;
|
|
2047
2311
|
}
|
|
2048
2312
|
resolvedFieldValues.set(field.id, value);
|
|
2049
2313
|
if (source !== undefined && source !== "default") {
|
|
@@ -2054,7 +2318,7 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
|
|
|
2054
2318
|
}
|
|
2055
2319
|
}
|
|
2056
2320
|
const dynamicResults = dynamicFields.length > 0
|
|
2057
|
-
? parseDynamicValues(dynamicFields, rawArgv, casing)
|
|
2321
|
+
? parseDynamicValues(dynamicFields, rawArgv, casing, errors)
|
|
2058
2322
|
: {
|
|
2059
2323
|
providedFieldIds: new Set(),
|
|
2060
2324
|
values: new Map()
|
|
@@ -2068,18 +2332,23 @@ async function resolveParams(fields, dynamicFields, variants, positionalValues,
|
|
|
2068
2332
|
if (field.optional || field.variantId !== undefined) {
|
|
2069
2333
|
continue;
|
|
2070
2334
|
}
|
|
2071
|
-
|
|
2335
|
+
errors.push({
|
|
2336
|
+
path: field.displayPath,
|
|
2337
|
+
message: `Missing required parameter "${field.displayPath}".`
|
|
2338
|
+
});
|
|
2339
|
+
continue;
|
|
2072
2340
|
}
|
|
2073
2341
|
setNestedValue(params, field.path, value);
|
|
2074
2342
|
}
|
|
2075
|
-
await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt);
|
|
2343
|
+
await enforceVariantConstraints(params, fields, dynamicFields, variants, resolvedFieldValues, dynamicResults.providedFieldIds, providedFieldIds, shouldPrompt, errors);
|
|
2344
|
+
throwValidationErrors(errors);
|
|
2076
2345
|
return params;
|
|
2077
2346
|
}
|
|
2078
2347
|
function getResolvedFlags(command) {
|
|
2079
2348
|
const flags = command.optsWithGlobals();
|
|
2080
2349
|
return flags;
|
|
2081
2350
|
}
|
|
2082
|
-
async function executeCommand(state, services, requirementOptions, runtimeOptions) {
|
|
2351
|
+
async function executeCommand(state, services, requirementOptions, runtimeOptions, onErrorReportContext) {
|
|
2083
2352
|
const logger = createLogger();
|
|
2084
2353
|
const primitives = {
|
|
2085
2354
|
logger,
|
|
@@ -2102,55 +2371,250 @@ async function executeCommand(state, services, requirementOptions, runtimeOption
|
|
|
2102
2371
|
logger.info(message);
|
|
2103
2372
|
}
|
|
2104
2373
|
};
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
params
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2374
|
+
let runtimeSecrets;
|
|
2375
|
+
let resolvedParams;
|
|
2376
|
+
try {
|
|
2377
|
+
await withOutputFormat(output, async () => {
|
|
2378
|
+
await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
|
|
2379
|
+
const params = await resolveParams(state.fields, state.dynamicFields, state.variants, state.positionalValues, optionValues, state.rawArgv, state.casing, state.presetsEnabled ? resolvedFlags.preset : undefined, shouldPrompt);
|
|
2380
|
+
resolvedParams = params;
|
|
2381
|
+
runtimeSecrets = runtime.secrets;
|
|
2382
|
+
const context = {
|
|
2383
|
+
...preflightContext,
|
|
2384
|
+
params
|
|
2385
|
+
};
|
|
2386
|
+
if (state.command.confirm &&
|
|
2387
|
+
!state.command.humanInLoop &&
|
|
2388
|
+
!resolvedFlags.yes &&
|
|
2389
|
+
process.stdin.isTTY) {
|
|
2390
|
+
for (const field of state.fields) {
|
|
2391
|
+
const value = field.path.reduce((current, segment) => current && typeof current === "object"
|
|
2392
|
+
? current[segment]
|
|
2393
|
+
: undefined, params);
|
|
2394
|
+
if (value !== undefined) {
|
|
2395
|
+
logger.resolved(field.displayPath, formatResolvedValue(value));
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
const proceed = await confirm({
|
|
2399
|
+
message: "Proceed?",
|
|
2400
|
+
initialValue: true
|
|
2401
|
+
});
|
|
2402
|
+
if (isCancel(proceed)) {
|
|
2403
|
+
cancel("Operation cancelled.");
|
|
2404
|
+
throw new UserError("Operation cancelled.");
|
|
2405
|
+
}
|
|
2406
|
+
if (proceed !== true) {
|
|
2407
|
+
throw new UserError("Operation cancelled.");
|
|
2122
2408
|
}
|
|
2123
2409
|
}
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
});
|
|
2128
|
-
if (isCancel(proceed)) {
|
|
2129
|
-
cancel("Operation cancelled.");
|
|
2130
|
-
throw new UserError("Operation cancelled.");
|
|
2410
|
+
const result = await invokeWithHumanInLoop(state.command, context, runtimeOptions, state.commandPath);
|
|
2411
|
+
if (output === "rich" && runtime.isFixture) {
|
|
2412
|
+
writeRichHeader(`${state.command.name} (fixture)`);
|
|
2131
2413
|
}
|
|
2132
|
-
if (
|
|
2133
|
-
|
|
2414
|
+
if (isHumanInLoopPending(result)) {
|
|
2415
|
+
renderHumanInLoopPending(result);
|
|
2416
|
+
return;
|
|
2134
2417
|
}
|
|
2418
|
+
const renderStatus = renderResult(state.command, result, output, primitives);
|
|
2419
|
+
if (renderStatus.mcpError) {
|
|
2420
|
+
process.exitCode = 1;
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
catch (error) {
|
|
2425
|
+
onErrorReportContext?.({
|
|
2426
|
+
command: state.command,
|
|
2427
|
+
commandPath: state.commandPath,
|
|
2428
|
+
params: resolvedParams,
|
|
2429
|
+
secrets: runtimeSecrets ?? runtime.secrets
|
|
2430
|
+
});
|
|
2431
|
+
throw error;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
function isStringRecord(value) {
|
|
2435
|
+
return isPlainObject(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
2436
|
+
}
|
|
2437
|
+
function isHttpErrorLike(error) {
|
|
2438
|
+
if (!isPlainObject(error)) {
|
|
2439
|
+
return false;
|
|
2440
|
+
}
|
|
2441
|
+
if (error.name !== "HttpError" || typeof error.message !== "string") {
|
|
2442
|
+
return false;
|
|
2443
|
+
}
|
|
2444
|
+
const request = error.request;
|
|
2445
|
+
const response = error.response;
|
|
2446
|
+
return (isPlainObject(request) &&
|
|
2447
|
+
typeof request.method === "string" &&
|
|
2448
|
+
typeof request.url === "string" &&
|
|
2449
|
+
isStringRecord(request.headers) &&
|
|
2450
|
+
isPlainObject(response) &&
|
|
2451
|
+
typeof response.status === "number" &&
|
|
2452
|
+
typeof response.statusText === "string" &&
|
|
2453
|
+
isStringRecord(response.headers) &&
|
|
2454
|
+
"body" in response);
|
|
2455
|
+
}
|
|
2456
|
+
function hasTypedOptionalField(value, field, type) {
|
|
2457
|
+
return !(field in value) || typeof value[field] === type;
|
|
2458
|
+
}
|
|
2459
|
+
function isNonEmptyString(value) {
|
|
2460
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2461
|
+
}
|
|
2462
|
+
function isProblemDetailsLike(body) {
|
|
2463
|
+
if (!isPlainObject(body)) {
|
|
2464
|
+
return false;
|
|
2465
|
+
}
|
|
2466
|
+
if (!hasTypedOptionalField(body, "type", "string")) {
|
|
2467
|
+
return false;
|
|
2468
|
+
}
|
|
2469
|
+
if (!hasTypedOptionalField(body, "title", "string")) {
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
if (!hasTypedOptionalField(body, "status", "number")) {
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
if (!hasTypedOptionalField(body, "detail", "string")) {
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
if (!hasTypedOptionalField(body, "instance", "string")) {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
return isNonEmptyString(body.title) || isNonEmptyString(body.detail);
|
|
2482
|
+
}
|
|
2483
|
+
function isGraphQLErrorEnvelopeLike(body) {
|
|
2484
|
+
if (!isPlainObject(body) || !Array.isArray(body.errors) || body.errors.length === 0) {
|
|
2485
|
+
return false;
|
|
2486
|
+
}
|
|
2487
|
+
return body.errors.every((error) => {
|
|
2488
|
+
if (!isPlainObject(error) || typeof error.message !== "string") {
|
|
2489
|
+
return false;
|
|
2135
2490
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
return;
|
|
2491
|
+
if ("path" in error) {
|
|
2492
|
+
const pathValue = error.path;
|
|
2493
|
+
if (!Array.isArray(pathValue) ||
|
|
2494
|
+
!pathValue.every((entry) => typeof entry === "string" || typeof entry === "number")) {
|
|
2495
|
+
return false;
|
|
2496
|
+
}
|
|
2143
2497
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2498
|
+
if ("extensions" in error) {
|
|
2499
|
+
if (!isPlainObject(error.extensions)) {
|
|
2500
|
+
return false;
|
|
2501
|
+
}
|
|
2502
|
+
if ("code" in error.extensions && typeof error.extensions.code !== "string") {
|
|
2503
|
+
return false;
|
|
2504
|
+
}
|
|
2147
2505
|
}
|
|
2506
|
+
return true;
|
|
2148
2507
|
});
|
|
2149
2508
|
}
|
|
2150
|
-
function
|
|
2509
|
+
function styleHttpErrorLine(value, style) {
|
|
2510
|
+
return process.stdout.isTTY === false ? value : style(value);
|
|
2511
|
+
}
|
|
2512
|
+
function formatHttpErrorStatus(value) {
|
|
2513
|
+
return styleHttpErrorLine(value, text.error);
|
|
2514
|
+
}
|
|
2515
|
+
function formatProblemDetailsBody(body) {
|
|
2516
|
+
const lines = [];
|
|
2517
|
+
if (isNonEmptyString(body.title)) {
|
|
2518
|
+
lines.push(`Problem: ${body.title}`);
|
|
2519
|
+
}
|
|
2520
|
+
if (isNonEmptyString(body.detail)) {
|
|
2521
|
+
lines.push(`Detail: ${body.detail}`);
|
|
2522
|
+
}
|
|
2523
|
+
if (body.type !== undefined) {
|
|
2524
|
+
lines.push(`Type: ${body.type}`);
|
|
2525
|
+
}
|
|
2526
|
+
if (body.instance !== undefined) {
|
|
2527
|
+
lines.push(`Instance: ${body.instance}`);
|
|
2528
|
+
}
|
|
2529
|
+
if (body.status !== undefined) {
|
|
2530
|
+
lines.push(`Status: ${body.status}`);
|
|
2531
|
+
}
|
|
2532
|
+
return lines.join("\n");
|
|
2533
|
+
}
|
|
2534
|
+
function formatGraphQLErrorEnvelopeBody(body) {
|
|
2535
|
+
return body.errors
|
|
2536
|
+
.map((error) => {
|
|
2537
|
+
const lines = [`GraphQL error: ${error.message}`];
|
|
2538
|
+
if (error.path !== undefined) {
|
|
2539
|
+
lines.push(` at path: ${error.path.join(".")}`);
|
|
2540
|
+
}
|
|
2541
|
+
if (error.extensions?.code !== undefined) {
|
|
2542
|
+
lines.push(` code: ${error.extensions.code}`);
|
|
2543
|
+
}
|
|
2544
|
+
return lines.join("\n");
|
|
2545
|
+
})
|
|
2546
|
+
.join("\n\n");
|
|
2547
|
+
}
|
|
2548
|
+
function formatHttpErrorBody(body) {
|
|
2549
|
+
if (typeof body === "string") {
|
|
2550
|
+
return body;
|
|
2551
|
+
}
|
|
2552
|
+
if (isProblemDetailsLike(body)) {
|
|
2553
|
+
return formatProblemDetailsBody(body);
|
|
2554
|
+
}
|
|
2555
|
+
if (isGraphQLErrorEnvelopeLike(body)) {
|
|
2556
|
+
return formatGraphQLErrorEnvelopeBody(body);
|
|
2557
|
+
}
|
|
2558
|
+
const serialized = JSON.stringify(body, null, 2);
|
|
2559
|
+
return serialized === undefined ? String(body) : serialized;
|
|
2560
|
+
}
|
|
2561
|
+
function indentHttpErrorBlock(value) {
|
|
2562
|
+
return value
|
|
2563
|
+
.split("\n")
|
|
2564
|
+
.map((line) => ` ${line}`)
|
|
2565
|
+
.join("\n");
|
|
2566
|
+
}
|
|
2567
|
+
function formatHttpHeaderValue(name, value) {
|
|
2568
|
+
return name.toLowerCase() === "authorization" ? "Bearer ****" : value;
|
|
2569
|
+
}
|
|
2570
|
+
function formatHttpErrorHeaders(headers) {
|
|
2571
|
+
return Object.entries(headers).map(([name, value]) => ` ${name}: ${formatHttpHeaderValue(name, value)}`);
|
|
2572
|
+
}
|
|
2573
|
+
function formatHttpErrorSnippet(body) {
|
|
2574
|
+
return formatHttpErrorBody(body).replace(/\s+/g, " ").trim().slice(0, 200);
|
|
2575
|
+
}
|
|
2576
|
+
function renderHttpError(error, options) {
|
|
2577
|
+
const detailed = options.verbose || options.debugStackMode !== undefined;
|
|
2578
|
+
const lines = [
|
|
2579
|
+
styleHttpErrorLine(`Request: ${error.request.method} ${error.request.url}`, text.muted)
|
|
2580
|
+
];
|
|
2581
|
+
if (detailed) {
|
|
2582
|
+
lines.push("", "Request headers:", ...formatHttpErrorHeaders(error.request.headers), "");
|
|
2583
|
+
if (error.request.body !== undefined) {
|
|
2584
|
+
lines.push("Request body:", indentHttpErrorBlock(formatHttpErrorBody(error.request.body)), "");
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
lines.push(formatHttpErrorStatus(`Status: ${error.response.status} ${error.response.statusText}`));
|
|
2588
|
+
if (detailed) {
|
|
2589
|
+
lines.push("", "Response headers:", ...formatHttpErrorHeaders(error.response.headers), "", "Response body:", indentHttpErrorBlock(formatHttpErrorBody(error.response.body)));
|
|
2590
|
+
}
|
|
2591
|
+
else {
|
|
2592
|
+
lines.push("", `Response body: ${formatHttpErrorSnippet(error.response.body)}`, "Re-run with --verbose to see headers and full body.");
|
|
2593
|
+
}
|
|
2594
|
+
process.stderr.write(`${lines.join("\n")}\n`);
|
|
2595
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
2596
|
+
if (options.debugStackMode !== undefined && stack) {
|
|
2597
|
+
process.stderr.write(`${formatDebugStack(stack, options.debugStackMode)}\n`);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function handleRunError(error, options) {
|
|
2151
2601
|
const logger = createLogger();
|
|
2152
2602
|
if (error instanceof UserError) {
|
|
2153
|
-
logger.error(error.message
|
|
2603
|
+
logger.error(appendUsagePointer(error.message, {
|
|
2604
|
+
rootUsageName: options.rootUsageName,
|
|
2605
|
+
commandPath: options.commandPath
|
|
2606
|
+
}));
|
|
2607
|
+
process.exitCode = 1;
|
|
2608
|
+
return;
|
|
2609
|
+
}
|
|
2610
|
+
if (error instanceof Error && error.name === "ToolcraftBugError") {
|
|
2611
|
+
logger.error(`toolcraft hit an internal invariant: ${error.message}\n` +
|
|
2612
|
+
`This is a bug in toolcraft or in the command definition; ` +
|
|
2613
|
+
`it cannot be worked around by changing argv. ` +
|
|
2614
|
+
`Re-run with --debug for a stack trace and file an issue.`);
|
|
2615
|
+
if (options.debugStackMode !== undefined && error.stack) {
|
|
2616
|
+
process.stderr.write(`${formatDebugStack(error.stack, options.debugStackMode)}\n`);
|
|
2617
|
+
}
|
|
2154
2618
|
process.exitCode = 1;
|
|
2155
2619
|
return;
|
|
2156
2620
|
}
|
|
@@ -2159,22 +2623,229 @@ function handleRunError(error, debug) {
|
|
|
2159
2623
|
if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
|
|
2160
2624
|
return;
|
|
2161
2625
|
}
|
|
2626
|
+
if (error.code === "commander.unknownCommand") {
|
|
2627
|
+
logger.error(appendUsagePointer(formatUnknownCommandError(error, options.program, options.argv ?? process.argv), {
|
|
2628
|
+
rootUsageName: options.rootUsageName,
|
|
2629
|
+
commandPath: options.commandPath
|
|
2630
|
+
}));
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
if (error.code === "commander.unknownOption") {
|
|
2634
|
+
const argv = options.argv ?? process.argv;
|
|
2635
|
+
logger.error(appendUsagePointer(formatUnknownOptionError(error, options.program, argv), {
|
|
2636
|
+
rootUsageName: options.rootUsageName,
|
|
2637
|
+
commandPath: options.commandPath.length > 0
|
|
2638
|
+
? options.commandPath
|
|
2639
|
+
: findCurrentCommanderCommandPath(options.program, argv)
|
|
2640
|
+
}));
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
if (isHttpErrorLike(error)) {
|
|
2646
|
+
renderHttpError(error, options);
|
|
2647
|
+
process.exitCode = 1;
|
|
2162
2648
|
return;
|
|
2163
2649
|
}
|
|
2164
2650
|
const message = error instanceof Error ? error.message : String(error);
|
|
2165
|
-
logger.error(
|
|
2166
|
-
if (
|
|
2167
|
-
process.stderr.write(`${error.stack}\n`);
|
|
2651
|
+
logger.error(options.debugStackMode !== undefined ? message : `${message} Use --debug for a stack trace.`);
|
|
2652
|
+
if (options.debugStackMode !== undefined && error instanceof Error && error.stack) {
|
|
2653
|
+
process.stderr.write(`${formatDebugStack(error.stack, options.debugStackMode)}\n`);
|
|
2168
2654
|
}
|
|
2169
2655
|
process.exitCode = 1;
|
|
2170
2656
|
}
|
|
2657
|
+
function formatInvalidEnumMessage(label, value, values, opts = {}) {
|
|
2658
|
+
const suggestions = suggest(value, opts.candidates ?? values.map((candidate) => String(candidate)), opts);
|
|
2659
|
+
const suggestionLine = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
|
|
2660
|
+
return `Invalid value for "${label}".${suggestionLine}Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`;
|
|
2661
|
+
}
|
|
2662
|
+
function formatUnknownCommandError(error, program, argv) {
|
|
2663
|
+
const input = extractQuotedCommanderValue(error.message) ?? "";
|
|
2664
|
+
const currentCommand = program === undefined ? undefined : findCurrentCommanderCommand(program, argv);
|
|
2665
|
+
return formatUnknownCommandMessage(input, currentCommand);
|
|
2666
|
+
}
|
|
2667
|
+
function appendUsagePointer(message, options) {
|
|
2668
|
+
if (message.includes("--help")) {
|
|
2669
|
+
return message;
|
|
2670
|
+
}
|
|
2671
|
+
const helpTarget = options.commandPath.length === 0
|
|
2672
|
+
? options.rootUsageName
|
|
2673
|
+
: `${options.rootUsageName} ${options.commandPath}`;
|
|
2674
|
+
return `${message}\nRun ${helpTarget} --help for usage.`;
|
|
2675
|
+
}
|
|
2676
|
+
function formatCliCommandPath(commandPath) {
|
|
2677
|
+
return commandPath
|
|
2678
|
+
.split(".")
|
|
2679
|
+
.filter((segment) => segment.length > 0)
|
|
2680
|
+
.join(" ");
|
|
2681
|
+
}
|
|
2682
|
+
function formatUnknownCommandMessage(input, currentCommand) {
|
|
2683
|
+
const suggestions = currentCommand === undefined
|
|
2684
|
+
? []
|
|
2685
|
+
: suggest(input, currentCommand.commands.map((command) => command.name()));
|
|
2686
|
+
return formatSuggestionMessage(`Unknown command "${input}".`, suggestions);
|
|
2687
|
+
}
|
|
2688
|
+
function formatUnknownOptionError(error, program, argv) {
|
|
2689
|
+
const input = extractQuotedCommanderValue(error.message) ?? "";
|
|
2690
|
+
const currentCommand = program === undefined ? undefined : findCurrentCommanderCommand(program, argv);
|
|
2691
|
+
const suggestions = currentCommand === undefined
|
|
2692
|
+
? []
|
|
2693
|
+
: suggest(input, currentCommand.options
|
|
2694
|
+
.map((option) => option.long)
|
|
2695
|
+
.filter((flag) => flag !== undefined));
|
|
2696
|
+
return formatSuggestionMessage(`Unknown option "${input}".`, suggestions);
|
|
2697
|
+
}
|
|
2698
|
+
function formatSuggestionMessage(message, suggestions) {
|
|
2699
|
+
if (suggestions.length === 0) {
|
|
2700
|
+
return message;
|
|
2701
|
+
}
|
|
2702
|
+
return `${message}\nDid you mean: ${suggestions.join(", ")}?`;
|
|
2703
|
+
}
|
|
2704
|
+
function extractQuotedCommanderValue(message) {
|
|
2705
|
+
const singleQuoted = extractBetweenQuotes(message, "'");
|
|
2706
|
+
if (singleQuoted !== undefined) {
|
|
2707
|
+
return singleQuoted;
|
|
2708
|
+
}
|
|
2709
|
+
return extractBetweenQuotes(message, '"');
|
|
2710
|
+
}
|
|
2711
|
+
function extractBetweenQuotes(message, quote) {
|
|
2712
|
+
const start = message.indexOf(quote);
|
|
2713
|
+
if (start === -1) {
|
|
2714
|
+
return undefined;
|
|
2715
|
+
}
|
|
2716
|
+
const end = message.indexOf(quote, start + 1);
|
|
2717
|
+
if (end === -1) {
|
|
2718
|
+
return undefined;
|
|
2719
|
+
}
|
|
2720
|
+
return message.slice(start + 1, end);
|
|
2721
|
+
}
|
|
2722
|
+
function resolveDebugStackMode(value) {
|
|
2723
|
+
if (value === true || value === "trim") {
|
|
2724
|
+
return "trim";
|
|
2725
|
+
}
|
|
2726
|
+
if (value === "raw") {
|
|
2727
|
+
return "raw";
|
|
2728
|
+
}
|
|
2729
|
+
return undefined;
|
|
2730
|
+
}
|
|
2731
|
+
function getDebugStackModeFromArgv(argv) {
|
|
2732
|
+
for (let index = 2; index < argv.length; index += 1) {
|
|
2733
|
+
const token = argv[index];
|
|
2734
|
+
if (token === undefined) {
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
if (token === "--debug") {
|
|
2738
|
+
return "trim";
|
|
2739
|
+
}
|
|
2740
|
+
if (token === "--debug=raw") {
|
|
2741
|
+
return "raw";
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
return undefined;
|
|
2745
|
+
}
|
|
2746
|
+
function findCurrentCommanderCommand(program, argv) {
|
|
2747
|
+
let current = program;
|
|
2748
|
+
const tokens = argv.slice(2);
|
|
2749
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
2750
|
+
const token = tokens[index];
|
|
2751
|
+
if (token === undefined || token === "--") {
|
|
2752
|
+
break;
|
|
2753
|
+
}
|
|
2754
|
+
if (token.startsWith("-")) {
|
|
2755
|
+
const option = current.options.find((candidate) => candidate.long === token || candidate.short === token);
|
|
2756
|
+
if (option?.required === true && !token.includes("=")) {
|
|
2757
|
+
index += 1;
|
|
2758
|
+
}
|
|
2759
|
+
continue;
|
|
2760
|
+
}
|
|
2761
|
+
const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
|
|
2762
|
+
if (child === undefined) {
|
|
2763
|
+
break;
|
|
2764
|
+
}
|
|
2765
|
+
current = child;
|
|
2766
|
+
}
|
|
2767
|
+
return current;
|
|
2768
|
+
}
|
|
2769
|
+
function findCurrentCommanderCommandPath(program, argv) {
|
|
2770
|
+
if (program === undefined) {
|
|
2771
|
+
return "";
|
|
2772
|
+
}
|
|
2773
|
+
let current = program;
|
|
2774
|
+
const pathSegments = [];
|
|
2775
|
+
const tokens = argv.slice(2);
|
|
2776
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
2777
|
+
const token = tokens[index];
|
|
2778
|
+
if (token === undefined || token === "--" || token.startsWith("-")) {
|
|
2779
|
+
break;
|
|
2780
|
+
}
|
|
2781
|
+
const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
|
|
2782
|
+
if (child === undefined) {
|
|
2783
|
+
break;
|
|
2784
|
+
}
|
|
2785
|
+
current = child;
|
|
2786
|
+
pathSegments.push(child.name());
|
|
2787
|
+
}
|
|
2788
|
+
return pathSegments.join(" ");
|
|
2789
|
+
}
|
|
2790
|
+
function findUnknownCommanderCommand(program, argv) {
|
|
2791
|
+
let current = program;
|
|
2792
|
+
const pathSegments = [];
|
|
2793
|
+
const tokens = argv.slice(2);
|
|
2794
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
2795
|
+
const token = tokens[index];
|
|
2796
|
+
if (token === undefined || token === "--") {
|
|
2797
|
+
return undefined;
|
|
2798
|
+
}
|
|
2799
|
+
if (token.startsWith("-")) {
|
|
2800
|
+
const option = current.options.find((candidate) => candidate.long === token || candidate.short === token);
|
|
2801
|
+
if (option?.required === true && !token.includes("=")) {
|
|
2802
|
+
index += 1;
|
|
2803
|
+
}
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
if (current.commands.length === 0 || getDefaultCommanderCommandName(current) !== undefined) {
|
|
2807
|
+
return undefined;
|
|
2808
|
+
}
|
|
2809
|
+
const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
|
|
2810
|
+
if (child === undefined) {
|
|
2811
|
+
return {
|
|
2812
|
+
input: token,
|
|
2813
|
+
currentCommand: current,
|
|
2814
|
+
commandPath: pathSegments.join(" ")
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
current = child;
|
|
2818
|
+
pathSegments.push(child.name());
|
|
2819
|
+
}
|
|
2820
|
+
return undefined;
|
|
2821
|
+
}
|
|
2822
|
+
function getDefaultCommanderCommandName(command) {
|
|
2823
|
+
const candidate = command;
|
|
2824
|
+
return typeof candidate._defaultCommandName === "string"
|
|
2825
|
+
? candidate._defaultCommandName
|
|
2826
|
+
: undefined;
|
|
2827
|
+
}
|
|
2828
|
+
function configureCommanderSuggestionOutput(command) {
|
|
2829
|
+
command.exitOverride();
|
|
2830
|
+
command.configureOutput({
|
|
2831
|
+
outputError: (message, write) => {
|
|
2832
|
+
if (message.includes("unknown command") || message.includes("unknown option")) {
|
|
2833
|
+
return;
|
|
2834
|
+
}
|
|
2835
|
+
write(message);
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2838
|
+
command.commands.forEach((child) => configureCommanderSuggestionOutput(child));
|
|
2839
|
+
}
|
|
2171
2840
|
export async function runCLI(roots, options = {}) {
|
|
2841
|
+
enableSourceMaps();
|
|
2172
2842
|
const root = mergeApprovalsGroup(normalizeRoots(roots, process.argv));
|
|
2173
2843
|
await resolveMcpProxies(root, { projectRoot: options.projectRoot });
|
|
2174
2844
|
const casing = options.casing ?? "kebab";
|
|
2175
2845
|
const services = (options.services ?? {});
|
|
2176
2846
|
const runtimeOptions = options.humanInLoop ?? {};
|
|
2177
2847
|
const version = options.version ?? findEntrypointPackageMetadata(process.argv[1])?.version;
|
|
2848
|
+
const rootUsageName = options.rootUsageName ?? inferProgramName(process.argv);
|
|
2178
2849
|
const servicesWithBuiltIns = {
|
|
2179
2850
|
...services,
|
|
2180
2851
|
runtimeOptions,
|
|
@@ -2200,9 +2871,14 @@ export async function runCLI(roots, options = {}) {
|
|
|
2200
2871
|
program.version(version, "--version");
|
|
2201
2872
|
}
|
|
2202
2873
|
let lastActionCommand;
|
|
2874
|
+
let resolvedCommandPath = "";
|
|
2875
|
+
let errorReportContext;
|
|
2203
2876
|
const execute = async (state) => {
|
|
2204
2877
|
lastActionCommand = state.actionCommand;
|
|
2205
|
-
|
|
2878
|
+
resolvedCommandPath = formatCliCommandPath(state.commandPath);
|
|
2879
|
+
await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeOptions, (context) => {
|
|
2880
|
+
errorReportContext = context;
|
|
2881
|
+
});
|
|
2206
2882
|
};
|
|
2207
2883
|
for (const child of root.children) {
|
|
2208
2884
|
const command = createNodeCommand(child, casing, globalLongOptionFlags, execute, presetsEnabled);
|
|
@@ -2214,6 +2890,16 @@ export async function runCLI(roots, options = {}) {
|
|
|
2214
2890
|
(command.name() === root.default.name || command.aliases().includes(root.default.name));
|
|
2215
2891
|
program.addCommand(command, isDefaultChild ? { isDefault: true } : undefined);
|
|
2216
2892
|
}
|
|
2893
|
+
configureCommanderSuggestionOutput(program);
|
|
2894
|
+
const unknownCommand = findUnknownCommanderCommand(program, process.argv);
|
|
2895
|
+
if (unknownCommand !== undefined) {
|
|
2896
|
+
createLogger().error(appendUsagePointer(formatUnknownCommandMessage(unknownCommand.input, unknownCommand.currentCommand), {
|
|
2897
|
+
rootUsageName,
|
|
2898
|
+
commandPath: unknownCommand.commandPath
|
|
2899
|
+
}));
|
|
2900
|
+
process.exitCode = 1;
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2217
2903
|
try {
|
|
2218
2904
|
await program.parseAsync(process.argv);
|
|
2219
2905
|
}
|
|
@@ -2222,8 +2908,31 @@ export async function runCLI(roots, options = {}) {
|
|
|
2222
2908
|
renderApprovalDeclined(error);
|
|
2223
2909
|
return;
|
|
2224
2910
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
: process.argv
|
|
2911
|
+
const resolvedFlags = lastActionCommand ? getResolvedFlags(lastActionCommand) : undefined;
|
|
2912
|
+
const report = await writeErrorReport({
|
|
2913
|
+
argv: process.argv,
|
|
2914
|
+
command: errorReportContext?.command,
|
|
2915
|
+
commandPath: errorReportContext?.commandPath ?? resolvedCommandPath,
|
|
2916
|
+
env: process.env,
|
|
2917
|
+
error,
|
|
2918
|
+
errorReports: options.errorReports,
|
|
2919
|
+
params: errorReportContext?.params,
|
|
2920
|
+
projectRoot: options.projectRoot,
|
|
2921
|
+
secrets: errorReportContext?.secrets,
|
|
2922
|
+
version
|
|
2923
|
+
});
|
|
2924
|
+
if (report !== undefined) {
|
|
2925
|
+
process.stderr.write(`Saved error report to ${report.displayPath}\n`);
|
|
2926
|
+
}
|
|
2927
|
+
handleRunError(error, {
|
|
2928
|
+
debugStackMode: resolvedFlags !== undefined
|
|
2929
|
+
? resolveDebugStackMode(resolvedFlags.debug)
|
|
2930
|
+
: getDebugStackModeFromArgv(process.argv),
|
|
2931
|
+
verbose: resolvedFlags ? Boolean(resolvedFlags.verbose) : process.argv.includes("--verbose"),
|
|
2932
|
+
program,
|
|
2933
|
+
argv: process.argv,
|
|
2934
|
+
rootUsageName,
|
|
2935
|
+
commandPath: resolvedCommandPath
|
|
2936
|
+
});
|
|
2228
2937
|
}
|
|
2229
2938
|
}
|