toolcraft 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/cli.compile-check.d.ts +1 -0
- package/dist/cli.compile-check.js +26 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +1312 -0
- package/dist/index.compile-check.d.ts +1 -0
- package/dist/index.compile-check.js +50 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +366 -0
- package/dist/mcp.compile-check.d.ts +1 -0
- package/dist/mcp.compile-check.js +26 -0
- package/dist/mcp.d.ts +31 -0
- package/dist/mcp.js +354 -0
- package/dist/number-schema.d.ts +3 -0
- package/dist/number-schema.js +8 -0
- package/dist/renderer.d.ts +5 -0
- package/dist/renderer.js +148 -0
- package/dist/schema-scope.d.ts +4 -0
- package/dist/schema-scope.js +34 -0
- package/dist/sdk.compile-check.d.ts +1 -0
- package/dist/sdk.compile-check.js +79 -0
- package/dist/sdk.d.ts +63 -0
- package/dist/sdk.js +218 -0
- package/node_modules/@poe-code/design-system/dist/acp/components.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/acp/components.js +121 -0
- package/node_modules/@poe-code/design-system/dist/acp/index.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/acp/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/acp/writer.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/acp/writer.js +21 -0
- package/node_modules/@poe-code/design-system/dist/components/command-errors.d.ts +16 -0
- package/node_modules/@poe-code/design-system/dist/components/command-errors.js +22 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +27 -0
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/components/index.js +7 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.js +60 -0
- package/node_modules/@poe-code/design-system/dist/components/symbols.d.ts +12 -0
- package/node_modules/@poe-code/design-system/dist/components/symbols.js +71 -0
- package/node_modules/@poe-code/design-system/dist/components/table.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/components/table.js +74 -0
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +14 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +104 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.d.ts +18 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/ansi.js +298 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.d.ts +25 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +189 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/border.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/border.js +123 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/footer.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/footer.js +57 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.d.ts +12 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/output-pane.js +254 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/stats-pane.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/components/stats-pane.js +121 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/dashboard.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/dashboard.js +167 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/demo.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/demo.js +145 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/index.js +4 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +99 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/layout.d.ts +25 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/layout.js +79 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/should-use-dashboard.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/should-use-dashboard.js +7 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/snapshot.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/snapshot.js +68 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/store.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/store.js +51 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.d.ts +37 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +233 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +36 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.js +1 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +33 -0
- package/node_modules/@poe-code/design-system/dist/index.js +31 -0
- package/node_modules/@poe-code/design-system/dist/internal/output-format.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/internal/output-format.js +22 -0
- package/node_modules/@poe-code/design-system/dist/internal/strip-ansi.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/internal/strip-ansi.js +3 -0
- package/node_modules/@poe-code/design-system/dist/internal/theme-detect.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/internal/theme-detect.js +49 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.d.ts +66 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.js +132 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +9 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +15 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.d.ts +18 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +101 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +39 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +16 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +74 -0
- package/node_modules/@poe-code/design-system/dist/prompts/theme.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/prompts/theme.js +12 -0
- package/node_modules/@poe-code/design-system/dist/static/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/static/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/static/menu.d.ts +11 -0
- package/node_modules/@poe-code/design-system/dist/static/menu.js +36 -0
- package/node_modules/@poe-code/design-system/dist/static/spinner.d.ts +14 -0
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +46 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/ast.d.ts +92 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/ast.js +1 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/demo-content.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/demo-content.js +139 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/index.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/index.js +8 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/block.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/block.js +1495 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/frontmatter.js +412 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/inline.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser/inline.js +1166 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/parser.js +42 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/renderer.js +572 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/testing/theme-render-fixture.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/terminal-markdown/testing/theme-render-fixture.js +27 -0
- package/node_modules/@poe-code/design-system/dist/tokens/colors.d.ts +35 -0
- package/node_modules/@poe-code/design-system/dist/tokens/colors.js +34 -0
- package/node_modules/@poe-code/design-system/dist/tokens/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/tokens/index.js +4 -0
- package/node_modules/@poe-code/design-system/dist/tokens/spacing.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/tokens/spacing.js +6 -0
- package/node_modules/@poe-code/design-system/dist/tokens/typography.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/tokens/typography.js +8 -0
- package/node_modules/@poe-code/design-system/dist/tokens/widths.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/tokens/widths.js +5 -0
- package/node_modules/@poe-code/design-system/package.json +25 -0
- package/package.json +57 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Command as CommanderCommand, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
4
|
+
import { cancel, confirm, createLogger, formatCommandList, formatOptionList, getTheme, isCancel, note, promptText, renderTable, resetOutputFormatCache, select, text, } from "@poe-code/design-system";
|
|
5
|
+
import { UserError, assertCommandRequirements, getCommandSourcePath, resolveCommandSecrets } from "./index.js";
|
|
6
|
+
import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
|
|
7
|
+
import { renderResult } from "./renderer.js";
|
|
8
|
+
const RESERVED_SERVICE_NAMES = new Set(["params", "secrets", "fetch", "fs", "env", "progress"]);
|
|
9
|
+
function inferProgramName(argv) {
|
|
10
|
+
const entrypoint = argv[1];
|
|
11
|
+
if (typeof entrypoint !== "string" || entrypoint.length === 0) {
|
|
12
|
+
return "agent-kit";
|
|
13
|
+
}
|
|
14
|
+
const parsed = path.parse(entrypoint);
|
|
15
|
+
return parsed.name.length > 0 ? parsed.name : "agent-kit";
|
|
16
|
+
}
|
|
17
|
+
function normalizeRoots(roots, argv) {
|
|
18
|
+
if (!Array.isArray(roots)) {
|
|
19
|
+
return roots;
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
kind: "group",
|
|
23
|
+
name: inferProgramName(argv),
|
|
24
|
+
aliases: [],
|
|
25
|
+
secrets: {},
|
|
26
|
+
children: roots,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const HELP_FLAGS = new Set(["--help", "-h"]);
|
|
30
|
+
function unwrapOptional(schema) {
|
|
31
|
+
if (schema.kind === "optional") {
|
|
32
|
+
return unwrapOptional(schema.inner);
|
|
33
|
+
}
|
|
34
|
+
return schema;
|
|
35
|
+
}
|
|
36
|
+
function splitWords(value) {
|
|
37
|
+
const words = [];
|
|
38
|
+
let current = "";
|
|
39
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
40
|
+
const char = value[index] ?? "";
|
|
41
|
+
const lower = char.toLowerCase();
|
|
42
|
+
const upper = char.toUpperCase();
|
|
43
|
+
const isSeparator = char === "-" || char === "_" || char === " " || char === ".";
|
|
44
|
+
if (isSeparator) {
|
|
45
|
+
if (current.length > 0) {
|
|
46
|
+
words.push(current.toLowerCase());
|
|
47
|
+
current = "";
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const isUppercase = char !== lower && char === upper;
|
|
52
|
+
const previous = value[index - 1];
|
|
53
|
+
const next = value[index + 1];
|
|
54
|
+
const previousIsLowercase = previous !== undefined && previous === previous.toLowerCase() && previous !== previous.toUpperCase();
|
|
55
|
+
const nextIsLowercase = next !== undefined && next === next.toLowerCase() && next !== next.toUpperCase();
|
|
56
|
+
if (isUppercase && current.length > 0 && (previousIsLowercase || nextIsLowercase)) {
|
|
57
|
+
words.push(current.toLowerCase());
|
|
58
|
+
current = char;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
current += char;
|
|
62
|
+
}
|
|
63
|
+
if (current.length > 0) {
|
|
64
|
+
words.push(current.toLowerCase());
|
|
65
|
+
}
|
|
66
|
+
return words;
|
|
67
|
+
}
|
|
68
|
+
function formatSegment(segment, casing) {
|
|
69
|
+
const separator = casing === "snake" ? "_" : "-";
|
|
70
|
+
return splitWords(segment).join(separator);
|
|
71
|
+
}
|
|
72
|
+
function toOptionFlag(path, casing) {
|
|
73
|
+
return `--${path.map((segment) => formatSegment(segment, casing)).join(".")}`;
|
|
74
|
+
}
|
|
75
|
+
function toOptionAttribute(path, casing) {
|
|
76
|
+
return path
|
|
77
|
+
.map((segment) => {
|
|
78
|
+
const formatted = formatSegment(segment, casing);
|
|
79
|
+
if (casing === "snake") {
|
|
80
|
+
return formatted;
|
|
81
|
+
}
|
|
82
|
+
const words = formatted.split("-");
|
|
83
|
+
return words
|
|
84
|
+
.map((word, index) => index === 0 ? word : `${word[0]?.toUpperCase() ?? ""}${word.slice(1)}`)
|
|
85
|
+
.join("");
|
|
86
|
+
})
|
|
87
|
+
.join(".");
|
|
88
|
+
}
|
|
89
|
+
function toDisplayPath(path) {
|
|
90
|
+
return path.join(".");
|
|
91
|
+
}
|
|
92
|
+
function collectFields(schema, casing, path = [], inheritedOptional = false) {
|
|
93
|
+
const fields = [];
|
|
94
|
+
for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
|
|
95
|
+
const nextPath = [...path, key];
|
|
96
|
+
const optional = inheritedOptional || rawChildSchema.kind === "optional";
|
|
97
|
+
const childSchema = unwrapOptional(rawChildSchema);
|
|
98
|
+
if (childSchema.kind === "object") {
|
|
99
|
+
fields.push(...collectFields(childSchema, casing, nextPath, optional));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
fields.push({
|
|
103
|
+
path: nextPath,
|
|
104
|
+
displayPath: toDisplayPath(nextPath),
|
|
105
|
+
optionAttribute: toOptionAttribute(nextPath, casing),
|
|
106
|
+
commanderOptionAttribute: toCommanderOptionAttribute(nextPath, casing),
|
|
107
|
+
optionFlag: toOptionFlag(nextPath, casing),
|
|
108
|
+
shortFlag: childSchema.short,
|
|
109
|
+
schema: childSchema,
|
|
110
|
+
description: childSchema.description,
|
|
111
|
+
optional,
|
|
112
|
+
hasDefault: childSchema.default !== undefined,
|
|
113
|
+
defaultValue: childSchema.default,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return fields;
|
|
117
|
+
}
|
|
118
|
+
function toCommanderOptionAttribute(path, casing) {
|
|
119
|
+
const optionAttribute = toOptionAttribute(path, casing);
|
|
120
|
+
const optionFlag = toOptionFlag(path, casing);
|
|
121
|
+
if (!GLOBAL_LONG_OPTION_FLAGS.has(optionFlag)) {
|
|
122
|
+
return optionAttribute;
|
|
123
|
+
}
|
|
124
|
+
return `param_${optionAttribute}`;
|
|
125
|
+
}
|
|
126
|
+
function assignPositionals(fields, positional) {
|
|
127
|
+
if (positional.length === 0) {
|
|
128
|
+
return fields;
|
|
129
|
+
}
|
|
130
|
+
const byPath = new Map(fields.map((field) => [field.displayPath, field]));
|
|
131
|
+
let variadicPositionSeen = false;
|
|
132
|
+
positional.forEach((name, index) => {
|
|
133
|
+
const field = byPath.get(name);
|
|
134
|
+
if (field === undefined) {
|
|
135
|
+
throw new UserError(`Positional parameter "${name}" does not exist in params.`);
|
|
136
|
+
}
|
|
137
|
+
if (field.schema.kind === "array") {
|
|
138
|
+
if (index !== positional.length - 1) {
|
|
139
|
+
throw new UserError(`Positional array parameter "${name}" must be the last positional.`);
|
|
140
|
+
}
|
|
141
|
+
variadicPositionSeen = true;
|
|
142
|
+
}
|
|
143
|
+
if (variadicPositionSeen && field.schema.kind !== "array") {
|
|
144
|
+
throw new UserError(`Positional parameter "${name}" cannot appear after a positional array.`);
|
|
145
|
+
}
|
|
146
|
+
field.positionalIndex = index;
|
|
147
|
+
field.variadicPosition = field.schema.kind === "array";
|
|
148
|
+
});
|
|
149
|
+
return fields;
|
|
150
|
+
}
|
|
151
|
+
function formatOptionFlags(field) {
|
|
152
|
+
const collidesWithGlobalFlag = GLOBAL_LONG_OPTION_FLAGS.has(field.optionFlag);
|
|
153
|
+
if (collidesWithGlobalFlag) {
|
|
154
|
+
if (field.shortFlag === undefined) {
|
|
155
|
+
throw new UserError(`Parameter "${field.displayPath}" uses reserved CLI flag "${field.optionFlag}". Add a short flag or rename the parameter.`);
|
|
156
|
+
}
|
|
157
|
+
return `-${field.shortFlag}`;
|
|
158
|
+
}
|
|
159
|
+
if (field.shortFlag === undefined) {
|
|
160
|
+
return field.optionFlag;
|
|
161
|
+
}
|
|
162
|
+
return `-${field.shortFlag}, ${field.optionFlag}`;
|
|
163
|
+
}
|
|
164
|
+
function formatPositionalToken(field) {
|
|
165
|
+
const optionalPositional = field.optional || field.hasDefault;
|
|
166
|
+
if (field.variadicPosition === true) {
|
|
167
|
+
return optionalPositional ? `[${field.displayPath}...]` : `<${field.displayPath}...>`;
|
|
168
|
+
}
|
|
169
|
+
return optionalPositional ? `[${field.displayPath}]` : `<${field.displayPath}>`;
|
|
170
|
+
}
|
|
171
|
+
function parseBooleanText(value, label) {
|
|
172
|
+
const normalized = value.trim().toLowerCase();
|
|
173
|
+
if (normalized === "true") {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (normalized === "false") {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected true or false.`);
|
|
180
|
+
}
|
|
181
|
+
function parseEnumValue(value, values, label) {
|
|
182
|
+
const match = values.find((candidate) => String(candidate) === value);
|
|
183
|
+
if (match === undefined) {
|
|
184
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected one of: ${values.map((candidate) => String(candidate)).join(", ")}.`);
|
|
185
|
+
}
|
|
186
|
+
return match;
|
|
187
|
+
}
|
|
188
|
+
function parseScalarValue(value, schema, label) {
|
|
189
|
+
switch (schema.kind) {
|
|
190
|
+
case "string":
|
|
191
|
+
return value;
|
|
192
|
+
case "number": {
|
|
193
|
+
const parsed = Number(value);
|
|
194
|
+
if (!isValidNumberSchemaValue(parsed, schema)) {
|
|
195
|
+
throw new InvalidArgumentError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(schema)}.`);
|
|
196
|
+
}
|
|
197
|
+
return parsed;
|
|
198
|
+
}
|
|
199
|
+
case "boolean":
|
|
200
|
+
return parseBooleanText(value, label);
|
|
201
|
+
case "enum":
|
|
202
|
+
return parseEnumValue(value, schema.values, label);
|
|
203
|
+
default:
|
|
204
|
+
throw new UserError(`Unsupported CLI schema kind "${schema.kind}".`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function splitArrayInput(value) {
|
|
208
|
+
const items = [];
|
|
209
|
+
let current = "";
|
|
210
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
211
|
+
const char = value[index] ?? "";
|
|
212
|
+
if (char === ",") {
|
|
213
|
+
const trimmed = current.trim();
|
|
214
|
+
if (trimmed.length > 0) {
|
|
215
|
+
items.push(trimmed);
|
|
216
|
+
}
|
|
217
|
+
current = "";
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
current += char;
|
|
221
|
+
}
|
|
222
|
+
const trimmed = current.trim();
|
|
223
|
+
if (trimmed.length > 0) {
|
|
224
|
+
items.push(trimmed);
|
|
225
|
+
}
|
|
226
|
+
return items;
|
|
227
|
+
}
|
|
228
|
+
function parseArrayValue(value, schema, label) {
|
|
229
|
+
const itemSchema = unwrapOptional(schema.item);
|
|
230
|
+
if (itemSchema.kind === "array" || itemSchema.kind === "object") {
|
|
231
|
+
throw new UserError(`Array parameter "${label}" must use scalar items.`);
|
|
232
|
+
}
|
|
233
|
+
return splitArrayInput(value).map((item) => parseScalarValue(item, itemSchema, label));
|
|
234
|
+
}
|
|
235
|
+
function createOption(field) {
|
|
236
|
+
const flags = formatOptionFlags(field);
|
|
237
|
+
const collidesWithGlobalFlag = GLOBAL_LONG_OPTION_FLAGS.has(field.optionFlag);
|
|
238
|
+
if (field.schema.kind === "boolean") {
|
|
239
|
+
if (collidesWithGlobalFlag) {
|
|
240
|
+
return [createCommanderOption(flags, field.description, field)];
|
|
241
|
+
}
|
|
242
|
+
const mainOption = createCommanderOption(`${flags} [value]`, field.description, field);
|
|
243
|
+
mainOption.preset(true);
|
|
244
|
+
// Commander v14 passes the preset value through argParser too, so guard with typeof check
|
|
245
|
+
mainOption.argParser((value) => typeof value === "boolean" ? value : parseBooleanText(value, field.displayPath));
|
|
246
|
+
return [
|
|
247
|
+
mainOption,
|
|
248
|
+
createCommanderOption(`--no-${field.optionFlag.slice(2)}`, field.description, field),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
if (field.schema.kind === "array") {
|
|
252
|
+
return [
|
|
253
|
+
createCommanderOption(`${flags} <value...>`, field.description, field).argParser((value, previous = []) => [
|
|
254
|
+
...previous,
|
|
255
|
+
...parseArrayValue(value, field.schema, field.displayPath),
|
|
256
|
+
]),
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
const option = createCommanderOption(`${flags} <value>`, field.description, field);
|
|
260
|
+
if (field.schema.kind === "enum" && field.schema.values.every((value) => typeof value === "string")) {
|
|
261
|
+
option.choices([...field.schema.values]);
|
|
262
|
+
}
|
|
263
|
+
option.argParser((value) => parseScalarValue(value, field.schema, field.displayPath));
|
|
264
|
+
return [option];
|
|
265
|
+
}
|
|
266
|
+
const GLOBAL_LONG_OPTION_FLAGS = new Set(["--preset", "--yes", "--output", "--verbose"]);
|
|
267
|
+
function createCommanderOption(flags, description, field) {
|
|
268
|
+
const option = new Option(flags, description);
|
|
269
|
+
if (field.commanderOptionAttribute !== field.optionAttribute) {
|
|
270
|
+
option.attributeName = () => field.commanderOptionAttribute;
|
|
271
|
+
}
|
|
272
|
+
return option;
|
|
273
|
+
}
|
|
274
|
+
function hasHelpFlag(argv) {
|
|
275
|
+
return argv.some((token) => HELP_FLAGS.has(token));
|
|
276
|
+
}
|
|
277
|
+
function resolveHelpOutput(argv) {
|
|
278
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
279
|
+
const token = argv[index] ?? "";
|
|
280
|
+
if (token === "--output") {
|
|
281
|
+
const value = argv[index + 1];
|
|
282
|
+
if (value === "rich" || value === "md" || value === "json") {
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
if (value === "markdown") {
|
|
286
|
+
return "md";
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (token.startsWith("--output=")) {
|
|
291
|
+
const value = token.slice("--output=".length);
|
|
292
|
+
if (value === "rich" || value === "md" || value === "json") {
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
if (value === "markdown") {
|
|
296
|
+
return "md";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return "rich";
|
|
301
|
+
}
|
|
302
|
+
function isNodeVisibleInScope(node, scope) {
|
|
303
|
+
if (node.kind === "command") {
|
|
304
|
+
return node.scope.includes(scope);
|
|
305
|
+
}
|
|
306
|
+
return (getVisibleChildren(node, scope).length > 0 ||
|
|
307
|
+
Boolean(node.default && node.default.scope.includes(scope)) ||
|
|
308
|
+
node.scope === undefined ||
|
|
309
|
+
node.scope.includes(scope));
|
|
310
|
+
}
|
|
311
|
+
function getVisibleChildren(group, scope) {
|
|
312
|
+
return group.children.filter((child) => isNodeVisibleInScope(child, scope));
|
|
313
|
+
}
|
|
314
|
+
function findVisibleChild(group, token, scope) {
|
|
315
|
+
return getVisibleChildren(group, scope).find((child) => child.name === token || child.aliases.includes(token));
|
|
316
|
+
}
|
|
317
|
+
function resolveHelpTarget(root, argv, scope, rootDisplayName) {
|
|
318
|
+
const breadcrumb = [rootDisplayName ?? root.name];
|
|
319
|
+
let current = root;
|
|
320
|
+
for (const token of argv.slice(2)) {
|
|
321
|
+
if (token.startsWith("-") || token === "help") {
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
if (current.kind !== "group") {
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
const child = findVisibleChild(current, token, scope);
|
|
328
|
+
if (child === undefined) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
breadcrumb.push(child.name);
|
|
332
|
+
current = child;
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
breadcrumb,
|
|
336
|
+
node: current,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function describeSchemaType(schema) {
|
|
340
|
+
switch (schema.kind) {
|
|
341
|
+
case "string":
|
|
342
|
+
return "string";
|
|
343
|
+
case "number":
|
|
344
|
+
return "number";
|
|
345
|
+
case "boolean":
|
|
346
|
+
return "boolean";
|
|
347
|
+
case "enum":
|
|
348
|
+
return "value";
|
|
349
|
+
case "array":
|
|
350
|
+
return `${describeSchemaType(unwrapOptional(schema.item))}...`;
|
|
351
|
+
default:
|
|
352
|
+
throw new UserError("Unsupported CLI schema kind.");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function formatHelpFieldFlags(field) {
|
|
356
|
+
if (field.positionalIndex !== undefined) {
|
|
357
|
+
return formatPositionalToken(field);
|
|
358
|
+
}
|
|
359
|
+
if (field.schema.kind === "boolean") {
|
|
360
|
+
return `${formatOptionFlags(field)} [value]`;
|
|
361
|
+
}
|
|
362
|
+
return `${formatOptionFlags(field)} <${describeSchemaType(field.schema)}>`;
|
|
363
|
+
}
|
|
364
|
+
function appendHelpMetadata(description, metadata) {
|
|
365
|
+
if (metadata.length === 0) {
|
|
366
|
+
return description;
|
|
367
|
+
}
|
|
368
|
+
if (description.length === 0) {
|
|
369
|
+
return `(${metadata.join(", ")})`;
|
|
370
|
+
}
|
|
371
|
+
return `${description} (${metadata.join(", ")})`;
|
|
372
|
+
}
|
|
373
|
+
function formatHelpFieldDescription(field) {
|
|
374
|
+
const description = field.description ?? field.displayPath;
|
|
375
|
+
const metadata = [];
|
|
376
|
+
if (!field.optional && !field.hasDefault) {
|
|
377
|
+
metadata.push("required");
|
|
378
|
+
}
|
|
379
|
+
if (field.hasDefault) {
|
|
380
|
+
metadata.push(`default: ${formatResolvedValue(field.defaultValue)}`);
|
|
381
|
+
}
|
|
382
|
+
return appendHelpMetadata(description, metadata);
|
|
383
|
+
}
|
|
384
|
+
function formatSecretRows(secrets) {
|
|
385
|
+
return Object.values(secrets).map((secret) => ({
|
|
386
|
+
flags: secret.env,
|
|
387
|
+
description: formatSecretDescription(secret),
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
function formatSecretDescription(secret) {
|
|
391
|
+
if (secret.description !== undefined && secret.description.length > 0) {
|
|
392
|
+
return secret.description;
|
|
393
|
+
}
|
|
394
|
+
return secret.optional === true ? "Optional secret" : "Required secret";
|
|
395
|
+
}
|
|
396
|
+
function formatCommandRows(group, scope) {
|
|
397
|
+
return getVisibleChildren(group, scope).map((child) => ({
|
|
398
|
+
name: child.aliases.length === 0 ? child.name : `${child.name} (${child.aliases.join(", ")})`,
|
|
399
|
+
description: child.description ?? "",
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
function formatGlobalOptionRows(showVersion) {
|
|
403
|
+
const rows = [
|
|
404
|
+
{
|
|
405
|
+
flags: "--preset <path>",
|
|
406
|
+
description: "Load parameter defaults from a JSON file",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
flags: "--yes",
|
|
410
|
+
description: "Accept defaults, skip prompts",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
flags: "--output <format>",
|
|
414
|
+
description: "Output format (rich, md, json)",
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
flags: "-h, --help",
|
|
418
|
+
description: "Show help",
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
if (showVersion) {
|
|
422
|
+
rows.push({
|
|
423
|
+
flags: "--version",
|
|
424
|
+
description: "Show version",
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return rows;
|
|
428
|
+
}
|
|
429
|
+
function renderHelpSections(sections) {
|
|
430
|
+
return sections.filter((section) => section.length > 0).join("\n\n");
|
|
431
|
+
}
|
|
432
|
+
function buildUsageLine(breadcrumb, rootUsageName, suffix) {
|
|
433
|
+
if (rootUsageName === undefined) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const subPath = breadcrumb.slice(1).join(" ");
|
|
437
|
+
return subPath ? `${rootUsageName} ${subPath} ${suffix}` : `${rootUsageName} ${suffix}`;
|
|
438
|
+
}
|
|
439
|
+
function renderGroupHelp(group, breadcrumb, scope, showVersion, rootUsageName) {
|
|
440
|
+
const sections = [];
|
|
441
|
+
const commandRows = formatCommandRows(group, scope);
|
|
442
|
+
if (commandRows.length > 0) {
|
|
443
|
+
sections.push(`${text.section("Commands:")}\n${formatCommandList(commandRows)}`);
|
|
444
|
+
}
|
|
445
|
+
sections.push(`${text.section("Global options:")}\n${formatOptionList(formatGlobalOptionRows(showVersion))}`);
|
|
446
|
+
return renderHelpDocument({
|
|
447
|
+
breadcrumb,
|
|
448
|
+
usageLine: buildUsageLine(breadcrumb, rootUsageName, "[options] [command]"),
|
|
449
|
+
description: group.description,
|
|
450
|
+
requiresAuth: group.requires?.auth === true,
|
|
451
|
+
sections,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function renderLeafHelp(command, breadcrumb, casing, rootUsageName) {
|
|
455
|
+
const sections = [];
|
|
456
|
+
const fields = assignPositionals(collectFields(command.params, casing), command.positional);
|
|
457
|
+
const optionRows = fields.map((field) => ({
|
|
458
|
+
flags: formatHelpFieldFlags(field),
|
|
459
|
+
description: formatHelpFieldDescription(field),
|
|
460
|
+
}));
|
|
461
|
+
if (optionRows.length > 0) {
|
|
462
|
+
sections.push(`${text.section("Options:")}\n${formatOptionList(optionRows)}`);
|
|
463
|
+
}
|
|
464
|
+
sections.push(`${text.section("Global options:")}\n${formatOptionList(formatGlobalOptionRows(false))}`);
|
|
465
|
+
const secretRows = formatSecretRows(command.secrets);
|
|
466
|
+
if (secretRows.length > 0) {
|
|
467
|
+
sections.push(`${text.section("Secrets (via environment):")}\n${formatOptionList(secretRows)}`);
|
|
468
|
+
}
|
|
469
|
+
const positionalFields = fields.filter((f) => f.positionalIndex !== undefined);
|
|
470
|
+
const usageSuffix = positionalFields.length > 0
|
|
471
|
+
? `[options] ${positionalFields.map(formatPositionalToken).join(" ")}`
|
|
472
|
+
: "[options]";
|
|
473
|
+
return renderHelpDocument({
|
|
474
|
+
breadcrumb,
|
|
475
|
+
usageLine: buildUsageLine(breadcrumb, rootUsageName, usageSuffix),
|
|
476
|
+
description: command.description,
|
|
477
|
+
requiresAuth: command.requires?.auth === true,
|
|
478
|
+
sections,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function renderHelpDocument(input) {
|
|
482
|
+
const lines = [text.heading(input.breadcrumb.join(" ")), ""];
|
|
483
|
+
if (input.usageLine !== undefined) {
|
|
484
|
+
lines.push(`${text.section("Usage:")} ${text.usageCommand(input.usageLine)}`, "");
|
|
485
|
+
}
|
|
486
|
+
if (input.description !== undefined) {
|
|
487
|
+
lines.push(input.description);
|
|
488
|
+
}
|
|
489
|
+
if (input.requiresAuth) {
|
|
490
|
+
lines.push("Requires: authentication");
|
|
491
|
+
}
|
|
492
|
+
if (input.description !== undefined || input.requiresAuth) {
|
|
493
|
+
lines.push("");
|
|
494
|
+
}
|
|
495
|
+
lines.push(renderHelpSections(input.sections));
|
|
496
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
497
|
+
}
|
|
498
|
+
async function renderGeneratedHelp(root, argv, options) {
|
|
499
|
+
const target = resolveHelpTarget(root, argv, "cli", options.rootDisplayName);
|
|
500
|
+
const output = resolveHelpOutput(argv);
|
|
501
|
+
const casing = options.casing ?? "kebab";
|
|
502
|
+
await withOutputFormat(output, async () => {
|
|
503
|
+
const rendered = target.node.kind === "group"
|
|
504
|
+
? renderGroupHelp(target.node, target.breadcrumb, "cli", options.version !== undefined, options.rootUsageName)
|
|
505
|
+
: renderLeafHelp(target.node, target.breadcrumb, casing, options.rootUsageName);
|
|
506
|
+
process.stdout.write(rendered);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function createNodeCommand(node, casing, execute) {
|
|
510
|
+
if (node.kind === "command") {
|
|
511
|
+
if (!node.scope.includes("cli")) {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
const command = new CommanderCommand(node.name);
|
|
515
|
+
const fields = assignPositionals(collectFields(node.params, casing), node.positional);
|
|
516
|
+
if (node.description !== undefined) {
|
|
517
|
+
command.description(node.description);
|
|
518
|
+
}
|
|
519
|
+
node.aliases.forEach((alias) => command.alias(alias));
|
|
520
|
+
command.addHelpCommand(false);
|
|
521
|
+
addGlobalOptions(command);
|
|
522
|
+
for (const field of fields) {
|
|
523
|
+
if (field.positionalIndex !== undefined) {
|
|
524
|
+
command.argument(formatPositionalToken(field));
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
for (const option of createOption(field)) {
|
|
528
|
+
command.addOption(option);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
command.action(async (...args) => {
|
|
532
|
+
const actionCommand = args[args.length - 1];
|
|
533
|
+
const positionalValues = args.slice(0, -2);
|
|
534
|
+
await execute({
|
|
535
|
+
command: node,
|
|
536
|
+
fields,
|
|
537
|
+
positionalValues,
|
|
538
|
+
actionCommand,
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
return command;
|
|
542
|
+
}
|
|
543
|
+
if (!isNodeVisibleInScope(node, "cli")) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
const visibleChildren = node.children
|
|
547
|
+
.map((child) => createNodeCommand(child, casing, execute))
|
|
548
|
+
.filter((child) => child !== null);
|
|
549
|
+
const group = new CommanderCommand(node.name);
|
|
550
|
+
if (node.description !== undefined) {
|
|
551
|
+
group.description(node.description);
|
|
552
|
+
}
|
|
553
|
+
node.aliases.forEach((alias) => group.alias(alias));
|
|
554
|
+
group.addHelpCommand(false);
|
|
555
|
+
addGlobalOptions(group);
|
|
556
|
+
for (const child of visibleChildren) {
|
|
557
|
+
const isDefaultChild = node.default !== undefined &&
|
|
558
|
+
node.default.scope.includes("cli") &&
|
|
559
|
+
(child.name() === node.default.name || child.aliases().includes(node.default.name));
|
|
560
|
+
group.addCommand(child, isDefaultChild ? { isDefault: true } : undefined);
|
|
561
|
+
}
|
|
562
|
+
return group;
|
|
563
|
+
}
|
|
564
|
+
function addGlobalOptions(command) {
|
|
565
|
+
command
|
|
566
|
+
.option("--preset <path>", "Load parameter defaults from a JSON file.")
|
|
567
|
+
.option("--yes", "Accept defaults and skip prompts.")
|
|
568
|
+
.option("--output <format>", "Output format.", (value) => {
|
|
569
|
+
if (value === "rich" || value === "md" || value === "json") {
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
if (value === "markdown") {
|
|
573
|
+
return "md";
|
|
574
|
+
}
|
|
575
|
+
throw new InvalidArgumentError('Invalid value for "--output". Expected one of: rich, md, markdown, json.');
|
|
576
|
+
})
|
|
577
|
+
.option("--verbose", "Print stack traces for unexpected errors.");
|
|
578
|
+
}
|
|
579
|
+
function setNestedValue(target, path, value) {
|
|
580
|
+
let cursor = target;
|
|
581
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
582
|
+
const segment = path[index] ?? "";
|
|
583
|
+
const existing = cursor[segment];
|
|
584
|
+
if (typeof existing === "object" && existing !== null) {
|
|
585
|
+
cursor = existing;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const next = {};
|
|
589
|
+
cursor[segment] = next;
|
|
590
|
+
cursor = next;
|
|
591
|
+
}
|
|
592
|
+
const leaf = path[path.length - 1];
|
|
593
|
+
if (leaf !== undefined) {
|
|
594
|
+
cursor[leaf] = value;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function formatResolvedValue(value) {
|
|
598
|
+
if (Array.isArray(value)) {
|
|
599
|
+
return value.map((item) => String(item)).join(", ");
|
|
600
|
+
}
|
|
601
|
+
if (typeof value === "string") {
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
return JSON.stringify(value);
|
|
605
|
+
}
|
|
606
|
+
async function promptForField(field) {
|
|
607
|
+
const schema = field.schema;
|
|
608
|
+
if (schema.kind === "enum") {
|
|
609
|
+
const options = schema.loadOptions
|
|
610
|
+
? await schema.loadOptions()
|
|
611
|
+
: schema.values.map((value) => ({
|
|
612
|
+
label: schema.labels?.[String(value)] ?? String(value),
|
|
613
|
+
value,
|
|
614
|
+
}));
|
|
615
|
+
const selected = await select({
|
|
616
|
+
message: field.description ?? field.displayPath,
|
|
617
|
+
options,
|
|
618
|
+
initialValue: field.hasDefault ? field.defaultValue : undefined,
|
|
619
|
+
});
|
|
620
|
+
if (isCancel(selected)) {
|
|
621
|
+
cancel("Operation cancelled.");
|
|
622
|
+
throw new UserError("Operation cancelled.");
|
|
623
|
+
}
|
|
624
|
+
return selected;
|
|
625
|
+
}
|
|
626
|
+
if (field.schema.kind === "boolean") {
|
|
627
|
+
const selected = await confirm({
|
|
628
|
+
message: field.displayPath,
|
|
629
|
+
initialValue: field.hasDefault ? Boolean(field.defaultValue) : undefined,
|
|
630
|
+
});
|
|
631
|
+
if (isCancel(selected)) {
|
|
632
|
+
cancel("Operation cancelled.");
|
|
633
|
+
throw new UserError("Operation cancelled.");
|
|
634
|
+
}
|
|
635
|
+
return selected;
|
|
636
|
+
}
|
|
637
|
+
const entered = await promptText({
|
|
638
|
+
message: field.displayPath,
|
|
639
|
+
initialValue: field.hasDefault && field.defaultValue !== undefined
|
|
640
|
+
? formatResolvedValue(field.defaultValue)
|
|
641
|
+
: undefined,
|
|
642
|
+
});
|
|
643
|
+
if (isCancel(entered)) {
|
|
644
|
+
cancel("Operation cancelled.");
|
|
645
|
+
throw new UserError("Operation cancelled.");
|
|
646
|
+
}
|
|
647
|
+
if (typeof entered !== "string") {
|
|
648
|
+
throw new UserError(`Missing required parameter "${field.displayPath}".`);
|
|
649
|
+
}
|
|
650
|
+
if (entered.trim().length === 0 && field.hasDefault) {
|
|
651
|
+
return field.defaultValue;
|
|
652
|
+
}
|
|
653
|
+
if (field.schema.kind === "array") {
|
|
654
|
+
return parseArrayValue(entered, field.schema, field.displayPath);
|
|
655
|
+
}
|
|
656
|
+
return parseScalarValue(entered, field.schema, field.displayPath);
|
|
657
|
+
}
|
|
658
|
+
function resolveOutput(resolvedFlags) {
|
|
659
|
+
if (resolvedFlags.json === true) {
|
|
660
|
+
return "json";
|
|
661
|
+
}
|
|
662
|
+
if (resolvedFlags.output !== undefined) {
|
|
663
|
+
return resolvedFlags.output;
|
|
664
|
+
}
|
|
665
|
+
return "rich";
|
|
666
|
+
}
|
|
667
|
+
const DESIGN_SYSTEM_OUTPUT_BY_MODE = {
|
|
668
|
+
rich: "terminal",
|
|
669
|
+
md: "markdown",
|
|
670
|
+
json: "json",
|
|
671
|
+
};
|
|
672
|
+
function toDesignSystemOutput(output) {
|
|
673
|
+
return DESIGN_SYSTEM_OUTPUT_BY_MODE[output];
|
|
674
|
+
}
|
|
675
|
+
async function withOutputFormat(output, fn) {
|
|
676
|
+
const previous = process.env.OUTPUT_FORMAT;
|
|
677
|
+
process.env.OUTPUT_FORMAT = toDesignSystemOutput(output);
|
|
678
|
+
resetOutputFormatCache();
|
|
679
|
+
try {
|
|
680
|
+
return await fn();
|
|
681
|
+
}
|
|
682
|
+
finally {
|
|
683
|
+
if (previous === undefined) {
|
|
684
|
+
delete process.env.OUTPUT_FORMAT;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
process.env.OUTPUT_FORMAT = previous;
|
|
688
|
+
}
|
|
689
|
+
resetOutputFormatCache();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function createFs() {
|
|
693
|
+
return {
|
|
694
|
+
readFile: async (path, encoding = "utf8") => readFile(path, { encoding }),
|
|
695
|
+
writeFile: async (path, contents) => {
|
|
696
|
+
await writeFile(path, contents);
|
|
697
|
+
},
|
|
698
|
+
exists: async (path) => {
|
|
699
|
+
try {
|
|
700
|
+
await access(path);
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function createEnv(values = process.env) {
|
|
710
|
+
return {
|
|
711
|
+
get(key) {
|
|
712
|
+
return values[key];
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function isPlainObject(value) {
|
|
717
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
718
|
+
}
|
|
719
|
+
function hasFieldValue(value) {
|
|
720
|
+
return value !== undefined;
|
|
721
|
+
}
|
|
722
|
+
function hasNestedField(fields, path) {
|
|
723
|
+
return fields.some((field) => path.length < field.path.length &&
|
|
724
|
+
path.every((segment, index) => field.path[index] === segment));
|
|
725
|
+
}
|
|
726
|
+
function describeExpectedPresetValue(schema) {
|
|
727
|
+
if (schema.kind === "array") {
|
|
728
|
+
return "an array";
|
|
729
|
+
}
|
|
730
|
+
if (schema.kind === "enum") {
|
|
731
|
+
return `one of: ${schema.values.map((value) => JSON.stringify(value)).join(", ")}`;
|
|
732
|
+
}
|
|
733
|
+
return `a ${schema.kind}`;
|
|
734
|
+
}
|
|
735
|
+
function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
|
|
736
|
+
if (value === null && schema.nullable === true) {
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
switch (schema.kind) {
|
|
740
|
+
case "string":
|
|
741
|
+
if (typeof value !== "string") {
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
return value;
|
|
745
|
+
case "number":
|
|
746
|
+
if (!isValidNumberSchemaValue(value, schema)) {
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
return value;
|
|
750
|
+
case "boolean":
|
|
751
|
+
if (typeof value !== "boolean") {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
return value;
|
|
755
|
+
case "enum": {
|
|
756
|
+
const match = schema.values.find((candidate) => Object.is(candidate, value));
|
|
757
|
+
if (match !== undefined) {
|
|
758
|
+
return match;
|
|
759
|
+
}
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
default:
|
|
763
|
+
throw new UserError(`Unsupported CLI schema kind "${schema.kind}".`);
|
|
764
|
+
}
|
|
765
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected ${describeExpectedPresetValue(schema)}.`);
|
|
766
|
+
}
|
|
767
|
+
function validatePresetFieldValue(value, field, presetPath) {
|
|
768
|
+
if (field.schema.kind !== "array") {
|
|
769
|
+
return validatePresetScalarValue(value, field.schema, field.displayPath, presetPath);
|
|
770
|
+
}
|
|
771
|
+
const itemSchema = unwrapOptional(field.schema.item);
|
|
772
|
+
if (itemSchema.kind === "array" || itemSchema.kind === "object") {
|
|
773
|
+
throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
|
|
774
|
+
}
|
|
775
|
+
if (!Array.isArray(value)) {
|
|
776
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array.`);
|
|
777
|
+
}
|
|
778
|
+
return value.map((item) => validatePresetScalarValue(item, itemSchema, field.displayPath, presetPath));
|
|
779
|
+
}
|
|
780
|
+
async function loadPresetValues(fields, presetPath) {
|
|
781
|
+
let rawPreset;
|
|
782
|
+
try {
|
|
783
|
+
rawPreset = await readFile(presetPath, {
|
|
784
|
+
encoding: "utf8",
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
if (typeof error === "object" &&
|
|
789
|
+
error !== null &&
|
|
790
|
+
"code" in error &&
|
|
791
|
+
error.code === "ENOENT") {
|
|
792
|
+
throw new UserError(`Preset file "${presetPath}" was not found.`);
|
|
793
|
+
}
|
|
794
|
+
const message = error instanceof Error && error.message.length > 0
|
|
795
|
+
? error.message
|
|
796
|
+
: "Unknown read error.";
|
|
797
|
+
throw new UserError(`Preset file "${presetPath}" could not be read: ${message}`);
|
|
798
|
+
}
|
|
799
|
+
let parsedPreset;
|
|
800
|
+
try {
|
|
801
|
+
parsedPreset = JSON.parse(rawPreset);
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
throw new UserError(`Preset file "${presetPath}" is not valid JSON.`);
|
|
805
|
+
}
|
|
806
|
+
if (!isPlainObject(parsedPreset)) {
|
|
807
|
+
throw new UserError(`Preset file "${presetPath}" must contain a JSON object.`);
|
|
808
|
+
}
|
|
809
|
+
const fieldByPath = new Map(fields.map((field) => [field.displayPath, field]));
|
|
810
|
+
const presetValues = {};
|
|
811
|
+
function visitObject(current, path) {
|
|
812
|
+
for (const [key, value] of Object.entries(current)) {
|
|
813
|
+
const nextPath = [...path, key];
|
|
814
|
+
const displayPath = toDisplayPath(nextPath);
|
|
815
|
+
const field = fieldByPath.get(displayPath);
|
|
816
|
+
if (field !== undefined) {
|
|
817
|
+
presetValues[field.optionAttribute] = validatePresetFieldValue(value, field, presetPath);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (!hasNestedField(fields, nextPath)) {
|
|
821
|
+
throw new UserError(`Preset file "${presetPath}" contains unknown parameter "${displayPath}".`);
|
|
822
|
+
}
|
|
823
|
+
if (!isPlainObject(value)) {
|
|
824
|
+
throw new UserError(`Preset file "${presetPath}" has an invalid value for "${displayPath}". Expected an object.`);
|
|
825
|
+
}
|
|
826
|
+
visitObject(value, nextPath);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
visitObject(parsedPreset, []);
|
|
830
|
+
return presetValues;
|
|
831
|
+
}
|
|
832
|
+
function isNumericFixtureSelector(value) {
|
|
833
|
+
if (value.length === 0) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
for (const char of value) {
|
|
837
|
+
if (char < "0" || char > "9") {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
function normalizeHttpMethod(value) {
|
|
844
|
+
return (value ?? "GET").toUpperCase();
|
|
845
|
+
}
|
|
846
|
+
function isReadLikeMethod(name) {
|
|
847
|
+
const normalized = name.toLowerCase();
|
|
848
|
+
return (normalized === "get" ||
|
|
849
|
+
normalized === "head" ||
|
|
850
|
+
normalized === "options" ||
|
|
851
|
+
normalized.startsWith("read") ||
|
|
852
|
+
normalized.startsWith("get") ||
|
|
853
|
+
normalized.startsWith("find") ||
|
|
854
|
+
normalized.startsWith("list") ||
|
|
855
|
+
normalized.startsWith("load") ||
|
|
856
|
+
normalized.startsWith("fetch") ||
|
|
857
|
+
normalized.startsWith("query") ||
|
|
858
|
+
normalized.startsWith("exists") ||
|
|
859
|
+
normalized.startsWith("has"));
|
|
860
|
+
}
|
|
861
|
+
function isWriteLikeMethod(name) {
|
|
862
|
+
const normalized = name.toLowerCase();
|
|
863
|
+
return (normalized === "post" ||
|
|
864
|
+
normalized === "put" ||
|
|
865
|
+
normalized === "patch" ||
|
|
866
|
+
normalized === "delete" ||
|
|
867
|
+
normalized.startsWith("write") ||
|
|
868
|
+
normalized.startsWith("set") ||
|
|
869
|
+
normalized.startsWith("save") ||
|
|
870
|
+
normalized.startsWith("create") ||
|
|
871
|
+
normalized.startsWith("update") ||
|
|
872
|
+
normalized.startsWith("delete") ||
|
|
873
|
+
normalized.startsWith("remove") ||
|
|
874
|
+
normalized.startsWith("insert"));
|
|
875
|
+
}
|
|
876
|
+
function matchesFixtureValue(expected, actual) {
|
|
877
|
+
if (typeof expected === "string" && typeof actual === "string" && expected.endsWith("%")) {
|
|
878
|
+
return actual.startsWith(expected.slice(0, -1));
|
|
879
|
+
}
|
|
880
|
+
if (Array.isArray(expected)) {
|
|
881
|
+
if (!Array.isArray(actual) || expected.length !== actual.length) {
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
return expected.every((item, index) => matchesFixtureValue(item, actual[index]));
|
|
885
|
+
}
|
|
886
|
+
if (isPlainObject(expected)) {
|
|
887
|
+
if (!isPlainObject(actual)) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
return Object.entries(expected).every(([key, value]) => matchesFixtureValue(value, actual[key]));
|
|
891
|
+
}
|
|
892
|
+
return Object.is(expected, actual);
|
|
893
|
+
}
|
|
894
|
+
function getFetchUrl(input) {
|
|
895
|
+
if (typeof input === "string") {
|
|
896
|
+
return input;
|
|
897
|
+
}
|
|
898
|
+
if (input instanceof URL) {
|
|
899
|
+
return input.toString();
|
|
900
|
+
}
|
|
901
|
+
return input.url;
|
|
902
|
+
}
|
|
903
|
+
function createFixtureResponse(response) {
|
|
904
|
+
const status = response.status ?? 200;
|
|
905
|
+
const headers = new Headers(response.headers);
|
|
906
|
+
if (response.body === undefined) {
|
|
907
|
+
return new Response(null, {
|
|
908
|
+
status,
|
|
909
|
+
headers,
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
if (typeof response.body === "string") {
|
|
913
|
+
return new Response(response.body, {
|
|
914
|
+
status,
|
|
915
|
+
headers,
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
if (!headers.has("content-type")) {
|
|
919
|
+
headers.set("content-type", "application/json");
|
|
920
|
+
}
|
|
921
|
+
return new Response(JSON.stringify(response.body), {
|
|
922
|
+
status,
|
|
923
|
+
headers,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
function createFixtureFetch(entries) {
|
|
927
|
+
return async (input, init) => {
|
|
928
|
+
const method = normalizeHttpMethod(init?.method ?? (input instanceof Request ? input.method : undefined));
|
|
929
|
+
const url = getFetchUrl(input);
|
|
930
|
+
const match = entries?.find((entry) => {
|
|
931
|
+
const requestMethod = normalizeHttpMethod(entry.request.method);
|
|
932
|
+
return requestMethod === method && entry.request.url === url;
|
|
933
|
+
});
|
|
934
|
+
if (match !== undefined) {
|
|
935
|
+
return createFixtureResponse(match.response);
|
|
936
|
+
}
|
|
937
|
+
if (isReadLikeMethod(method)) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
return new Response(null, {
|
|
941
|
+
status: 204,
|
|
942
|
+
});
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
function createFixtureFs(definition) {
|
|
946
|
+
const fsDefinition = isPlainObject(definition) ? definition : {};
|
|
947
|
+
const readFileEntries = isPlainObject(fsDefinition.readFile) ? fsDefinition.readFile : {};
|
|
948
|
+
const existsEntries = isPlainObject(fsDefinition.exists) ? fsDefinition.exists : {};
|
|
949
|
+
return {
|
|
950
|
+
readFile: async (filePath) => {
|
|
951
|
+
if (Object.prototype.hasOwnProperty.call(readFileEntries, filePath)) {
|
|
952
|
+
return String(readFileEntries[filePath]);
|
|
953
|
+
}
|
|
954
|
+
return null;
|
|
955
|
+
},
|
|
956
|
+
writeFile: async () => undefined,
|
|
957
|
+
exists: async (filePath) => {
|
|
958
|
+
if (Object.prototype.hasOwnProperty.call(existsEntries, filePath)) {
|
|
959
|
+
return Boolean(existsEntries[filePath]);
|
|
960
|
+
}
|
|
961
|
+
return Object.prototype.hasOwnProperty.call(readFileEntries, filePath);
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function resolveFixtureMethodResult(methodName, definition, args) {
|
|
966
|
+
if (Array.isArray(definition)) {
|
|
967
|
+
for (const entry of definition) {
|
|
968
|
+
if (!isPlainObject(entry)) {
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
const explicitMatcher = isPlainObject(entry.request) ? entry.request : undefined;
|
|
972
|
+
const matcher = explicitMatcher ??
|
|
973
|
+
Object.fromEntries(Object.entries(entry).filter(([key]) => key !== "result" && key !== "response" && key !== "error"));
|
|
974
|
+
const firstArg = args[0];
|
|
975
|
+
let matched = false;
|
|
976
|
+
if (Array.isArray(matcher.args)) {
|
|
977
|
+
matched = matchesFixtureValue(matcher.args, args);
|
|
978
|
+
}
|
|
979
|
+
else if (Object.keys(matcher).length === 0) {
|
|
980
|
+
matched = true;
|
|
981
|
+
}
|
|
982
|
+
else if (isPlainObject(firstArg)) {
|
|
983
|
+
matched = matchesFixtureValue(matcher, firstArg);
|
|
984
|
+
}
|
|
985
|
+
else if (args.length === 1 && Object.keys(matcher).length === 1) {
|
|
986
|
+
const [[, expectedValue]] = Object.entries(matcher);
|
|
987
|
+
matched = matchesFixtureValue(expectedValue, firstArg);
|
|
988
|
+
}
|
|
989
|
+
if (!matched) {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (entry.error !== undefined) {
|
|
993
|
+
throw new Error(String(entry.error));
|
|
994
|
+
}
|
|
995
|
+
if (Object.prototype.hasOwnProperty.call(entry, "result")) {
|
|
996
|
+
return Promise.resolve(entry.result);
|
|
997
|
+
}
|
|
998
|
+
if (Object.prototype.hasOwnProperty.call(entry, "response")) {
|
|
999
|
+
return Promise.resolve(entry.response);
|
|
1000
|
+
}
|
|
1001
|
+
return Promise.resolve(null);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (isPlainObject(definition)) {
|
|
1005
|
+
const firstArg = args[0];
|
|
1006
|
+
if (typeof firstArg === "string" && Object.prototype.hasOwnProperty.call(definition, firstArg)) {
|
|
1007
|
+
return Promise.resolve(definition[firstArg]);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (isWriteLikeMethod(methodName)) {
|
|
1011
|
+
return Promise.resolve(undefined);
|
|
1012
|
+
}
|
|
1013
|
+
return Promise.resolve(null);
|
|
1014
|
+
}
|
|
1015
|
+
function createFixtureService(definition) {
|
|
1016
|
+
const methods = isPlainObject(definition) ? definition : {};
|
|
1017
|
+
return new Proxy({}, {
|
|
1018
|
+
get(_target, property) {
|
|
1019
|
+
if (property === "then") {
|
|
1020
|
+
return undefined;
|
|
1021
|
+
}
|
|
1022
|
+
const methodName = String(property);
|
|
1023
|
+
return async (...args) => resolveFixtureMethodResult(methodName, methods[methodName], args);
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
function resolveFixturePath(commandPath) {
|
|
1028
|
+
const parsed = path.parse(commandPath);
|
|
1029
|
+
return path.join(parsed.dir, `${parsed.name}.fixture.json`);
|
|
1030
|
+
}
|
|
1031
|
+
function selectFixtureScenario(scenarios, selector) {
|
|
1032
|
+
if (isNumericFixtureSelector(selector)) {
|
|
1033
|
+
const index = Number(selector) - 1;
|
|
1034
|
+
const scenario = scenarios[index];
|
|
1035
|
+
if (scenario === undefined) {
|
|
1036
|
+
throw new UserError(`Fixture scenario index ${selector} is out of range. Available scenarios: ${scenarios.length}.`);
|
|
1037
|
+
}
|
|
1038
|
+
return scenario;
|
|
1039
|
+
}
|
|
1040
|
+
const scenario = scenarios.find((entry) => entry.name === selector);
|
|
1041
|
+
if (scenario === undefined) {
|
|
1042
|
+
throw new UserError(`Fixture scenario "${selector}" was not found.`);
|
|
1043
|
+
}
|
|
1044
|
+
return scenario;
|
|
1045
|
+
}
|
|
1046
|
+
async function loadFixtureScenario(command, selector) {
|
|
1047
|
+
const commandPath = getCommandSourcePath(command);
|
|
1048
|
+
if (commandPath === undefined) {
|
|
1049
|
+
throw new UserError(`Fixture mode could not determine the source file for command "${command.name}".`);
|
|
1050
|
+
}
|
|
1051
|
+
const fixturePath = resolveFixturePath(commandPath);
|
|
1052
|
+
let rawFixture;
|
|
1053
|
+
try {
|
|
1054
|
+
rawFixture = await readFile(fixturePath, {
|
|
1055
|
+
encoding: "utf8",
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
throw new UserError(`Fixture file not found for command "${command.name}". Expected ${fixturePath}.`);
|
|
1060
|
+
}
|
|
1061
|
+
let parsed;
|
|
1062
|
+
try {
|
|
1063
|
+
parsed = JSON.parse(rawFixture);
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
throw new UserError(`Fixture file ${fixturePath} is not valid JSON.`);
|
|
1067
|
+
}
|
|
1068
|
+
if (!Array.isArray(parsed)) {
|
|
1069
|
+
throw new UserError(`Fixture file ${fixturePath} must contain a JSON array of scenarios.`);
|
|
1070
|
+
}
|
|
1071
|
+
return selectFixtureScenario(parsed, selector);
|
|
1072
|
+
}
|
|
1073
|
+
function resolveFixtureSecrets(command) {
|
|
1074
|
+
return Object.fromEntries(Object.keys(command.secrets).map((name) => [name, "fixture-secret"]));
|
|
1075
|
+
}
|
|
1076
|
+
function createFixtureEnvValues(command) {
|
|
1077
|
+
const values = {
|
|
1078
|
+
...process.env,
|
|
1079
|
+
POE_API_KEY: process.env.POE_API_KEY ?? "fixture-secret",
|
|
1080
|
+
};
|
|
1081
|
+
for (const secret of Object.values(command.secrets)) {
|
|
1082
|
+
values[secret.env] = values[secret.env] ?? "fixture-secret";
|
|
1083
|
+
}
|
|
1084
|
+
return values;
|
|
1085
|
+
}
|
|
1086
|
+
async function resolveFixtureRuntime(command, services, requirementOptions) {
|
|
1087
|
+
const selector = process.env.AGENT_KIT_FIXTURE;
|
|
1088
|
+
if (selector === undefined || selector.length === 0) {
|
|
1089
|
+
return {
|
|
1090
|
+
env: createEnv(),
|
|
1091
|
+
fetch: globalThis.fetch,
|
|
1092
|
+
fs: createFs(),
|
|
1093
|
+
isFixture: false,
|
|
1094
|
+
requirementOptions,
|
|
1095
|
+
secrets: resolveCommandSecrets(command),
|
|
1096
|
+
services,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
const scenario = await loadFixtureScenario(command, selector);
|
|
1100
|
+
const scenarioServices = isPlainObject(scenario.services) ? scenario.services : {};
|
|
1101
|
+
const customServiceNames = new Set([
|
|
1102
|
+
...Object.keys(services),
|
|
1103
|
+
...Object.keys(scenarioServices).filter((name) => !RESERVED_SERVICE_NAMES.has(name)),
|
|
1104
|
+
]);
|
|
1105
|
+
const fixtureServices = Object.fromEntries([...customServiceNames].map((name) => [name, createFixtureService(scenarioServices[name])]));
|
|
1106
|
+
const fixtureEnvValues = createFixtureEnvValues(command);
|
|
1107
|
+
return {
|
|
1108
|
+
env: createEnv(fixtureEnvValues),
|
|
1109
|
+
fetch: createFixtureFetch(scenarioServices.fetch),
|
|
1110
|
+
fs: createFixtureFs(scenarioServices.fs),
|
|
1111
|
+
isFixture: true,
|
|
1112
|
+
requirementOptions: {
|
|
1113
|
+
...requirementOptions,
|
|
1114
|
+
env: fixtureEnvValues,
|
|
1115
|
+
},
|
|
1116
|
+
secrets: resolveFixtureSecrets(command),
|
|
1117
|
+
services: fixtureServices,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function writeRichHeader(title) {
|
|
1121
|
+
const padding = Math.max(12, 34 - title.length);
|
|
1122
|
+
process.stdout.write(`── ${title} ${"─".repeat(padding)}\n`);
|
|
1123
|
+
}
|
|
1124
|
+
function validateServices(services) {
|
|
1125
|
+
for (const name of Object.keys(services)) {
|
|
1126
|
+
if (RESERVED_SERVICE_NAMES.has(name)) {
|
|
1127
|
+
throw new Error(`Service name "${name}" is reserved. Choose a different name.`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async function resolveParams(fields, positionalValues, optionValues, presetPath, shouldPrompt) {
|
|
1132
|
+
const params = {};
|
|
1133
|
+
const presetValues = typeof presetPath === "string" && presetPath.length > 0
|
|
1134
|
+
? await loadPresetValues(fields, presetPath)
|
|
1135
|
+
: {};
|
|
1136
|
+
for (const field of fields) {
|
|
1137
|
+
let value;
|
|
1138
|
+
if (field.positionalIndex !== undefined) {
|
|
1139
|
+
const positionalValue = positionalValues[field.positionalIndex];
|
|
1140
|
+
if (field.schema.kind === "array") {
|
|
1141
|
+
if (Array.isArray(positionalValue) && positionalValue.length > 0) {
|
|
1142
|
+
const itemSchema = unwrapOptional(field.schema.item);
|
|
1143
|
+
if (itemSchema.kind === "array" || itemSchema.kind === "object") {
|
|
1144
|
+
throw new UserError(`Array parameter "${field.displayPath}" must use scalar items.`);
|
|
1145
|
+
}
|
|
1146
|
+
value = positionalValue.map((item) => parseScalarValue(String(item), itemSchema, field.displayPath));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
else if (typeof positionalValue === "string" && positionalValue.length > 0) {
|
|
1150
|
+
value = parseScalarValue(positionalValue, field.schema, field.displayPath);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if (value === undefined &&
|
|
1154
|
+
Object.prototype.hasOwnProperty.call(optionValues, field.commanderOptionAttribute) &&
|
|
1155
|
+
hasFieldValue(optionValues[field.commanderOptionAttribute])) {
|
|
1156
|
+
value = optionValues[field.commanderOptionAttribute];
|
|
1157
|
+
}
|
|
1158
|
+
if (value === undefined &&
|
|
1159
|
+
field.commanderOptionAttribute === field.optionAttribute &&
|
|
1160
|
+
Object.prototype.hasOwnProperty.call(optionValues, field.optionAttribute) &&
|
|
1161
|
+
hasFieldValue(optionValues[field.optionAttribute])) {
|
|
1162
|
+
value = optionValues[field.optionAttribute];
|
|
1163
|
+
}
|
|
1164
|
+
if (value === undefined &&
|
|
1165
|
+
Object.prototype.hasOwnProperty.call(presetValues, field.optionAttribute)) {
|
|
1166
|
+
value = presetValues[field.optionAttribute];
|
|
1167
|
+
}
|
|
1168
|
+
if (value === undefined && shouldPrompt && !field.optional) {
|
|
1169
|
+
value = await promptForField(field);
|
|
1170
|
+
}
|
|
1171
|
+
if (value === undefined && field.hasDefault) {
|
|
1172
|
+
value = field.defaultValue;
|
|
1173
|
+
}
|
|
1174
|
+
if (value === undefined) {
|
|
1175
|
+
if (field.optional) {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
throw new UserError(`Missing required parameter "${field.displayPath}".`);
|
|
1179
|
+
}
|
|
1180
|
+
setNestedValue(params, field.path, value);
|
|
1181
|
+
}
|
|
1182
|
+
return params;
|
|
1183
|
+
}
|
|
1184
|
+
function getResolvedFlags(command) {
|
|
1185
|
+
const flags = command.optsWithGlobals();
|
|
1186
|
+
return flags;
|
|
1187
|
+
}
|
|
1188
|
+
async function executeCommand(state, services, requirementOptions) {
|
|
1189
|
+
const logger = createLogger();
|
|
1190
|
+
const primitives = {
|
|
1191
|
+
logger,
|
|
1192
|
+
renderTable,
|
|
1193
|
+
getTheme,
|
|
1194
|
+
note,
|
|
1195
|
+
};
|
|
1196
|
+
const optionValues = state.actionCommand.optsWithGlobals();
|
|
1197
|
+
const resolvedFlags = optionValues;
|
|
1198
|
+
const output = resolveOutput(resolvedFlags);
|
|
1199
|
+
const shouldPrompt = !resolvedFlags.yes && Boolean(process.stdin.isTTY);
|
|
1200
|
+
const runtime = await resolveFixtureRuntime(state.command, services, requirementOptions);
|
|
1201
|
+
const preflightContext = {
|
|
1202
|
+
...runtime.services,
|
|
1203
|
+
secrets: runtime.secrets,
|
|
1204
|
+
fetch: runtime.fetch,
|
|
1205
|
+
fs: runtime.fs,
|
|
1206
|
+
env: runtime.env,
|
|
1207
|
+
progress(message) {
|
|
1208
|
+
logger.info(message);
|
|
1209
|
+
},
|
|
1210
|
+
};
|
|
1211
|
+
await withOutputFormat(output, async () => {
|
|
1212
|
+
await assertCommandRequirements(state.command, preflightContext, runtime.requirementOptions);
|
|
1213
|
+
const params = await resolveParams(state.fields, state.positionalValues, optionValues, resolvedFlags.preset, shouldPrompt);
|
|
1214
|
+
const context = {
|
|
1215
|
+
...preflightContext,
|
|
1216
|
+
params,
|
|
1217
|
+
};
|
|
1218
|
+
if (state.command.confirm && !resolvedFlags.yes && process.stdin.isTTY) {
|
|
1219
|
+
for (const field of state.fields) {
|
|
1220
|
+
const value = field.path.reduce((current, segment) => current && typeof current === "object"
|
|
1221
|
+
? current[segment]
|
|
1222
|
+
: undefined, params);
|
|
1223
|
+
if (value !== undefined) {
|
|
1224
|
+
logger.resolved(field.displayPath, formatResolvedValue(value));
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const proceed = await confirm({
|
|
1228
|
+
message: "Proceed?",
|
|
1229
|
+
initialValue: true,
|
|
1230
|
+
});
|
|
1231
|
+
if (isCancel(proceed)) {
|
|
1232
|
+
cancel("Operation cancelled.");
|
|
1233
|
+
throw new UserError("Operation cancelled.");
|
|
1234
|
+
}
|
|
1235
|
+
if (proceed !== true) {
|
|
1236
|
+
throw new UserError("Operation cancelled.");
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const result = await state.command.handler(context);
|
|
1240
|
+
if (output === "rich" && runtime.isFixture) {
|
|
1241
|
+
writeRichHeader(`${state.command.name} (fixture)`);
|
|
1242
|
+
}
|
|
1243
|
+
renderResult(state.command, result, output, primitives);
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
function handleRunError(error, verbose) {
|
|
1247
|
+
const logger = createLogger();
|
|
1248
|
+
if (error instanceof UserError) {
|
|
1249
|
+
logger.error(error.message);
|
|
1250
|
+
process.exitCode = 1;
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (error instanceof CommanderError) {
|
|
1254
|
+
process.exitCode = error.exitCode;
|
|
1255
|
+
if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1261
|
+
logger.error(verbose ? message : `${message} Use --verbose for a stack trace.`);
|
|
1262
|
+
if (verbose && error instanceof Error && error.stack) {
|
|
1263
|
+
process.stderr.write(`${error.stack}\n`);
|
|
1264
|
+
}
|
|
1265
|
+
process.exitCode = 1;
|
|
1266
|
+
}
|
|
1267
|
+
export async function runCLI(roots, options = {}) {
|
|
1268
|
+
const root = normalizeRoots(roots, process.argv);
|
|
1269
|
+
const casing = options.casing ?? "kebab";
|
|
1270
|
+
const services = (options.services ?? {});
|
|
1271
|
+
const requirementOptions = {
|
|
1272
|
+
apiVersion: options.apiVersion,
|
|
1273
|
+
};
|
|
1274
|
+
validateServices(services);
|
|
1275
|
+
if (hasHelpFlag(process.argv)) {
|
|
1276
|
+
await renderGeneratedHelp(root, process.argv, options);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const program = new CommanderCommand();
|
|
1280
|
+
program.name(root.name);
|
|
1281
|
+
program.exitOverride();
|
|
1282
|
+
program.showHelpAfterError();
|
|
1283
|
+
program.addHelpCommand(false);
|
|
1284
|
+
addGlobalOptions(program);
|
|
1285
|
+
if (options.version !== undefined) {
|
|
1286
|
+
program.version(options.version, "--version");
|
|
1287
|
+
}
|
|
1288
|
+
const execute = async (state) => {
|
|
1289
|
+
try {
|
|
1290
|
+
await executeCommand(state, services, requirementOptions);
|
|
1291
|
+
}
|
|
1292
|
+
catch (error) {
|
|
1293
|
+
handleRunError(error, Boolean(getResolvedFlags(state.actionCommand).verbose));
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
for (const child of root.children) {
|
|
1297
|
+
const command = createNodeCommand(child, casing, execute);
|
|
1298
|
+
if (command === null) {
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const isDefaultChild = root.default !== undefined &&
|
|
1302
|
+
root.default.scope.includes("cli") &&
|
|
1303
|
+
(command.name() === root.default.name || command.aliases().includes(root.default.name));
|
|
1304
|
+
program.addCommand(command, isDefaultChild ? { isDefault: true } : undefined);
|
|
1305
|
+
}
|
|
1306
|
+
try {
|
|
1307
|
+
await program.parseAsync(process.argv);
|
|
1308
|
+
}
|
|
1309
|
+
catch (error) {
|
|
1310
|
+
handleRunError(error, process.argv.includes("--verbose"));
|
|
1311
|
+
}
|
|
1312
|
+
}
|