padrone 1.0.0 → 1.2.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 +51 -0
- package/LICENSE +1 -1
- package/README.md +92 -49
- package/dist/args-CKNh7Dm9.mjs +175 -0
- package/dist/args-CKNh7Dm9.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 +1348 -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 +404 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/formatter-Dvx7jFXr.d.mts +82 -0
- package/dist/formatter-Dvx7jFXr.d.mts.map +1 -0
- package/dist/help-mUIX0T0V.mjs +1195 -0
- package/dist/help-mUIX0T0V.mjs.map +1 -0
- package/dist/index.d.mts +122 -438
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1240 -1161
- 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-qrtt0135.d.mts +1037 -0
- package/dist/types-qrtt0135.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 -20
- package/src/args.ts +365 -0
- package/src/cli/completions.ts +29 -0
- package/src/cli/docs.ts +86 -0
- package/src/cli/doctor.ts +312 -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 +184 -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 +501 -0
- package/src/completion.ts +110 -97
- package/src/create.ts +1044 -284
- package/src/docs/index.ts +607 -0
- package/src/errors.ts +131 -0
- package/src/formatter.ts +149 -63
- package/src/help.ts +151 -55
- package/src/index.ts +13 -15
- package/src/interactive.ts +169 -0
- package/src/parse.ts +31 -16
- 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 +12 -12
- package/src/type-utils.ts +124 -14
- package/src/types.ts +803 -144
- package/src/update-check.ts +244 -0
- package/src/wrap.ts +185 -0
- 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 parseStdinConfig, i as parsePositionalConfig, n as detectUnknownArgs, o as preprocessArgs, r as extractSchemaMetadata, t as coerceArgs } from "./args-CKNh7Dm9.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-mUIX0T0V.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,29 +272,43 @@ 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] =
|
|
311
|
+
const [keyStr = "", value] = splitNamedArgValue(part.slice(1));
|
|
1053
312
|
const p = {
|
|
1054
313
|
type: "alias",
|
|
1055
314
|
key: [keyStr],
|
|
@@ -1073,9 +332,9 @@ function parseCliInputToParts(input) {
|
|
|
1073
332
|
return result;
|
|
1074
333
|
}
|
|
1075
334
|
/**
|
|
1076
|
-
* Split
|
|
335
|
+
* Split named arg key and value, handling quoted values after =.
|
|
1077
336
|
*/
|
|
1078
|
-
function
|
|
337
|
+
function splitNamedArgValue(str) {
|
|
1079
338
|
const eqIndex = str.indexOf("=");
|
|
1080
339
|
if (eqIndex === -1) return [str, void 0];
|
|
1081
340
|
const key = str.slice(0, eqIndex);
|
|
@@ -1146,11 +405,275 @@ function parseArrayItems(input) {
|
|
|
1146
405
|
if (current || items.length > 0) items.push(current.trim());
|
|
1147
406
|
return items;
|
|
1148
407
|
}
|
|
1149
|
-
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/repl-loop.ts
|
|
410
|
+
/**
|
|
411
|
+
* Creates a REPL async iterable for running commands interactively.
|
|
412
|
+
*/
|
|
413
|
+
function createReplIterator(deps, options) {
|
|
414
|
+
const { existingCommand, evalCommand, replActiveRef } = deps;
|
|
415
|
+
if (replActiveRef.value) {
|
|
416
|
+
getCommandRuntime(existingCommand).error("REPL is already running. Nested REPL sessions are not supported.");
|
|
417
|
+
return (async function* () {})();
|
|
418
|
+
}
|
|
419
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
420
|
+
const programName = existingCommand.name || "padrone";
|
|
421
|
+
const useAnsi = runtime.format === "ansi" || runtime.format === "auto" && typeof process !== "undefined" && !process.env.NO_COLOR && !process.env.CI && process.stdout?.isTTY;
|
|
422
|
+
const commandHistory = [];
|
|
423
|
+
const resolveScope = (scope) => {
|
|
424
|
+
const parts = scope.split(/\s+/);
|
|
425
|
+
const stack = [];
|
|
426
|
+
let current = existingCommand;
|
|
427
|
+
for (const part of parts) {
|
|
428
|
+
const found = findCommandByName(part, current.commands);
|
|
429
|
+
if (!found) break;
|
|
430
|
+
stack.push(found);
|
|
431
|
+
current = found;
|
|
432
|
+
}
|
|
433
|
+
return stack;
|
|
434
|
+
};
|
|
435
|
+
async function* replIterator() {
|
|
436
|
+
replActiveRef.value = true;
|
|
437
|
+
const showGreeting = options?.greeting !== false;
|
|
438
|
+
const showHint = options?.hint !== false;
|
|
439
|
+
if (showGreeting || showHint) runtime.output("");
|
|
440
|
+
if (showGreeting) if (options?.greeting) runtime.output(options.greeting);
|
|
441
|
+
else {
|
|
442
|
+
const displayName = existingCommand.title || programName;
|
|
443
|
+
const version = existingCommand.version ? getVersion(existingCommand.version) : void 0;
|
|
444
|
+
const greeting = version ? `Welcome to ${displayName} v${version}` : `Welcome to ${displayName}`;
|
|
445
|
+
runtime.output(greeting);
|
|
446
|
+
}
|
|
447
|
+
if (showHint) {
|
|
448
|
+
const hintText = (typeof options?.hint === "string" ? options.hint : void 0) ?? "Type \".help\" for more information, \".exit\" to quit.";
|
|
449
|
+
runtime.output(useAnsi ? `\x1b[2m${hintText}\x1b[0m` : hintText);
|
|
450
|
+
}
|
|
451
|
+
if (showGreeting || showHint) runtime.output("");
|
|
452
|
+
const scopeStack = options?.scope ? resolveScope(options.scope) : [];
|
|
453
|
+
const getScopeCommand = () => scopeStack.length ? scopeStack[scopeStack.length - 1] : existingCommand;
|
|
454
|
+
const getScopePath = () => scopeStack.map((c) => c.name).join(" ");
|
|
455
|
+
const buildPrompt = () => {
|
|
456
|
+
if (options?.prompt) return typeof options.prompt === "function" ? options.prompt() : options.prompt;
|
|
457
|
+
const scopePath = getScopePath();
|
|
458
|
+
const label = scopePath ? `${programName}/${scopePath.replace(/ /g, "/")}` : programName;
|
|
459
|
+
return useAnsi ? `\x1b[1m${label}\x1b[0m ❯ ` : `${label} ❯ `;
|
|
460
|
+
};
|
|
461
|
+
const buildScopedCompleter = () => {
|
|
462
|
+
return buildReplCompleter(getScopeCommand(), { inScope: scopeStack.length > 0 });
|
|
463
|
+
};
|
|
464
|
+
const sessionConfig = { history: options?.history };
|
|
465
|
+
if (options?.completion !== false) sessionConfig.completer = buildScopedCompleter();
|
|
466
|
+
const session = runtime.readLine ? void 0 : createTerminalReplSession(sessionConfig);
|
|
467
|
+
const questionFn = session ? (prompt) => session.question(prompt) : runtime.readLine;
|
|
468
|
+
const updateCompleter = () => {
|
|
469
|
+
if (options?.completion === false) return;
|
|
470
|
+
const completer = buildScopedCompleter();
|
|
471
|
+
if (session) session.completer = completer;
|
|
472
|
+
sessionConfig.completer = completer;
|
|
473
|
+
};
|
|
474
|
+
let lastSigintTime = 0;
|
|
475
|
+
try {
|
|
476
|
+
while (true) {
|
|
477
|
+
const input = await questionFn(buildPrompt());
|
|
478
|
+
if (input === null) break;
|
|
479
|
+
if (input === REPL_SIGINT) {
|
|
480
|
+
const now = Date.now();
|
|
481
|
+
if (now - lastSigintTime < 2e3) break;
|
|
482
|
+
lastSigintTime = now;
|
|
483
|
+
runtime.output("(press Ctrl+C again to exit, or Ctrl+D)");
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const trimmed = input.trim();
|
|
487
|
+
if (!trimmed) continue;
|
|
488
|
+
lastSigintTime = 0;
|
|
489
|
+
commandHistory.push(trimmed);
|
|
490
|
+
if (trimmed === ".exit" || trimmed === ".quit") break;
|
|
491
|
+
if (trimmed === ".clear") {
|
|
492
|
+
runtime.output("\x1B[2J\x1B[H");
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (trimmed === ".help") {
|
|
496
|
+
const lines = [
|
|
497
|
+
"REPL Commands:",
|
|
498
|
+
" . Execute the current scoped command",
|
|
499
|
+
" .help Print this help message",
|
|
500
|
+
" .exit Exit the REPL",
|
|
501
|
+
" .clear Clear the screen",
|
|
502
|
+
" .history Show command history",
|
|
503
|
+
" .scope <cmd> Scope into a subcommand",
|
|
504
|
+
" .scope .. Go up one scope level"
|
|
505
|
+
];
|
|
506
|
+
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.");
|
|
507
|
+
runtime.output(lines.join("\n"));
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (trimmed === ".history") {
|
|
511
|
+
const entries = commandHistory.slice(0, -1);
|
|
512
|
+
if (entries.length === 0) runtime.output("No history.");
|
|
513
|
+
else runtime.output(entries.map((entry, i) => `${i + 1} ${entry}`).join("\n"));
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (trimmed.startsWith(".scope ") || trimmed === ".scope") {
|
|
517
|
+
const target = trimmed.slice(6).trim();
|
|
518
|
+
if (target === ".." || target === "") {
|
|
519
|
+
if (scopeStack.length > 0) {
|
|
520
|
+
scopeStack.pop();
|
|
521
|
+
updateCompleter();
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
const found = findCommandByName(target, getScopeCommand().commands);
|
|
525
|
+
if (found) if (found.commands?.length) {
|
|
526
|
+
scopeStack.push(found);
|
|
527
|
+
updateCompleter();
|
|
528
|
+
} else runtime.error(`"${target}" has no subcommands to scope into.`);
|
|
529
|
+
else runtime.error(`Unknown command: ${target}`);
|
|
530
|
+
}
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if (trimmed === "..") {
|
|
534
|
+
if (scopeStack.length > 0) {
|
|
535
|
+
scopeStack.pop();
|
|
536
|
+
updateCompleter();
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
let evalInput = trimmed;
|
|
541
|
+
if (trimmed === ".") evalInput = "";
|
|
542
|
+
const prefix = options?.outputPrefix;
|
|
543
|
+
const prefixLines = prefix ? (text) => text.split("\n").map((l) => prefix + l).join("\n") : void 0;
|
|
544
|
+
const savedRuntimes = [];
|
|
545
|
+
if (prefixLines) {
|
|
546
|
+
const prefixedRuntime = {
|
|
547
|
+
...existingCommand.runtime,
|
|
548
|
+
output: (...args) => {
|
|
549
|
+
const first = args[0];
|
|
550
|
+
runtime.output(typeof first === "string" ? prefixLines(first) : first, ...args.slice(1));
|
|
551
|
+
},
|
|
552
|
+
error: (text) => runtime.error(prefixLines(text))
|
|
553
|
+
};
|
|
554
|
+
const patchAll = (cmd) => {
|
|
555
|
+
savedRuntimes.push({
|
|
556
|
+
cmd,
|
|
557
|
+
runtime: cmd.runtime
|
|
558
|
+
});
|
|
559
|
+
cmd.runtime = prefixedRuntime;
|
|
560
|
+
cmd.commands?.forEach(patchAll);
|
|
561
|
+
};
|
|
562
|
+
patchAll(existingCommand);
|
|
563
|
+
}
|
|
564
|
+
const sp = options?.spacing;
|
|
565
|
+
const isSpacingObject = typeof sp === "object" && sp !== null && !Array.isArray(sp);
|
|
566
|
+
const spacingBefore = isSpacingObject ? sp.before : sp;
|
|
567
|
+
const spacingAfter = isSpacingObject ? sp.after : sp;
|
|
568
|
+
const emitSpacingLine = (value) => {
|
|
569
|
+
if (typeof value === "string") {
|
|
570
|
+
const sep = value.length === 1 ? value.repeat(typeof process !== "undefined" && process.stdout?.columns ? process.stdout.columns : 80) : value;
|
|
571
|
+
runtime.output(sep);
|
|
572
|
+
} else if (value) runtime.output("");
|
|
573
|
+
};
|
|
574
|
+
const emitSpacing = (value) => {
|
|
575
|
+
if (!value) return;
|
|
576
|
+
if (Array.isArray(value)) for (const line of value) emitSpacingLine(line);
|
|
577
|
+
else emitSpacingLine(value);
|
|
578
|
+
};
|
|
579
|
+
emitSpacing(spacingBefore);
|
|
580
|
+
const scopePath = getScopePath();
|
|
581
|
+
const scopedInput = scopePath ? evalInput ? `${scopePath} ${evalInput}` : scopePath : evalInput;
|
|
582
|
+
try {
|
|
583
|
+
const result = await evalCommand(scopedInput, options?.autoOutput === false ? { autoOutput: false } : void 0);
|
|
584
|
+
if (result.argsResult?.issues) {
|
|
585
|
+
const msg = `Validation error:\n${result.argsResult.issues.map((i) => ` - ${i.path?.join(".") || "root"}: ${i.message}`).join("\n")}`;
|
|
586
|
+
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
587
|
+
}
|
|
588
|
+
yield result;
|
|
589
|
+
} catch (err) {
|
|
590
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
591
|
+
runtime.error(prefixLines ? prefixLines(msg) : msg);
|
|
592
|
+
} finally {
|
|
593
|
+
for (const { cmd, runtime: saved } of savedRuntimes) cmd.runtime = saved;
|
|
594
|
+
emitSpacing(spacingAfter);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
} finally {
|
|
598
|
+
replActiveRef.value = false;
|
|
599
|
+
session?.close();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return replIterator();
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/wrap.ts
|
|
606
|
+
/**
|
|
607
|
+
* Converts parsed arguments to CLI arguments for an external command.
|
|
608
|
+
*/
|
|
609
|
+
function argsToCliArgs(input, positional = []) {
|
|
610
|
+
const args = [];
|
|
611
|
+
if (!input) return args;
|
|
612
|
+
const positionalValues = {};
|
|
613
|
+
const regularArguments = {};
|
|
614
|
+
for (const [key, value] of Object.entries(input)) if (positional.includes(key) || positional.includes(`...${key}`)) positionalValues[key] = value;
|
|
615
|
+
else regularArguments[key] = value;
|
|
616
|
+
for (const [key, value] of Object.entries(regularArguments)) {
|
|
617
|
+
if (value === void 0 || value === null) continue;
|
|
618
|
+
const flag = `--${key}`;
|
|
619
|
+
if (typeof value === "boolean") {
|
|
620
|
+
if (value) args.push(flag);
|
|
621
|
+
} else if (Array.isArray(value)) for (const item of value) args.push(flag, String(item));
|
|
622
|
+
else args.push(flag, String(value));
|
|
623
|
+
}
|
|
624
|
+
for (const posKey of positional) {
|
|
625
|
+
const isVariadic = posKey.startsWith("...");
|
|
626
|
+
const value = positionalValues[isVariadic ? posKey.slice(3) : posKey];
|
|
627
|
+
if (value === void 0 || value === null) continue;
|
|
628
|
+
if (isVariadic && Array.isArray(value)) args.push(...value.map(String));
|
|
629
|
+
else args.push(String(value));
|
|
630
|
+
}
|
|
631
|
+
return args;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Creates an action handler that wraps an external CLI tool.
|
|
635
|
+
* @param config - Configuration for wrapping the external command (includes optional schema)
|
|
636
|
+
* @param commandArguments - The command's arguments schema
|
|
637
|
+
* @param commandPositional - Default positional config from the wrapping command
|
|
638
|
+
*/
|
|
639
|
+
function createWrapHandler(config, commandArguments, commandPositional) {
|
|
640
|
+
return async (args) => {
|
|
641
|
+
const { command, args: fixedArgs = [], inheritStdio = true, positional = commandPositional, schema: wrapSchema } = config;
|
|
642
|
+
const validationResult = (wrapSchema ? typeof wrapSchema === "function" ? wrapSchema(commandArguments) : wrapSchema : commandArguments)["~standard"].validate(args);
|
|
643
|
+
const processResult = (result) => {
|
|
644
|
+
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);
|
|
645
|
+
return result.value;
|
|
646
|
+
};
|
|
647
|
+
const regularArgs = argsToCliArgs(validationResult instanceof Promise ? await validationResult.then(processResult) : processResult(validationResult), positional);
|
|
648
|
+
const allArgs = [...fixedArgs, ...regularArgs];
|
|
649
|
+
const proc = Bun.spawn([command, ...allArgs], {
|
|
650
|
+
stdout: inheritStdio ? "inherit" : "pipe",
|
|
651
|
+
stderr: inheritStdio ? "inherit" : "pipe",
|
|
652
|
+
stdin: inheritStdio ? "inherit" : "ignore"
|
|
653
|
+
});
|
|
654
|
+
const exitCode = await proc.exited;
|
|
655
|
+
let stdout;
|
|
656
|
+
let stderr;
|
|
657
|
+
if (!inheritStdio) {
|
|
658
|
+
if (proc.stdout) {
|
|
659
|
+
const stdoutBuffer = await new Response(proc.stdout).arrayBuffer();
|
|
660
|
+
stdout = new TextDecoder().decode(stdoutBuffer);
|
|
661
|
+
}
|
|
662
|
+
if (proc.stderr) {
|
|
663
|
+
const stderrBuffer = await new Response(proc.stderr).arrayBuffer();
|
|
664
|
+
stderr = new TextDecoder().decode(stderrBuffer);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
exitCode,
|
|
669
|
+
stdout,
|
|
670
|
+
stderr,
|
|
671
|
+
success: exitCode === 0
|
|
672
|
+
};
|
|
673
|
+
};
|
|
674
|
+
}
|
|
1150
675
|
//#endregion
|
|
1151
676
|
//#region src/create.ts
|
|
1152
|
-
const commandSymbol = Symbol("padrone_command");
|
|
1153
|
-
const noop = () => void 0;
|
|
1154
677
|
function createPadrone(name) {
|
|
1155
678
|
return createPadroneBuilder({
|
|
1156
679
|
name,
|
|
@@ -1158,162 +681,261 @@ function createPadrone(name) {
|
|
|
1158
681
|
commands: []
|
|
1159
682
|
});
|
|
1160
683
|
}
|
|
1161
|
-
function createPadroneBuilder(
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
const subCommand = findCommandByName(name.slice(alias.length + 1), cmd.commands);
|
|
1176
|
-
if (subCommand) return subCommand;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
684
|
+
function createPadroneBuilder(inputCommand) {
|
|
685
|
+
const existingCommand = inputCommand.commands?.length && inputCommand.commands.some((c) => c.parent && c.parent !== inputCommand) ? {
|
|
686
|
+
...inputCommand,
|
|
687
|
+
commands: inputCommand.commands.map((c) => c.parent && c.parent !== inputCommand ? {
|
|
688
|
+
...c,
|
|
689
|
+
parent: inputCommand
|
|
690
|
+
} : c)
|
|
691
|
+
} : inputCommand;
|
|
692
|
+
/** Creates the action context passed to command handlers. References `builder` which is defined later but only called at runtime. */
|
|
693
|
+
const createActionContext = (cmd) => ({
|
|
694
|
+
runtime: getCommandRuntime(cmd),
|
|
695
|
+
command: cmd,
|
|
696
|
+
program: builder
|
|
697
|
+
});
|
|
1181
698
|
const find = (command) => {
|
|
1182
699
|
if (typeof command !== "string") return findCommandByName(command.path, existingCommand.commands);
|
|
1183
700
|
return findCommandByName(command, existingCommand.commands);
|
|
1184
701
|
};
|
|
1185
702
|
/**
|
|
1186
|
-
* Parses CLI input to find the command and extract raw
|
|
703
|
+
* Parses CLI input to find the command and extract raw arguments without validation.
|
|
1187
704
|
*/
|
|
1188
705
|
const parseCommand = (input) => {
|
|
1189
|
-
input ??=
|
|
1190
|
-
if (!input)
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
706
|
+
input ??= getCommandRuntime(existingCommand).argv().join(" ") || void 0;
|
|
707
|
+
if (!input) {
|
|
708
|
+
const defaultCommand = findCommandByName("", existingCommand.commands);
|
|
709
|
+
if (defaultCommand) return {
|
|
710
|
+
command: defaultCommand,
|
|
711
|
+
rawArgs: {},
|
|
712
|
+
args: [],
|
|
713
|
+
unmatchedTerms: []
|
|
714
|
+
};
|
|
715
|
+
return {
|
|
716
|
+
command: existingCommand,
|
|
717
|
+
rawArgs: {},
|
|
718
|
+
args: [],
|
|
719
|
+
unmatchedTerms: []
|
|
720
|
+
};
|
|
721
|
+
}
|
|
1195
722
|
const parts = parseCliInputToParts(input);
|
|
1196
723
|
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
1197
724
|
const args = parts.filter((p) => p.type === "arg").map((p) => p.value);
|
|
1198
725
|
let curCommand = existingCommand;
|
|
726
|
+
let unmatchedTerms = [];
|
|
1199
727
|
if (terms[0] === existingCommand.name) terms.shift();
|
|
1200
728
|
for (let i = 0; i < terms.length; i++) {
|
|
1201
729
|
const found = findCommandByName(terms[i] || "", curCommand.commands);
|
|
1202
730
|
if (found) curCommand = found;
|
|
1203
731
|
else {
|
|
1204
|
-
|
|
732
|
+
unmatchedTerms = terms.slice(i);
|
|
733
|
+
args.unshift(...unmatchedTerms);
|
|
1205
734
|
break;
|
|
1206
735
|
}
|
|
1207
736
|
}
|
|
737
|
+
if (unmatchedTerms.length === 0 && curCommand.commands?.length) {
|
|
738
|
+
const defaultCommand = findCommandByName("", curCommand.commands);
|
|
739
|
+
if (defaultCommand) curCommand = defaultCommand;
|
|
740
|
+
}
|
|
1208
741
|
if (!curCommand) return {
|
|
1209
742
|
command: existingCommand,
|
|
1210
|
-
|
|
1211
|
-
args
|
|
743
|
+
rawArgs: {},
|
|
744
|
+
args,
|
|
745
|
+
unmatchedTerms
|
|
1212
746
|
};
|
|
1213
|
-
const
|
|
1214
|
-
const { aliases } = curCommand.
|
|
1215
|
-
const
|
|
1216
|
-
if (curCommand.
|
|
1217
|
-
const jsonSchema = curCommand.
|
|
747
|
+
const argsMeta = curCommand.meta?.fields;
|
|
748
|
+
const { aliases } = curCommand.argsSchema ? extractSchemaMetadata(curCommand.argsSchema, argsMeta) : { aliases: {} };
|
|
749
|
+
const arrayArguments = /* @__PURE__ */ new Set();
|
|
750
|
+
if (curCommand.argsSchema) try {
|
|
751
|
+
const jsonSchema = curCommand.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1218
752
|
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
1219
|
-
for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array")
|
|
753
|
+
for (const [key, prop] of Object.entries(jsonSchema.properties)) if (prop?.type === "array") arrayArguments.add(key);
|
|
1220
754
|
}
|
|
1221
755
|
} catch {}
|
|
1222
|
-
const
|
|
1223
|
-
const
|
|
1224
|
-
for (const
|
|
1225
|
-
const key =
|
|
756
|
+
const argParts = parts.filter((p) => p.type === "named" || p.type === "alias");
|
|
757
|
+
const rawArgs = {};
|
|
758
|
+
for (const arg of argParts) {
|
|
759
|
+
const key = arg.type === "alias" && arg.key.length === 1 && aliases[arg.key[0]] ? [aliases[arg.key[0]]] : arg.key;
|
|
1226
760
|
const rootKey = key[0];
|
|
1227
|
-
if (
|
|
1228
|
-
setNestedValue(
|
|
761
|
+
if (arg.type === "named" && arg.negated) {
|
|
762
|
+
setNestedValue(rawArgs, key, false);
|
|
1229
763
|
continue;
|
|
1230
764
|
}
|
|
1231
|
-
const value =
|
|
1232
|
-
if (
|
|
1233
|
-
const existing = getNestedValue(
|
|
765
|
+
const value = arg.value ?? true;
|
|
766
|
+
if (arrayArguments.has(rootKey)) {
|
|
767
|
+
const existing = getNestedValue(rawArgs, key);
|
|
1234
768
|
if (existing !== void 0) if (Array.isArray(existing)) if (Array.isArray(value)) existing.push(...value);
|
|
1235
769
|
else existing.push(value);
|
|
1236
|
-
else if (Array.isArray(value)) setNestedValue(
|
|
1237
|
-
else setNestedValue(
|
|
1238
|
-
else setNestedValue(
|
|
1239
|
-
} else setNestedValue(
|
|
770
|
+
else if (Array.isArray(value)) setNestedValue(rawArgs, key, [existing, ...value]);
|
|
771
|
+
else setNestedValue(rawArgs, key, [existing, value]);
|
|
772
|
+
else setNestedValue(rawArgs, key, Array.isArray(value) ? value : [value]);
|
|
773
|
+
} else setNestedValue(rawArgs, key, value);
|
|
1240
774
|
}
|
|
1241
775
|
return {
|
|
1242
776
|
command: curCommand,
|
|
1243
|
-
|
|
1244
|
-
args
|
|
777
|
+
rawArgs,
|
|
778
|
+
args,
|
|
779
|
+
unmatchedTerms
|
|
1245
780
|
};
|
|
1246
781
|
};
|
|
1247
782
|
/**
|
|
1248
|
-
*
|
|
783
|
+
* Preprocesses raw arguments: applies env/config values and maps positional arguments.
|
|
784
|
+
* Also performs auto-coercion (string→number/boolean) and unknown arg detection.
|
|
1249
785
|
*/
|
|
1250
|
-
const
|
|
1251
|
-
|
|
786
|
+
const buildCommandArgs = (command, rawArgs, args, context) => {
|
|
787
|
+
let preprocessedArgs = preprocessArgs(rawArgs, {
|
|
1252
788
|
aliases: {},
|
|
1253
|
-
|
|
1254
|
-
|
|
789
|
+
stdinData: context?.stdinData,
|
|
790
|
+
envData: context?.envData,
|
|
791
|
+
configData: context?.configData
|
|
1255
792
|
});
|
|
1256
793
|
const positionalConfig = command.meta?.positional ? parsePositionalConfig(command.meta.positional) : [];
|
|
1257
794
|
if (positionalConfig.length > 0) {
|
|
1258
795
|
let argIndex = 0;
|
|
1259
|
-
for (
|
|
796
|
+
for (let i = 0; i < positionalConfig.length; i++) {
|
|
797
|
+
const { name, variadic } = positionalConfig[i];
|
|
1260
798
|
if (argIndex >= args.length) break;
|
|
1261
799
|
if (variadic) {
|
|
1262
|
-
const nonVariadicAfter = positionalConfig.slice(
|
|
1263
|
-
name,
|
|
1264
|
-
variadic
|
|
1265
|
-
}) + 1).filter((p) => !p.variadic).length;
|
|
800
|
+
const nonVariadicAfter = positionalConfig.slice(i + 1).filter((p) => !p.variadic).length;
|
|
1266
801
|
const variadicEnd = args.length - nonVariadicAfter;
|
|
1267
|
-
|
|
802
|
+
preprocessedArgs[name] = args.slice(argIndex, variadicEnd);
|
|
1268
803
|
argIndex = variadicEnd;
|
|
804
|
+
} else if (i === positionalConfig.length - 1 && args.length > argIndex + 1) {
|
|
805
|
+
preprocessedArgs[name] = args.slice(argIndex).join(" ");
|
|
806
|
+
argIndex = args.length;
|
|
1269
807
|
} else {
|
|
1270
|
-
|
|
808
|
+
preprocessedArgs[name] = args[argIndex];
|
|
1271
809
|
argIndex++;
|
|
1272
810
|
}
|
|
1273
811
|
}
|
|
1274
812
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
813
|
+
if (command.argsSchema) preprocessedArgs = coerceArgs(preprocessedArgs, command.argsSchema);
|
|
814
|
+
return preprocessedArgs;
|
|
815
|
+
};
|
|
816
|
+
/**
|
|
817
|
+
* Detects unknown options in args that aren't defined in the schema.
|
|
818
|
+
* Returns unknown key info with suggestions, or empty array if schema is loose.
|
|
819
|
+
*/
|
|
820
|
+
const checkUnknownArgs = (command, preprocessedArgs) => {
|
|
821
|
+
if (!command.argsSchema) return [];
|
|
822
|
+
const argsMeta = command.meta?.fields;
|
|
823
|
+
const { aliases } = extractSchemaMetadata(command.argsSchema, argsMeta);
|
|
824
|
+
return detectUnknownArgs(preprocessedArgs, command.argsSchema, aliases, suggestSimilar);
|
|
1282
825
|
};
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
826
|
+
/**
|
|
827
|
+
* Validates preprocessed arguments against the command's schema.
|
|
828
|
+
* First checks for unknown args (strict by default), then runs schema validation.
|
|
829
|
+
* Returns sync or async result depending on the schema's validate method.
|
|
830
|
+
*/
|
|
831
|
+
const validateCommandArgs = (command, preprocessedArgs) => {
|
|
832
|
+
const unknownArgs = checkUnknownArgs(command, preprocessedArgs);
|
|
833
|
+
if (unknownArgs.length > 0) return {
|
|
834
|
+
args: void 0,
|
|
835
|
+
argsResult: { issues: unknownArgs.map(({ key, suggestion }) => ({
|
|
836
|
+
path: [key],
|
|
837
|
+
message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
|
|
838
|
+
})) }
|
|
1288
839
|
};
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
if (envValidated instanceof Promise) throw new Error("Async validation is not supported. Env schema validate() must return a synchronous result.");
|
|
1295
|
-
if (!envValidated.issues) envData = envValidated.value;
|
|
1296
|
-
}
|
|
1297
|
-
const { options, optionsResult } = validateOptions(command, rawOptions, args, {
|
|
1298
|
-
envData,
|
|
1299
|
-
configData: parseOptions?.configData
|
|
840
|
+
const argsParsed = command.argsSchema ? command.argsSchema["~standard"].validate(preprocessedArgs) : { value: preprocessedArgs };
|
|
841
|
+
const hasArgs = command.argsSchema || Object.keys(preprocessedArgs).length > 0;
|
|
842
|
+
const buildResult = (parsed) => ({
|
|
843
|
+
args: parsed.issues ? void 0 : hasArgs ? parsed.value : void 0,
|
|
844
|
+
argsResult: parsed
|
|
1300
845
|
});
|
|
1301
|
-
return
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
846
|
+
return thenMaybe(argsParsed, buildResult);
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* Preprocesses and validates raw arguments against the command's schema.
|
|
850
|
+
* Returns sync or async result depending on the schema's validate method.
|
|
851
|
+
*/
|
|
852
|
+
const validateArgs = (command, rawArgs, args, context) => {
|
|
853
|
+
return validateCommandArgs(command, buildCommandArgs(command, rawArgs, args, context));
|
|
854
|
+
};
|
|
855
|
+
const parse = (input) => {
|
|
856
|
+
const state = {};
|
|
857
|
+
const parseCtx = {
|
|
858
|
+
input,
|
|
859
|
+
command: existingCommand,
|
|
860
|
+
state
|
|
861
|
+
};
|
|
862
|
+
const coreParse = () => {
|
|
863
|
+
const { command, rawArgs, args } = parseCommand(parseCtx.input);
|
|
864
|
+
return {
|
|
865
|
+
command,
|
|
866
|
+
rawArgs,
|
|
867
|
+
positionalArgs: args
|
|
868
|
+
};
|
|
869
|
+
};
|
|
870
|
+
const parsedOrPromise = runPluginChain("parse", existingCommand.plugins ?? [], parseCtx, coreParse);
|
|
871
|
+
const continueAfterParse = (parsed) => {
|
|
872
|
+
const { command } = parsed;
|
|
873
|
+
const commandPlugins = collectPlugins(command);
|
|
874
|
+
const validateCtx = {
|
|
875
|
+
command,
|
|
876
|
+
rawArgs: parsed.rawArgs,
|
|
877
|
+
positionalArgs: parsed.positionalArgs,
|
|
878
|
+
state
|
|
879
|
+
};
|
|
880
|
+
const coreValidate = () => {
|
|
881
|
+
const resolveEnvSchema = (cmd) => {
|
|
882
|
+
if (cmd.envSchema !== void 0) return cmd.envSchema;
|
|
883
|
+
if (cmd.parent) return resolveEnvSchema(cmd.parent);
|
|
884
|
+
};
|
|
885
|
+
const envSchema = resolveEnvSchema(command);
|
|
886
|
+
const readStdinForParse = () => {
|
|
887
|
+
const stdinConfig = command.meta?.stdin;
|
|
888
|
+
if (!stdinConfig) return {};
|
|
889
|
+
const { field, as } = parseStdinConfig(stdinConfig);
|
|
890
|
+
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
|
|
891
|
+
const stdin = resolveStdin(getCommandRuntime(existingCommand));
|
|
892
|
+
if (!stdin) return {};
|
|
893
|
+
if (as === "lines") return (async () => {
|
|
894
|
+
const lines = [];
|
|
895
|
+
for await (const line of stdin.lines()) lines.push(line);
|
|
896
|
+
return { [field]: lines };
|
|
897
|
+
})();
|
|
898
|
+
return stdin.text().then((text) => text ? { [field]: text } : {});
|
|
899
|
+
};
|
|
900
|
+
const finalize = (envData, stdinData) => {
|
|
901
|
+
return thenMaybe(validateArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
|
|
902
|
+
stdinData,
|
|
903
|
+
envData
|
|
904
|
+
}), (v) => v);
|
|
905
|
+
};
|
|
906
|
+
let envData;
|
|
907
|
+
const afterEnv = (envResult) => {
|
|
908
|
+
return thenMaybe(readStdinForParse(), (stdinData) => {
|
|
909
|
+
return finalize(envResult, Object.keys(stdinData).length > 0 ? stdinData : void 0);
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
if (envSchema) {
|
|
913
|
+
const rawEnv = getCommandRuntime(existingCommand).env();
|
|
914
|
+
return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
|
|
915
|
+
if (!result.issues) envData = result.value;
|
|
916
|
+
return afterEnv(envData);
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
return afterEnv(envData);
|
|
920
|
+
};
|
|
921
|
+
return warnIfUnexpectedAsync(thenMaybe(runPluginChain("validate", commandPlugins, validateCtx, coreValidate), (v) => ({
|
|
922
|
+
command,
|
|
923
|
+
args: v.args,
|
|
924
|
+
argsResult: v.argsResult
|
|
925
|
+
})), command);
|
|
1305
926
|
};
|
|
927
|
+
return thenMaybe(parsedOrPromise, continueAfterParse);
|
|
1306
928
|
};
|
|
1307
|
-
const stringify = (command = "",
|
|
929
|
+
const stringify = (command = "", args) => {
|
|
1308
930
|
const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1309
|
-
if (!commandObj) throw new
|
|
931
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1310
932
|
const parts = [];
|
|
1311
933
|
if (commandObj.path) parts.push(commandObj.path);
|
|
1312
934
|
const positionalConfig = commandObj.meta?.positional ? parsePositionalConfig(commandObj.meta.positional) : [];
|
|
1313
935
|
const positionalNames = new Set(positionalConfig.map((p) => p.name));
|
|
1314
|
-
if (
|
|
936
|
+
if (args && typeof args === "object") {
|
|
1315
937
|
for (const { name, variadic } of positionalConfig) {
|
|
1316
|
-
const value =
|
|
938
|
+
const value = args[name];
|
|
1317
939
|
if (value === void 0) continue;
|
|
1318
940
|
if (variadic && Array.isArray(value)) for (const v of value) {
|
|
1319
941
|
const vStr = String(v);
|
|
@@ -1340,7 +962,7 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1340
962
|
else parts.push(`--${key}=${value}`);
|
|
1341
963
|
else parts.push(`--${key}=${value}`);
|
|
1342
964
|
};
|
|
1343
|
-
for (const [key, value] of Object.entries(
|
|
965
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1344
966
|
if (value === void 0 || positionalNames.has(key)) continue;
|
|
1345
967
|
stringifyValue(key, value);
|
|
1346
968
|
}
|
|
@@ -1355,16 +977,16 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1355
977
|
if (!input) return null;
|
|
1356
978
|
const parts = parseCliInputToParts(input);
|
|
1357
979
|
const terms = parts.filter((p) => p.type === "term").map((p) => p.value);
|
|
1358
|
-
const
|
|
980
|
+
const args = parts.filter((p) => p.type === "named" || p.type === "alias");
|
|
1359
981
|
const keyIs = (key, name) => key.length === 1 && key[0] === name;
|
|
1360
|
-
const hasHelpFlag =
|
|
982
|
+
const hasHelpFlag = args.some((p) => p.type === "named" && keyIs(p.key, "help") || p.type === "alias" && keyIs(p.key, "h"));
|
|
1361
983
|
const getDetailLevel = () => {
|
|
1362
|
-
for (const
|
|
1363
|
-
if (
|
|
1364
|
-
if (
|
|
984
|
+
for (const arg of args) {
|
|
985
|
+
if (arg.type === "named" && keyIs(arg.key, "detail") && typeof arg.value === "string") {
|
|
986
|
+
if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
|
|
1365
987
|
}
|
|
1366
|
-
if (
|
|
1367
|
-
if (
|
|
988
|
+
if (arg.type === "alias" && keyIs(arg.key, "d") && typeof arg.value === "string") {
|
|
989
|
+
if (arg.value === "minimal" || arg.value === "standard" || arg.value === "full") return arg.value;
|
|
1368
990
|
}
|
|
1369
991
|
}
|
|
1370
992
|
};
|
|
@@ -1379,17 +1001,17 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1379
1001
|
"json",
|
|
1380
1002
|
"auto"
|
|
1381
1003
|
];
|
|
1382
|
-
for (const
|
|
1383
|
-
if (
|
|
1384
|
-
if (validFormats.includes(
|
|
1004
|
+
for (const arg of args) {
|
|
1005
|
+
if (arg.type === "named" && keyIs(arg.key, "format") && typeof arg.value === "string") {
|
|
1006
|
+
if (validFormats.includes(arg.value)) return arg.value;
|
|
1385
1007
|
}
|
|
1386
|
-
if (
|
|
1387
|
-
if (validFormats.includes(
|
|
1008
|
+
if (arg.type === "alias" && keyIs(arg.key, "f") && typeof arg.value === "string") {
|
|
1009
|
+
if (validFormats.includes(arg.value)) return arg.value;
|
|
1388
1010
|
}
|
|
1389
1011
|
}
|
|
1390
1012
|
};
|
|
1391
1013
|
const format = getFormat();
|
|
1392
|
-
const hasVersionFlag =
|
|
1014
|
+
const hasVersionFlag = args.some((p) => p.type === "named" && keyIs(p.key, "version") || p.type === "alias" && (keyIs(p.key, "v") || keyIs(p.key, "V")));
|
|
1393
1015
|
const normalizedTerms = [...terms];
|
|
1394
1016
|
if (normalizedTerms[0] === existingCommand.name) normalizedTerms.shift();
|
|
1395
1017
|
const userHelpCommand = findCommandByName("help", existingCommand.commands);
|
|
@@ -1404,6 +1026,24 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1404
1026
|
format
|
|
1405
1027
|
};
|
|
1406
1028
|
}
|
|
1029
|
+
if (!userHelpCommand && normalizedTerms.length > 0 && normalizedTerms[normalizedTerms.length - 1] === "help") {
|
|
1030
|
+
const commandTerms = normalizedTerms.slice(0, -1);
|
|
1031
|
+
let targetCommand;
|
|
1032
|
+
let current = existingCommand;
|
|
1033
|
+
for (const term of commandTerms) {
|
|
1034
|
+
const found = findCommandByName(term, current.commands);
|
|
1035
|
+
if (found) {
|
|
1036
|
+
targetCommand = found;
|
|
1037
|
+
current = found;
|
|
1038
|
+
} else break;
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
type: "help",
|
|
1042
|
+
command: targetCommand,
|
|
1043
|
+
detail,
|
|
1044
|
+
format
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1407
1047
|
if (!userVersionCommand && normalizedTerms[0] === "version") return { type: "version" };
|
|
1408
1048
|
if (!userCompletionCommand && normalizedTerms[0] === "completion") {
|
|
1409
1049
|
const shellArg = normalizedTerms[1];
|
|
@@ -1414,7 +1054,8 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1414
1054
|
"zsh",
|
|
1415
1055
|
"fish",
|
|
1416
1056
|
"powershell"
|
|
1417
|
-
].includes(shellArg) ? shellArg : void 0
|
|
1057
|
+
].includes(shellArg) ? shellArg : void 0,
|
|
1058
|
+
setup: args.some((p) => p.type === "named" && keyIs(p.key, "setup"))
|
|
1418
1059
|
};
|
|
1419
1060
|
}
|
|
1420
1061
|
if (hasHelpFlag) {
|
|
@@ -1427,6 +1068,10 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1427
1068
|
};
|
|
1428
1069
|
}
|
|
1429
1070
|
if (hasVersionFlag && normalizedTerms.length === 0) return { type: "version" };
|
|
1071
|
+
if (args.some((p) => p.type === "named" && keyIs(p.key, "repl"))) return {
|
|
1072
|
+
type: "repl",
|
|
1073
|
+
scope: normalizedTerms.length > 0 ? normalizedTerms.join(" ") : void 0
|
|
1074
|
+
};
|
|
1430
1075
|
return null;
|
|
1431
1076
|
};
|
|
1432
1077
|
/**
|
|
@@ -1434,124 +1079,455 @@ function createPadroneBuilder(existingCommand) {
|
|
|
1434
1079
|
*/
|
|
1435
1080
|
const extractConfigPath = (input) => {
|
|
1436
1081
|
if (!input) return void 0;
|
|
1437
|
-
const
|
|
1438
|
-
for (const
|
|
1439
|
-
if (
|
|
1440
|
-
if (
|
|
1082
|
+
const args = parseCliInputToParts(input).filter((p) => p.type === "named" || p.type === "alias");
|
|
1083
|
+
for (const arg of args) {
|
|
1084
|
+
if (arg.type === "named" && arg.key.length === 1 && arg.key[0] === "config" && typeof arg.value === "string") return arg.value;
|
|
1085
|
+
if (arg.type === "alias" && arg.key.length === 1 && arg.key[0] === "c" && typeof arg.value === "string") return arg.value;
|
|
1441
1086
|
}
|
|
1442
1087
|
};
|
|
1443
|
-
|
|
1444
|
-
|
|
1088
|
+
/**
|
|
1089
|
+
* Core execution logic shared by eval() and cli().
|
|
1090
|
+
* errorMode controls validation error behavior:
|
|
1091
|
+
* - 'soft': return result with issues (eval behavior)
|
|
1092
|
+
* - 'hard': print error + help and throw (cli-without-input behavior)
|
|
1093
|
+
*/
|
|
1094
|
+
const execCommand = (resolvedInput, evalOptions, errorMode = "soft") => {
|
|
1095
|
+
const baseRuntime = getCommandRuntime(existingCommand);
|
|
1096
|
+
const runtime = evalOptions?.runtime ? Object.assign({}, baseRuntime, Object.fromEntries(Object.entries(evalOptions.runtime).filter(([, v]) => v !== void 0))) : baseRuntime;
|
|
1445
1097
|
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1446
1098
|
if (builtin) {
|
|
1447
1099
|
if (builtin.type === "help") {
|
|
1448
1100
|
const helpText = generateHelp(existingCommand, builtin.command ?? existingCommand, {
|
|
1449
1101
|
detail: builtin.detail,
|
|
1450
|
-
format: builtin.format
|
|
1102
|
+
format: builtin.format ?? runtime.format
|
|
1451
1103
|
});
|
|
1452
|
-
|
|
1104
|
+
runtime.output(helpText);
|
|
1453
1105
|
return {
|
|
1454
1106
|
command: existingCommand,
|
|
1455
1107
|
args: void 0,
|
|
1456
|
-
options: void 0,
|
|
1457
1108
|
result: helpText
|
|
1458
1109
|
};
|
|
1459
1110
|
}
|
|
1460
1111
|
if (builtin.type === "version") {
|
|
1461
1112
|
const version = getVersion(existingCommand.version);
|
|
1462
|
-
|
|
1113
|
+
runtime.output(version);
|
|
1463
1114
|
return {
|
|
1464
1115
|
command: existingCommand,
|
|
1465
|
-
|
|
1116
|
+
args: void 0,
|
|
1466
1117
|
result: version
|
|
1467
1118
|
};
|
|
1468
1119
|
}
|
|
1469
|
-
if (builtin.type === "completion") {
|
|
1120
|
+
if (builtin.type === "completion") return import("./completion.mjs").then(({ detectShell, generateCompletionOutput, setupCompletions }) => {
|
|
1121
|
+
if (builtin.setup) {
|
|
1122
|
+
const shell = builtin.shell ?? detectShell();
|
|
1123
|
+
if (!shell) throw new Error("Could not detect shell. Specify one: completion bash --setup");
|
|
1124
|
+
const result = setupCompletions(existingCommand.name, shell);
|
|
1125
|
+
const message = `${result.updated ? "Updated" : "Added"} ${existingCommand.name} completions in ${result.file}`;
|
|
1126
|
+
runtime.output(message);
|
|
1127
|
+
return {
|
|
1128
|
+
command: existingCommand,
|
|
1129
|
+
args: void 0,
|
|
1130
|
+
result: message
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1470
1133
|
const completionScript = generateCompletionOutput(existingCommand, builtin.shell);
|
|
1471
|
-
|
|
1134
|
+
runtime.output(completionScript);
|
|
1472
1135
|
return {
|
|
1473
1136
|
command: existingCommand,
|
|
1474
|
-
|
|
1137
|
+
args: void 0,
|
|
1475
1138
|
result: completionScript
|
|
1476
1139
|
};
|
|
1477
|
-
}
|
|
1140
|
+
});
|
|
1478
1141
|
}
|
|
1479
|
-
const
|
|
1480
|
-
const
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1142
|
+
const state = {};
|
|
1143
|
+
const rootPlugins = existingCommand.plugins ?? [];
|
|
1144
|
+
const runPipeline = () => {
|
|
1145
|
+
const parseCtx = {
|
|
1146
|
+
input: resolvedInput,
|
|
1147
|
+
command: existingCommand,
|
|
1148
|
+
state
|
|
1149
|
+
};
|
|
1150
|
+
const coreParse = () => {
|
|
1151
|
+
const { command, rawArgs, args, unmatchedTerms } = parseCommand(parseCtx.input);
|
|
1152
|
+
const hasSubcommands = command.commands && command.commands.length > 0;
|
|
1153
|
+
const hasSchema = command.argsSchema != null;
|
|
1154
|
+
if (!command.action && (hasSubcommands || !hasSchema) && unmatchedTerms.length === 0) {
|
|
1155
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format });
|
|
1156
|
+
runtime.output(helpText);
|
|
1157
|
+
return {
|
|
1158
|
+
command,
|
|
1159
|
+
rawArgs: { "~help": helpText },
|
|
1160
|
+
positionalArgs: []
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
if (unmatchedTerms.length > 0) {
|
|
1164
|
+
if (!(command.meta?.positional && command.meta.positional.length > 0)) {
|
|
1165
|
+
const isRootCommand = command === existingCommand;
|
|
1166
|
+
const commandDisplayName = command.name || command.aliases?.[0] || command.path || "(default)";
|
|
1167
|
+
const candidateNames = [];
|
|
1168
|
+
if (isRootCommand && existingCommand.commands) {
|
|
1169
|
+
for (const cmd of existingCommand.commands) if (!cmd.hidden) {
|
|
1170
|
+
candidateNames.push(cmd.name);
|
|
1171
|
+
if (cmd.aliases) candidateNames.push(...cmd.aliases);
|
|
1172
|
+
}
|
|
1173
|
+
} else if (command.commands) {
|
|
1174
|
+
for (const cmd of command.commands) if (!cmd.hidden) {
|
|
1175
|
+
candidateNames.push(cmd.name);
|
|
1176
|
+
if (cmd.aliases) candidateNames.push(...cmd.aliases);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const suggestion = suggestSimilar(unmatchedTerms[0], candidateNames);
|
|
1180
|
+
const suggestions = suggestion ? [suggestion] : [];
|
|
1181
|
+
const baseMsg = isRootCommand ? `Unknown command: ${unmatchedTerms[0]}` : `Unexpected arguments for '${commandDisplayName}': ${unmatchedTerms.join(" ")}`;
|
|
1182
|
+
const errorMsg = suggestions.length ? `${baseMsg}\n\n ${suggestions[0]}` : baseMsg;
|
|
1183
|
+
if (errorMode === "hard") {
|
|
1184
|
+
runtime.error(errorMsg);
|
|
1185
|
+
if (suggestions.length > 0) {
|
|
1186
|
+
const visibleCommands = ((isRootCommand ? existingCommand : command).commands ?? []).filter((c) => !c.hidden && c.name);
|
|
1187
|
+
if (visibleCommands.length > 0) {
|
|
1188
|
+
const cmdList = visibleCommands.map((c) => c.name).join(", ");
|
|
1189
|
+
runtime.output(`\nAvailable commands: ${cmdList}`);
|
|
1190
|
+
}
|
|
1191
|
+
} else {
|
|
1192
|
+
const helpText = generateHelp(existingCommand, isRootCommand ? existingCommand : command, { format: runtime.format });
|
|
1193
|
+
runtime.error(helpText);
|
|
1194
|
+
}
|
|
1195
|
+
throw new RoutingError(errorMsg, {
|
|
1196
|
+
suggestions,
|
|
1197
|
+
command: command.path || command.name
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
throw new RoutingError(errorMsg, {
|
|
1201
|
+
suggestions,
|
|
1202
|
+
command: command.path || command.name
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return {
|
|
1207
|
+
command,
|
|
1208
|
+
rawArgs,
|
|
1209
|
+
positionalArgs: args
|
|
1210
|
+
};
|
|
1211
|
+
};
|
|
1212
|
+
const parsedOrPromise = runPluginChain("parse", rootPlugins, parseCtx, coreParse);
|
|
1213
|
+
const continueAfterParse = (parsed) => {
|
|
1214
|
+
const { command } = parsed;
|
|
1215
|
+
const commandPlugins = collectPlugins(command);
|
|
1216
|
+
if (parsed.rawArgs["~help"]) return {
|
|
1217
|
+
command,
|
|
1218
|
+
args: void 0,
|
|
1219
|
+
result: parsed.rawArgs["~help"]
|
|
1220
|
+
};
|
|
1221
|
+
const validateCtx = {
|
|
1222
|
+
command,
|
|
1223
|
+
rawArgs: parsed.rawArgs,
|
|
1224
|
+
positionalArgs: parsed.positionalArgs,
|
|
1225
|
+
state
|
|
1226
|
+
};
|
|
1227
|
+
const coreValidate = () => {
|
|
1228
|
+
let flagInteractive;
|
|
1229
|
+
if (hasInteractiveConfig(command.meta)) {
|
|
1230
|
+
if (validateCtx.rawArgs.interactive !== void 0) {
|
|
1231
|
+
flagInteractive = validateCtx.rawArgs.interactive !== false && validateCtx.rawArgs.interactive !== "false";
|
|
1232
|
+
delete validateCtx.rawArgs.interactive;
|
|
1233
|
+
}
|
|
1234
|
+
if (validateCtx.rawArgs.i !== void 0) {
|
|
1235
|
+
flagInteractive = validateCtx.rawArgs.i !== false && validateCtx.rawArgs.i !== "false";
|
|
1236
|
+
delete validateCtx.rawArgs.i;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const runtimeDefault = runtime.interactive === "forced" ? true : runtime.interactive === "disabled" ? false : void 0;
|
|
1240
|
+
const effectiveInteractive = flagInteractive ?? evalOptions?.interactive ?? runtimeDefault;
|
|
1241
|
+
const stdinIsPiped = !!command.meta?.stdin && (runtime.stdin ? !runtime.stdin.isTTY : typeof process !== "undefined" && process.stdin?.isTTY !== true);
|
|
1242
|
+
const interactivitySuppressed = runtime.interactive === "unsupported" || effectiveInteractive === false || stdinIsPiped && effectiveInteractive !== true;
|
|
1243
|
+
const forceInteractive = !interactivitySuppressed && effectiveInteractive === true;
|
|
1244
|
+
const configPath = extractConfigPath(parseCtx.input);
|
|
1245
|
+
const resolveConfigFiles = (cmd) => {
|
|
1246
|
+
if (cmd.configFiles !== void 0) return cmd.configFiles;
|
|
1247
|
+
if (cmd.parent) return resolveConfigFiles(cmd.parent);
|
|
1248
|
+
};
|
|
1249
|
+
const effectiveConfigFiles = resolveConfigFiles(command);
|
|
1250
|
+
const resolveConfigSchema = (cmd) => {
|
|
1251
|
+
if (cmd.configSchema !== void 0) return cmd.configSchema;
|
|
1252
|
+
if (cmd.parent) return resolveConfigSchema(cmd.parent);
|
|
1253
|
+
};
|
|
1254
|
+
const configSchema = resolveConfigSchema(command);
|
|
1255
|
+
const resolveEnvSchema = (cmd) => {
|
|
1256
|
+
if (cmd.envSchema !== void 0) return cmd.envSchema;
|
|
1257
|
+
if (cmd.parent) return resolveEnvSchema(cmd.parent);
|
|
1258
|
+
};
|
|
1259
|
+
const envSchema = resolveEnvSchema(command);
|
|
1260
|
+
let configData;
|
|
1261
|
+
if (configPath) configData = runtime.loadConfigFile(configPath);
|
|
1262
|
+
else if (effectiveConfigFiles?.length) {
|
|
1263
|
+
const foundConfigPath = runtime.findFile(effectiveConfigFiles);
|
|
1264
|
+
if (foundConfigPath) configData = runtime.loadConfigFile(foundConfigPath) ?? configData;
|
|
1265
|
+
}
|
|
1266
|
+
const validateConfig = () => {
|
|
1267
|
+
if (configData && configSchema) return thenMaybe(configSchema["~standard"].validate(configData), (result) => {
|
|
1268
|
+
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 });
|
|
1269
|
+
return result.value;
|
|
1270
|
+
});
|
|
1271
|
+
return configData;
|
|
1272
|
+
};
|
|
1273
|
+
const validateEnv = () => {
|
|
1274
|
+
let envData;
|
|
1275
|
+
if (envSchema) {
|
|
1276
|
+
const rawEnv = runtime.env();
|
|
1277
|
+
return thenMaybe(envSchema["~standard"].validate(rawEnv), (result) => {
|
|
1278
|
+
if (!result.issues) envData = result.value;
|
|
1279
|
+
return envData;
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
return envData;
|
|
1283
|
+
};
|
|
1284
|
+
const readStdin = () => {
|
|
1285
|
+
const stdinConfig = command.meta?.stdin;
|
|
1286
|
+
if (!stdinConfig) return {};
|
|
1287
|
+
const { field, as } = parseStdinConfig(stdinConfig);
|
|
1288
|
+
if (field in validateCtx.rawArgs && validateCtx.rawArgs[field] !== void 0) return {};
|
|
1289
|
+
const stdin = resolveStdin(runtime);
|
|
1290
|
+
if (!stdin) return {};
|
|
1291
|
+
if (as === "lines") return (async () => {
|
|
1292
|
+
const lines = [];
|
|
1293
|
+
for await (const line of stdin.lines()) lines.push(line);
|
|
1294
|
+
return { [field]: lines };
|
|
1295
|
+
})();
|
|
1296
|
+
return stdin.text().then((text) => {
|
|
1297
|
+
if (!text) return {};
|
|
1298
|
+
return { [field]: text };
|
|
1299
|
+
});
|
|
1300
|
+
};
|
|
1301
|
+
const finalizeValidation = (validatedConfigData, envData, stdinData) => {
|
|
1302
|
+
const preprocessedArgs = buildCommandArgs(command, validateCtx.rawArgs, validateCtx.positionalArgs, {
|
|
1303
|
+
stdinData,
|
|
1304
|
+
envData,
|
|
1305
|
+
configData: validatedConfigData
|
|
1306
|
+
});
|
|
1307
|
+
if (!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta)) {
|
|
1308
|
+
const unknowns = checkUnknownArgs(command, preprocessedArgs);
|
|
1309
|
+
if (unknowns.length > 0) return {
|
|
1310
|
+
args: void 0,
|
|
1311
|
+
argsResult: { issues: unknowns.map(({ key, suggestion }) => ({
|
|
1312
|
+
path: [key],
|
|
1313
|
+
message: suggestion ? `Unknown option: "${key}". ${suggestion}` : `Unknown option: "${key}"`
|
|
1314
|
+
})) }
|
|
1315
|
+
};
|
|
1316
|
+
if (command.argsSchema) {
|
|
1317
|
+
const providedKeys = new Set(Object.keys(preprocessedArgs).filter((k) => preprocessedArgs[k] !== void 0));
|
|
1318
|
+
const earlyCheck = command.argsSchema["~standard"].validate(preprocessedArgs);
|
|
1319
|
+
const checkForProvidedFieldErrors = (result) => {
|
|
1320
|
+
if (!result.issues) return void 0;
|
|
1321
|
+
const providedFieldIssues = result.issues.filter((issue) => {
|
|
1322
|
+
const rootKey = issue.path?.[0];
|
|
1323
|
+
return rootKey !== void 0 && providedKeys.has(String(rootKey));
|
|
1324
|
+
});
|
|
1325
|
+
if (providedFieldIssues.length > 0) return {
|
|
1326
|
+
args: void 0,
|
|
1327
|
+
argsResult: { issues: providedFieldIssues }
|
|
1328
|
+
};
|
|
1329
|
+
};
|
|
1330
|
+
const earlyResult = thenMaybe(earlyCheck, (result) => {
|
|
1331
|
+
const errors = checkForProvidedFieldErrors(result);
|
|
1332
|
+
if (errors) return errors;
|
|
1333
|
+
});
|
|
1334
|
+
if (earlyResult instanceof Promise) return earlyResult.then((err) => {
|
|
1335
|
+
if (err) return err;
|
|
1336
|
+
return continueWithPrompt(preprocessedArgs);
|
|
1337
|
+
});
|
|
1338
|
+
if (earlyResult) return earlyResult;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return continueWithPrompt(preprocessedArgs);
|
|
1342
|
+
};
|
|
1343
|
+
const continueWithPrompt = (preprocessedArgs) => {
|
|
1344
|
+
return thenMaybe(!interactivitySuppressed && runtime.prompt && hasInteractiveConfig(command.meta) ? promptInteractiveFields(preprocessedArgs, command, runtime, forceInteractive || void 0) : preprocessedArgs, (filledArgs) => {
|
|
1345
|
+
return thenMaybe(validateCommandArgs(command, filledArgs), (v) => v);
|
|
1346
|
+
});
|
|
1347
|
+
};
|
|
1348
|
+
return thenMaybe(validateConfig(), (cfgData) => {
|
|
1349
|
+
return thenMaybe(validateEnv(), (envData) => {
|
|
1350
|
+
return thenMaybe(readStdin(), (stdinData) => {
|
|
1351
|
+
return finalizeValidation(cfgData, envData, Object.keys(stdinData).length > 0 ? stdinData : void 0);
|
|
1352
|
+
});
|
|
1353
|
+
});
|
|
1354
|
+
});
|
|
1355
|
+
};
|
|
1356
|
+
const validatedOrPromise = runPluginChain("validate", commandPlugins, validateCtx, coreValidate);
|
|
1357
|
+
const continueAfterValidate = (v) => {
|
|
1358
|
+
if (v.argsResult?.issues) {
|
|
1359
|
+
let knownOptions;
|
|
1360
|
+
const getKnownOptions = () => {
|
|
1361
|
+
if (knownOptions) return knownOptions;
|
|
1362
|
+
knownOptions = [];
|
|
1363
|
+
if (command.argsSchema) try {
|
|
1364
|
+
const js = command.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1365
|
+
if (js.type === "object" && js.properties) knownOptions = Object.keys(js.properties);
|
|
1366
|
+
} catch {}
|
|
1367
|
+
return knownOptions;
|
|
1368
|
+
};
|
|
1369
|
+
const issueMessages = v.argsResult.issues.map((i) => {
|
|
1370
|
+
const base = ` - ${i.path?.join(".") || "root"}: ${i.message}`;
|
|
1371
|
+
const unrecognizedKeys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
|
|
1372
|
+
if (unrecognizedKeys?.length) {
|
|
1373
|
+
const hints = unrecognizedKeys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
|
|
1374
|
+
if (hints.length) return `${base}\n ${hints.join("\n ")}`;
|
|
1375
|
+
}
|
|
1376
|
+
return base;
|
|
1377
|
+
}).join("\n");
|
|
1378
|
+
if (errorMode === "hard") {
|
|
1379
|
+
const helpText = generateHelp(existingCommand, command, { format: runtime.format });
|
|
1380
|
+
runtime.error(`Validation error:\n${issueMessages}`);
|
|
1381
|
+
runtime.error(helpText);
|
|
1382
|
+
throw new ValidationError(`Validation error:\n${issueMessages}`, v.argsResult.issues, {
|
|
1383
|
+
suggestions: v.argsResult.issues.flatMap((i) => {
|
|
1384
|
+
const keys = i.keys ?? i.message?.match(/[Uu]nrecognized key(?:s)?[^"]*"([^"]+)"/)?.slice(1);
|
|
1385
|
+
if (!keys?.length) return [];
|
|
1386
|
+
return keys.map((k) => suggestSimilar(k, getKnownOptions())).filter(Boolean);
|
|
1387
|
+
}),
|
|
1388
|
+
command: command.path || command.name
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
command,
|
|
1393
|
+
args: void 0,
|
|
1394
|
+
argsResult: v.argsResult,
|
|
1395
|
+
result: void 0
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
const executeCtx = {
|
|
1399
|
+
command,
|
|
1400
|
+
args: v.args,
|
|
1401
|
+
state
|
|
1402
|
+
};
|
|
1403
|
+
const coreExecute = () => {
|
|
1404
|
+
const handler = command.action ?? noop;
|
|
1405
|
+
const ctx = evalOptions?.runtime ? {
|
|
1406
|
+
...createActionContext(command),
|
|
1407
|
+
runtime
|
|
1408
|
+
} : createActionContext(command);
|
|
1409
|
+
return { result: handler(executeCtx.args, ctx) };
|
|
1410
|
+
};
|
|
1411
|
+
return thenMaybe(runPluginChain("execute", commandPlugins, executeCtx, coreExecute), (e) => {
|
|
1412
|
+
const commandResult = {
|
|
1413
|
+
command,
|
|
1414
|
+
args: v.args,
|
|
1415
|
+
argsResult: v.argsResult,
|
|
1416
|
+
result: e.result
|
|
1417
|
+
};
|
|
1418
|
+
if (command.autoOutput ?? evalOptions?.autoOutput ?? true) {
|
|
1419
|
+
const outputOrPromise = outputValue(e.result, runtime.output);
|
|
1420
|
+
if (outputOrPromise instanceof Promise) return outputOrPromise.then(() => commandResult);
|
|
1421
|
+
}
|
|
1422
|
+
return commandResult;
|
|
1423
|
+
});
|
|
1424
|
+
};
|
|
1425
|
+
return warnIfUnexpectedAsync(thenMaybe(validatedOrPromise, continueAfterValidate), command);
|
|
1426
|
+
};
|
|
1427
|
+
return thenMaybe(parsedOrPromise, continueAfterParse);
|
|
1494
1428
|
};
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1429
|
+
return wrapWithLifecycle(rootPlugins, existingCommand, state, resolvedInput, runPipeline, (result) => ({
|
|
1430
|
+
command: existingCommand,
|
|
1431
|
+
args: void 0,
|
|
1432
|
+
argsResult: void 0,
|
|
1433
|
+
result
|
|
1434
|
+
}));
|
|
1435
|
+
};
|
|
1436
|
+
const evalCommand = (input, evalOptions) => {
|
|
1437
|
+
return execCommand(input, evalOptions, "soft");
|
|
1438
|
+
};
|
|
1439
|
+
/**
|
|
1440
|
+
* Collects plugins from the command's parent chain (root → ... → target).
|
|
1441
|
+
* Root/program plugins come first (outermost), target command's plugins last (innermost).
|
|
1442
|
+
*
|
|
1443
|
+
* The `programRoot` parameter provides the current program command, because
|
|
1444
|
+
* subcommands' `.parent` references may be stale (builders are immutable — each
|
|
1445
|
+
* method returns a new builder, so a subcommand's parent was captured before
|
|
1446
|
+
* `.use()` was called on the program). We substitute `programRoot` for the
|
|
1447
|
+
* top of the chain to ensure program-level plugins are always included.
|
|
1448
|
+
*/
|
|
1449
|
+
const collectPlugins = (cmd) => {
|
|
1450
|
+
const chain = [];
|
|
1451
|
+
let current = cmd;
|
|
1452
|
+
while (current) {
|
|
1453
|
+
if (!current.parent) {
|
|
1454
|
+
if (existingCommand.plugins?.length) chain.unshift(existingCommand.plugins);
|
|
1455
|
+
} else if (current.plugins?.length) chain.unshift(current.plugins);
|
|
1456
|
+
current = current.parent;
|
|
1501
1457
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1458
|
+
return chain.flat();
|
|
1459
|
+
};
|
|
1460
|
+
let replFn;
|
|
1461
|
+
const replActiveRef = { value: false };
|
|
1462
|
+
const cli = (cliOptions) => {
|
|
1463
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1464
|
+
const resolvedInput = runtime.argv().join(" ") || void 0;
|
|
1465
|
+
if (cliOptions?.repl !== false) {
|
|
1466
|
+
const builtin = checkBuiltinCommands(resolvedInput);
|
|
1467
|
+
if (builtin?.type === "repl") {
|
|
1468
|
+
const replPrefs = {
|
|
1469
|
+
...typeof cliOptions?.repl === "object" ? cliOptions.repl : {},
|
|
1470
|
+
scope: builtin.scope,
|
|
1471
|
+
autoOutput: (typeof cliOptions?.repl === "object" ? cliOptions.repl.autoOutput : void 0) ?? cliOptions?.autoOutput
|
|
1472
|
+
};
|
|
1473
|
+
const drainRepl = async () => {
|
|
1474
|
+
for await (const _ of replFn(replPrefs));
|
|
1475
|
+
return {
|
|
1476
|
+
command: existingCommand,
|
|
1477
|
+
args: void 0,
|
|
1478
|
+
result: void 0
|
|
1479
|
+
};
|
|
1480
|
+
};
|
|
1481
|
+
return drainRepl();
|
|
1508
1482
|
}
|
|
1509
|
-
configData = configValidated.value;
|
|
1510
1483
|
}
|
|
1511
|
-
let
|
|
1512
|
-
if (
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1484
|
+
let updateCheckPromise;
|
|
1485
|
+
if (existingCommand.updateCheck) {
|
|
1486
|
+
if (!(resolvedInput && parseCliInputToParts(resolvedInput).some((p) => p.type === "named" && p.key.length === 1 && p.key[0] === "no-update-check"))) {
|
|
1487
|
+
const currentVersion = getVersion(existingCommand.version);
|
|
1488
|
+
updateCheckPromise = import("./update-check-EbNDkzyV.mjs").then(({ createUpdateChecker }) => createUpdateChecker(existingCommand.name, currentVersion, existingCommand.updateCheck, runtime));
|
|
1489
|
+
}
|
|
1517
1490
|
}
|
|
1518
|
-
const
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1491
|
+
const result = execCommand(resolvedInput, cliOptions, "hard");
|
|
1492
|
+
if (updateCheckPromise) {
|
|
1493
|
+
if (result instanceof Promise) return result.then(async (r) => {
|
|
1494
|
+
(await updateCheckPromise)?.();
|
|
1495
|
+
return r;
|
|
1496
|
+
});
|
|
1497
|
+
updateCheckPromise.then((show) => show?.());
|
|
1498
|
+
}
|
|
1499
|
+
return result;
|
|
1526
1500
|
};
|
|
1527
|
-
const run = (command,
|
|
1501
|
+
const run = (command, args) => {
|
|
1528
1502
|
const commandObj = typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1529
|
-
if (!commandObj) throw new
|
|
1530
|
-
if (!commandObj.
|
|
1531
|
-
|
|
1503
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1504
|
+
if (!commandObj.action) throw new RoutingError(`Command "${commandObj.path}" has no action`, { command: commandObj.path });
|
|
1505
|
+
const executeCtx = {
|
|
1532
1506
|
command: commandObj,
|
|
1533
|
-
|
|
1534
|
-
|
|
1507
|
+
args,
|
|
1508
|
+
state: {}
|
|
1509
|
+
};
|
|
1510
|
+
const coreExecute = () => {
|
|
1511
|
+
return { result: commandObj.action(executeCtx.args, createActionContext(commandObj)) };
|
|
1535
1512
|
};
|
|
1513
|
+
const executedOrPromise = runPluginChain("execute", collectPlugins(commandObj), executeCtx, coreExecute);
|
|
1514
|
+
const toResult = (e) => ({
|
|
1515
|
+
command: commandObj,
|
|
1516
|
+
args,
|
|
1517
|
+
result: e.result
|
|
1518
|
+
});
|
|
1519
|
+
if (executedOrPromise instanceof Promise) return executedOrPromise.then(toResult);
|
|
1520
|
+
return toResult(executedOrPromise);
|
|
1536
1521
|
};
|
|
1537
1522
|
const tool = () => {
|
|
1538
|
-
const description =
|
|
1539
|
-
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.
|
|
1540
|
-
|
|
1541
|
-
<help_output>
|
|
1542
|
-
${generateHelp(existingCommand, void 0, {
|
|
1543
|
-
format: "text",
|
|
1544
|
-
detail: "full"
|
|
1545
|
-
})}
|
|
1546
|
-
</help_output>
|
|
1547
|
-
`;
|
|
1523
|
+
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" })}`;
|
|
1548
1524
|
return {
|
|
1549
1525
|
type: "function",
|
|
1550
1526
|
name: existingCommand.name,
|
|
1551
1527
|
strict: true,
|
|
1552
1528
|
title: existingCommand.description,
|
|
1553
1529
|
description,
|
|
1554
|
-
inputExamples: [{ input: { command: "<command> [
|
|
1530
|
+
inputExamples: [{ input: { command: "<command> [positionals...] [arguments...]" } }],
|
|
1555
1531
|
inputSchema: {
|
|
1556
1532
|
[Symbol.for("vercel.ai.schema")]: true,
|
|
1557
1533
|
jsonSchema: {
|
|
@@ -1572,57 +1548,105 @@ ${generateHelp(existingCommand, void 0, {
|
|
|
1572
1548
|
};
|
|
1573
1549
|
}
|
|
1574
1550
|
},
|
|
1575
|
-
needsApproval: (input) => {
|
|
1576
|
-
const
|
|
1577
|
-
if (typeof command.needsApproval === "function") return command.needsApproval(
|
|
1578
|
-
return !!command.needsApproval;
|
|
1551
|
+
needsApproval: async (input) => {
|
|
1552
|
+
const parsed = await parse(input.command);
|
|
1553
|
+
if (typeof parsed.command.needsApproval === "function") return parsed.command.needsApproval(parsed.args);
|
|
1554
|
+
return !!parsed.command.needsApproval;
|
|
1579
1555
|
},
|
|
1580
|
-
execute: (input) => {
|
|
1581
|
-
|
|
1556
|
+
execute: async (input) => {
|
|
1557
|
+
const output = [];
|
|
1558
|
+
const errors = [];
|
|
1559
|
+
return {
|
|
1560
|
+
result: (await evalCommand(input.command, {
|
|
1561
|
+
autoOutput: false,
|
|
1562
|
+
runtime: {
|
|
1563
|
+
output: (...args) => output.push(args.map(String).join(" ")),
|
|
1564
|
+
error: (text) => errors.push(text),
|
|
1565
|
+
interactive: "unsupported",
|
|
1566
|
+
format: "text"
|
|
1567
|
+
}
|
|
1568
|
+
})).result,
|
|
1569
|
+
logs: output.join("\n"),
|
|
1570
|
+
error: errors.join("\n")
|
|
1571
|
+
};
|
|
1582
1572
|
}
|
|
1583
1573
|
};
|
|
1584
1574
|
};
|
|
1585
|
-
|
|
1575
|
+
const builder = {
|
|
1586
1576
|
configure(config) {
|
|
1587
1577
|
return createPadroneBuilder({
|
|
1588
1578
|
...existingCommand,
|
|
1589
1579
|
...config
|
|
1590
1580
|
});
|
|
1591
1581
|
},
|
|
1592
|
-
|
|
1593
|
-
|
|
1582
|
+
runtime(runtimeConfig) {
|
|
1583
|
+
return createPadroneBuilder({
|
|
1584
|
+
...existingCommand,
|
|
1585
|
+
runtime: {
|
|
1586
|
+
...existingCommand.runtime,
|
|
1587
|
+
...runtimeConfig
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
},
|
|
1591
|
+
async() {
|
|
1594
1592
|
return createPadroneBuilder({
|
|
1595
1593
|
...existingCommand,
|
|
1596
|
-
|
|
1597
|
-
|
|
1594
|
+
isAsync: true
|
|
1595
|
+
});
|
|
1596
|
+
},
|
|
1597
|
+
arguments(schema, meta) {
|
|
1598
|
+
const resolvedArgs = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
|
|
1599
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedArgs) || hasInteractiveConfig(meta);
|
|
1600
|
+
return createPadroneBuilder({
|
|
1601
|
+
...existingCommand,
|
|
1602
|
+
argsSchema: resolvedArgs,
|
|
1603
|
+
meta,
|
|
1604
|
+
isAsync
|
|
1598
1605
|
});
|
|
1599
1606
|
},
|
|
1600
1607
|
configFile(file, schema) {
|
|
1601
1608
|
const configFiles = file === void 0 ? void 0 : Array.isArray(file) ? file : [file];
|
|
1602
|
-
const resolvedConfig = typeof schema === "function" ? schema(existingCommand.
|
|
1609
|
+
const resolvedConfig = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema ?? existingCommand.argsSchema;
|
|
1610
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedConfig);
|
|
1603
1611
|
return createPadroneBuilder({
|
|
1604
1612
|
...existingCommand,
|
|
1605
1613
|
configFiles,
|
|
1606
|
-
|
|
1614
|
+
configSchema: resolvedConfig,
|
|
1615
|
+
isAsync
|
|
1607
1616
|
});
|
|
1608
1617
|
},
|
|
1609
1618
|
env(schema) {
|
|
1610
|
-
const resolvedEnv = typeof schema === "function" ? schema(existingCommand.
|
|
1619
|
+
const resolvedEnv = typeof schema === "function" ? schema(existingCommand.argsSchema) : schema;
|
|
1620
|
+
const isAsync = existingCommand.isAsync || isAsyncBranded(resolvedEnv);
|
|
1611
1621
|
return createPadroneBuilder({
|
|
1612
1622
|
...existingCommand,
|
|
1613
|
-
envSchema: resolvedEnv
|
|
1623
|
+
envSchema: resolvedEnv,
|
|
1624
|
+
isAsync
|
|
1614
1625
|
});
|
|
1615
1626
|
},
|
|
1616
1627
|
action(handler = noop) {
|
|
1628
|
+
const baseHandler = existingCommand.action ?? noop;
|
|
1617
1629
|
return createPadroneBuilder({
|
|
1618
1630
|
...existingCommand,
|
|
1619
|
-
handler
|
|
1631
|
+
action: (args, ctx) => handler(args, ctx, baseHandler)
|
|
1632
|
+
});
|
|
1633
|
+
},
|
|
1634
|
+
wrap(config) {
|
|
1635
|
+
const handler = createWrapHandler(config, existingCommand.argsSchema, existingCommand.meta?.positional);
|
|
1636
|
+
return createPadroneBuilder({
|
|
1637
|
+
...existingCommand,
|
|
1638
|
+
action: handler
|
|
1620
1639
|
});
|
|
1621
1640
|
},
|
|
1622
1641
|
command(nameOrNames, builderFn) {
|
|
1623
1642
|
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
1624
1643
|
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
1625
|
-
const
|
|
1644
|
+
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
1645
|
+
const initialCommand = existingSubcommand ? {
|
|
1646
|
+
...existingSubcommand,
|
|
1647
|
+
aliases: aliases ?? existingSubcommand.aliases,
|
|
1648
|
+
parent: existingCommand
|
|
1649
|
+
} : {
|
|
1626
1650
|
name,
|
|
1627
1651
|
path: existingCommand.path ? `${existingCommand.path} ${name}` : name,
|
|
1628
1652
|
aliases,
|
|
@@ -1631,39 +1655,94 @@ ${generateHelp(existingCommand, void 0, {
|
|
|
1631
1655
|
};
|
|
1632
1656
|
const builder = createPadroneBuilder(initialCommand);
|
|
1633
1657
|
const commandObj = (builderFn?.(builder))?.[commandSymbol] ?? initialCommand;
|
|
1658
|
+
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, commandObj) : commandObj;
|
|
1659
|
+
const commands = existingCommand.commands || [];
|
|
1660
|
+
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1661
|
+
const updatedCommands = existingIndex >= 0 ? [
|
|
1662
|
+
...commands.slice(0, existingIndex),
|
|
1663
|
+
mergedCommandObj,
|
|
1664
|
+
...commands.slice(existingIndex + 1)
|
|
1665
|
+
] : [...commands, mergedCommandObj];
|
|
1666
|
+
return createPadroneBuilder({
|
|
1667
|
+
...existingCommand,
|
|
1668
|
+
commands: updatedCommands
|
|
1669
|
+
});
|
|
1670
|
+
},
|
|
1671
|
+
mount(nameOrNames, program) {
|
|
1672
|
+
const name = Array.isArray(nameOrNames) ? nameOrNames[0] : nameOrNames;
|
|
1673
|
+
const aliases = Array.isArray(nameOrNames) && nameOrNames.length > 1 ? nameOrNames.slice(1) : void 0;
|
|
1674
|
+
const programCommand = program[commandSymbol];
|
|
1675
|
+
if (!programCommand) throw new RoutingError("Cannot mount: not a valid Padrone program");
|
|
1676
|
+
const remounted = repathCommandTree(programCommand, name, existingCommand.path || "", existingCommand);
|
|
1677
|
+
remounted.aliases = aliases;
|
|
1678
|
+
const existingSubcommand = existingCommand.commands?.find((c) => c.name === name);
|
|
1679
|
+
const mergedCommandObj = existingSubcommand ? mergeCommands(existingSubcommand, remounted) : remounted;
|
|
1680
|
+
const commands = existingCommand.commands || [];
|
|
1681
|
+
const existingIndex = commands.findIndex((c) => c.name === name);
|
|
1682
|
+
const updatedCommands = existingIndex >= 0 ? [
|
|
1683
|
+
...commands.slice(0, existingIndex),
|
|
1684
|
+
mergedCommandObj,
|
|
1685
|
+
...commands.slice(existingIndex + 1)
|
|
1686
|
+
] : [...commands, mergedCommandObj];
|
|
1634
1687
|
return createPadroneBuilder({
|
|
1635
1688
|
...existingCommand,
|
|
1636
|
-
commands:
|
|
1689
|
+
commands: updatedCommands
|
|
1690
|
+
});
|
|
1691
|
+
},
|
|
1692
|
+
use(plugin) {
|
|
1693
|
+
return createPadroneBuilder({
|
|
1694
|
+
...existingCommand,
|
|
1695
|
+
plugins: [...existingCommand.plugins ?? [], plugin]
|
|
1696
|
+
});
|
|
1697
|
+
},
|
|
1698
|
+
updateCheck(config = {}) {
|
|
1699
|
+
return createPadroneBuilder({
|
|
1700
|
+
...existingCommand,
|
|
1701
|
+
updateCheck: config
|
|
1637
1702
|
});
|
|
1638
1703
|
},
|
|
1639
1704
|
run,
|
|
1640
1705
|
find,
|
|
1641
1706
|
parse,
|
|
1642
1707
|
stringify,
|
|
1708
|
+
eval: evalCommand,
|
|
1643
1709
|
cli,
|
|
1644
1710
|
tool,
|
|
1711
|
+
repl: replFn = (options) => {
|
|
1712
|
+
return createReplIterator({
|
|
1713
|
+
existingCommand,
|
|
1714
|
+
evalCommand,
|
|
1715
|
+
replActiveRef
|
|
1716
|
+
}, options);
|
|
1717
|
+
},
|
|
1645
1718
|
api() {
|
|
1646
1719
|
function buildApi(command) {
|
|
1647
|
-
const runCommand = ((
|
|
1720
|
+
const runCommand = ((args) => run(command, args).result);
|
|
1648
1721
|
if (!command.commands) return runCommand;
|
|
1649
1722
|
for (const cmd of command.commands) runCommand[cmd.name] = buildApi(cmd);
|
|
1650
1723
|
return runCommand;
|
|
1651
1724
|
}
|
|
1652
1725
|
return buildApi(existingCommand);
|
|
1653
1726
|
},
|
|
1654
|
-
help(command,
|
|
1727
|
+
help(command, prefs) {
|
|
1655
1728
|
const commandObj = !command ? existingCommand : typeof command === "string" ? findCommandByName(command, existingCommand.commands) : command;
|
|
1656
|
-
if (!commandObj) throw new
|
|
1657
|
-
|
|
1729
|
+
if (!commandObj) throw new RoutingError(`Command "${command ?? ""}" not found`);
|
|
1730
|
+
const runtime = getCommandRuntime(existingCommand);
|
|
1731
|
+
return generateHelp(existingCommand, commandObj, {
|
|
1732
|
+
...prefs,
|
|
1733
|
+
format: prefs?.format ?? runtime.format
|
|
1734
|
+
});
|
|
1658
1735
|
},
|
|
1659
|
-
completion(shell) {
|
|
1736
|
+
async completion(shell) {
|
|
1737
|
+
const { generateCompletionOutput } = await import("./completion.mjs");
|
|
1660
1738
|
return generateCompletionOutput(existingCommand, shell);
|
|
1661
1739
|
},
|
|
1662
1740
|
"~types": {},
|
|
1663
1741
|
[commandSymbol]: existingCommand
|
|
1664
1742
|
};
|
|
1743
|
+
return builder;
|
|
1665
1744
|
}
|
|
1666
|
-
|
|
1667
1745
|
//#endregion
|
|
1668
|
-
export { createPadrone };
|
|
1746
|
+
export { ActionError, ConfigError, PadroneError, REPL_SIGINT, RoutingError, ValidationError, asyncSchema, buildReplCompleter, createPadrone };
|
|
1747
|
+
|
|
1669
1748
|
//# sourceMappingURL=index.mjs.map
|