padrone 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -1
- package/LICENSE +1 -1
- package/README.md +60 -30
- package/dist/args-DFEI7_G_.mjs +197 -0
- package/dist/args-DFEI7_G_.mjs.map +1 -0
- package/dist/chunk-y_GBKt04.mjs +5 -0
- package/dist/codegen/index.d.mts +305 -0
- package/dist/codegen/index.d.mts.map +1 -0
- package/dist/codegen/index.mjs +1358 -0
- package/dist/codegen/index.mjs.map +1 -0
- package/dist/completion.d.mts +64 -0
- package/dist/completion.d.mts.map +1 -0
- package/dist/completion.mjs +417 -0
- package/dist/completion.mjs.map +1 -0
- package/dist/docs/index.d.mts +34 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +405 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/formatter-XroimS3Q.d.mts +83 -0
- package/dist/formatter-XroimS3Q.d.mts.map +1 -0
- package/dist/help-CgGP7hQU.mjs +1229 -0
- package/dist/help-CgGP7hQU.mjs.map +1 -0
- package/dist/index.d.mts +120 -546
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1220 -1204
- package/dist/index.mjs.map +1 -1
- package/dist/test.d.mts +112 -0
- package/dist/test.d.mts.map +1 -0
- package/dist/test.mjs +138 -0
- package/dist/test.mjs.map +1 -0
- package/dist/types-BS7RP5Ls.d.mts +1059 -0
- package/dist/types-BS7RP5Ls.d.mts.map +1 -0
- package/dist/update-check-EbNDkzyV.mjs +146 -0
- package/dist/update-check-EbNDkzyV.mjs.map +1 -0
- package/package.json +61 -21
- package/src/args.ts +457 -0
- package/src/cli/completions.ts +29 -0
- package/src/cli/docs.ts +86 -0
- package/src/cli/doctor.ts +330 -0
- package/src/cli/index.ts +159 -0
- package/src/cli/init.ts +135 -0
- package/src/cli/link.ts +320 -0
- package/src/cli/wrap.ts +152 -0
- package/src/codegen/README.md +118 -0
- package/src/codegen/code-builder.ts +226 -0
- package/src/codegen/discovery.ts +232 -0
- package/src/codegen/file-emitter.ts +73 -0
- package/src/codegen/generators/barrel-file.ts +16 -0
- package/src/codegen/generators/command-file.ts +197 -0
- package/src/codegen/generators/command-tree.ts +124 -0
- package/src/codegen/index.ts +33 -0
- package/src/codegen/parsers/fish.ts +163 -0
- package/src/codegen/parsers/help.ts +378 -0
- package/src/codegen/parsers/merge.ts +158 -0
- package/src/codegen/parsers/zsh.ts +221 -0
- package/src/codegen/schema-to-code.ts +199 -0
- package/src/codegen/template.ts +69 -0
- package/src/codegen/types.ts +143 -0
- package/src/colorizer.ts +2 -2
- package/src/command-utils.ts +504 -0
- package/src/completion.ts +110 -97
- package/src/create.ts +1048 -308
- package/src/docs/index.ts +607 -0
- package/src/errors.ts +131 -0
- package/src/formatter.ts +195 -73
- package/src/help.ts +159 -58
- package/src/index.ts +12 -15
- package/src/interactive.ts +169 -0
- package/src/parse.ts +52 -21
- package/src/repl-loop.ts +317 -0
- package/src/runtime.ts +304 -0
- package/src/shell-utils.ts +83 -0
- package/src/test.ts +285 -0
- package/src/type-helpers.ts +10 -10
- package/src/type-utils.ts +124 -14
- package/src/types.ts +752 -154
- package/src/update-check.ts +244 -0
- package/src/wrap.ts +44 -40
- package/src/zod.d.ts +2 -2
- package/src/options.ts +0 -180
package/dist/index.mjs
CHANGED
|
@@ -1,983 +1,228 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region
|
|
4
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
//#endregion
|
|
7
|
-
//#region src/options.ts
|
|
1
|
+
import { a as parsePositionalConfig, i as extractSchemaMetadata, n as coerceArgs, o as parseStdinConfig, r as detectUnknownArgs, s as preprocessArgs } from "./args-DFEI7_G_.mjs";
|
|
2
|
+
import { S as getVersion, _ as warnIfUnexpectedAsync, a as commandSymbol, b as createTerminalReplSession, c as hasInteractiveConfig, d as noop, f as outputValue, g as thenMaybe, h as suggestSimilar, i as buildReplCompleter, l as isAsyncBranded, m as runPluginChain, o as findCommandByName, p as repathCommandTree, r as asyncSchema, s as getCommandRuntime, t as generateHelp, u as mergeCommands, v as wrapWithLifecycle, x as resolveStdin, y as REPL_SIGINT } from "./help-CgGP7hQU.mjs";
|
|
3
|
+
//#region src/errors.ts
|
|
8
4
|
/**
|
|
9
|
-
*
|
|
5
|
+
* Base error class for all Padrone errors.
|
|
6
|
+
* Carries structured metadata for user-friendly formatting and programmatic handling.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* throw new PadroneError('Something went wrong', {
|
|
11
|
+
* exitCode: 1,
|
|
12
|
+
* suggestions: ['Try --help for usage information'],
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
10
15
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
var PadroneError = class extends Error {
|
|
17
|
+
exitCode;
|
|
18
|
+
suggestions;
|
|
19
|
+
command;
|
|
20
|
+
phase;
|
|
21
|
+
constructor(message, options) {
|
|
22
|
+
super(message, options?.cause ? { cause: options.cause } : void 0);
|
|
23
|
+
this.name = "PadroneError";
|
|
24
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
25
|
+
this.suggestions = options?.suggestions ?? [];
|
|
26
|
+
this.command = options?.command;
|
|
27
|
+
this.phase = options?.phase;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns a serializable representation of the error,
|
|
31
|
+
* suitable for non-terminal runtimes (web UIs, APIs, etc.).
|
|
32
|
+
*/
|
|
33
|
+
toJSON() {
|
|
14
34
|
return {
|
|
15
|
-
name:
|
|
16
|
-
|
|
35
|
+
name: this.name,
|
|
36
|
+
message: this.message,
|
|
37
|
+
exitCode: this.exitCode,
|
|
38
|
+
suggestions: this.suggestions,
|
|
39
|
+
command: this.command,
|
|
40
|
+
phase: this.phase
|
|
17
41
|
};
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Extract all option metadata from schema and meta in a single pass.
|
|
22
|
-
* This consolidates aliases, env bindings, and config keys extraction.
|
|
23
|
-
*/
|
|
24
|
-
function extractSchemaMetadata(schema, meta) {
|
|
25
|
-
const aliases = {};
|
|
26
|
-
if (meta) for (const [key, value] of Object.entries(meta)) {
|
|
27
|
-
if (!value) continue;
|
|
28
|
-
if (value.alias) {
|
|
29
|
-
const list = typeof value.alias === "string" ? [value.alias] : value.alias;
|
|
30
|
-
for (const aliasKey of list) if (typeof aliasKey === "string" && aliasKey && aliasKey !== key) aliases[aliasKey] = key;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
35
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) for (const [propertyName, propertySchema] of Object.entries(jsonSchema.properties)) {
|
|
36
|
-
if (!propertySchema) continue;
|
|
37
|
-
const propAlias = propertySchema.alias;
|
|
38
|
-
if (propAlias) {
|
|
39
|
-
const list = typeof propAlias === "string" ? [propAlias] : propAlias;
|
|
40
|
-
if (Array.isArray(list)) {
|
|
41
|
-
for (const aliasKey of list) if (typeof aliasKey === "string" && aliasKey && aliasKey !== propertyName && !(aliasKey in aliases)) aliases[aliasKey] = propertyName;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
} catch {}
|
|
46
|
-
return { aliases };
|
|
47
|
-
}
|
|
48
|
-
function preprocessAliases(data, aliases) {
|
|
49
|
-
const result = { ...data };
|
|
50
|
-
for (const [aliasKey, fullOptionName] of Object.entries(aliases)) if (aliasKey in data && aliasKey !== fullOptionName) {
|
|
51
|
-
const aliasValue = data[aliasKey];
|
|
52
|
-
if (!(fullOptionName in result)) result[fullOptionName] = aliasValue;
|
|
53
|
-
delete result[aliasKey];
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Apply values directly to options.
|
|
59
|
-
* CLI values take precedence over the provided values.
|
|
60
|
-
*/
|
|
61
|
-
function applyValues(data, values) {
|
|
62
|
-
const result = { ...data };
|
|
63
|
-
for (const [key, value] of Object.entries(values)) {
|
|
64
|
-
if (key in result && result[key] !== void 0) continue;
|
|
65
|
-
if (value !== void 0) result[key] = value;
|
|
66
|
-
}
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Combined preprocessing of options with all features.
|
|
71
|
-
* Precedence order (highest to lowest): CLI args > env vars > config file
|
|
72
|
-
*/
|
|
73
|
-
function preprocessOptions(data, ctx) {
|
|
74
|
-
let result = { ...data };
|
|
75
|
-
if (ctx.aliases && Object.keys(ctx.aliases).length > 0) result = preprocessAliases(result, ctx.aliases);
|
|
76
|
-
if (ctx.envData) result = applyValues(result, ctx.envData);
|
|
77
|
-
if (ctx.configData) result = applyValues(result, ctx.configData);
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/completion.ts
|
|
83
|
-
/**
|
|
84
|
-
* Detects the current shell from environment variables and process info.
|
|
85
|
-
* @returns The detected shell type, or undefined if unknown
|
|
86
|
-
*/
|
|
87
|
-
function detectShell() {
|
|
88
|
-
if (typeof process === "undefined") return void 0;
|
|
89
|
-
const shellEnv = process.env.SHELL || "";
|
|
90
|
-
if (shellEnv.includes("zsh")) return "zsh";
|
|
91
|
-
if (shellEnv.includes("bash")) return "bash";
|
|
92
|
-
if (shellEnv.includes("fish")) return "fish";
|
|
93
|
-
if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) return "powershell";
|
|
94
|
-
try {
|
|
95
|
-
const { execSync } = __require("node:child_process");
|
|
96
|
-
const ppid = process.ppid;
|
|
97
|
-
if (ppid) {
|
|
98
|
-
const processName = execSync(`ps -p ${ppid} -o comm=`, {
|
|
99
|
-
encoding: "utf-8",
|
|
100
|
-
stdio: [
|
|
101
|
-
"pipe",
|
|
102
|
-
"pipe",
|
|
103
|
-
"ignore"
|
|
104
|
-
]
|
|
105
|
-
}).trim();
|
|
106
|
-
if (processName.includes("zsh")) return "zsh";
|
|
107
|
-
if (processName.includes("bash")) return "bash";
|
|
108
|
-
if (processName.includes("fish")) return "fish";
|
|
109
|
-
}
|
|
110
|
-
} catch {}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Collects all commands from a program recursively.
|
|
114
|
-
*/
|
|
115
|
-
function collectAllCommands(cmd) {
|
|
116
|
-
const result = [];
|
|
117
|
-
if (cmd.commands) {
|
|
118
|
-
for (const subcmd of cmd.commands) if (!subcmd.hidden) {
|
|
119
|
-
result.push(subcmd);
|
|
120
|
-
result.push(...collectAllCommands(subcmd));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Extracts all option names from a command's schema.
|
|
127
|
-
*/
|
|
128
|
-
function extractOptions(cmd) {
|
|
129
|
-
const options = [];
|
|
130
|
-
if (!cmd.options) return options;
|
|
131
|
-
try {
|
|
132
|
-
const optionsMeta = cmd.meta?.options;
|
|
133
|
-
const { aliases } = extractSchemaMetadata(cmd.options, optionsMeta);
|
|
134
|
-
const aliasToOption = {};
|
|
135
|
-
for (const [opt, alias] of Object.entries(aliases)) aliasToOption[alias] = opt;
|
|
136
|
-
const jsonSchema = cmd.options["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
137
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) for (const [key, prop] of Object.entries(jsonSchema.properties)) {
|
|
138
|
-
const alias = Object.entries(aliases).find(([opt]) => opt === key)?.[1];
|
|
139
|
-
options.push({
|
|
140
|
-
name: key,
|
|
141
|
-
alias,
|
|
142
|
-
isBoolean: prop?.type === "boolean"
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
} catch {}
|
|
146
|
-
return options;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Generates a Bash completion script for the program.
|
|
150
|
-
*/
|
|
151
|
-
function generateBashCompletion(program) {
|
|
152
|
-
const programName = program.name;
|
|
153
|
-
const commands = collectAllCommands(program);
|
|
154
|
-
const commandNames = commands.map((c) => c.name).join(" ");
|
|
155
|
-
const allOptions = /* @__PURE__ */ new Set();
|
|
156
|
-
allOptions.add("--help");
|
|
157
|
-
allOptions.add("--version");
|
|
158
|
-
for (const cmd of [program, ...commands]) for (const opt of extractOptions(cmd)) {
|
|
159
|
-
allOptions.add(`--${opt.name}`);
|
|
160
|
-
if (opt.alias) allOptions.add(`-${opt.alias}`);
|
|
161
42
|
}
|
|
162
|
-
|
|
163
|
-
return `###-begin-${programName}-completion-###
|
|
164
|
-
#
|
|
165
|
-
# ${programName} command completion script
|
|
166
|
-
#
|
|
167
|
-
# Installation: ${programName} completion >> ~/.bashrc (or ~/.zshrc)
|
|
168
|
-
# Or, maybe: ${programName} completion > /usr/local/etc/bash_completion.d/${programName}
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
if type complete &>/dev/null; then
|
|
172
|
-
_${programName}_completion() {
|
|
173
|
-
local cur prev words cword
|
|
174
|
-
if type _get_comp_words_by_ref &>/dev/null; then
|
|
175
|
-
_get_comp_words_by_ref -n = -n @ -n : -w words -i cword
|
|
176
|
-
else
|
|
177
|
-
cword="$COMP_CWORD"
|
|
178
|
-
words=("\${COMP_WORDS[@]}")
|
|
179
|
-
fi
|
|
180
|
-
|
|
181
|
-
cur="\${words[cword]}"
|
|
182
|
-
prev="\${words[cword-1]}"
|
|
183
|
-
|
|
184
|
-
local commands="${commandNames}"
|
|
185
|
-
local options="${optionsList}"
|
|
186
|
-
|
|
187
|
-
# Complete options when current word starts with -
|
|
188
|
-
if [[ "$cur" == -* ]]; then
|
|
189
|
-
COMPREPLY=($(compgen -W "$options" -- "$cur"))
|
|
190
|
-
return 0
|
|
191
|
-
fi
|
|
192
|
-
|
|
193
|
-
# Complete commands
|
|
194
|
-
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
|
|
195
|
-
}
|
|
196
|
-
complete -o bashdefault -o default -o nospace -F _${programName}_completion ${programName}
|
|
197
|
-
elif type compdef &>/dev/null; then
|
|
198
|
-
_${programName}_completion() {
|
|
199
|
-
local si=$IFS
|
|
200
|
-
local commands="${commandNames}"
|
|
201
|
-
local options="${optionsList}"
|
|
202
|
-
|
|
203
|
-
if [[ "\${words[CURRENT]}" == -* ]]; then
|
|
204
|
-
compadd -- \${=options}
|
|
205
|
-
else
|
|
206
|
-
compadd -- \${=commands}
|
|
207
|
-
fi
|
|
208
|
-
IFS=$si
|
|
209
|
-
}
|
|
210
|
-
compdef _${programName}_completion ${programName}
|
|
211
|
-
elif type compctl &>/dev/null; then
|
|
212
|
-
_${programName}_completion() {
|
|
213
|
-
local commands="${commandNames}"
|
|
214
|
-
local options="${optionsList}"
|
|
215
|
-
|
|
216
|
-
if [[ "\${words[CURRENT]}" == -* ]]; then
|
|
217
|
-
reply=(\${=options})
|
|
218
|
-
else
|
|
219
|
-
reply=(\${=commands})
|
|
220
|
-
fi
|
|
221
|
-
}
|
|
222
|
-
compctl -K _${programName}_completion ${programName}
|
|
223
|
-
fi
|
|
224
|
-
###-end-${programName}-completion-###`;
|
|
225
|
-
}
|
|
43
|
+
};
|
|
226
44
|
/**
|
|
227
|
-
*
|
|
45
|
+
* Thrown when command routing fails — unknown command, unexpected arguments, etc.
|
|
228
46
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const optionCompletions = [];
|
|
237
|
-
optionCompletions.push(" '--help[Show help information]'");
|
|
238
|
-
optionCompletions.push(" '--version[Show version number]'");
|
|
239
|
-
const seenOptions = new Set(["help", "version"]);
|
|
240
|
-
for (const cmd of [program, ...commands]) for (const opt of extractOptions(cmd)) {
|
|
241
|
-
if (seenOptions.has(opt.name)) continue;
|
|
242
|
-
seenOptions.add(opt.name);
|
|
243
|
-
const escapedDesc = (cmd.meta?.options?.[opt.name]?.description || "").replace(/'/g, "'\\''").replace(/\[/g, "\\[").replace(/\]/g, "\\]");
|
|
244
|
-
if (opt.alias) optionCompletions.push(` {-${opt.alias},--${opt.name}}'[${escapedDesc}]'`);
|
|
245
|
-
else optionCompletions.push(` '--${opt.name}[${escapedDesc}]'`);
|
|
47
|
+
var RoutingError = class extends PadroneError {
|
|
48
|
+
constructor(message, options) {
|
|
49
|
+
super(message, {
|
|
50
|
+
phase: "parse",
|
|
51
|
+
...options
|
|
52
|
+
});
|
|
53
|
+
this.name = "RoutingError";
|
|
246
54
|
}
|
|
247
|
-
|
|
248
|
-
###-begin-${programName}-completion-###
|
|
249
|
-
#
|
|
250
|
-
# ${programName} command completion script for Zsh
|
|
251
|
-
#
|
|
252
|
-
# Installation: ${programName} completion >> ~/.zshrc
|
|
253
|
-
# Or: ${programName} completion > ~/.zsh/completions/_${programName}
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
_${programName}() {
|
|
257
|
-
local -a commands
|
|
258
|
-
local -a options
|
|
259
|
-
|
|
260
|
-
commands=(
|
|
261
|
-
${commandCompletions}
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
options=(
|
|
265
|
-
${optionCompletions.join("\n")}
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
_arguments -s \\
|
|
269
|
-
$options \\
|
|
270
|
-
'1: :->command' \\
|
|
271
|
-
'*::arg:->args'
|
|
272
|
-
|
|
273
|
-
case "$state" in
|
|
274
|
-
command)
|
|
275
|
-
_describe 'command' commands
|
|
276
|
-
;;
|
|
277
|
-
esac
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
_${programName}
|
|
281
|
-
###-end-${programName}-completion-###`;
|
|
282
|
-
}
|
|
55
|
+
};
|
|
283
56
|
/**
|
|
284
|
-
*
|
|
57
|
+
* Thrown when argument or schema validation fails.
|
|
58
|
+
* Carries the structured issues from the schema validator.
|
|
285
59
|
*/
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
"
|
|
294
|
-
|
|
295
|
-
"#",
|
|
296
|
-
"",
|
|
297
|
-
`# Clear existing completions`,
|
|
298
|
-
`complete -c ${programName} -e`,
|
|
299
|
-
"",
|
|
300
|
-
"# Commands"
|
|
301
|
-
];
|
|
302
|
-
for (const cmd of commands) {
|
|
303
|
-
const escapedDesc = (cmd.description || cmd.title || "").replace(/'/g, "\\'");
|
|
304
|
-
lines.push(`complete -c ${programName} -n "__fish_use_subcommand" -a "${cmd.name}" -d '${escapedDesc}'`);
|
|
60
|
+
var ValidationError = class extends PadroneError {
|
|
61
|
+
issues;
|
|
62
|
+
constructor(message, issues, options) {
|
|
63
|
+
super(message, {
|
|
64
|
+
phase: "validate",
|
|
65
|
+
...options
|
|
66
|
+
});
|
|
67
|
+
this.name = "ValidationError";
|
|
68
|
+
this.issues = issues;
|
|
305
69
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const escapedDesc = (cmd.meta?.options?.[opt.name]?.description || "").replace(/'/g, "\\'");
|
|
315
|
-
if (opt.alias) lines.push(`complete -c ${programName} -s ${opt.alias} -l ${opt.name} -d '${escapedDesc}'`);
|
|
316
|
-
else lines.push(`complete -c ${programName} -l ${opt.name} -d '${escapedDesc}'`);
|
|
70
|
+
toJSON() {
|
|
71
|
+
return {
|
|
72
|
+
...super.toJSON(),
|
|
73
|
+
issues: this.issues.map((i) => ({
|
|
74
|
+
path: i.path?.map(String),
|
|
75
|
+
message: i.message
|
|
76
|
+
}))
|
|
77
|
+
};
|
|
317
78
|
}
|
|
318
|
-
|
|
319
|
-
return lines.join("\n");
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Generates a PowerShell completion script for the program.
|
|
323
|
-
*/
|
|
324
|
-
function generatePowerShellCompletion(program) {
|
|
325
|
-
const programName = program.name;
|
|
326
|
-
return `###-begin-${programName}-completion-###
|
|
327
|
-
#
|
|
328
|
-
# ${programName} command completion script for PowerShell
|
|
329
|
-
#
|
|
330
|
-
# Installation: ${programName} completion >> $PROFILE
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
Register-ArgumentCompleter -Native -CommandName ${programName} -ScriptBlock {
|
|
334
|
-
param($wordToComplete, $commandAst, $cursorPosition)
|
|
335
|
-
|
|
336
|
-
$commands = @(${collectAllCommands(program).map((c) => `'${c.name}'`).join(", ")})
|
|
337
|
-
$options = @('--help', '--version')
|
|
338
|
-
|
|
339
|
-
if ($wordToComplete -like '-*') {
|
|
340
|
-
$options | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
341
|
-
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
342
|
-
}
|
|
343
|
-
} else {
|
|
344
|
-
$commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
345
|
-
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
###-end-${programName}-completion-###`;
|
|
350
|
-
}
|
|
79
|
+
};
|
|
351
80
|
/**
|
|
352
|
-
*
|
|
81
|
+
* Thrown when config file loading or validation fails.
|
|
353
82
|
*/
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
83
|
+
var ConfigError = class extends PadroneError {
|
|
84
|
+
constructor(message, options) {
|
|
85
|
+
super(message, {
|
|
86
|
+
phase: "config",
|
|
87
|
+
...options
|
|
88
|
+
});
|
|
89
|
+
this.name = "ConfigError";
|
|
361
90
|
}
|
|
362
|
-
}
|
|
91
|
+
};
|
|
363
92
|
/**
|
|
364
|
-
*
|
|
93
|
+
* Thrown from user action handlers to surface structured errors with exit codes and suggestions.
|
|
94
|
+
* This is the primary error class users should throw from their command actions.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* throw new ActionError('Missing environment', {
|
|
99
|
+
* exitCode: 1,
|
|
100
|
+
* suggestions: ['Use --env production or --env staging'],
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
365
103
|
*/
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
case "zsh": return `# Add to ~/.zshrc:
|
|
374
|
-
${programName} completion zsh >> ~/.zshrc
|
|
375
|
-
|
|
376
|
-
# Or add to completions directory:
|
|
377
|
-
${programName} completion zsh > ~/.zsh/completions/_${programName}`;
|
|
378
|
-
case "fish": return `# Install to Fish completions:
|
|
379
|
-
${programName} completion fish > ~/.config/fish/completions/${programName}.fish`;
|
|
380
|
-
case "powershell": return `# Add to PowerShell profile:
|
|
381
|
-
${programName} completion powershell >> $PROFILE`;
|
|
382
|
-
default: return `# Run: ${programName} completion <shell>
|
|
383
|
-
# Supported shells: bash, zsh, fish, powershell`;
|
|
104
|
+
var ActionError = class extends PadroneError {
|
|
105
|
+
constructor(message, options) {
|
|
106
|
+
super(message, {
|
|
107
|
+
phase: "execute",
|
|
108
|
+
...options
|
|
109
|
+
});
|
|
110
|
+
this.name = "ActionError";
|
|
384
111
|
}
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Generates the completion output with automatic shell detection.
|
|
388
|
-
* If shell is not specified, detects the current shell and provides instructions.
|
|
389
|
-
*/
|
|
390
|
-
function generateCompletionOutput(program, shell) {
|
|
391
|
-
const programName = program.name;
|
|
392
|
-
if (shell) return generateCompletion(program, shell);
|
|
393
|
-
const detectedShell = detectShell();
|
|
394
|
-
if (detectedShell) return `# Detected shell: ${detectedShell}
|
|
395
|
-
#
|
|
396
|
-
${getCompletionInstallInstructions(programName, detectedShell)}
|
|
397
|
-
#
|
|
398
|
-
# Or evaluate directly (temporary, for current session only):
|
|
399
|
-
# eval "$(${programName} completion ${detectedShell})"
|
|
400
|
-
|
|
401
|
-
${generateCompletion(program, detectedShell)}`;
|
|
402
|
-
return `# Shell auto-detection failed.
|
|
403
|
-
#
|
|
404
|
-
# Usage: ${programName} completion <shell>
|
|
405
|
-
#
|
|
406
|
-
# Supported shells:
|
|
407
|
-
# bash - Bash completion script
|
|
408
|
-
# zsh - Zsh completion script
|
|
409
|
-
# fish - Fish completion script
|
|
410
|
-
# powershell - PowerShell completion script
|
|
411
|
-
#
|
|
412
|
-
# Example:
|
|
413
|
-
# ${programName} completion bash >> ~/.bashrc
|
|
414
|
-
# ${programName} completion zsh >> ~/.zshrc
|
|
415
|
-
# ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
|
|
416
|
-
# ${programName} completion powershell >> $PROFILE`;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
//#endregion
|
|
420
|
-
//#region src/colorizer.ts
|
|
421
|
-
const colors = {
|
|
422
|
-
reset: "\x1B[0m",
|
|
423
|
-
bold: "\x1B[1m",
|
|
424
|
-
dim: "\x1B[2m",
|
|
425
|
-
italic: "\x1B[3m",
|
|
426
|
-
underline: "\x1B[4m",
|
|
427
|
-
strikethrough: "\x1B[9m",
|
|
428
|
-
cyan: "\x1B[36m",
|
|
429
|
-
green: "\x1B[32m",
|
|
430
|
-
yellow: "\x1B[33m",
|
|
431
|
-
blue: "\x1B[34m",
|
|
432
|
-
magenta: "\x1B[35m",
|
|
433
|
-
gray: "\x1B[90m"
|
|
434
112
|
};
|
|
435
|
-
function createColorizer() {
|
|
436
|
-
return {
|
|
437
|
-
command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
|
|
438
|
-
option: (text) => `${colors.green}${text}${colors.reset}`,
|
|
439
|
-
type: (text) => `${colors.yellow}${text}${colors.reset}`,
|
|
440
|
-
description: (text) => `${colors.dim}${text}${colors.reset}`,
|
|
441
|
-
label: (text) => `${colors.bold}${text}${colors.reset}`,
|
|
442
|
-
meta: (text) => `${colors.gray}${text}${colors.reset}`,
|
|
443
|
-
example: (text) => `${colors.underline}${text}${colors.reset}`,
|
|
444
|
-
exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
|
|
445
|
-
deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
113
|
//#endregion
|
|
450
|
-
//#region src/
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
exampleValue: (text) => text,
|
|
461
|
-
deprecated: (text) => text
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
function createAnsiStyler() {
|
|
465
|
-
const colorizer = createColorizer();
|
|
466
|
-
return {
|
|
467
|
-
command: colorizer.command,
|
|
468
|
-
option: colorizer.option,
|
|
469
|
-
type: colorizer.type,
|
|
470
|
-
description: colorizer.description,
|
|
471
|
-
label: colorizer.label,
|
|
472
|
-
meta: colorizer.meta,
|
|
473
|
-
example: colorizer.example,
|
|
474
|
-
exampleValue: colorizer.exampleValue,
|
|
475
|
-
deprecated: colorizer.deprecated
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function createConsoleStyler() {
|
|
479
|
-
const colors = {
|
|
480
|
-
reset: "\x1B[0m",
|
|
481
|
-
bold: "\x1B[1m",
|
|
482
|
-
dim: "\x1B[2m",
|
|
483
|
-
italic: "\x1B[3m",
|
|
484
|
-
underline: "\x1B[4m",
|
|
485
|
-
strikethrough: "\x1B[9m",
|
|
486
|
-
cyan: "\x1B[36m",
|
|
487
|
-
green: "\x1B[32m",
|
|
488
|
-
yellow: "\x1B[33m",
|
|
489
|
-
gray: "\x1B[90m"
|
|
490
|
-
};
|
|
491
|
-
return {
|
|
492
|
-
command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
|
|
493
|
-
option: (text) => `${colors.green}${text}${colors.reset}`,
|
|
494
|
-
type: (text) => `${colors.yellow}${text}${colors.reset}`,
|
|
495
|
-
description: (text) => `${colors.dim}${text}${colors.reset}`,
|
|
496
|
-
label: (text) => `${colors.bold}${text}${colors.reset}`,
|
|
497
|
-
meta: (text) => `${colors.gray}${text}${colors.reset}`,
|
|
498
|
-
example: (text) => `${colors.underline}${text}${colors.reset}`,
|
|
499
|
-
exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
|
|
500
|
-
deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
|
|
114
|
+
//#region src/interactive.ts
|
|
115
|
+
/**
|
|
116
|
+
* Auto-detect the prompt type for a field based on its JSON schema property definition.
|
|
117
|
+
*/
|
|
118
|
+
function detectPromptConfig(name, propSchema, description) {
|
|
119
|
+
const message = description || propSchema?.description || name;
|
|
120
|
+
if (!propSchema) return {
|
|
121
|
+
name,
|
|
122
|
+
message,
|
|
123
|
+
type: "input"
|
|
501
124
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
type: (text) => `\`${text}\``,
|
|
508
|
-
description: (text) => text,
|
|
509
|
-
label: (text) => `### ${text}`,
|
|
510
|
-
meta: (text) => `*${text}*`,
|
|
511
|
-
example: (text) => `**${text}**`,
|
|
512
|
-
exampleValue: (text) => `\`${text}\``,
|
|
513
|
-
deprecated: (text) => `~~${text}~~`
|
|
125
|
+
if (propSchema.type === "boolean") return {
|
|
126
|
+
name,
|
|
127
|
+
message,
|
|
128
|
+
type: "confirm",
|
|
129
|
+
default: propSchema.default
|
|
514
130
|
};
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
|
|
525
|
-
label: (text) => `<h3>${escapeHtml(text)}</h3>`,
|
|
526
|
-
meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
|
|
527
|
-
example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
|
|
528
|
-
exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
|
|
529
|
-
deprecated: (text) => `<del style="color: #999;">${escapeHtml(text)}</del>`
|
|
131
|
+
if (propSchema.enum) return {
|
|
132
|
+
name,
|
|
133
|
+
message,
|
|
134
|
+
type: "select",
|
|
135
|
+
choices: propSchema.enum.map((v) => ({
|
|
136
|
+
label: String(v),
|
|
137
|
+
value: v
|
|
138
|
+
})),
|
|
139
|
+
default: propSchema.default
|
|
530
140
|
};
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
141
|
+
if (propSchema.type === "array" && propSchema.items?.enum) return {
|
|
142
|
+
name,
|
|
143
|
+
message,
|
|
144
|
+
type: "multiselect",
|
|
145
|
+
choices: propSchema.items.enum.map((v) => ({
|
|
146
|
+
label: String(v),
|
|
147
|
+
value: v
|
|
148
|
+
})),
|
|
149
|
+
default: propSchema.default
|
|
538
150
|
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (level === 0) return "";
|
|
545
|
-
if (level === 1) return " ";
|
|
546
|
-
return " ";
|
|
547
|
-
},
|
|
548
|
-
join: (parts) => parts.filter(Boolean).join(" "),
|
|
549
|
-
usageLabel: "Usage:"
|
|
151
|
+
if (propSchema.format === "password") return {
|
|
152
|
+
name,
|
|
153
|
+
message,
|
|
154
|
+
type: "password",
|
|
155
|
+
default: propSchema.default
|
|
550
156
|
};
|
|
551
|
-
}
|
|
552
|
-
function createHtmlLayout() {
|
|
553
157
|
return {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
usageLabel: "<strong>Usage:</strong>"
|
|
158
|
+
name,
|
|
159
|
+
message,
|
|
160
|
+
type: "input",
|
|
161
|
+
default: propSchema.default
|
|
559
162
|
};
|
|
560
163
|
}
|
|
561
164
|
/**
|
|
562
|
-
*
|
|
165
|
+
* Prompt for missing interactive fields.
|
|
166
|
+
* Runs after env/config preprocessing and before schema validation.
|
|
167
|
+
*
|
|
168
|
+
* When `force` is true, all configured interactive fields are prompted even if they already
|
|
169
|
+
* have values. The current values are used as defaults in the prompts.
|
|
563
170
|
*/
|
|
564
|
-
function
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const subcommands = info.subcommands;
|
|
577
|
-
lines.push(styler.label("Commands:"));
|
|
578
|
-
const maxNameLength = Math.max(...subcommands.map((c) => {
|
|
579
|
-
const aliases = c.aliases ? ` (${c.aliases.join(", ")})` : "";
|
|
580
|
-
return (c.name + aliases).length;
|
|
581
|
-
}));
|
|
582
|
-
for (const subCmd of subcommands) {
|
|
583
|
-
const aliases = subCmd.aliases ? ` (${subCmd.aliases.join(", ")})` : "";
|
|
584
|
-
const commandDisplay = subCmd.name + aliases;
|
|
585
|
-
const padding = " ".repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
|
|
586
|
-
const isDeprecated = !!subCmd.deprecated;
|
|
587
|
-
const lineParts = [isDeprecated ? styler.deprecated(commandDisplay) : styler.command(subCmd.name) + aliases, padding];
|
|
588
|
-
const displayText = subCmd.title ?? subCmd.description;
|
|
589
|
-
if (displayText) lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
|
|
590
|
-
if (isDeprecated) {
|
|
591
|
-
const deprecatedMeta = typeof subCmd.deprecated === "string" ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(" (deprecated)");
|
|
592
|
-
lineParts.push(deprecatedMeta);
|
|
593
|
-
}
|
|
594
|
-
lines.push(indent(1) + lineParts.join(""));
|
|
595
|
-
}
|
|
596
|
-
lines.push("");
|
|
597
|
-
lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
|
|
598
|
-
return lines;
|
|
599
|
-
}
|
|
600
|
-
function formatArgumentsSection(info) {
|
|
601
|
-
const lines = [];
|
|
602
|
-
const args = info.arguments;
|
|
603
|
-
lines.push(styler.label("Arguments:"));
|
|
604
|
-
for (const arg of args) {
|
|
605
|
-
const parts = [styler.option(arg.name)];
|
|
606
|
-
if (arg.optional) parts.push(styler.meta("(optional)"));
|
|
607
|
-
if (arg.default !== void 0) parts.push(styler.meta(`(default: ${String(arg.default)})`));
|
|
608
|
-
lines.push(indent(1) + join(parts));
|
|
609
|
-
if (arg.description) lines.push(indent(2) + styler.description(arg.description));
|
|
610
|
-
}
|
|
611
|
-
return lines;
|
|
612
|
-
}
|
|
613
|
-
function formatOptionsSection(info) {
|
|
614
|
-
const lines = [];
|
|
615
|
-
const options = info.options;
|
|
616
|
-
lines.push(styler.label("Options:"));
|
|
617
|
-
const maxNameLength = Math.max(...options.map((opt) => opt.name.length));
|
|
618
|
-
for (const opt of options) {
|
|
619
|
-
const optionName = opt.negatable ? `--[no-]${opt.name}` : `--${opt.name}`;
|
|
620
|
-
const aliasNames = opt.aliases && opt.aliases.length > 0 ? opt.aliases.map((a) => `-${a}`).join(", ") : "";
|
|
621
|
-
const fullOptionName = aliasNames ? `${optionName}, ${aliasNames}` : optionName;
|
|
622
|
-
const padding = " ".repeat(Math.max(0, maxNameLength - opt.name.length + 2));
|
|
623
|
-
const isDeprecated = !!opt.deprecated;
|
|
624
|
-
const parts = [isDeprecated ? styler.deprecated(fullOptionName) : styler.option(fullOptionName)];
|
|
625
|
-
if (opt.type) parts.push(styler.type(`<${opt.type}>`));
|
|
626
|
-
if (opt.optional && !opt.deprecated) parts.push(styler.meta("(optional)"));
|
|
627
|
-
if (opt.default !== void 0) parts.push(styler.meta(`(default: ${String(opt.default)})`));
|
|
628
|
-
if (opt.enum) parts.push(styler.meta(`(choices: ${opt.enum.join(", ")})`));
|
|
629
|
-
if (opt.variadic) parts.push(styler.meta("(repeatable)"));
|
|
630
|
-
if (isDeprecated) {
|
|
631
|
-
const deprecatedMeta = typeof opt.deprecated === "string" ? styler.meta(`(deprecated: ${opt.deprecated})`) : styler.meta("(deprecated)");
|
|
632
|
-
parts.push(deprecatedMeta);
|
|
633
|
-
}
|
|
634
|
-
const description = opt.description ? styler.description(opt.description) : "";
|
|
635
|
-
lines.push(indent(1) + join(parts) + padding + description);
|
|
636
|
-
if (opt.env) {
|
|
637
|
-
const envVars = typeof opt.env === "string" ? [opt.env] : opt.env;
|
|
638
|
-
const envParts = [styler.example("Env:"), styler.exampleValue(envVars.join(", "))];
|
|
639
|
-
lines.push(indent(3) + join(envParts));
|
|
640
|
-
}
|
|
641
|
-
if (opt.configKey) {
|
|
642
|
-
const configParts = [styler.example("Config:"), styler.exampleValue(opt.configKey)];
|
|
643
|
-
lines.push(indent(3) + join(configParts));
|
|
644
|
-
}
|
|
645
|
-
if (opt.examples && opt.examples.length > 0) {
|
|
646
|
-
const exampleValues = opt.examples.map((example) => typeof example === "string" ? example : JSON.stringify(example)).join(", ");
|
|
647
|
-
const exampleParts = [styler.example("Example:"), styler.exampleValue(exampleValues)];
|
|
648
|
-
lines.push(indent(3) + join(exampleParts));
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
return lines;
|
|
652
|
-
}
|
|
653
|
-
return { format(info) {
|
|
654
|
-
const lines = [];
|
|
655
|
-
if (info.deprecated) {
|
|
656
|
-
const deprecationMessage = typeof info.deprecated === "string" ? `⚠️ This command is deprecated: ${info.deprecated}` : "⚠️ This command is deprecated";
|
|
657
|
-
lines.push(styler.deprecated(deprecationMessage));
|
|
658
|
-
lines.push("");
|
|
659
|
-
}
|
|
660
|
-
lines.push(...formatUsageSection(info));
|
|
661
|
-
lines.push("");
|
|
662
|
-
if (info.title) {
|
|
663
|
-
lines.push(styler.label(info.title));
|
|
664
|
-
lines.push("");
|
|
665
|
-
}
|
|
666
|
-
if (info.aliases && info.aliases.length > 0) {
|
|
667
|
-
lines.push(styler.meta(`Aliases: ${info.aliases.join(", ")}`));
|
|
668
|
-
lines.push("");
|
|
669
|
-
}
|
|
670
|
-
if (info.description) {
|
|
671
|
-
lines.push(styler.description(info.description));
|
|
672
|
-
lines.push("");
|
|
673
|
-
}
|
|
674
|
-
if (info.subcommands && info.subcommands.length > 0) {
|
|
675
|
-
lines.push(...formatSubcommandsSection(info));
|
|
676
|
-
lines.push("");
|
|
677
|
-
}
|
|
678
|
-
if (info.arguments && info.arguments.length > 0) {
|
|
679
|
-
lines.push(...formatArgumentsSection(info));
|
|
680
|
-
lines.push("");
|
|
681
|
-
}
|
|
682
|
-
if (info.options && info.options.length > 0) {
|
|
683
|
-
lines.push(...formatOptionsSection(info));
|
|
684
|
-
lines.push("");
|
|
685
|
-
}
|
|
686
|
-
if (info.nestedCommands?.length) {
|
|
687
|
-
lines.push(styler.label("Subcommand Details:"));
|
|
688
|
-
lines.push("");
|
|
689
|
-
for (const nestedCmd of info.nestedCommands) {
|
|
690
|
-
lines.push(styler.meta("─".repeat(60)));
|
|
691
|
-
lines.push(this.format(nestedCmd));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
const result = lines.join(newline);
|
|
695
|
-
return wrapDocument ? wrapDocument(result) : result;
|
|
696
|
-
} };
|
|
697
|
-
}
|
|
698
|
-
function createJsonFormatter() {
|
|
699
|
-
return { format(info) {
|
|
700
|
-
return JSON.stringify(info, null, 2);
|
|
701
|
-
} };
|
|
702
|
-
}
|
|
703
|
-
function shouldUseAnsi() {
|
|
704
|
-
if (typeof process === "undefined") return false;
|
|
705
|
-
if (process.env.NO_COLOR) return false;
|
|
706
|
-
if (process.env.CI) return false;
|
|
707
|
-
if (process.stdout && typeof process.stdout.isTTY === "boolean") return process.stdout.isTTY;
|
|
708
|
-
return false;
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Creates a minimal formatter that outputs just a single-line usage string.
|
|
712
|
-
*/
|
|
713
|
-
function createMinimalFormatter() {
|
|
714
|
-
return { format(info) {
|
|
715
|
-
const parts = [info.usage.command];
|
|
716
|
-
if (info.usage.hasSubcommands) parts.push("[command]");
|
|
717
|
-
if (info.usage.hasArguments) parts.push("[args...]");
|
|
718
|
-
if (info.usage.hasOptions) parts.push("[options]");
|
|
719
|
-
return parts.join(" ");
|
|
720
|
-
} };
|
|
721
|
-
}
|
|
722
|
-
function createFormatter(format, detail = "standard") {
|
|
723
|
-
if (detail === "minimal") return createMinimalFormatter();
|
|
724
|
-
if (format === "json") return createJsonFormatter();
|
|
725
|
-
if (format === "ansi" || format === "auto" && shouldUseAnsi()) return createGenericFormatter(createAnsiStyler(), createTextLayout());
|
|
726
|
-
if (format === "console") return createGenericFormatter(createConsoleStyler(), createTextLayout());
|
|
727
|
-
if (format === "markdown") return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout());
|
|
728
|
-
if (format === "html") return createGenericFormatter(createHtmlStyler(), createHtmlLayout());
|
|
729
|
-
return createGenericFormatter(createTextStyler(), createTextLayout());
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
//#endregion
|
|
733
|
-
//#region src/utils.ts
|
|
734
|
-
function getRootCommand(cmd) {
|
|
735
|
-
let current = cmd;
|
|
736
|
-
while (current.parent) current = current.parent;
|
|
737
|
-
return current;
|
|
738
|
-
}
|
|
739
|
-
/**
|
|
740
|
-
* Attempts to get the version from various sources:
|
|
741
|
-
* 1. Explicit version set on the command
|
|
742
|
-
* 2. npm_package_version environment variable (set by npm/yarn/pnpm when running scripts)
|
|
743
|
-
* 3. package.json in current or parent directories
|
|
744
|
-
* @param explicitVersion - Version explicitly set via .version()
|
|
745
|
-
* @returns The version string or '0.0.0' if not found
|
|
746
|
-
*/
|
|
747
|
-
function getVersion(explicitVersion) {
|
|
748
|
-
if (explicitVersion) return explicitVersion;
|
|
749
|
-
if (typeof process !== "undefined" && process.env?.npm_package_version) return process.env.npm_package_version;
|
|
750
|
-
if (typeof process !== "undefined") try {
|
|
751
|
-
const fs = __require("node:fs");
|
|
752
|
-
const path = __require("node:path");
|
|
753
|
-
let dir = process.cwd();
|
|
754
|
-
for (let i = 0; i < 10; i++) {
|
|
755
|
-
const pkgPath = path.join(dir, "package.json");
|
|
756
|
-
if (fs.existsSync(pkgPath)) {
|
|
757
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
758
|
-
if (pkg.version) return pkg.version;
|
|
759
|
-
}
|
|
760
|
-
const parentDir = path.dirname(dir);
|
|
761
|
-
if (parentDir === dir) break;
|
|
762
|
-
dir = parentDir;
|
|
763
|
-
}
|
|
171
|
+
async function promptInteractiveFields(data, command, runtime, force) {
|
|
172
|
+
if (!runtime.prompt) return data;
|
|
173
|
+
const meta = command.meta;
|
|
174
|
+
const interactiveConfig = meta?.interactive;
|
|
175
|
+
const optionalInteractiveConfig = meta?.optionalInteractive;
|
|
176
|
+
if (!interactiveConfig && !optionalInteractiveConfig) return data;
|
|
177
|
+
let jsonProperties = {};
|
|
178
|
+
let requiredFields = /* @__PURE__ */ new Set();
|
|
179
|
+
if (command.argsSchema) try {
|
|
180
|
+
const jsonSchema = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
181
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) jsonProperties = jsonSchema.properties;
|
|
182
|
+
if (Array.isArray(jsonSchema.required)) requiredFields = new Set(jsonSchema.required);
|
|
764
183
|
} catch {}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
* Loads and parses a config file from the given path.
|
|
769
|
-
* Supports JSON, JSONC (JSON with comments), and attempts to parse other formats.
|
|
770
|
-
* @param configPath - Path to the config file
|
|
771
|
-
* @returns Parsed config data or undefined if loading fails
|
|
772
|
-
*/
|
|
773
|
-
function loadConfigFile(configPath) {
|
|
774
|
-
if (typeof process === "undefined") return void 0;
|
|
775
|
-
try {
|
|
776
|
-
const fs = __require("node:fs");
|
|
777
|
-
const path = __require("node:path");
|
|
778
|
-
const absolutePath = path.isAbsolute(configPath) ? configPath : path.resolve(process.cwd(), configPath);
|
|
779
|
-
if (!fs.existsSync(absolutePath)) {
|
|
780
|
-
console.error(`Config file not found: ${absolutePath}`);
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
const getContent = () => fs.readFileSync(absolutePath, "utf-8");
|
|
784
|
-
const ext = path.extname(absolutePath).toLowerCase();
|
|
785
|
-
if (ext === ".yaml" || ext === ".yml") return Bun.YAML.parse(getContent());
|
|
786
|
-
if (ext === ".toml") return Bun.TOML.parse(getContent());
|
|
787
|
-
if (ext === ".json") {
|
|
788
|
-
if (Bun.JSONC) return Bun.JSONC.parse(getContent());
|
|
789
|
-
try {
|
|
790
|
-
return JSON.parse(getContent());
|
|
791
|
-
} catch {
|
|
792
|
-
return Bun.JSONC.parse(getContent());
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if (ext === ".jsonc") return Bun.JSONC.parse(getContent());
|
|
796
|
-
if (ext === ".js" || ext === ".cjs" || ext === ".mjs" || ext === ".ts" || ext === ".cts" || ext === ".mts") return __require(absolutePath);
|
|
797
|
-
try {
|
|
798
|
-
return JSON.parse(getContent());
|
|
799
|
-
} catch {
|
|
800
|
-
console.error(`Unable to parse config file: ${absolutePath}`);
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
} catch (error) {
|
|
804
|
-
console.error(`Error loading config file: ${error}`);
|
|
805
|
-
return;
|
|
184
|
+
const fieldDescriptions = {};
|
|
185
|
+
if (meta?.fields) {
|
|
186
|
+
for (const [key, value] of Object.entries(meta.fields)) if (value?.description) fieldDescriptions[key] = value.description;
|
|
806
187
|
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const fs = __require("node:fs");
|
|
818
|
-
const path = __require("node:path");
|
|
819
|
-
const cwd = process.cwd();
|
|
820
|
-
for (const configFile of configFiles) {
|
|
821
|
-
const configPath = path.isAbsolute(configFile) ? configFile : path.resolve(cwd, configFile);
|
|
822
|
-
if (fs.existsSync(configPath)) return configPath;
|
|
823
|
-
}
|
|
824
|
-
} catch {}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/help.ts
|
|
829
|
-
/**
|
|
830
|
-
* Extract positional arguments info from schema based on meta.positional config.
|
|
831
|
-
*/
|
|
832
|
-
function extractPositionalArgsInfo(schema, meta) {
|
|
833
|
-
const args = [];
|
|
834
|
-
const positionalNames = /* @__PURE__ */ new Set();
|
|
835
|
-
if (!schema || !meta?.positional || meta.positional.length === 0) return {
|
|
836
|
-
args,
|
|
837
|
-
positionalNames
|
|
838
|
-
};
|
|
839
|
-
const positionalConfig = parsePositionalConfig(meta.positional);
|
|
840
|
-
try {
|
|
841
|
-
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
842
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
843
|
-
const properties = jsonSchema.properties;
|
|
844
|
-
const required = jsonSchema.required || [];
|
|
845
|
-
for (const { name, variadic } of positionalConfig) {
|
|
846
|
-
const prop = properties[name];
|
|
847
|
-
if (!prop) continue;
|
|
848
|
-
positionalNames.add(name);
|
|
849
|
-
const optMeta = meta.options?.[name];
|
|
850
|
-
args.push({
|
|
851
|
-
name: variadic ? `...${name}` : name,
|
|
852
|
-
description: optMeta?.description ?? prop.description,
|
|
853
|
-
optional: !required.includes(name),
|
|
854
|
-
default: prop.default,
|
|
855
|
-
type: variadic ? `array<${prop.items?.type || "string"}>` : prop.type
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
} catch {}
|
|
860
|
-
return {
|
|
861
|
-
args,
|
|
862
|
-
positionalNames
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
function extractOptionsInfo(schema, meta, positionalNames) {
|
|
866
|
-
const result = [];
|
|
867
|
-
if (!schema) return result;
|
|
868
|
-
if (!schema["~standard"].vendor.includes("zod")) return result;
|
|
869
|
-
const optionsMeta = meta?.options;
|
|
870
|
-
try {
|
|
871
|
-
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
872
|
-
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
873
|
-
const properties = jsonSchema.properties;
|
|
874
|
-
const required = jsonSchema.required || [];
|
|
875
|
-
const propertyNames = new Set(Object.keys(properties));
|
|
876
|
-
const hasExplicitNegation = (key) => {
|
|
877
|
-
const camelNegated = `no${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
878
|
-
if (propertyNames.has(camelNegated)) return true;
|
|
879
|
-
const kebabNegated = `no-${key}`;
|
|
880
|
-
if (propertyNames.has(kebabNegated)) return true;
|
|
881
|
-
return false;
|
|
882
|
-
};
|
|
883
|
-
const isNegationOf = (key) => {
|
|
884
|
-
if (key.startsWith("no") && key.length > 2 && key[2] === key[2]?.toUpperCase()) {
|
|
885
|
-
const positiveKey = key.charAt(2).toLowerCase() + key.slice(3);
|
|
886
|
-
if (propertyNames.has(positiveKey)) return true;
|
|
887
|
-
}
|
|
888
|
-
if (key.startsWith("no-")) {
|
|
889
|
-
const positiveKey = key.slice(3);
|
|
890
|
-
if (propertyNames.has(positiveKey)) return true;
|
|
891
|
-
}
|
|
892
|
-
return false;
|
|
893
|
-
};
|
|
894
|
-
for (const [key, prop] of Object.entries(properties)) {
|
|
895
|
-
if (positionalNames?.has(key)) continue;
|
|
896
|
-
const isOptional = !required.includes(key);
|
|
897
|
-
const enumValues = prop.enum;
|
|
898
|
-
const optMeta = optionsMeta?.[key];
|
|
899
|
-
const propType = prop.type;
|
|
900
|
-
const isNegatable = propType === "boolean" && !hasExplicitNegation(key) && !isNegationOf(key);
|
|
901
|
-
result.push({
|
|
902
|
-
name: key,
|
|
903
|
-
description: optMeta?.description ?? prop.description,
|
|
904
|
-
optional: isOptional,
|
|
905
|
-
default: prop.default,
|
|
906
|
-
type: propType,
|
|
907
|
-
enum: enumValues,
|
|
908
|
-
deprecated: optMeta?.deprecated ?? prop?.deprecated,
|
|
909
|
-
hidden: optMeta?.hidden ?? prop?.hidden,
|
|
910
|
-
examples: optMeta?.examples ?? prop?.examples,
|
|
911
|
-
variadic: propType === "array",
|
|
912
|
-
negatable: isNegatable
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
} catch {}
|
|
917
|
-
return result;
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Builds a comprehensive HelpInfo structure from a command.
|
|
921
|
-
* This is the single source of truth that all formatters use.
|
|
922
|
-
* @param cmd - The command to build help info for
|
|
923
|
-
* @param detail - The level of detail ('minimal', 'standard', or 'full')
|
|
924
|
-
*/
|
|
925
|
-
function getHelpInfo(cmd, detail = "standard") {
|
|
926
|
-
const rootCmd = getRootCommand(cmd);
|
|
927
|
-
const commandName = cmd.path || cmd.name || "program";
|
|
928
|
-
const { args: positionalArgs, positionalNames } = cmd.options ? extractPositionalArgsInfo(cmd.options, cmd.meta) : {
|
|
929
|
-
args: [],
|
|
930
|
-
positionalNames: /* @__PURE__ */ new Set()
|
|
931
|
-
};
|
|
932
|
-
const hasArguments = positionalArgs.length > 0;
|
|
933
|
-
const helpInfo = {
|
|
934
|
-
name: commandName,
|
|
935
|
-
title: cmd.title,
|
|
936
|
-
description: cmd.description,
|
|
937
|
-
aliases: cmd.aliases,
|
|
938
|
-
deprecated: cmd.deprecated,
|
|
939
|
-
hidden: cmd.hidden,
|
|
940
|
-
usage: {
|
|
941
|
-
command: rootCmd === cmd ? commandName : `${rootCmd.name} ${commandName}`,
|
|
942
|
-
hasSubcommands: !!(cmd.commands && cmd.commands.length > 0),
|
|
943
|
-
hasArguments,
|
|
944
|
-
hasOptions: !!cmd.options
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
if (cmd.commands && cmd.commands.length > 0) {
|
|
948
|
-
const visibleCommands = detail === "full" ? cmd.commands : cmd.commands.filter((c) => !c.hidden);
|
|
949
|
-
helpInfo.subcommands = visibleCommands.map((c) => {
|
|
950
|
-
return {
|
|
951
|
-
name: c.name,
|
|
952
|
-
title: c.title,
|
|
953
|
-
description: c.description,
|
|
954
|
-
aliases: c.aliases,
|
|
955
|
-
deprecated: c.deprecated,
|
|
956
|
-
hidden: c.hidden
|
|
957
|
-
};
|
|
958
|
-
});
|
|
959
|
-
if (detail === "full") helpInfo.nestedCommands = visibleCommands.map((c) => getHelpInfo(c, "full"));
|
|
188
|
+
const result = { ...data };
|
|
189
|
+
let fieldsToPrompt = [];
|
|
190
|
+
if (interactiveConfig === true) if (force) fieldsToPrompt = [...requiredFields];
|
|
191
|
+
else fieldsToPrompt = [...requiredFields].filter((name) => result[name] === void 0);
|
|
192
|
+
else if (Array.isArray(interactiveConfig)) if (force) fieldsToPrompt = [...interactiveConfig];
|
|
193
|
+
else fieldsToPrompt = interactiveConfig.filter((name) => result[name] === void 0);
|
|
194
|
+
for (const field of fieldsToPrompt) {
|
|
195
|
+
const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
|
|
196
|
+
if (force && result[field] !== void 0) config.default = result[field];
|
|
197
|
+
result[field] = await runtime.prompt(config);
|
|
960
198
|
}
|
|
961
|
-
|
|
962
|
-
if (
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
199
|
+
let optionalFields = [];
|
|
200
|
+
if (optionalInteractiveConfig === true) if (force) optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name));
|
|
201
|
+
else optionalFields = Object.keys(jsonProperties).filter((name) => !requiredFields.has(name) && result[name] === void 0);
|
|
202
|
+
else if (Array.isArray(optionalInteractiveConfig)) if (force) optionalFields = [...optionalInteractiveConfig];
|
|
203
|
+
else optionalFields = optionalInteractiveConfig.filter((name) => result[name] === void 0);
|
|
204
|
+
if (optionalFields.length > 0) {
|
|
205
|
+
const selected = await runtime.prompt({
|
|
206
|
+
name: "_optionalFields",
|
|
207
|
+
message: "Would you also like to configure:",
|
|
208
|
+
type: "multiselect",
|
|
209
|
+
choices: optionalFields.map((f) => {
|
|
210
|
+
const label = fieldDescriptions[f] || jsonProperties[f]?.description || f;
|
|
211
|
+
const currentValue = result[f];
|
|
212
|
+
return {
|
|
213
|
+
label: force && currentValue !== void 0 ? `${label} (current: ${currentValue})` : label,
|
|
214
|
+
value: f
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
});
|
|
218
|
+
if (Array.isArray(selected)) for (const field of selected) {
|
|
219
|
+
const config = detectPromptConfig(field, jsonProperties[field], fieldDescriptions[field]);
|
|
220
|
+
if (force && result[field] !== void 0) config.default = result[field];
|
|
221
|
+
result[field] = await runtime.prompt(config);
|
|
970
222
|
}
|
|
971
|
-
const visibleOptions = optionsInfo.filter((opt) => !opt.hidden);
|
|
972
|
-
if (visibleOptions.length > 0) helpInfo.options = visibleOptions;
|
|
973
223
|
}
|
|
974
|
-
return
|
|
975
|
-
}
|
|
976
|
-
function generateHelp(rootCommand, commandObj = rootCommand, options) {
|
|
977
|
-
const helpInfo = getHelpInfo(commandObj, options?.detail);
|
|
978
|
-
return createFormatter(options?.format ?? "auto", options?.detail).format(helpInfo);
|
|
224
|
+
return result;
|
|
979
225
|
}
|
|
980
|
-
|
|
981
226
|
//#endregion
|
|
982
227
|
//#region src/parse.ts
|
|
983
228
|
/**
|
|
@@ -1027,36 +272,76 @@ function parseCliInputToParts(input) {
|
|
|
1027
272
|
const result = [];
|
|
1028
273
|
let pendingValue;
|
|
1029
274
|
let allowTerm = true;
|
|
275
|
+
let afterDoubleDash = false;
|
|
1030
276
|
for (const part of parts) {
|
|
1031
277
|
if (!part) continue;
|
|
278
|
+
if (part === "--" && !afterDoubleDash) {
|
|
279
|
+
if (pendingValue) pendingValue = void 0;
|
|
280
|
+
afterDoubleDash = true;
|
|
281
|
+
allowTerm = false;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (afterDoubleDash) {
|
|
285
|
+
result.push({
|
|
286
|
+
type: "arg",
|
|
287
|
+
value: part
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
1032
291
|
const wasPending = pendingValue;
|
|
1033
292
|
pendingValue = void 0;
|
|
1034
293
|
if (part.startsWith("--no-") && part.length > 5) {
|
|
1035
294
|
const p = {
|
|
1036
|
-
type: "
|
|
295
|
+
type: "named",
|
|
1037
296
|
key: part.slice(5).split("."),
|
|
1038
297
|
value: void 0,
|
|
1039
298
|
negated: true
|
|
1040
299
|
};
|
|
1041
300
|
result.push(p);
|
|
1042
301
|
} else if (part.startsWith("--")) {
|
|
1043
|
-
const [keyStr = "", value] =
|
|
302
|
+
const [keyStr = "", value] = splitNamedArgValue(part.slice(2));
|
|
1044
303
|
const p = {
|
|
1045
|
-
type: "
|
|
304
|
+
type: "named",
|
|
1046
305
|
key: keyStr.split("."),
|
|
1047
306
|
value
|
|
1048
307
|
};
|
|
1049
308
|
if (typeof value === "undefined") pendingValue = p;
|
|
1050
309
|
result.push(p);
|
|
1051
310
|
} else if (part.startsWith("-") && part.length > 1 && !/^-\d/.test(part)) {
|
|
1052
|
-
const [keyStr = "", value] =
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
311
|
+
const [keyStr = "", value] = splitNamedArgValue(part.slice(1));
|
|
312
|
+
if (keyStr.length > 1 && typeof value === "undefined") {
|
|
313
|
+
for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
|
|
314
|
+
type: "alias",
|
|
315
|
+
key: [keyStr[ci]],
|
|
316
|
+
value: void 0
|
|
317
|
+
});
|
|
318
|
+
const lastFlag = {
|
|
319
|
+
type: "alias",
|
|
320
|
+
key: [keyStr[keyStr.length - 1]],
|
|
321
|
+
value: void 0
|
|
322
|
+
};
|
|
323
|
+
pendingValue = lastFlag;
|
|
324
|
+
result.push(lastFlag);
|
|
325
|
+
} else if (keyStr.length > 1 && typeof value !== "undefined") {
|
|
326
|
+
for (let ci = 0; ci < keyStr.length - 1; ci++) result.push({
|
|
327
|
+
type: "alias",
|
|
328
|
+
key: [keyStr[ci]],
|
|
329
|
+
value: void 0
|
|
330
|
+
});
|
|
331
|
+
result.push({
|
|
332
|
+
type: "alias",
|
|
333
|
+
key: [keyStr[keyStr.length - 1]],
|
|
334
|
+
value
|
|
335
|
+
});
|
|
336
|
+
} else {
|
|
337
|
+
const p = {
|
|
338
|
+
type: "alias",
|
|
339
|
+
key: [keyStr],
|
|
340
|
+
value
|
|
341
|
+
};
|
|
342
|
+
if (typeof value === "undefined") pendingValue = p;
|
|
343
|
+
result.push(p);
|
|
344
|
+
}
|
|
1060
345
|
} else if (wasPending) wasPending.value = part;
|
|
1061
346
|
else if (/^[a-zA-Z0-9_-]+$/.test(part) && allowTerm) result.push({
|
|
1062
347
|
type: "term",
|
|
@@ -1073,9 +358,9 @@ function parseCliInputToParts(input) {
|
|
|
1073
358
|
return result;
|
|
1074
359
|
}
|
|
1075
360
|
/**
|
|
1076
|
-
* Split
|
|
361
|
+
* Split named arg key and value, handling quoted values after =.
|
|
1077
362
|
*/
|
|
1078
|
-
function
|
|
363
|
+
function splitNamedArgValue(str) {
|
|
1079
364
|
const eqIndex = str.indexOf("=");
|
|
1080
365
|
if (eqIndex === -1) return [str, void 0];
|
|
1081
366
|
const key = str.slice(0, eqIndex);
|
|
@@ -1146,20 +431,215 @@ function parseArrayItems(input) {
|
|
|
1146
431
|
if (current || items.length > 0) items.push(current.trim());
|
|
1147
432
|
return items;
|
|
1148
433
|
}
|
|
1149
|
-
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/repl-loop.ts
|
|
436
|
+
/**
|
|
437
|
+
* Creates a REPL async iterable for running commands interactively.
|
|
438
|
+
*/
|
|
439
|
+
function createReplIterator(deps, options) {
|
|
440
|
+
const { existingCommand, evalCommand, replActiveRef } = deps;
|
|
441
|
+
if (replActiveRef.value) {
|
|
442
|
+
getCommandRuntime(existingCommand).error("REPL is already running. Nested REPL sessions are not supported.");
|
|
443
|
+
return (async function* () {})();
|
|
444
|
+
}
|
|
445
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
446
|
+
const programName = existingCommand.name || "padrone";
|
|
447
|
+
const useAnsi = runtime.format === "ansi" || runtime.format === "auto" && typeof process !== "undefined" && !process.env.NO_COLOR && !process.env.CI && process.stdout?.isTTY;
|
|
448
|
+
const commandHistory = [];
|
|
449
|
+
const resolveScope = (scope) => {
|
|
450
|
+
const parts = scope.split(/\s+/);
|
|
451
|
+
const stack = [];
|
|
452
|
+
let current = existingCommand;
|
|
453
|
+
for (const part of parts) {
|
|
454
|
+
const found = findCommandByName(part, current.commands);
|
|
455
|
+
if (!found) break;
|
|
456
|
+
stack.push(found);
|
|
457
|
+
current = found;
|
|
458
|
+
}
|
|
459
|
+
return stack;
|
|
460
|
+
};
|
|
461
|
+
async function* replIterator() {
|
|
462
|
+
replActiveRef.value = true;
|
|
463
|
+
const showGreeting = options?.greeting !== false;
|
|
464
|
+
const showHint = options?.hint !== false;
|
|
465
|
+
if (showGreeting || showHint) runtime.output("");
|
|
466
|
+
if (showGreeting) if (options?.greeting) runtime.output(options.greeting);
|
|
467
|
+
else {
|
|
468
|
+
const displayName = existingCommand.title || programName;
|
|
469
|
+
const version = existingCommand.version ? getVersion(existingCommand.version) : void 0;
|
|
470
|
+
const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
|
|
471
|
+
runtime.output(greeting);
|
|
472
|
+
}
|
|
473
|
+
if (showHint) {
|
|
474
|
+
const hintText = (typeof options?.hint === "string" ? options.hint : void 0) ?? "Type \".help\" for more information, \".exit\" to quit.";
|
|
475
|
+
runtime.output(useAnsi ? `\x1b[2m${hintText}\x1b[0m` : hintText);
|
|
476
|
+
}
|
|
477
|
+
if (showGreeting || showHint) runtime.output("");
|
|
478
|
+
const scopeStack = options?.scope ? resolveScope(options.scope) : [];
|
|
479
|
+
const getScopeCommand = () => scopeStack.length ? scopeStack[scopeStack.length - 1] : existingCommand;
|
|
480
|
+
const getScopePath = () => scopeStack.map((c) => c.name).join(" ");
|
|
481
|
+
const buildPrompt = () => {
|
|
482
|
+
if (options?.prompt) return typeof options.prompt === "function" ? options.prompt() : options.prompt;
|
|
483
|
+
const scopePath = getScopePath();
|
|
484
|
+
const label = scopePath ? `${programName}/${scopePath.replace(/ /g, "/")}` : programName;
|
|
485
|
+
return useAnsi ? `\x1b[1m${label}\x1b[0m ❯ ` : `${label} ❯ `;
|
|
486
|
+
};
|
|
487
|
+
const buildScopedCompleter = () => {
|
|
488
|
+
return buildReplCompleter(getScopeCommand(), { inScope: scopeStack.length > 0 });
|
|
489
|
+
};
|
|
490
|
+
const sessionConfig = { history: options?.history };
|
|
491
|
+
if (options?.completion !== false) sessionConfig.completer = buildScopedCompleter();
|
|
492
|
+
const session = runtime.readLine ? void 0 : createTerminalReplSession(sessionConfig);
|
|
493
|
+
const questionFn = session ? (prompt) => session.question(prompt) : runtime.readLine;
|
|
494
|
+
const updateCompleter = () => {
|
|
495
|
+
if (options?.completion === false) return;
|
|
496
|
+
const completer = buildScopedCompleter();
|
|
497
|
+
if (session) session.completer = completer;
|
|
498
|
+
sessionConfig.completer = completer;
|
|
499
|
+
};
|
|
500
|
+
let lastSigintTime = 0;
|
|
501
|
+
try {
|
|
502
|
+
while (true) {
|
|
503
|
+
const input = await questionFn(buildPrompt());
|
|
504
|
+
if (input === null) break;
|
|
505
|
+
if (input === REPL_SIGINT) {
|
|
506
|
+
const now = Date.now();
|
|
507
|
+
if (now - lastSigintTime < 2e3) break;
|
|
508
|
+
lastSigintTime = now;
|
|
509
|
+
runtime.output("(press Ctrl+C again to exit, or Ctrl+D)");
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const trimmed = input.trim();
|
|
513
|
+
if (!trimmed) continue;
|
|
514
|
+
lastSigintTime = 0;
|
|
515
|
+
commandHistory.push(trimmed);
|
|
516
|
+
if (trimmed === ".exit" || trimmed === ".quit") break;
|
|
517
|
+
if (trimmed === ".clear") {
|
|
518
|
+
runtime.output("\x1B[2J\x1B[H");
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (trimmed === ".help") {
|
|
522
|
+
const lines = [
|
|
523
|
+
"REPL Commands:",
|
|
524
|
+
" . Execute the current scoped command",
|
|
525
|
+
" .help Print this help message",
|
|
526
|
+
" .exit Exit the REPL",
|
|
527
|
+
" .clear Clear the screen",
|
|
528
|
+
" .history Show command history",
|
|
529
|
+
" .scope <cmd> Scope into a subcommand",
|
|
530
|
+
" .scope .. Go up one scope level"
|
|
531
|
+
];
|
|
532
|
+
lines.push("", "Keybindings:", " Ctrl+C Cancel current line (press twice to exit)", " Ctrl+D Exit the REPL", " Up/Down Navigate history", " Tab Auto-complete", "", "Type \"help\" to see available commands.");
|
|
533
|
+
runtime.output(lines.join("\n"));
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (trimmed === ".history") {
|
|
537
|
+
const entries = commandHistory.slice(0, -1);
|
|
538
|
+
if (entries.length === 0) runtime.output("No history.");
|
|
539
|
+
else runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join("\n"));
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (trimmed.startsWith(".scope ") || trimmed === ".scope") {
|
|
543
|
+
const target = trimmed.slice(6).trim();
|
|
544
|
+
if (target === ".." || target === "") {
|
|
545
|
+
if (scopeStack.length > 0) {
|
|
546
|
+
scopeStack.pop();
|
|
547
|
+
updateCompleter();
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
const found = findCommandByName(target, getScopeCommand().commands);
|
|
551
|
+
if (found) if (found.commands?.length) {
|
|
552
|
+
scopeStack.push(found);
|
|
553
|
+
updateCompleter();
|
|
554
|
+
} else runtime.error(`"${target}" has no subcommands to scope into.`);
|
|
555
|
+
else runtime.error(`Unknown command: ${target}`);
|
|
556
|
+
}
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (trimmed === "..") {
|
|
560
|
+
if (scopeStack.length > 0) {
|
|
561
|
+
scopeStack.pop();
|
|
562
|
+
updateCompleter();
|
|
563
|
+
}
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
let evalInput = trimmed;
|
|
567
|
+
if (trimmed === ".") evalInput = "";
|
|
568
|
+
const prefix = options?.outputPrefix;
|
|
569
|
+
const prefixLines = prefix ? (text) => text.split("\n").map((l) => prefix + l).join("\n") : void 0;
|
|
570
|
+
const savedRuntimes = [];
|
|
571
|
+
if (prefixLines) {
|
|
572
|
+
const prefixedRuntime = {
|
|
573
|
+
...existingCommand.runtime,
|
|
574
|
+
output: (...args) => {
|
|
575
|
+
const first = args[0];
|
|
576
|
+
runtime.output(typeof first === "string" ? prefixLines(first) : first, ...args.slice(1));
|
|
577
|
+
},
|
|
578
|
+
error: (text) => runtime.error(prefixLines(text))
|
|
579
|
+
};
|
|
580
|
+
const patchAll = (cmd) => {
|
|
581
|
+
savedRuntimes.push({
|
|
582
|
+
cmd,
|
|
583
|
+
runtime: cmd.runtime
|
|
584
|
+
});
|
|
585
|
+
cmd.runtime = prefixedRuntime;
|
|
586
|
+
cmd.commands?.forEach(patchAll);
|
|
587
|
+
};
|
|
588
|
+
patchAll(existingCommand);
|
|
589
|
+
}
|
|
590
|
+
const sp = options?.spacing;
|
|
591
|
+
const isSpacingObject = typeof sp === "object" && sp !== null && !Array.isArray(sp);
|
|
592
|
+
const spacingBefore = isSpacingObject ? sp.before : sp;
|
|
593
|
+
const spacingAfter = isSpacingObject ? sp.after : sp;
|
|
594
|
+
const emitSpacingLine = (value) => {
|
|
595
|
+
if (typeof value === "string") {
|
|
596
|
+
const sep = value.length === 1 ? value.repeat(typeof process !== "undefined" && process.stdout?.columns ? process.stdout.columns : 80) : value;
|
|
597
|
+
runtime.output(sep);
|
|
598
|
+
} else if (value) runtime.output("");
|
|
599
|
+
};
|
|
600
|
+
const emitSpacing = (value) => {
|
|
601
|
+
if (!value) return;
|
|
602
|
+
if (Array.isArray(value)) for (const line of value) emitSpacingLine(line);
|
|
603
|
+
else emitSpacingLine(value);
|
|
604
|
+
};
|
|
605
|
+
emitSpacing(spacingBefore);
|
|
606
|
+
const scopePath = getScopePath();
|
|
607
|
+
const scopedInput = scopePath ? evalInput ? `${scopePath} ${evalInput}` : scopePath : evalInput;
|
|
608
|
+
try {
|
|
609
|
+
const result = await evalCommand(scopedInput, options?.autoOutput === false ? { autoOutput: false } : void 0);
|
|
610
|
+
if (result.argsResult?.issues) {
|
|
611
|
+
const msg = `Validation error:\n${result.argsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`;
|
|
612
|
+
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
613
|
+
}
|
|
614
|
+
yield result;
|
|
615
|
+
} catch (err) {
|
|
616
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
617
|
+
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
618
|
+
} finally {
|
|
619
|
+
for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;
|
|
620
|
+
emitSpacing(spacingAfter);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} finally {
|
|
624
|
+
replActiveRef.value = false;
|
|
625
|
+
session?.close();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return replIterator();
|
|
629
|
+
}
|
|
1150
630
|
//#endregion
|
|
1151
631
|
//#region src/wrap.ts
|
|
1152
632
|
/**
|
|
1153
|
-
* Converts parsed
|
|
633
|
+
* Converts parsed arguments to CLI arguments for an external command.
|
|
1154
634
|
*/
|
|
1155
|
-
function
|
|
635
|
+
function argsToCliArgs(input, positional = []) {
|
|
1156
636
|
const args = [];
|
|
1157
|
-
if (!
|
|
637
|
+
if (!input) return args;
|
|
1158
638
|
const positionalValues = {};
|
|
1159
|
-
const
|
|
1160
|
-
for (const [key, value] of Object.entries(
|
|
1161
|
-
else
|
|
1162
|
-
for (const [key, value] of Object.entries(
|
|
639
|
+
const regularArguments = {};
|
|
640
|
+
for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
|
|
641
|
+
else regularArguments[key] = value;
|
|
642
|
+
for (const [key, value] of Object.entries(regularArguments)) {
|
|
1163
643
|
if (value === void 0 || value === null) continue;
|
|
1164
644
|
const flag = `--${key}`;
|
|
1165
645
|
if (typeof value === "boolean") {
|
|
@@ -1179,21 +659,19 @@ function optionsToArgs(options, positional = []) {
|
|
|
1179
659
|
/**
|
|
1180
660
|
* Creates an action handler that wraps an external CLI tool.
|
|
1181
661
|
* @param config - Configuration for wrapping the external command (includes optional schema)
|
|
1182
|
-
* @param
|
|
662
|
+
* @param commandArguments - The command's arguments schema
|
|
1183
663
|
* @param commandPositional - Default positional config from the wrapping command
|
|
1184
664
|
*/
|
|
1185
|
-
function createWrapHandler(config,
|
|
1186
|
-
return async (
|
|
665
|
+
function createWrapHandler(config, commandArguments, commandPositional) {
|
|
666
|
+
return async (args) => {
|
|
1187
667
|
const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
|
|
1188
|
-
const
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
const
|
|
1195
|
-
const optionArgs = optionsToArgs(externalOptions, positional);
|
|
1196
|
-
const allArgs = [...fixedArgs, ...optionArgs];
|
|
668
|
+
const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
|
|
669
|
+
const processResult = (result) => {
|
|
670
|
+
if (result.issues) throw new ValidationError(`Wrap schema validation failed:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, result.issues);
|
|
671
|
+
return result.value;
|
|
672
|
+
};
|
|
673
|
+
const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
|
|
674
|
+
const allArgs = [...fixedArgs, ...regularArgs];
|
|
1197
675
|
const proc = Bun.spawn([command, ...allArgs], {
|
|
1198
676
|
stdout: inheritStdio ? "inherit" : "pipe",
|
|
1199
677
|
stderr: inheritStdio ? "inherit" : "pipe",
|
|
@@ -1220,11 +698,8 @@ function createWrapHandler(config, commandOptions, commandPositional) {
|
|
|
1220
698
|
};
|
|
1221
699
|
};
|
|
1222
700
|
}
|
|
1223
|
-
|
|
1224
701
|
//#endregion
|
|
1225
702
|
//#region src/create.ts
|
|
1226
|
-
const commandSymbol = Symbol("padrone_command");
|
|
1227
|
-
const noop = () => void 0;
|
|
1228
703
|
function createPadrone(name) {
|
|
1229
704
|
return createPadroneBuilder({
|
|
1230
705
|
name,
|
|
@@ -1232,162 +707,268 @@ function createPadrone(name) {
|
|
|
1232
707
|
commands: []
|
|
1233
708
|
});
|
|
1234
709
|
}
|
|
1235
|
-
function createPadroneBuilder(
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
const subCommand = findCommandByName(name.slice(alias.length + 1), cmd.commands);
|
|
1250
|
-
if (subCommand) return subCommand;
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
710
|
+
function createPadroneBuilder(inputCommand) {
|
|
711
|
+
const existingCommand = inputCommand.commands?.length && inputCommand.commands.some((c) => c.parent && c.parent !== inputCommand) ? {
|
|
712
|
+
...inputCommand,
|
|
713
|
+
commands: inputCommand.commands.map((c) => c.parent && c.parent !== inputCommand ? {
|
|
714
|
+
...c,
|
|
715
|
+
parent: inputCommand
|
|
716
|
+
} : c)
|
|
717
|
+
} : inputCommand;
|
|
718
|
+
/** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
|
|
719
|
+
const createActionContext = (cmd) => ({
|
|
720
|
+
runtime: getCommandRuntime(cmd),
|
|
721
|
+
command: cmd,
|
|
722
|
+
program: builder
|
|
723
|
+
});
|
|
1255
724
|
const find = (command) => {
|
|
1256
725
|
if (typeof command !== "string") return findCommandByName(command.path, existingCommand.commands);
|
|
1257
726
|
return findCommandByName(command, existingCommand.commands);
|
|
1258
727
|
};
|
|
1259
728
|
/**
|
|
1260
|
-
* Parses CLI input to find the command and extract raw
|
|
729
|
+
* Parses CLI input to find the command and extract raw arguments without validation.
|
|
1261
730
|
*/
|
|
1262
731
|
const parseCommand = (input) => {
|
|
1263
|
-
input ??=
|
|
1264
|
-
if (!input)
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
732
|
+
input ??= getCommandRuntime(existingCommand).argv().join(" ") || void 0;
|
|
733
|
+
if (!input) {
|
|
734
|
+
const defaultCommand = findCommandByName("", existingCommand.commands);
|
|
735
|
+
if (defaultCommand) return {
|
|
736
|
+
command: defaultCommand,
|
|
737
|
+
rawArgs: {},
|
|
738
|
+
args: [],
|
|
739
|
+
unmatchedTerms: []
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
command: existingCommand,
|
|
743
|
+
rawArgs: {},
|
|
744
|
+
args: [],
|
|
745
|
+
unmatchedTerms: []
|
|
746
|
+
};
|
|
747
|
+
}
|
|
1269
748
|
const parts = parseCliInputToParts(input);
|
|
1270
749
|
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
1271
750
|
const args = parts.filter((p) => p.type === "arg").map((p) => p.value);
|
|
1272
751
|
let curCommand = existingCommand;
|
|
752
|
+
let unmatchedTerms = [];
|
|
1273
753
|
if (terms[0] === existingCommand.name) terms.shift();
|
|
1274
754
|
for (let i = 0; i < terms.length; i++) {
|
|
1275
755
|
const found = findCommandByName(terms[i] || "", curCommand.commands);
|
|
1276
756
|
if (found) curCommand = found;
|
|
1277
757
|
else {
|
|
1278
|
-
|
|
758
|
+
unmatchedTerms = terms.slice(i);
|
|
759
|
+
args.unshift(...unmatchedTerms);
|
|
1279
760
|
break;
|
|
1280
761
|
}
|
|
1281
762
|
}
|
|
763
|
+
if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
|
|
764
|
+
const defaultCommand = findCommandByName("", curCommand.commands);
|
|
765
|
+
if (defaultCommand) curCommand = defaultCommand;
|
|
766
|
+
}
|
|
1282
767
|
if (!curCommand) return {
|
|
1283
768
|
command: existingCommand,
|
|
1284
|
-
|
|
1285
|
-
args
|
|
769
|
+
rawArgs: {},
|
|
770
|
+
args,
|
|
771
|
+
unmatchedTerms
|
|
1286
772
|
};
|
|
1287
|
-
const
|
|
1288
|
-
const { aliases } = curCommand.
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
773
|
+
const argsMeta = curCommand.meta?.fields;
|
|
774
|
+
const { flags, aliases } = curCommand.argsSchema ? extractSchemaMetadata(curCommand.argsSchema, argsMeta, curCommand.meta?.autoAlias) : {
|
|
775
|
+
flags: {},
|
|
776
|
+
aliases: {}
|
|
777
|
+
};
|
|
778
|
+
const arrayArguments = /* @__PURE__ */ new Set();
|
|
779
|
+
if (curCommand.argsSchema) try {
|
|
780
|
+
const jsonSchema = curCommand.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1292
781
|
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
1293
|
-
for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array")
|
|
782
|
+
for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayArguments.add(key);
|
|
1294
783
|
}
|
|
1295
784
|
} catch {}
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
for (const
|
|
1299
|
-
|
|
785
|
+
const argParts = parts.filter((p) => p.type === "named" || p.type === "alias");
|
|
786
|
+
const rawArgs = {};
|
|
787
|
+
for (const arg of argParts) {
|
|
788
|
+
let key;
|
|
789
|
+
if (arg.type === "alias" && arg.key.length === 1 && flags[arg.key[0]]) key = [flags[arg.key[0]]];
|
|
790
|
+
else if (arg.type === "named" && arg.key.length === 1 && aliases[arg.key[0]]) key = [aliases[arg.key[0]]];
|
|
791
|
+
else key = arg.key;
|
|
1300
792
|
const rootKey = key[0];
|
|
1301
|
-
if (
|
|
1302
|
-
setNestedValue(
|
|
793
|
+
if (arg.type === "named" && arg.negated) {
|
|
794
|
+
setNestedValue(rawArgs, key, false);
|
|
1303
795
|
continue;
|
|
1304
796
|
}
|
|
1305
|
-
const value =
|
|
1306
|
-
if (
|
|
1307
|
-
const existing = getNestedValue(
|
|
797
|
+
const value = arg.value ?? true;
|
|
798
|
+
if (arrayArguments.has(rootKey)) {
|
|
799
|
+
const existing = getNestedValue(rawArgs, key);
|
|
1308
800
|
if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
|
|
1309
801
|
else existing.push(value);
|
|
1310
|
-
else if (Array.isArray(value)) setNestedValue(
|
|
1311
|
-
else setNestedValue(
|
|
1312
|
-
else setNestedValue(
|
|
1313
|
-
} else setNestedValue(
|
|
802
|
+
else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
803
|
+
else setNestedValue(rawArgs, key, [existing, value]);
|
|
804
|
+
else setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
|
|
805
|
+
} else setNestedValue(rawArgs, key, value);
|
|
1314
806
|
}
|
|
1315
807
|
return {
|
|
1316
808
|
command: curCommand,
|
|
1317
|
-
|
|
1318
|
-
args
|
|
809
|
+
rawArgs,
|
|
810
|
+
args,
|
|
811
|
+
unmatchedTerms
|
|
1319
812
|
};
|
|
1320
813
|
};
|
|
1321
814
|
/**
|
|
1322
|
-
*
|
|
815
|
+
* Preprocesses raw arguments: applies env/config values and maps positional arguments.
|
|
816
|
+
* Also performs auto-coercion (string→number/boolean) and unknown arg detection.
|
|
1323
817
|
*/
|
|
1324
|
-
const
|
|
1325
|
-
|
|
818
|
+
const buildCommandArgs = (command, rawArgs, args, context) => {
|
|
819
|
+
let preprocessedArgs = preprocessArgs(rawArgs, {
|
|
820
|
+
flags: {},
|
|
1326
821
|
aliases: {},
|
|
1327
|
-
|
|
1328
|
-
|
|
822
|
+
stdinData: context?.stdinData,
|
|
823
|
+
envData: context?.envData,
|
|
824
|
+
configData: context?.configData
|
|
1329
825
|
});
|
|
1330
826
|
const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
|
|
1331
827
|
if (positionalConfig.length > 0) {
|
|
1332
828
|
let argIndex = 0;
|
|
1333
|
-
for (
|
|
829
|
+
for (let i = 0; i < positionalConfig.length; i++) {
|
|
830
|
+
const { name, variadic } = positionalConfig[i];
|
|
1334
831
|
if (argIndex >= args.length) break;
|
|
1335
832
|
if (variadic) {
|
|
1336
|
-
const nonVariadicAfter = positionalConfig.slice(
|
|
1337
|
-
name,
|
|
1338
|
-
variadic
|
|
1339
|
-
}) + 1).filter((p) => !p.variadic).length;
|
|
833
|
+
const nonVariadicAfter = positionalConfig.slice(i + 1).filter((p) => !p.variadic).length;
|
|
1340
834
|
const variadicEnd = args.length - nonVariadicAfter;
|
|
1341
|
-
|
|
835
|
+
preprocessedArgs[name] = args.slice(argIndex, variadicEnd);
|
|
1342
836
|
argIndex = variadicEnd;
|
|
837
|
+
} else if (i === positionalConfig.length - 1 && args.length > argIndex + 1) {
|
|
838
|
+
preprocessedArgs[name] = args.slice(argIndex).join(" ");
|
|
839
|
+
argIndex = args.length;
|
|
1343
840
|
} else {
|
|
1344
|
-
|
|
841
|
+
preprocessedArgs[name] = args[argIndex];
|
|
1345
842
|
argIndex++;
|
|
1346
843
|
}
|
|
1347
844
|
}
|
|
1348
845
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
const hasOptions = command.options || Object.keys(preprocessedOptions).length > 0;
|
|
1352
|
-
return {
|
|
1353
|
-
options: optionsParsed.issues ? void 0 : hasOptions ? optionsParsed.value : void 0,
|
|
1354
|
-
optionsResult: optionsParsed
|
|
1355
|
-
};
|
|
846
|
+
if (command.argsSchema) preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
|
|
847
|
+
return preprocessedArgs;
|
|
1356
848
|
};
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
849
|
+
/**
|
|
850
|
+
* Detects unknown options in args that aren't defined in the schema.
|
|
851
|
+
* Returns unknown key info with suggestions, or empty array if schema is loose.
|
|
852
|
+
*/
|
|
853
|
+
const checkUnknownArgs = (command, preprocessedArgs) => {
|
|
854
|
+
if (!command.argsSchema) return [];
|
|
855
|
+
const argsMeta = command.meta?.fields;
|
|
856
|
+
const { flags, aliases } = extractSchemaMetadata(command.argsSchema, argsMeta, command.meta?.autoAlias);
|
|
857
|
+
return detectUnknownArgs(preprocessedArgs, command.argsSchema, flags, aliases, suggestSimilar);
|
|
858
|
+
};
|
|
859
|
+
/**
|
|
860
|
+
* Validates preprocessed arguments against the command's schema.
|
|
861
|
+
* First checks for unknown args (strict by default), then runs schema validation.
|
|
862
|
+
* Returns sync or async result depending on the schema's validate method.
|
|
863
|
+
*/
|
|
864
|
+
const validateCommandArgs = (command, preprocessedArgs) => {
|
|
865
|
+
const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
|
|
866
|
+
if (unknownArgs.length > 0) return {
|
|
867
|
+
args: void 0,
|
|
868
|
+
argsResult: { issues: unknownArgs.map(({ key, suggestion }) => ({
|
|
869
|
+
path: [key],
|
|
870
|
+
message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
|
|
871
|
+
})) }
|
|
1362
872
|
};
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
|
|
1369
|
-
if (!envValidated.issues) envData = envValidated.value;
|
|
1370
|
-
}
|
|
1371
|
-
const { options, optionsResult } = validateOptions(command, rawOptions, args, {
|
|
1372
|
-
envData,
|
|
1373
|
-
configData: parseOptions?.configData
|
|
873
|
+
const argsParsed = command.argsSchema ? command.argsSchema["~standard"].validate(preprocessedArgs) : { value: preprocessedArgs };
|
|
874
|
+
const hasArgs = command.argsSchema || Object.keys(preprocessedArgs).length > 0;
|
|
875
|
+
const buildResult = (parsed) => ({
|
|
876
|
+
args: parsed.issues ? void 0 : hasArgs ? parsed.value : void 0,
|
|
877
|
+
argsResult: parsed
|
|
1374
878
|
});
|
|
1375
|
-
return
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
879
|
+
return thenMaybe(argsParsed, buildResult);
|
|
880
|
+
};
|
|
881
|
+
/**
|
|
882
|
+
* Preprocesses and validates raw arguments against the command's schema.
|
|
883
|
+
* Returns sync or async result depending on the schema's validate method.
|
|
884
|
+
*/
|
|
885
|
+
const validateArgs = (command, rawArgs, args, context) => {
|
|
886
|
+
return validateCommandArgs(command, buildCommandArgs(command, rawArgs, args, context));
|
|
887
|
+
};
|
|
888
|
+
const parse = (input) => {
|
|
889
|
+
const state = {};
|
|
890
|
+
const parseCtx = {
|
|
891
|
+
input,
|
|
892
|
+
command: existingCommand,
|
|
893
|
+
state
|
|
1379
894
|
};
|
|
895
|
+
const coreParse = () => {
|
|
896
|
+
const { command, rawArgs, args } = parseCommand(parseCtx.input);
|
|
897
|
+
return {
|
|
898
|
+
command,
|
|
899
|
+
rawArgs,
|
|
900
|
+
positionalArgs: args
|
|
901
|
+
};
|
|
902
|
+
};
|
|
903
|
+
const parsedOrPromise = runPluginChain("parse", existingCommand.plugins ?? [], parseCtx, coreParse);
|
|
904
|
+
const continueAfterParse = (parsed) => {
|
|
905
|
+
const { command } = parsed;
|
|
906
|
+
const commandPlugins = collectPlugins(command);
|
|
907
|
+
const validateCtx = {
|
|
908
|
+
command,
|
|
909
|
+
rawArgs: parsed.rawArgs,
|
|
910
|
+
positionalArgs: parsed.positionalArgs,
|
|
911
|
+
state
|
|
912
|
+
};
|
|
913
|
+
const coreValidate = () => {
|
|
914
|
+
const resolveEnvSchema = (cmd) => {
|
|
915
|
+
if (cmd.envSchema !== void 0) return cmd.envSchema;
|
|
916
|
+
if (cmd.parent) return resolveEnvSchema(cmd.parent);
|
|
917
|
+
};
|
|
918
|
+
const envSchema = resolveEnvSchema(command);
|
|
919
|
+
const readStdinForParse = () => {
|
|
920
|
+
const stdinConfig = command.meta?.stdin;
|
|
921
|
+
if (!stdinConfig) return {};
|
|
922
|
+
const { field, as } = parseStdinConfig(stdinConfig);
|
|
923
|
+
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
|
|
924
|
+
const stdin = resolveStdin(getCommandRuntime(existingCommand));
|
|
925
|
+
if (!stdin) return {};
|
|
926
|
+
if (as === "lines") return (async () => {
|
|
927
|
+
const lines = [];
|
|
928
|
+
for await (const line of stdin.lines()) lines.push(line);
|
|
929
|
+
return { [field]: lines };
|
|
930
|
+
})();
|
|
931
|
+
return stdin.text().then((text) => text ? { [field]: text } : {});
|
|
932
|
+
};
|
|
933
|
+
const finalize = (envData, stdinData) => {
|
|
934
|
+
return thenMaybe(validateArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
|
|
935
|
+
stdinData,
|
|
936
|
+
envData
|
|
937
|
+
}), (v) => v);
|
|
938
|
+
};
|
|
939
|
+
let envData;
|
|
940
|
+
const afterEnv = (envResult) => {
|
|
941
|
+
return thenMaybe(readStdinForParse(), (stdinData) => {
|
|
942
|
+
return finalize(envResult, Object.keys(stdinData).length > 0 ? stdinData : void 0);
|
|
943
|
+
});
|
|
944
|
+
};
|
|
945
|
+
if (envSchema) {
|
|
946
|
+
const rawEnv = getCommandRuntime(existingCommand).env();
|
|
947
|
+
return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
|
|
948
|
+
if (!result.issues) envData = result.value;
|
|
949
|
+
return afterEnv(envData);
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
return afterEnv(envData);
|
|
953
|
+
};
|
|
954
|
+
return warnIfUnexpectedAsync(thenMaybe(runPluginChain("validate", commandPlugins, validateCtx, coreValidate), (v) => ({
|
|
955
|
+
command,
|
|
956
|
+
args: v.args,
|
|
957
|
+
argsResult: v.argsResult
|
|
958
|
+
})), command);
|
|
959
|
+
};
|
|
960
|
+
return thenMaybe(parsedOrPromise, continueAfterParse);
|
|
1380
961
|
};
|
|
1381
|
-
const stringify = (command = "",
|
|
962
|
+
const stringify = (command = "", args) => {
|
|
1382
963
|
const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1383
|
-
if (!commandObj) throw new
|
|
964
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1384
965
|
const parts = [];
|
|
1385
966
|
if (commandObj.path) parts.push(commandObj.path);
|
|
1386
967
|
const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
|
|
1387
968
|
const positionalNames = new Set(positionalConfig.map((p) => p.name));
|
|
1388
|
-
if (
|
|
969
|
+
if (args && typeof args === "object") {
|
|
1389
970
|
for (const { name, variadic } of positionalConfig) {
|
|
1390
|
-
const value =
|
|
971
|
+
const value = args[name];
|
|
1391
972
|
if (value === void 0) continue;
|
|
1392
973
|
if (variadic && Array.isArray(value)) for (const v of value) {
|
|
1393
974
|
const vStr = String(v);
|
|
@@ -1414,7 +995,7 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1414
995
|
else parts.push(`--${key}=${value}`);
|
|
1415
996
|
else parts.push(`--${key}=${value}`);
|
|
1416
997
|
};
|
|
1417
|
-
for (const [key, value] of Object.entries(
|
|
998
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1418
999
|
if (value === void 0 || positionalNames.has(key)) continue;
|
|
1419
1000
|
stringifyValue(key, value);
|
|
1420
1001
|
}
|
|
@@ -1429,16 +1010,16 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1429
1010
|
if (!input) return null;
|
|
1430
1011
|
const parts = parseCliInputToParts(input);
|
|
1431
1012
|
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
1432
|
-
const
|
|
1013
|
+
const args = parts.filter((p) => p.type === "named" || p.type === "alias");
|
|
1433
1014
|
const keyIs = (key, name) => key.length === 1 && key[0] === name;
|
|
1434
|
-
const hasHelpFlag =
|
|
1015
|
+
const hasHelpFlag = args.some((p) => p.type === "named" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
|
|
1435
1016
|
const getDetailLevel = () => {
|
|
1436
|
-
for (const
|
|
1437
|
-
if (
|
|
1438
|
-
if (
|
|
1017
|
+
for (const arg of args) {
|
|
1018
|
+
if (arg.type === "named" && keyIs(arg.key, "detail") && typeof arg.value === "string") {
|
|
1019
|
+
if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
|
|
1439
1020
|
}
|
|
1440
|
-
if (
|
|
1441
|
-
if (
|
|
1021
|
+
if (arg.type === "alias" && keyIs(arg.key, "d") && typeof arg.value === "string") {
|
|
1022
|
+
if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
|
|
1442
1023
|
}
|
|
1443
1024
|
}
|
|
1444
1025
|
};
|
|
@@ -1453,17 +1034,17 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1453
1034
|
"json",
|
|
1454
1035
|
"auto"
|
|
1455
1036
|
];
|
|
1456
|
-
for (const
|
|
1457
|
-
if (
|
|
1458
|
-
if (validFormats.includes(
|
|
1037
|
+
for (const arg of args) {
|
|
1038
|
+
if (arg.type === "named" && keyIs(arg.key, "format") && typeof arg.value === "string") {
|
|
1039
|
+
if (validFormats.includes(arg.value)) return arg.value;
|
|
1459
1040
|
}
|
|
1460
|
-
if (
|
|
1461
|
-
if (validFormats.includes(
|
|
1041
|
+
if (arg.type === "alias" && keyIs(arg.key, "f") && typeof arg.value === "string") {
|
|
1042
|
+
if (validFormats.includes(arg.value)) return arg.value;
|
|
1462
1043
|
}
|
|
1463
1044
|
}
|
|
1464
1045
|
};
|
|
1465
1046
|
const format = getFormat();
|
|
1466
|
-
const hasVersionFlag =
|
|
1047
|
+
const hasVersionFlag = args.some((p) => p.type === "named" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
|
|
1467
1048
|
const normalizedTerms = [...terms];
|
|
1468
1049
|
if (normalizedTerms[0] === existingCommand.name) normalizedTerms.shift();
|
|
1469
1050
|
const userHelpCommand = findCommandByName("help", existingCommand.commands);
|
|
@@ -1478,6 +1059,24 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1478
1059
|
format
|
|
1479
1060
|
};
|
|
1480
1061
|
}
|
|
1062
|
+
if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === "help") {
|
|
1063
|
+
const commandTerms = normalizedTerms.slice(0, -1);
|
|
1064
|
+
let targetCommand;
|
|
1065
|
+
let current = existingCommand;
|
|
1066
|
+
for (const term of commandTerms) {
|
|
1067
|
+
const found = findCommandByName(term, current.commands);
|
|
1068
|
+
if (found) {
|
|
1069
|
+
targetCommand = found;
|
|
1070
|
+
current = found;
|
|
1071
|
+
} else break;
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
type: "help",
|
|
1075
|
+
command: targetCommand,
|
|
1076
|
+
detail,
|
|
1077
|
+
format
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1481
1080
|
if (!userVersionCommand && normalizedTerms[0] === "version") return { type: "version" };
|
|
1482
1081
|
if (!userCompletionCommand && normalizedTerms[0] === "completion") {
|
|
1483
1082
|
const shellArg = normalizedTerms[1];
|
|
@@ -1488,7 +1087,8 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1488
1087
|
"zsh",
|
|
1489
1088
|
"fish",
|
|
1490
1089
|
"powershell"
|
|
1491
|
-
].includes(shellArg) ? shellArg : void 0
|
|
1090
|
+
].includes(shellArg) ? shellArg : void 0,
|
|
1091
|
+
setup: args.some((p) => p.type === "named" && keyIs(p.key, "setup"))
|
|
1492
1092
|
};
|
|
1493
1093
|
}
|
|
1494
1094
|
if (hasHelpFlag) {
|
|
@@ -1501,6 +1101,10 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1501
1101
|
};
|
|
1502
1102
|
}
|
|
1503
1103
|
if (hasVersionFlag && normalizedTerms.length === 0) return { type: "version" };
|
|
1104
|
+
if (args.some((p) => p.type === "named" && keyIs(p.key, "repl"))) return {
|
|
1105
|
+
type: "repl",
|
|
1106
|
+
scope: normalizedTerms.length > 0 ? normalizedTerms.join(" ") : void 0
|
|
1107
|
+
};
|
|
1504
1108
|
return null;
|
|
1505
1109
|
};
|
|
1506
1110
|
/**
|
|
@@ -1508,139 +1112,455 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1508
1112
|
*/
|
|
1509
1113
|
const extractConfigPath = (input) => {
|
|
1510
1114
|
if (!input) return void 0;
|
|
1511
|
-
const
|
|
1512
|
-
for (const
|
|
1513
|
-
if (
|
|
1514
|
-
if (
|
|
1115
|
+
const args = parseCliInputToParts(input).filter((p) => p.type === "named" || p.type === "alias");
|
|
1116
|
+
for (const arg of args) {
|
|
1117
|
+
if (arg.type === "named" && arg.key.length === 1 && arg.key[0] === "config" && typeof arg.value === "string") return arg.value;
|
|
1118
|
+
if (arg.type === "alias" && arg.key.length === 1 && arg.key[0] === "c" && typeof arg.value === "string") return arg.value;
|
|
1515
1119
|
}
|
|
1516
1120
|
};
|
|
1517
|
-
|
|
1518
|
-
|
|
1121
|
+
/**
|
|
1122
|
+
* Core execution logic shared by eval() and cli().
|
|
1123
|
+
* errorMode controls validation error behavior:
|
|
1124
|
+
* - 'soft': return result with issues (eval behavior)
|
|
1125
|
+
* - 'hard': print error + help and throw (cli-without-input behavior)
|
|
1126
|
+
*/
|
|
1127
|
+
const execCommand = (resolvedInput, evalOptions, errorMode = "soft") => {
|
|
1128
|
+
const baseRuntime = getCommandRuntime(existingCommand);
|
|
1129
|
+
const runtime = evalOptions?.runtime ? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== void 0))) : baseRuntime;
|
|
1519
1130
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1520
1131
|
if (builtin) {
|
|
1521
1132
|
if (builtin.type === "help") {
|
|
1522
1133
|
const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
|
|
1523
1134
|
detail: builtin.detail,
|
|
1524
|
-
format: builtin.format
|
|
1135
|
+
format: builtin.format ?? runtime.format
|
|
1525
1136
|
});
|
|
1526
|
-
|
|
1137
|
+
runtime.output(helpText);
|
|
1527
1138
|
return {
|
|
1528
1139
|
command: existingCommand,
|
|
1529
1140
|
args: void 0,
|
|
1530
|
-
options: void 0,
|
|
1531
1141
|
result: helpText
|
|
1532
1142
|
};
|
|
1533
1143
|
}
|
|
1534
1144
|
if (builtin.type === "version") {
|
|
1535
1145
|
const version = getVersion(existingCommand.version);
|
|
1536
|
-
|
|
1146
|
+
runtime.output(version);
|
|
1537
1147
|
return {
|
|
1538
1148
|
command: existingCommand,
|
|
1539
|
-
|
|
1149
|
+
args: void 0,
|
|
1540
1150
|
result: version
|
|
1541
1151
|
};
|
|
1542
1152
|
}
|
|
1543
|
-
if (builtin.type === "completion") {
|
|
1153
|
+
if (builtin.type === "completion") return import("./completion.mjs").then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
|
|
1154
|
+
if (builtin.setup) {
|
|
1155
|
+
const shell = builtin.shell ?? detectShell();
|
|
1156
|
+
if (!shell) throw new Error("Could not detect shell. Specify one: completion bash --setup");
|
|
1157
|
+
const result = setupCompletions(existingCommand.name, shell);
|
|
1158
|
+
const message = `${result.updated ? "Updated" : "Added"} ${existingCommand.name} completions in ${result.file}`;
|
|
1159
|
+
runtime.output(message);
|
|
1160
|
+
return {
|
|
1161
|
+
command: existingCommand,
|
|
1162
|
+
args: void 0,
|
|
1163
|
+
result: message
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1544
1166
|
const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
|
|
1545
|
-
|
|
1167
|
+
runtime.output(completionScript);
|
|
1546
1168
|
return {
|
|
1547
1169
|
command: existingCommand,
|
|
1548
|
-
|
|
1170
|
+
args: void 0,
|
|
1549
1171
|
result: completionScript
|
|
1550
1172
|
};
|
|
1551
|
-
}
|
|
1173
|
+
});
|
|
1552
1174
|
}
|
|
1553
|
-
const
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1175
|
+
const state = {};
|
|
1176
|
+
const rootPlugins = existingCommand.plugins ?? [];
|
|
1177
|
+
const runPipeline = () => {
|
|
1178
|
+
const parseCtx = {
|
|
1179
|
+
input: resolvedInput,
|
|
1180
|
+
command: existingCommand,
|
|
1181
|
+
state
|
|
1182
|
+
};
|
|
1183
|
+
const coreParse = () => {
|
|
1184
|
+
const { command, rawArgs, args, unmatchedTerms } = parseCommand(parseCtx.input);
|
|
1185
|
+
const hasSubcommands = command.commands && command.commands.length > 0;
|
|
1186
|
+
const hasSchema = command.argsSchema != null;
|
|
1187
|
+
if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
|
|
1188
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format });
|
|
1189
|
+
runtime.output(helpText);
|
|
1190
|
+
return {
|
|
1191
|
+
command,
|
|
1192
|
+
rawArgs: { "~help": helpText },
|
|
1193
|
+
positionalArgs: []
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
if (unmatchedTerms.length > 0) {
|
|
1197
|
+
if (!(command.meta?.positional && command.meta.positional.length > 0)) {
|
|
1198
|
+
const isRootCommand = command === existingCommand;
|
|
1199
|
+
const commandDisplayName = command.name || command.aliases?.[0] || command.path || "(default)";
|
|
1200
|
+
const candidateNames = [];
|
|
1201
|
+
if (isRootCommand && existingCommand.commands) {
|
|
1202
|
+
for (const cmd of existingCommand.commands) if (!cmd.hidden) {
|
|
1203
|
+
candidateNames.push(cmd.name);
|
|
1204
|
+
if (cmd.aliases) candidateNames.push(...cmd.aliases);
|
|
1205
|
+
}
|
|
1206
|
+
} else if (command.commands) {
|
|
1207
|
+
for (const cmd of command.commands) if (!cmd.hidden) {
|
|
1208
|
+
candidateNames.push(cmd.name);
|
|
1209
|
+
if (cmd.aliases) candidateNames.push(...cmd.aliases);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const suggestion = suggestSimilar(unmatchedTerms[0], candidateNames);
|
|
1213
|
+
const suggestions = suggestion ? [suggestion] : [];
|
|
1214
|
+
const baseMsg = isRootCommand ? `Unknown command: ${unmatchedTerms[0]}` : `Unexpected arguments for '${commandDisplayName}': ${unmatchedTerms.join(" ")}`;
|
|
1215
|
+
const errorMsg = suggestions.length ? `${baseMsg}\n\n ${suggestions[0]}` : baseMsg;
|
|
1216
|
+
if (errorMode === "hard") {
|
|
1217
|
+
runtime.error(errorMsg);
|
|
1218
|
+
if (suggestions.length > 0) {
|
|
1219
|
+
const visibleCommands = ((isRootCommand ? existingCommand : command).commands ?? []).filter((c) => !c.hidden && c.name);
|
|
1220
|
+
if (visibleCommands.length > 0) {
|
|
1221
|
+
const cmdList = visibleCommands.map((c) => c.name).join(", ");
|
|
1222
|
+
runtime.output(`\nAvailable commands: ${cmdList}`);
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, { format: runtime.format });
|
|
1226
|
+
runtime.error(helpText);
|
|
1227
|
+
}
|
|
1228
|
+
throw new RoutingError(errorMsg, {
|
|
1229
|
+
suggestions,
|
|
1230
|
+
command: command.path || command.name
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
throw new RoutingError(errorMsg, {
|
|
1234
|
+
suggestions,
|
|
1235
|
+
command: command.path || command.name
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
command,
|
|
1241
|
+
rawArgs,
|
|
1242
|
+
positionalArgs: args
|
|
1243
|
+
};
|
|
1244
|
+
};
|
|
1245
|
+
const parsedOrPromise = runPluginChain("parse", rootPlugins, parseCtx, coreParse);
|
|
1246
|
+
const continueAfterParse = (parsed) => {
|
|
1247
|
+
const { command } = parsed;
|
|
1248
|
+
const commandPlugins = collectPlugins(command);
|
|
1249
|
+
if (parsed.rawArgs["~help"]) return {
|
|
1250
|
+
command,
|
|
1251
|
+
args: void 0,
|
|
1252
|
+
result: parsed.rawArgs["~help"]
|
|
1253
|
+
};
|
|
1254
|
+
const validateCtx = {
|
|
1255
|
+
command,
|
|
1256
|
+
rawArgs: parsed.rawArgs,
|
|
1257
|
+
positionalArgs: parsed.positionalArgs,
|
|
1258
|
+
state
|
|
1259
|
+
};
|
|
1260
|
+
const coreValidate = () => {
|
|
1261
|
+
let flagInteractive;
|
|
1262
|
+
if (hasInteractiveConfig(command.meta)) {
|
|
1263
|
+
if (validateCtx.rawArgs.interactive !== void 0) {
|
|
1264
|
+
flagInteractive = validateCtx.rawArgs.interactive !== false && validateCtx.rawArgs.interactive !== "false";
|
|
1265
|
+
delete validateCtx.rawArgs.interactive;
|
|
1266
|
+
}
|
|
1267
|
+
if (validateCtx.rawArgs.i !== void 0) {
|
|
1268
|
+
flagInteractive = validateCtx.rawArgs.i !== false && validateCtx.rawArgs.i !== "false";
|
|
1269
|
+
delete validateCtx.rawArgs.i;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const runtimeDefault = runtime.interactive === "forced" ? true : runtime.interactive === "disabled" ? false : void 0;
|
|
1273
|
+
const effectiveInteractive = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
|
|
1274
|
+
const stdinIsPiped = !!command.meta?.stdin && (runtime.stdin ? !runtime.stdin.isTTY : typeof process !== "undefined" && process.stdin?.isTTY !== true);
|
|
1275
|
+
const interactivitySuppressed = runtime.interactive === "unsupported" || effectiveInteractive === false || stdinIsPiped && effectiveInteractive !== true;
|
|
1276
|
+
const forceInteractive = !interactivitySuppressed && effectiveInteractive === true;
|
|
1277
|
+
const configPath = extractConfigPath(parseCtx.input);
|
|
1278
|
+
const resolveConfigFiles = (cmd) => {
|
|
1279
|
+
if (cmd.configFiles !== void 0) return cmd.configFiles;
|
|
1280
|
+
if (cmd.parent) return resolveConfigFiles(cmd.parent);
|
|
1281
|
+
};
|
|
1282
|
+
const effectiveConfigFiles = resolveConfigFiles(command);
|
|
1283
|
+
const resolveConfigSchema = (cmd) => {
|
|
1284
|
+
if (cmd.configSchema !== void 0) return cmd.configSchema;
|
|
1285
|
+
if (cmd.parent) return resolveConfigSchema(cmd.parent);
|
|
1286
|
+
};
|
|
1287
|
+
const configSchema = resolveConfigSchema(command);
|
|
1288
|
+
const resolveEnvSchema = (cmd) => {
|
|
1289
|
+
if (cmd.envSchema !== void 0) return cmd.envSchema;
|
|
1290
|
+
if (cmd.parent) return resolveEnvSchema(cmd.parent);
|
|
1291
|
+
};
|
|
1292
|
+
const envSchema = resolveEnvSchema(command);
|
|
1293
|
+
let configData;
|
|
1294
|
+
if (configPath) configData = runtime.loadConfigFile(configPath);
|
|
1295
|
+
else if (effectiveConfigFiles?.length) {
|
|
1296
|
+
const foundConfigPath = runtime.findFile(effectiveConfigFiles);
|
|
1297
|
+
if (foundConfigPath) configData = runtime.loadConfigFile(foundConfigPath) ?? configData;
|
|
1298
|
+
}
|
|
1299
|
+
const validateConfig = () => {
|
|
1300
|
+
if (configData && configSchema) return thenMaybe(configSchema["~standard"].validate(configData), (result) => {
|
|
1301
|
+
if (result.issues) throw new ConfigError(`Invalid config file:\n${result.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`, { command: command.path || command.name });
|
|
1302
|
+
return result.value;
|
|
1303
|
+
});
|
|
1304
|
+
return configData;
|
|
1305
|
+
};
|
|
1306
|
+
const validateEnv = () => {
|
|
1307
|
+
let envData;
|
|
1308
|
+
if (envSchema) {
|
|
1309
|
+
const rawEnv = runtime.env();
|
|
1310
|
+
return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
|
|
1311
|
+
if (!result.issues) envData = result.value;
|
|
1312
|
+
return envData;
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return envData;
|
|
1316
|
+
};
|
|
1317
|
+
const readStdin = () => {
|
|
1318
|
+
const stdinConfig = command.meta?.stdin;
|
|
1319
|
+
if (!stdinConfig) return {};
|
|
1320
|
+
const { field, as } = parseStdinConfig(stdinConfig);
|
|
1321
|
+
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
|
|
1322
|
+
const stdin = resolveStdin(runtime);
|
|
1323
|
+
if (!stdin) return {};
|
|
1324
|
+
if (as === "lines") return (async () => {
|
|
1325
|
+
const lines = [];
|
|
1326
|
+
for await (const line of stdin.lines()) lines.push(line);
|
|
1327
|
+
return { [field]: lines };
|
|
1328
|
+
})();
|
|
1329
|
+
return stdin.text().then((text) => {
|
|
1330
|
+
if (!text) return {};
|
|
1331
|
+
return { [field]: text };
|
|
1332
|
+
});
|
|
1333
|
+
};
|
|
1334
|
+
const finalizeValidation = (validatedConfigData, envData, stdinData) => {
|
|
1335
|
+
const preprocessedArgs = buildCommandArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
|
|
1336
|
+
stdinData,
|
|
1337
|
+
envData,
|
|
1338
|
+
configData: validatedConfigData
|
|
1339
|
+
});
|
|
1340
|
+
if (!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta)) {
|
|
1341
|
+
const unknowns = checkUnknownArgs(command, preprocessedArgs);
|
|
1342
|
+
if (unknowns.length > 0) return {
|
|
1343
|
+
args: void 0,
|
|
1344
|
+
argsResult: { issues: unknowns.map(({ key, suggestion }) => ({
|
|
1345
|
+
path: [key],
|
|
1346
|
+
message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
|
|
1347
|
+
})) }
|
|
1348
|
+
};
|
|
1349
|
+
if (command.argsSchema) {
|
|
1350
|
+
const providedKeys = new Set(Object.keys(preprocessedArgs).filter((k) => preprocessedArgs[k] !== void 0));
|
|
1351
|
+
const earlyCheck = command.argsSchema["~standard"].validate(preprocessedArgs);
|
|
1352
|
+
const checkForProvidedFieldErrors = (result) => {
|
|
1353
|
+
if (!result.issues) return void 0;
|
|
1354
|
+
const providedFieldIssues = result.issues.filter((issue) => {
|
|
1355
|
+
const rootKey = issue.path?.[0];
|
|
1356
|
+
return rootKey !== void 0 && providedKeys.has(String(rootKey));
|
|
1357
|
+
});
|
|
1358
|
+
if (providedFieldIssues.length > 0) return {
|
|
1359
|
+
args: void 0,
|
|
1360
|
+
argsResult: { issues: providedFieldIssues }
|
|
1361
|
+
};
|
|
1362
|
+
};
|
|
1363
|
+
const earlyResult = thenMaybe(earlyCheck, (result) => {
|
|
1364
|
+
const errors = checkForProvidedFieldErrors(result);
|
|
1365
|
+
if (errors) return errors;
|
|
1366
|
+
});
|
|
1367
|
+
if (earlyResult instanceof Promise) return earlyResult.then((err) => {
|
|
1368
|
+
if (err) return err;
|
|
1369
|
+
return continueWithPrompt(preprocessedArgs);
|
|
1370
|
+
});
|
|
1371
|
+
if (earlyResult) return earlyResult;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return continueWithPrompt(preprocessedArgs);
|
|
1375
|
+
};
|
|
1376
|
+
const continueWithPrompt = (preprocessedArgs) => {
|
|
1377
|
+
return thenMaybe(!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta) ? promptInteractiveFields(preprocessedArgs, command, runtime, forceInteractive || void 0) : preprocessedArgs, (filledArgs) => {
|
|
1378
|
+
return thenMaybe(validateCommandArgs(command, filledArgs), (v) => v);
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
return thenMaybe(validateConfig(), (cfgData) => {
|
|
1382
|
+
return thenMaybe(validateEnv(), (envData) => {
|
|
1383
|
+
return thenMaybe(readStdin(), (stdinData) => {
|
|
1384
|
+
return finalizeValidation(cfgData, envData, Object.keys(stdinData).length > 0 ? stdinData : void 0);
|
|
1385
|
+
});
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
};
|
|
1389
|
+
const validatedOrPromise = runPluginChain("validate", commandPlugins, validateCtx, coreValidate);
|
|
1390
|
+
const continueAfterValidate = (v) => {
|
|
1391
|
+
if (v.argsResult?.issues) {
|
|
1392
|
+
let knownOptions;
|
|
1393
|
+
const getKnownOptions = () => {
|
|
1394
|
+
if (knownOptions) return knownOptions;
|
|
1395
|
+
knownOptions = [];
|
|
1396
|
+
if (command.argsSchema) try {
|
|
1397
|
+
const js = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1398
|
+
if (js.type === "object" && js.properties) knownOptions = Object.keys(js.properties);
|
|
1399
|
+
} catch {}
|
|
1400
|
+
return knownOptions;
|
|
1401
|
+
};
|
|
1402
|
+
const issueMessages = v.argsResult.issues.map((i) => {
|
|
1403
|
+
const base = ` - ${i.path?.join(".") || "root"}: ${i.message}`;
|
|
1404
|
+
const unrecognizedKeys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
|
|
1405
|
+
if (unrecognizedKeys?.length) {
|
|
1406
|
+
const hints = unrecognizedKeys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
|
|
1407
|
+
if (hints.length) return `${base}\n ${hints.join("\n ")}`;
|
|
1408
|
+
}
|
|
1409
|
+
return base;
|
|
1410
|
+
}).join("\n");
|
|
1411
|
+
if (errorMode === "hard") {
|
|
1412
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format });
|
|
1413
|
+
runtime.error(`Validation error:\n${issueMessages}`);
|
|
1414
|
+
runtime.error(helpText);
|
|
1415
|
+
throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues, {
|
|
1416
|
+
suggestions: v.argsResult.issues.flatMap((i) => {
|
|
1417
|
+
const keys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
|
|
1418
|
+
if (!keys?.length) return [];
|
|
1419
|
+
return keys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
|
|
1420
|
+
}),
|
|
1421
|
+
command: command.path || command.name
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return {
|
|
1425
|
+
command,
|
|
1426
|
+
args: void 0,
|
|
1427
|
+
argsResult: v.argsResult,
|
|
1428
|
+
result: void 0
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
const executeCtx = {
|
|
1432
|
+
command,
|
|
1433
|
+
args: v.args,
|
|
1434
|
+
state
|
|
1435
|
+
};
|
|
1436
|
+
const coreExecute = () => {
|
|
1437
|
+
const handler = command.action ?? noop;
|
|
1438
|
+
const ctx = evalOptions?.runtime ? {
|
|
1439
|
+
...createActionContext(command),
|
|
1440
|
+
runtime
|
|
1441
|
+
} : createActionContext(command);
|
|
1442
|
+
return { result: handler(executeCtx.args, ctx) };
|
|
1443
|
+
};
|
|
1444
|
+
return thenMaybe(runPluginChain("execute", commandPlugins, executeCtx, coreExecute), (e) => {
|
|
1445
|
+
const commandResult = {
|
|
1446
|
+
command,
|
|
1447
|
+
args: v.args,
|
|
1448
|
+
argsResult: v.argsResult,
|
|
1449
|
+
result: e.result
|
|
1450
|
+
};
|
|
1451
|
+
if (command.autoOutput ?? evalOptions?.autoOutput ?? true) {
|
|
1452
|
+
const outputOrPromise = outputValue(e.result, runtime.output);
|
|
1453
|
+
if (outputOrPromise instanceof Promise) return outputOrPromise.then(() => commandResult);
|
|
1454
|
+
}
|
|
1455
|
+
return commandResult;
|
|
1456
|
+
});
|
|
1457
|
+
};
|
|
1458
|
+
return warnIfUnexpectedAsync(thenMaybe(validatedOrPromise, continueAfterValidate), command);
|
|
1459
|
+
};
|
|
1460
|
+
return thenMaybe(parsedOrPromise, continueAfterParse);
|
|
1568
1461
|
};
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1462
|
+
return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) => ({
|
|
1463
|
+
command: existingCommand,
|
|
1464
|
+
args: void 0,
|
|
1465
|
+
argsResult: void 0,
|
|
1466
|
+
result
|
|
1467
|
+
}));
|
|
1468
|
+
};
|
|
1469
|
+
const evalCommand = (input, evalOptions) => {
|
|
1470
|
+
return execCommand(input, evalOptions, "soft");
|
|
1471
|
+
};
|
|
1472
|
+
/**
|
|
1473
|
+
* Collects plugins from the command's parent chain (root → ... → target).
|
|
1474
|
+
* Root/program plugins come first (outermost), target command's plugins last (innermost).
|
|
1475
|
+
*
|
|
1476
|
+
* The `programRoot` parameter provides the current program command, because
|
|
1477
|
+
* subcommands' `.parent` references may be stale (builders are immutable — each
|
|
1478
|
+
* method returns a new builder, so a subcommand's parent was captured before
|
|
1479
|
+
* `.use()` was called on the program). We substitute `programRoot` for the
|
|
1480
|
+
* top of the chain to ensure program-level plugins are always included.
|
|
1481
|
+
*/
|
|
1482
|
+
const collectPlugins = (cmd) => {
|
|
1483
|
+
const chain = [];
|
|
1484
|
+
let current = cmd;
|
|
1485
|
+
while (current) {
|
|
1486
|
+
if (!current.parent) {
|
|
1487
|
+
if (existingCommand.plugins?.length) chain.unshift(existingCommand.plugins);
|
|
1488
|
+
} else if (current.plugins?.length) chain.unshift(current.plugins);
|
|
1489
|
+
current = current.parent;
|
|
1575
1490
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1491
|
+
return chain.flat();
|
|
1492
|
+
};
|
|
1493
|
+
let replFn;
|
|
1494
|
+
const replActiveRef = { value: false };
|
|
1495
|
+
const cli = (cliOptions) => {
|
|
1496
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1497
|
+
const resolvedInput = runtime.argv().join(" ") || void 0;
|
|
1498
|
+
if (cliOptions?.repl !== false) {
|
|
1499
|
+
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1500
|
+
if (builtin?.type === "repl") {
|
|
1501
|
+
const replPrefs = {
|
|
1502
|
+
...typeof cliOptions?.repl === "object" ? cliOptions.repl : {},
|
|
1503
|
+
scope: builtin.scope,
|
|
1504
|
+
autoOutput: (typeof cliOptions?.repl === "object" ? cliOptions.repl.autoOutput : void 0) ?? cliOptions?.autoOutput
|
|
1505
|
+
};
|
|
1506
|
+
const drainRepl = async () => {
|
|
1507
|
+
for await (const _ of replFn(replPrefs));
|
|
1508
|
+
return {
|
|
1509
|
+
command: existingCommand,
|
|
1510
|
+
args: void 0,
|
|
1511
|
+
result: void 0
|
|
1512
|
+
};
|
|
1513
|
+
};
|
|
1514
|
+
return drainRepl();
|
|
1582
1515
|
}
|
|
1583
|
-
configData = configValidated.value;
|
|
1584
|
-
}
|
|
1585
|
-
let envData = cliOptions?.envData;
|
|
1586
|
-
if (envSchema) {
|
|
1587
|
-
const rawEnv = cliOptions?.env ?? (typeof process !== "undefined" ? process.env : {});
|
|
1588
|
-
const envValidated = envSchema["~standard"].validate(rawEnv);
|
|
1589
|
-
if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
|
|
1590
|
-
if (!envValidated.issues) envData = envValidated.value;
|
|
1591
1516
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
const issueMessages = optionsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n");
|
|
1598
|
-
if (input === void 0) {
|
|
1599
|
-
const helpText = generateHelp(existingCommand, command, { format: "text" });
|
|
1600
|
-
console.error(`Validation error:\n${issueMessages}`);
|
|
1601
|
-
console.error(helpText);
|
|
1602
|
-
throw new Error(`Validation error:\n${issueMessages}`);
|
|
1517
|
+
let updateCheckPromise;
|
|
1518
|
+
if (existingCommand.updateCheck) {
|
|
1519
|
+
if (!(resolvedInput && parseCliInputToParts(resolvedInput).some((p) => p.type === "named" && p.key.length === 1 && p.key[0] === "no-update-check"))) {
|
|
1520
|
+
const currentVersion = getVersion(existingCommand.version);
|
|
1521
|
+
updateCheckPromise = import("./update-check-EbNDkzyV.mjs").then(({ createUpdateChecker }) => createUpdateChecker(existingCommand.name, currentVersion, existingCommand.updateCheck, runtime));
|
|
1603
1522
|
}
|
|
1604
|
-
return {
|
|
1605
|
-
command,
|
|
1606
|
-
options: void 0,
|
|
1607
|
-
optionsResult,
|
|
1608
|
-
result: void 0
|
|
1609
|
-
};
|
|
1610
1523
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1524
|
+
const result = execCommand(resolvedInput, cliOptions, "hard");
|
|
1525
|
+
if (updateCheckPromise) {
|
|
1526
|
+
if (result instanceof Promise) return result.then(async (r) => {
|
|
1527
|
+
(await updateCheckPromise)?.();
|
|
1528
|
+
return r;
|
|
1529
|
+
});
|
|
1530
|
+
updateCheckPromise.then((show) => show?.());
|
|
1531
|
+
}
|
|
1532
|
+
return result;
|
|
1615
1533
|
};
|
|
1616
|
-
const run = (command,
|
|
1534
|
+
const run = (command, args) => {
|
|
1617
1535
|
const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1618
|
-
if (!commandObj) throw new
|
|
1619
|
-
if (!commandObj.
|
|
1620
|
-
|
|
1536
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1537
|
+
if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
|
|
1538
|
+
const executeCtx = {
|
|
1621
1539
|
command: commandObj,
|
|
1622
|
-
|
|
1623
|
-
|
|
1540
|
+
args,
|
|
1541
|
+
state: {}
|
|
1542
|
+
};
|
|
1543
|
+
const coreExecute = () => {
|
|
1544
|
+
return { result: commandObj.action(executeCtx.args, createActionContext(commandObj)) };
|
|
1624
1545
|
};
|
|
1546
|
+
const executedOrPromise = runPluginChain("execute", collectPlugins(commandObj), executeCtx, coreExecute);
|
|
1547
|
+
const toResult = (e) => ({
|
|
1548
|
+
command: commandObj,
|
|
1549
|
+
args,
|
|
1550
|
+
result: e.result
|
|
1551
|
+
});
|
|
1552
|
+
if (executedOrPromise instanceof Promise) return executedOrPromise.then(toResult);
|
|
1553
|
+
return toResult(executedOrPromise);
|
|
1625
1554
|
};
|
|
1626
1555
|
const tool = () => {
|
|
1627
|
-
const description =
|
|
1628
|
-
This is a CLI tool created with Padrone. You can run any of the defined commands described in the help text below. If you need assistance, refer to the documentation or use the help command.
|
|
1629
|
-
|
|
1630
|
-
<help_output>
|
|
1631
|
-
${generateHelp(existingCommand, void 0, {
|
|
1632
|
-
format: "text",
|
|
1633
|
-
detail: "full"
|
|
1634
|
-
})}
|
|
1635
|
-
</help_output>
|
|
1636
|
-
`;
|
|
1556
|
+
const description = `Run a command. Pass the full command string including arguments. Use "help <command>" for detailed usage.\n\n${generateHelp(existingCommand, void 0, { format: "text" })}`;
|
|
1637
1557
|
return {
|
|
1638
1558
|
type: "function",
|
|
1639
1559
|
name: existingCommand.name,
|
|
1640
1560
|
strict: true,
|
|
1641
1561
|
title: existingCommand.description,
|
|
1642
1562
|
description,
|
|
1643
|
-
inputExamples: [{ input: { command: "<command> [
|
|
1563
|
+
inputExamples: [{ input: { command: "<command> [positionals...] [arguments...]" } }],
|
|
1644
1564
|
inputSchema: {
|
|
1645
1565
|
[Symbol.for("vercel.ai.schema")]: true,
|
|
1646
1566
|
jsonSchema: {
|
|
@@ -1661,64 +1581,105 @@ ${generateHelp(existingCommand, void 0, {
|
|
|
1661
1581
|
};
|
|
1662
1582
|
}
|
|
1663
1583
|
},
|
|
1664
|
-
needsApproval: (input) => {
|
|
1665
|
-
const
|
|
1666
|
-
if (typeof command.needsApproval === "function") return command.needsApproval(
|
|
1667
|
-
return !!command.needsApproval;
|
|
1584
|
+
needsApproval: async (input) => {
|
|
1585
|
+
const parsed = await parse(input.command);
|
|
1586
|
+
if (typeof parsed.command.needsApproval === "function") return parsed.command.needsApproval(parsed.args);
|
|
1587
|
+
return !!parsed.command.needsApproval;
|
|
1668
1588
|
},
|
|
1669
|
-
execute: (input) => {
|
|
1670
|
-
|
|
1589
|
+
execute: async (input) => {
|
|
1590
|
+
const output = [];
|
|
1591
|
+
const errors = [];
|
|
1592
|
+
return {
|
|
1593
|
+
result: (await evalCommand(input.command, {
|
|
1594
|
+
autoOutput: false,
|
|
1595
|
+
runtime: {
|
|
1596
|
+
output: (...args) => output.push(args.map(String).join(" ")),
|
|
1597
|
+
error: (text) => errors.push(text),
|
|
1598
|
+
interactive: "unsupported",
|
|
1599
|
+
format: "text"
|
|
1600
|
+
}
|
|
1601
|
+
})).result,
|
|
1602
|
+
logs: output.join("\n"),
|
|
1603
|
+
error: errors.join("\n")
|
|
1604
|
+
};
|
|
1671
1605
|
}
|
|
1672
1606
|
};
|
|
1673
1607
|
};
|
|
1674
|
-
|
|
1608
|
+
const builder = {
|
|
1675
1609
|
configure(config) {
|
|
1676
1610
|
return createPadroneBuilder({
|
|
1677
1611
|
...existingCommand,
|
|
1678
1612
|
...config
|
|
1679
1613
|
});
|
|
1680
1614
|
},
|
|
1681
|
-
|
|
1682
|
-
|
|
1615
|
+
runtime(runtimeConfig) {
|
|
1616
|
+
return createPadroneBuilder({
|
|
1617
|
+
...existingCommand,
|
|
1618
|
+
runtime: {
|
|
1619
|
+
...existingCommand.runtime,
|
|
1620
|
+
...runtimeConfig
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
},
|
|
1624
|
+
async() {
|
|
1683
1625
|
return createPadroneBuilder({
|
|
1684
1626
|
...existingCommand,
|
|
1685
|
-
|
|
1686
|
-
|
|
1627
|
+
isAsync: true
|
|
1628
|
+
});
|
|
1629
|
+
},
|
|
1630
|
+
arguments(schema, meta) {
|
|
1631
|
+
const resolvedArgs = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
|
|
1632
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedArgs) || hasInteractiveConfig(meta);
|
|
1633
|
+
return createPadroneBuilder({
|
|
1634
|
+
...existingCommand,
|
|
1635
|
+
argsSchema: resolvedArgs,
|
|
1636
|
+
meta,
|
|
1637
|
+
isAsync
|
|
1687
1638
|
});
|
|
1688
1639
|
},
|
|
1689
1640
|
configFile(file, schema) {
|
|
1690
1641
|
const configFiles = file === void 0 ? void 0 : Array.isArray(file) ? file : [file];
|
|
1691
|
-
const resolvedConfig = typeof schema === "function" ? schema(existingCommand.
|
|
1642
|
+
const resolvedConfig = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema ?? existingCommand.argsSchema;
|
|
1643
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedConfig);
|
|
1692
1644
|
return createPadroneBuilder({
|
|
1693
1645
|
...existingCommand,
|
|
1694
1646
|
configFiles,
|
|
1695
|
-
|
|
1647
|
+
configSchema: resolvedConfig,
|
|
1648
|
+
isAsync
|
|
1696
1649
|
});
|
|
1697
1650
|
},
|
|
1698
1651
|
env(schema) {
|
|
1699
|
-
const resolvedEnv = typeof schema === "function" ? schema(existingCommand.
|
|
1652
|
+
const resolvedEnv = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
|
|
1653
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
|
|
1700
1654
|
return createPadroneBuilder({
|
|
1701
1655
|
...existingCommand,
|
|
1702
|
-
envSchema: resolvedEnv
|
|
1656
|
+
envSchema: resolvedEnv,
|
|
1657
|
+
isAsync
|
|
1703
1658
|
});
|
|
1704
1659
|
},
|
|
1705
1660
|
action(handler = noop) {
|
|
1661
|
+
const baseHandler = existingCommand.action ?? noop;
|
|
1706
1662
|
return createPadroneBuilder({
|
|
1707
1663
|
...existingCommand,
|
|
1708
|
-
handler
|
|
1664
|
+
action: (args, ctx) => handler(args, ctx, baseHandler)
|
|
1709
1665
|
});
|
|
1710
1666
|
},
|
|
1711
1667
|
wrap(config) {
|
|
1712
|
-
const handler = createWrapHandler(config, existingCommand.
|
|
1668
|
+
const handler = createWrapHandler(config, existingCommand.argsSchema, existingCommand.meta?.positional);
|
|
1713
1669
|
return createPadroneBuilder({
|
|
1714
1670
|
...existingCommand,
|
|
1715
|
-
handler
|
|
1671
|
+
action: handler
|
|
1716
1672
|
});
|
|
1717
1673
|
},
|
|
1718
1674
|
command(nameOrNames, builderFn) {
|
|
1719
1675
|
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
1720
1676
|
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
1721
|
-
const
|
|
1677
|
+
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
1678
|
+
const initialCommand = existingSubcommand ? {
|
|
1679
|
+
...existingSubcommand,
|
|
1680
|
+
aliases: aliases ?? existingSubcommand.aliases,
|
|
1681
|
+
parent: existingCommand
|
|
1682
|
+
} : {
|
|
1722
1683
|
name,
|
|
1723
1684
|
path: existingCommand.path ? `${existingCommand.path} ${name}` : name,
|
|
1724
1685
|
aliases,
|
|
@@ -1727,39 +1688,94 @@ ${generateHelp(existingCommand, void 0, {
|
|
|
1727
1688
|
};
|
|
1728
1689
|
const builder = createPadroneBuilder(initialCommand);
|
|
1729
1690
|
const commandObj = (builderFn?.(builder))?.[commandSymbol] ?? initialCommand;
|
|
1691
|
+
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
|
|
1692
|
+
const commands = existingCommand.commands || [];
|
|
1693
|
+
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1694
|
+
const updatedCommands = existingIndex >= 0 ? [
|
|
1695
|
+
...commands.slice(0, existingIndex),
|
|
1696
|
+
mergedCommandObj,
|
|
1697
|
+
...commands.slice(existingIndex + 1)
|
|
1698
|
+
] : [...commands, mergedCommandObj];
|
|
1699
|
+
return createPadroneBuilder({
|
|
1700
|
+
...existingCommand,
|
|
1701
|
+
commands: updatedCommands
|
|
1702
|
+
});
|
|
1703
|
+
},
|
|
1704
|
+
mount(nameOrNames, program) {
|
|
1705
|
+
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
1706
|
+
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
1707
|
+
const programCommand = program[commandSymbol];
|
|
1708
|
+
if (!programCommand) throw new RoutingError("Cannot mount: not a valid Padrone program");
|
|
1709
|
+
const remounted = repathCommandTree(programCommand, name, existingCommand.path || "", existingCommand);
|
|
1710
|
+
remounted.aliases = aliases;
|
|
1711
|
+
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
1712
|
+
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, remounted) : remounted;
|
|
1713
|
+
const commands = existingCommand.commands || [];
|
|
1714
|
+
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1715
|
+
const updatedCommands = existingIndex >= 0 ? [
|
|
1716
|
+
...commands.slice(0, existingIndex),
|
|
1717
|
+
mergedCommandObj,
|
|
1718
|
+
...commands.slice(existingIndex + 1)
|
|
1719
|
+
] : [...commands, mergedCommandObj];
|
|
1730
1720
|
return createPadroneBuilder({
|
|
1731
1721
|
...existingCommand,
|
|
1732
|
-
commands:
|
|
1722
|
+
commands: updatedCommands
|
|
1723
|
+
});
|
|
1724
|
+
},
|
|
1725
|
+
use(plugin) {
|
|
1726
|
+
return createPadroneBuilder({
|
|
1727
|
+
...existingCommand,
|
|
1728
|
+
plugins: [...existingCommand.plugins ?? [], plugin]
|
|
1729
|
+
});
|
|
1730
|
+
},
|
|
1731
|
+
updateCheck(config = {}) {
|
|
1732
|
+
return createPadroneBuilder({
|
|
1733
|
+
...existingCommand,
|
|
1734
|
+
updateCheck: config
|
|
1733
1735
|
});
|
|
1734
1736
|
},
|
|
1735
1737
|
run,
|
|
1736
1738
|
find,
|
|
1737
1739
|
parse,
|
|
1738
1740
|
stringify,
|
|
1741
|
+
eval: evalCommand,
|
|
1739
1742
|
cli,
|
|
1740
1743
|
tool,
|
|
1744
|
+
repl: replFn = (options) => {
|
|
1745
|
+
return createReplIterator({
|
|
1746
|
+
existingCommand,
|
|
1747
|
+
evalCommand,
|
|
1748
|
+
replActiveRef
|
|
1749
|
+
}, options);
|
|
1750
|
+
},
|
|
1741
1751
|
api() {
|
|
1742
1752
|
function buildApi(command) {
|
|
1743
|
-
const runCommand = ((
|
|
1753
|
+
const runCommand = ((args) => run(command, args).result);
|
|
1744
1754
|
if (!command.commands) return runCommand;
|
|
1745
1755
|
for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
|
|
1746
1756
|
return runCommand;
|
|
1747
1757
|
}
|
|
1748
1758
|
return buildApi(existingCommand);
|
|
1749
1759
|
},
|
|
1750
|
-
help(command,
|
|
1760
|
+
help(command, prefs) {
|
|
1751
1761
|
const commandObj = !command ? existingCommand : typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1752
|
-
if (!commandObj) throw new
|
|
1753
|
-
|
|
1762
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1763
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1764
|
+
return generateHelp(existingCommand, commandObj, {
|
|
1765
|
+
...prefs,
|
|
1766
|
+
format: prefs?.format ?? runtime.format
|
|
1767
|
+
});
|
|
1754
1768
|
},
|
|
1755
|
-
completion(shell) {
|
|
1769
|
+
async completion(shell) {
|
|
1770
|
+
const { generateCompletionOutput } = await import("./completion.mjs");
|
|
1756
1771
|
return generateCompletionOutput(existingCommand, shell);
|
|
1757
1772
|
},
|
|
1758
1773
|
"~types": {},
|
|
1759
1774
|
[commandSymbol]: existingCommand
|
|
1760
1775
|
};
|
|
1776
|
+
return builder;
|
|
1761
1777
|
}
|
|
1762
|
-
|
|
1763
1778
|
//#endregion
|
|
1764
|
-
export { createPadrone };
|
|
1779
|
+
export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, ValidationError, asyncSchema, buildReplCompleter, createPadrone };
|
|
1780
|
+
|
|
1765
1781
|
//# sourceMappingURL=index.mjs.map
|