padrone 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -1
- package/LICENSE +1 -1
- package/README.md +60 -30
- package/dist/args-DFEI7_G_.mjs +197 -0
- package/dist/args-DFEI7_G_.mjs.map +1 -0
- package/dist/chunk-y_GBKt04.mjs +5 -0
- package/dist/codegen/index.d.mts +305 -0
- package/dist/codegen/index.d.mts.map +1 -0
- package/dist/codegen/index.mjs +1358 -0
- package/dist/codegen/index.mjs.map +1 -0
- package/dist/completion.d.mts +64 -0
- package/dist/completion.d.mts.map +1 -0
- package/dist/completion.mjs +417 -0
- package/dist/completion.mjs.map +1 -0
- package/dist/docs/index.d.mts +34 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +405 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/formatter-XroimS3Q.d.mts +83 -0
- package/dist/formatter-XroimS3Q.d.mts.map +1 -0
- package/dist/help-CgGP7hQU.mjs +1229 -0
- package/dist/help-CgGP7hQU.mjs.map +1 -0
- package/dist/index.d.mts +120 -546
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1220 -1204
- package/dist/index.mjs.map +1 -1
- package/dist/test.d.mts +112 -0
- package/dist/test.d.mts.map +1 -0
- package/dist/test.mjs +138 -0
- package/dist/test.mjs.map +1 -0
- package/dist/types-BS7RP5Ls.d.mts +1059 -0
- package/dist/types-BS7RP5Ls.d.mts.map +1 -0
- package/dist/update-check-EbNDkzyV.mjs +146 -0
- package/dist/update-check-EbNDkzyV.mjs.map +1 -0
- package/package.json +61 -21
- package/src/args.ts +457 -0
- package/src/cli/completions.ts +29 -0
- package/src/cli/docs.ts +86 -0
- package/src/cli/doctor.ts +330 -0
- package/src/cli/index.ts +159 -0
- package/src/cli/init.ts +135 -0
- package/src/cli/link.ts +320 -0
- package/src/cli/wrap.ts +152 -0
- package/src/codegen/README.md +118 -0
- package/src/codegen/code-builder.ts +226 -0
- package/src/codegen/discovery.ts +232 -0
- package/src/codegen/file-emitter.ts +73 -0
- package/src/codegen/generators/barrel-file.ts +16 -0
- package/src/codegen/generators/command-file.ts +197 -0
- package/src/codegen/generators/command-tree.ts +124 -0
- package/src/codegen/index.ts +33 -0
- package/src/codegen/parsers/fish.ts +163 -0
- package/src/codegen/parsers/help.ts +378 -0
- package/src/codegen/parsers/merge.ts +158 -0
- package/src/codegen/parsers/zsh.ts +221 -0
- package/src/codegen/schema-to-code.ts +199 -0
- package/src/codegen/template.ts +69 -0
- package/src/codegen/types.ts +143 -0
- package/src/colorizer.ts +2 -2
- package/src/command-utils.ts +504 -0
- package/src/completion.ts +110 -97
- package/src/create.ts +1048 -308
- package/src/docs/index.ts +607 -0
- package/src/errors.ts +131 -0
- package/src/formatter.ts +195 -73
- package/src/help.ts +159 -58
- package/src/index.ts +12 -15
- package/src/interactive.ts +169 -0
- package/src/parse.ts +52 -21
- package/src/repl-loop.ts +317 -0
- package/src/runtime.ts +304 -0
- package/src/shell-utils.ts +83 -0
- package/src/test.ts +285 -0
- package/src/type-helpers.ts +10 -10
- package/src/type-utils.ts +124 -14
- package/src/types.ts +752 -154
- package/src/update-check.ts +244 -0
- package/src/wrap.ts +44 -40
- package/src/zod.d.ts +2 -2
- package/src/options.ts +0 -180
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
import { t as __require } from "./chunk-y_GBKt04.mjs";
|
|
2
|
+
import { a as parsePositionalConfig, i as extractSchemaMetadata, o as parseStdinConfig, t as camelToKebab } from "./args-DFEI7_G_.mjs";
|
|
3
|
+
//#region src/utils.ts
|
|
4
|
+
function getRootCommand(cmd) {
|
|
5
|
+
let current = cmd;
|
|
6
|
+
while (current.parent) current = current.parent;
|
|
7
|
+
return current;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Attempts to get the version from various sources:
|
|
11
|
+
* 1. Explicit version set on the command
|
|
12
|
+
* 2. npm_package_version environment variable (set by npm/yarn/pnpm when running scripts)
|
|
13
|
+
* 3. package.json in current or parent directories
|
|
14
|
+
* @param explicitVersion - Version explicitly set via .version()
|
|
15
|
+
* @returns The version string or '0.0.0' if not found
|
|
16
|
+
*/
|
|
17
|
+
function getVersion(explicitVersion) {
|
|
18
|
+
if (explicitVersion) return explicitVersion;
|
|
19
|
+
if (typeof process !== "undefined" && process.env?.npm_package_version) return process.env.npm_package_version;
|
|
20
|
+
if (typeof process !== "undefined") try {
|
|
21
|
+
const fs = __require("node:fs");
|
|
22
|
+
const path = __require("node:path");
|
|
23
|
+
let dir = process.cwd();
|
|
24
|
+
for (let i = 0; i < 10; i++) {
|
|
25
|
+
const pkgPath = path.join(dir, "package.json");
|
|
26
|
+
if (fs.existsSync(pkgPath)) {
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
28
|
+
if (pkg.version) return pkg.version;
|
|
29
|
+
}
|
|
30
|
+
const parentDir = path.dirname(dir);
|
|
31
|
+
if (parentDir === dir) break;
|
|
32
|
+
dir = parentDir;
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
return "0.0.0";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Loads and parses a config file from the given path.
|
|
39
|
+
* Supports JSON, JSONC (JSON with comments), and attempts to parse other formats.
|
|
40
|
+
* @param configPath - Path to the config file
|
|
41
|
+
* @returns Parsed config data or undefined if loading fails
|
|
42
|
+
*/
|
|
43
|
+
function loadConfigFile(configPath) {
|
|
44
|
+
if (typeof process === "undefined") return void 0;
|
|
45
|
+
try {
|
|
46
|
+
const fs = __require("node:fs");
|
|
47
|
+
const path = __require("node:path");
|
|
48
|
+
const absolutePath = path.isAbsolute(configPath) ? configPath : path.resolve(process.cwd(), configPath);
|
|
49
|
+
if (!fs.existsSync(absolutePath)) {
|
|
50
|
+
console.error(`Config file not found: ${absolutePath}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const getContent = () => fs.readFileSync(absolutePath, "utf-8");
|
|
54
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
55
|
+
if (ext === ".yaml" || ext === ".yml") return Bun.YAML.parse(getContent());
|
|
56
|
+
if (ext === ".toml") return Bun.TOML.parse(getContent());
|
|
57
|
+
if (ext === ".json") {
|
|
58
|
+
if (Bun.JSONC) return Bun.JSONC.parse(getContent());
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(getContent());
|
|
61
|
+
} catch {
|
|
62
|
+
return Bun.JSONC.parse(getContent());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (ext === ".jsonc") return Bun.JSONC.parse(getContent());
|
|
66
|
+
if (ext === ".js" || ext === ".cjs" || ext === ".mjs" || ext === ".ts" || ext === ".cts" || ext === ".mts") return __require(absolutePath);
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(getContent());
|
|
69
|
+
} catch {
|
|
70
|
+
console.error(`Unable to parse config file: ${absolutePath}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(`Error loading config file: ${error}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Searches for a config file from a list of possible file names.
|
|
80
|
+
* Searches in the current working directory.
|
|
81
|
+
* @param configFiles - Array of possible config file names to search for
|
|
82
|
+
* @returns The path to the first found config file, or undefined if none found
|
|
83
|
+
*/
|
|
84
|
+
function findConfigFile(configFiles) {
|
|
85
|
+
if (typeof process === "undefined" || !configFiles?.length) return void 0;
|
|
86
|
+
try {
|
|
87
|
+
const fs = __require("node:fs");
|
|
88
|
+
const path = __require("node:path");
|
|
89
|
+
const cwd = process.cwd();
|
|
90
|
+
for (const configFile of configFiles) {
|
|
91
|
+
const configPath = path.isAbsolute(configFile) ? configFile : path.resolve(cwd, configFile);
|
|
92
|
+
if (fs.existsSync(configPath)) return configPath;
|
|
93
|
+
}
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/runtime.ts
|
|
98
|
+
/**
|
|
99
|
+
* Default terminal prompt implementation powered by Enquirer.
|
|
100
|
+
* Lazily imported to avoid loading Enquirer when not needed.
|
|
101
|
+
*/
|
|
102
|
+
async function defaultTerminalPrompt(config) {
|
|
103
|
+
const Enquirer = (await import("enquirer")).default;
|
|
104
|
+
const question = {
|
|
105
|
+
type: config.type,
|
|
106
|
+
name: config.name,
|
|
107
|
+
message: config.message
|
|
108
|
+
};
|
|
109
|
+
if (config.default !== void 0) question.initial = config.default;
|
|
110
|
+
if (config.choices) question.choices = config.choices.map((c) => ({
|
|
111
|
+
name: String(c.value),
|
|
112
|
+
message: c.label
|
|
113
|
+
}));
|
|
114
|
+
return (await Enquirer.prompt(question))[config.name];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Creates a persistent Node.js readline session for the REPL.
|
|
118
|
+
* Enables up/down arrow history navigation and tab completion.
|
|
119
|
+
* Used internally by `repl()` when no custom `readLine` is provided.
|
|
120
|
+
*/
|
|
121
|
+
/**
|
|
122
|
+
* Sentinel value returned by the terminal REPL session when Ctrl+C is pressed.
|
|
123
|
+
* Distinguished from empty string (user pressed enter) and null (EOF/Ctrl+D).
|
|
124
|
+
*/
|
|
125
|
+
const REPL_SIGINT = Symbol("REPL_SIGINT");
|
|
126
|
+
function createTerminalReplSession(config) {
|
|
127
|
+
let history = config.history ? [...config.history] : [];
|
|
128
|
+
let currentCompleter = config.completer;
|
|
129
|
+
return {
|
|
130
|
+
set completer(fn) {
|
|
131
|
+
currentCompleter = fn;
|
|
132
|
+
},
|
|
133
|
+
async question(prompt) {
|
|
134
|
+
const { createInterface } = await import("node:readline");
|
|
135
|
+
const opts = {
|
|
136
|
+
input: process.stdin,
|
|
137
|
+
output: process.stdout,
|
|
138
|
+
terminal: true,
|
|
139
|
+
history: [...history],
|
|
140
|
+
historySize: Math.max(history.length, 1e3)
|
|
141
|
+
};
|
|
142
|
+
if (currentCompleter) opts.completer = currentCompleter;
|
|
143
|
+
const rl = createInterface(opts);
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
let resolved = false;
|
|
146
|
+
const settle = (value) => {
|
|
147
|
+
if (resolved) return;
|
|
148
|
+
resolved = true;
|
|
149
|
+
rl.close();
|
|
150
|
+
resolve(value);
|
|
151
|
+
};
|
|
152
|
+
rl.question(prompt, (answer) => {
|
|
153
|
+
if (Array.isArray(rl.history)) history = [...rl.history];
|
|
154
|
+
settle(answer);
|
|
155
|
+
});
|
|
156
|
+
rl.once("SIGINT", () => {
|
|
157
|
+
process.stdout.write("\n");
|
|
158
|
+
settle(REPL_SIGINT);
|
|
159
|
+
});
|
|
160
|
+
rl.once("close", () => {
|
|
161
|
+
process.stdout.write("\n");
|
|
162
|
+
settle(null);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
close() {}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Auto-detect interactive mode when not explicitly set.
|
|
171
|
+
* Returns 'disabled' in CI environments or non-TTY contexts, 'supported' otherwise.
|
|
172
|
+
*/
|
|
173
|
+
function detectInteractiveMode() {
|
|
174
|
+
if (typeof process === "undefined") return "disabled";
|
|
175
|
+
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) return "disabled";
|
|
176
|
+
if (!process.stdout?.isTTY) return "disabled";
|
|
177
|
+
return "supported";
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Creates the default Node.js/Bun runtime.
|
|
181
|
+
*/
|
|
182
|
+
/**
|
|
183
|
+
* Creates a default stdin reader from `process.stdin`.
|
|
184
|
+
* Only created when a command actually declares a `stdin` meta field.
|
|
185
|
+
*/
|
|
186
|
+
function createDefaultStdin() {
|
|
187
|
+
return {
|
|
188
|
+
get isTTY() {
|
|
189
|
+
if (typeof process === "undefined") return true;
|
|
190
|
+
return process.stdin?.isTTY === true;
|
|
191
|
+
},
|
|
192
|
+
async text() {
|
|
193
|
+
if (typeof process === "undefined") return "";
|
|
194
|
+
const chunks = [];
|
|
195
|
+
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
196
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
197
|
+
},
|
|
198
|
+
async *lines() {
|
|
199
|
+
if (typeof process === "undefined") return;
|
|
200
|
+
const { createInterface } = await import("node:readline");
|
|
201
|
+
const rl = createInterface({ input: process.stdin });
|
|
202
|
+
try {
|
|
203
|
+
for await (const line of rl) yield line;
|
|
204
|
+
} finally {
|
|
205
|
+
rl.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createDefaultRuntime() {
|
|
211
|
+
return {
|
|
212
|
+
output: (...args) => console.log(...args),
|
|
213
|
+
error: (text) => console.error(text),
|
|
214
|
+
argv: () => typeof process !== "undefined" ? process.argv.slice(2) : [],
|
|
215
|
+
env: () => typeof process !== "undefined" ? process.env : {},
|
|
216
|
+
format: "auto",
|
|
217
|
+
loadConfigFile,
|
|
218
|
+
findFile: findConfigFile,
|
|
219
|
+
prompt: defaultTerminalPrompt,
|
|
220
|
+
interactive: detectInteractiveMode()
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Merges a partial runtime with the default runtime.
|
|
225
|
+
*/
|
|
226
|
+
/**
|
|
227
|
+
* Returns the stdin abstraction: custom runtime stdin > default process.stdin.
|
|
228
|
+
* Returns `undefined` when no custom stdin is provided and process.stdin is not piped.
|
|
229
|
+
*/
|
|
230
|
+
function resolveStdin(partial) {
|
|
231
|
+
if (partial?.stdin) return partial.stdin;
|
|
232
|
+
const defaultStdin = createDefaultStdin();
|
|
233
|
+
if (defaultStdin.isTTY) return void 0;
|
|
234
|
+
return defaultStdin;
|
|
235
|
+
}
|
|
236
|
+
function resolveRuntime(partial) {
|
|
237
|
+
const defaults = createDefaultRuntime();
|
|
238
|
+
if (!partial) return defaults;
|
|
239
|
+
return {
|
|
240
|
+
output: partial.output ?? defaults.output,
|
|
241
|
+
error: partial.error ?? defaults.error,
|
|
242
|
+
argv: partial.argv ?? defaults.argv,
|
|
243
|
+
env: partial.env ?? defaults.env,
|
|
244
|
+
format: partial.format ?? defaults.format,
|
|
245
|
+
loadConfigFile: partial.loadConfigFile ?? defaults.loadConfigFile,
|
|
246
|
+
findFile: partial.findFile ?? defaults.findFile,
|
|
247
|
+
interactive: partial.interactive ?? defaults.interactive,
|
|
248
|
+
prompt: partial.prompt ?? defaults.prompt,
|
|
249
|
+
readLine: partial.readLine ?? defaults.readLine,
|
|
250
|
+
stdin: partial.stdin
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/command-utils.ts
|
|
255
|
+
/**
|
|
256
|
+
* Brands a schema as async, signaling that its `validate()` may return a Promise.
|
|
257
|
+
* When an async-branded schema is passed to `.arguments()`, `.configFile()`, or `.env()`,
|
|
258
|
+
* the command's `parse()` and `cli()` will return Promises.
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const schema = asyncSchema(z.object({
|
|
263
|
+
* name: z.string(),
|
|
264
|
+
* }).check(async (data) => {
|
|
265
|
+
* // async validation logic
|
|
266
|
+
* }));
|
|
267
|
+
*
|
|
268
|
+
* const program = createPadrone('app')
|
|
269
|
+
* .command('greet', (c) => c.arguments(schema).action((args) => args.name));
|
|
270
|
+
*
|
|
271
|
+
* // parse() now returns Promise<PadroneParseResult>
|
|
272
|
+
* const result = await program.parse('greet --name world');
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
function asyncSchema(schema) {
|
|
276
|
+
return Object.assign(schema, { "~async": true });
|
|
277
|
+
}
|
|
278
|
+
const commandSymbol = Symbol("padrone_command");
|
|
279
|
+
const noop = () => void 0;
|
|
280
|
+
/** Config keys that are merged when overriding a command. */
|
|
281
|
+
const configKeys = [
|
|
282
|
+
"title",
|
|
283
|
+
"description",
|
|
284
|
+
"version",
|
|
285
|
+
"deprecated",
|
|
286
|
+
"hidden",
|
|
287
|
+
"needsApproval",
|
|
288
|
+
"autoOutput",
|
|
289
|
+
"updateCheck"
|
|
290
|
+
];
|
|
291
|
+
/**
|
|
292
|
+
* Merges an existing command with an override.
|
|
293
|
+
* - Config fields are shallow-merged (new overrides old).
|
|
294
|
+
* - Action, arguments, meta, config schema, env schema are taken from the override if set.
|
|
295
|
+
* - Subcommands are recursively merged by name.
|
|
296
|
+
*/
|
|
297
|
+
function mergeCommands(existing, override) {
|
|
298
|
+
const merged = { ...existing };
|
|
299
|
+
for (const key of configKeys) if (override[key] !== void 0) merged[key] = override[key];
|
|
300
|
+
if (override.action !== existing.action) merged.action = override.action;
|
|
301
|
+
if (override.argsSchema !== existing.argsSchema) merged.argsSchema = override.argsSchema;
|
|
302
|
+
if (override.meta !== existing.meta) merged.meta = override.meta;
|
|
303
|
+
if (override.configSchema !== existing.configSchema) merged.configSchema = override.configSchema;
|
|
304
|
+
if (override.envSchema !== existing.envSchema) merged.envSchema = override.envSchema;
|
|
305
|
+
if (override.configFiles !== existing.configFiles) merged.configFiles = override.configFiles;
|
|
306
|
+
if (override.isAsync !== existing.isAsync) merged.isAsync = override.isAsync || existing.isAsync;
|
|
307
|
+
if (override.runtime !== existing.runtime) merged.runtime = override.runtime;
|
|
308
|
+
if (override.plugins !== existing.plugins) merged.plugins = override.plugins;
|
|
309
|
+
if (override.aliases !== existing.aliases) merged.aliases = override.aliases;
|
|
310
|
+
if (override.commands) {
|
|
311
|
+
const baseCommands = [...existing.commands || []];
|
|
312
|
+
for (const overrideChild of override.commands) {
|
|
313
|
+
const existingIndex = baseCommands.findIndex((c) => c.name === overrideChild.name);
|
|
314
|
+
if (existingIndex >= 0) baseCommands[existingIndex] = mergeCommands(baseCommands[existingIndex], overrideChild);
|
|
315
|
+
else baseCommands.push(overrideChild);
|
|
316
|
+
}
|
|
317
|
+
merged.commands = baseCommands;
|
|
318
|
+
}
|
|
319
|
+
return merged;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Maps over a value that may or may not be a Promise.
|
|
323
|
+
* If the value is a Promise, chains with `.then()`. Otherwise, calls the function synchronously.
|
|
324
|
+
* This preserves sync behavior for sync schemas and async behavior for async schemas.
|
|
325
|
+
*/
|
|
326
|
+
function thenMaybe(value, fn) {
|
|
327
|
+
if (value instanceof Promise) return value.then(fn);
|
|
328
|
+
return fn(value);
|
|
329
|
+
}
|
|
330
|
+
function isIterator(value) {
|
|
331
|
+
return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
|
|
332
|
+
}
|
|
333
|
+
function isAsyncIterator(value) {
|
|
334
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Writes a command's return value to output, handling promises, iterators, and async iterators.
|
|
338
|
+
* Values are passed directly to the output function without stringification —
|
|
339
|
+
* runtimes like Node/Bun already format objects via console.log.
|
|
340
|
+
* Returns void or a Promise depending on whether async consumption is needed.
|
|
341
|
+
*/
|
|
342
|
+
function outputValue(value, output) {
|
|
343
|
+
if (value == null) return;
|
|
344
|
+
if (isAsyncIterator(value)) return (async () => {
|
|
345
|
+
const iter = value[Symbol.asyncIterator]();
|
|
346
|
+
while (true) {
|
|
347
|
+
const { done, value: item } = await iter.next();
|
|
348
|
+
if (done) break;
|
|
349
|
+
if (item != null) output(item);
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
if (typeof value !== "string" && !Array.isArray(value) && isIterator(value)) {
|
|
353
|
+
const iter = value[Symbol.iterator]();
|
|
354
|
+
while (true) {
|
|
355
|
+
const { done, value: item } = iter.next();
|
|
356
|
+
if (done) break;
|
|
357
|
+
if (item != null) output(item);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (value instanceof Promise) return value.then((resolved) => outputValue(resolved, output));
|
|
362
|
+
output(value);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Runs a plugin chain for a given phase using the onion/middleware pattern.
|
|
366
|
+
* Plugins are sorted by `order` (ascending, stable), then composed so that
|
|
367
|
+
* the first plugin in sorted order is the outermost wrapper.
|
|
368
|
+
* If no plugins handle this phase, `core` is called directly.
|
|
369
|
+
*/
|
|
370
|
+
function runPluginChain(phase, plugins, ctx, core) {
|
|
371
|
+
const phasePlugins = plugins.filter((p) => p[phase]);
|
|
372
|
+
if (phasePlugins.length === 0) return core();
|
|
373
|
+
phasePlugins.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
374
|
+
let next = core;
|
|
375
|
+
for (let i = phasePlugins.length - 1; i >= 0; i--) {
|
|
376
|
+
const handler = phasePlugins[i][phase];
|
|
377
|
+
const prevNext = next;
|
|
378
|
+
next = () => handler(ctx, prevNext);
|
|
379
|
+
}
|
|
380
|
+
return next();
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Wraps a pipeline with start → error → shutdown lifecycle hooks.
|
|
384
|
+
* - `start` plugins wrap the pipeline (onion pattern, root plugins only).
|
|
385
|
+
* - On error: `error` plugins run (can transform/suppress the error).
|
|
386
|
+
* - Always: `shutdown` plugins run (success or failure).
|
|
387
|
+
*/
|
|
388
|
+
function wrapWithLifecycle(plugins, command, state, input, pipeline, wrapErrorResult) {
|
|
389
|
+
const hasStart = plugins.some((p) => p.start);
|
|
390
|
+
const hasError = plugins.some((p) => p.error);
|
|
391
|
+
const hasShutdown = plugins.some((p) => p.shutdown);
|
|
392
|
+
if (!hasStart && !hasError && !hasShutdown) return pipeline();
|
|
393
|
+
const runShutdown = (error, result) => {
|
|
394
|
+
if (!hasShutdown) return;
|
|
395
|
+
return runPluginChain("shutdown", plugins, {
|
|
396
|
+
command,
|
|
397
|
+
state,
|
|
398
|
+
error,
|
|
399
|
+
result
|
|
400
|
+
}, () => {});
|
|
401
|
+
};
|
|
402
|
+
const runError = (error) => {
|
|
403
|
+
if (!hasError) {
|
|
404
|
+
const s = runShutdown(error);
|
|
405
|
+
if (s instanceof Promise) return s.then(() => {
|
|
406
|
+
throw error;
|
|
407
|
+
});
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
return thenMaybe(runPluginChain("error", plugins, {
|
|
411
|
+
command,
|
|
412
|
+
state,
|
|
413
|
+
error
|
|
414
|
+
}, () => ({ error })), (er) => {
|
|
415
|
+
if (er.error !== void 0) return thenMaybe(runShutdown(er.error), () => {
|
|
416
|
+
throw er.error;
|
|
417
|
+
});
|
|
418
|
+
const wrapped = wrapErrorResult ? wrapErrorResult(er.result) : er.result;
|
|
419
|
+
return thenMaybe(runShutdown(void 0, wrapped), () => wrapped);
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
const handleSuccess = (result) => {
|
|
423
|
+
const s = runShutdown(void 0, result);
|
|
424
|
+
if (s instanceof Promise) return s.then(() => result);
|
|
425
|
+
return result;
|
|
426
|
+
};
|
|
427
|
+
const startCtx = {
|
|
428
|
+
command,
|
|
429
|
+
state,
|
|
430
|
+
input
|
|
431
|
+
};
|
|
432
|
+
let result;
|
|
433
|
+
try {
|
|
434
|
+
result = hasStart ? runPluginChain("start", plugins, startCtx, pipeline) : pipeline();
|
|
435
|
+
} catch (e) {
|
|
436
|
+
return runError(e);
|
|
437
|
+
}
|
|
438
|
+
if (result instanceof Promise) return result.then(handleSuccess, runError);
|
|
439
|
+
return handleSuccess(result);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Resolves the runtime for a command by walking up the parent chain.
|
|
443
|
+
* Returns a fully resolved runtime with all defaults filled in.
|
|
444
|
+
*/
|
|
445
|
+
function getCommandRuntime(cmd) {
|
|
446
|
+
let current = cmd;
|
|
447
|
+
while (current) {
|
|
448
|
+
if (current.runtime) return resolveRuntime(current.runtime);
|
|
449
|
+
current = current.parent;
|
|
450
|
+
}
|
|
451
|
+
return resolveRuntime();
|
|
452
|
+
}
|
|
453
|
+
function isAsyncBranded(schema) {
|
|
454
|
+
return !!schema && typeof schema === "object" && "~async" in schema && schema["~async"] === true;
|
|
455
|
+
}
|
|
456
|
+
function hasInteractiveConfig(meta) {
|
|
457
|
+
if (!meta || typeof meta !== "object") return false;
|
|
458
|
+
const m = meta;
|
|
459
|
+
return m.interactive === true || Array.isArray(m.interactive) || m.optionalInteractive === true || Array.isArray(m.optionalInteractive);
|
|
460
|
+
}
|
|
461
|
+
function warnIfUnexpectedAsync(value, command) {
|
|
462
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return value;
|
|
463
|
+
if (value instanceof Promise && !command.isAsync) getCommandRuntime(command).error(`[padrone] Command "${command.path || command.name}" returned a Promise from validation, but was not marked as async. Use \`.async()\` on the builder or \`asyncSchema()\` to brand your schema. Without this, TypeScript will infer a sync return type and the result will be a Promise at runtime.`);
|
|
464
|
+
return value;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Recursively re-paths a command tree under a new parent path, updating parent references.
|
|
468
|
+
*/
|
|
469
|
+
function repathCommandTree(cmd, newName, parentPath, parent) {
|
|
470
|
+
const newPath = parentPath ? `${parentPath} ${newName}` : newName;
|
|
471
|
+
const remounted = {
|
|
472
|
+
...cmd,
|
|
473
|
+
name: newName,
|
|
474
|
+
path: newPath,
|
|
475
|
+
parent,
|
|
476
|
+
version: void 0
|
|
477
|
+
};
|
|
478
|
+
if (cmd.commands?.length) remounted.commands = cmd.commands.map((child) => repathCommandTree(child, child.name, newPath, remounted));
|
|
479
|
+
return remounted;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Builds a completer function for the REPL from the command tree.
|
|
483
|
+
* Completes command names, subcommand names, option names (--foo), and aliases (-f).
|
|
484
|
+
* Also includes dot-prefixed built-in REPL commands (.exit, .clear, .scope, .help, .history).
|
|
485
|
+
*/
|
|
486
|
+
function buildReplCompleter(rootCommand, builtins) {
|
|
487
|
+
return (line) => {
|
|
488
|
+
const parts = line.trimStart().split(/\s+/);
|
|
489
|
+
const lastPart = parts[parts.length - 1] ?? "";
|
|
490
|
+
if (lastPart.startsWith(".")) {
|
|
491
|
+
const dotCmds = [
|
|
492
|
+
".exit",
|
|
493
|
+
".clear",
|
|
494
|
+
".help",
|
|
495
|
+
".history"
|
|
496
|
+
];
|
|
497
|
+
if (rootCommand.commands?.some((c) => c.commands?.length) || builtins.inScope) dotCmds.push(".scope");
|
|
498
|
+
const hits = dotCmds.filter((c) => c.startsWith(lastPart));
|
|
499
|
+
return [hits.length ? hits : dotCmds, lastPart];
|
|
500
|
+
}
|
|
501
|
+
if (lastPart.startsWith("-")) {
|
|
502
|
+
const commandParts = parts.slice(0, -1).filter((p) => !p.startsWith("-"));
|
|
503
|
+
let targetCommand = rootCommand;
|
|
504
|
+
for (const part of commandParts) {
|
|
505
|
+
const sub = targetCommand.commands?.find((c) => c.name === part || c.aliases?.includes(part));
|
|
506
|
+
if (sub) targetCommand = sub;
|
|
507
|
+
else break;
|
|
508
|
+
}
|
|
509
|
+
const options = [];
|
|
510
|
+
if (targetCommand.argsSchema) try {
|
|
511
|
+
const argsMeta = targetCommand.meta?.fields;
|
|
512
|
+
const { flags, aliases } = extractSchemaMetadata(targetCommand.argsSchema, argsMeta, targetCommand.meta?.autoAlias);
|
|
513
|
+
const jsonSchema = targetCommand.argsSchema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
514
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
515
|
+
for (const key of Object.keys(jsonSchema.properties)) options.push(`--${key}`);
|
|
516
|
+
for (const flag of Object.keys(flags)) options.push(`-${flag}`);
|
|
517
|
+
for (const alias of Object.keys(aliases)) options.push(`--${alias}`);
|
|
518
|
+
}
|
|
519
|
+
} catch {}
|
|
520
|
+
options.push("--help", "-h");
|
|
521
|
+
const hits = options.filter((o) => o.startsWith(lastPart));
|
|
522
|
+
return [hits.length ? hits : options, lastPart];
|
|
523
|
+
}
|
|
524
|
+
const commandParts = parts.filter((p) => !p.startsWith("-"));
|
|
525
|
+
let targetCommand = rootCommand;
|
|
526
|
+
for (let i = 0; i < commandParts.length - 1; i++) {
|
|
527
|
+
const sub = targetCommand.commands?.find((c) => c.name === commandParts[i] || c.aliases?.includes(commandParts[i]));
|
|
528
|
+
if (sub) targetCommand = sub;
|
|
529
|
+
else break;
|
|
530
|
+
}
|
|
531
|
+
const candidates = [];
|
|
532
|
+
if (targetCommand.commands) {
|
|
533
|
+
for (const cmd of targetCommand.commands) if (!cmd.hidden) {
|
|
534
|
+
candidates.push(cmd.name);
|
|
535
|
+
if (cmd.aliases) candidates.push(...cmd.aliases);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (targetCommand === rootCommand) {
|
|
539
|
+
candidates.push(".help", ".exit", ".clear", ".history");
|
|
540
|
+
if (rootCommand.commands?.some((c) => c.commands?.length) || builtins.inScope) candidates.push(".scope");
|
|
541
|
+
if (builtins.inScope) candidates.push("..");
|
|
542
|
+
}
|
|
543
|
+
const hits = candidates.filter((c) => c.startsWith(lastPart));
|
|
544
|
+
return [hits.length ? hits : candidates, lastPart];
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Computes the Levenshtein edit distance between two strings.
|
|
549
|
+
*/
|
|
550
|
+
function levenshtein(a, b) {
|
|
551
|
+
const m = a.length;
|
|
552
|
+
const n = b.length;
|
|
553
|
+
const dp = Array.from({ length: n + 1 }, (_, i) => i);
|
|
554
|
+
for (let i = 1; i <= m; i++) {
|
|
555
|
+
let prev = dp[0];
|
|
556
|
+
dp[0] = i;
|
|
557
|
+
for (let j = 1; j <= n; j++) {
|
|
558
|
+
const temp = dp[j];
|
|
559
|
+
dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
|
|
560
|
+
prev = temp;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return dp[n];
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Finds the closest match from a list of candidates using Levenshtein distance.
|
|
567
|
+
* Returns the suggestion string (e.g. 'Did you mean "deploy"?') or empty string if no good match.
|
|
568
|
+
* Threshold: distance must be at most 40% of the longer string's length (min 1, max 3).
|
|
569
|
+
*/
|
|
570
|
+
function suggestSimilar(input, candidates) {
|
|
571
|
+
if (candidates.length === 0) return "";
|
|
572
|
+
const lower = input.toLowerCase();
|
|
573
|
+
let bestDist = Infinity;
|
|
574
|
+
let bestMatch = "";
|
|
575
|
+
for (const candidate of candidates) {
|
|
576
|
+
const dist = levenshtein(lower, candidate.toLowerCase());
|
|
577
|
+
if (dist < bestDist) {
|
|
578
|
+
bestDist = dist;
|
|
579
|
+
bestMatch = candidate;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const maxLen = Math.max(input.length, bestMatch.length);
|
|
583
|
+
const threshold = Math.min(3, Math.max(1, Math.ceil(maxLen * .4)));
|
|
584
|
+
if (bestDist > 0 && bestDist <= threshold) return `Did you mean "${bestMatch}"?`;
|
|
585
|
+
return "";
|
|
586
|
+
}
|
|
587
|
+
function findCommandByName(name, commands) {
|
|
588
|
+
if (!commands) return void 0;
|
|
589
|
+
const foundByName = commands.find((cmd) => cmd.name === name);
|
|
590
|
+
if (foundByName) return foundByName;
|
|
591
|
+
const foundByAlias = commands.find((cmd) => cmd.aliases?.includes(name));
|
|
592
|
+
if (foundByAlias) return foundByAlias;
|
|
593
|
+
for (const cmd of commands) {
|
|
594
|
+
if (cmd.commands && name.startsWith(`${cmd.name} `)) {
|
|
595
|
+
const subCommand = findCommandByName(name.slice(cmd.name.length + 1), cmd.commands);
|
|
596
|
+
if (subCommand) return subCommand;
|
|
597
|
+
}
|
|
598
|
+
if (cmd.commands && cmd.aliases) {
|
|
599
|
+
for (const alias of cmd.aliases) if (name.startsWith(`${alias} `)) {
|
|
600
|
+
const subCommand = findCommandByName(name.slice(alias.length + 1), cmd.commands);
|
|
601
|
+
if (subCommand) return subCommand;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region src/colorizer.ts
|
|
608
|
+
const colors = {
|
|
609
|
+
reset: "\x1B[0m",
|
|
610
|
+
bold: "\x1B[1m",
|
|
611
|
+
dim: "\x1B[2m",
|
|
612
|
+
italic: "\x1B[3m",
|
|
613
|
+
underline: "\x1B[4m",
|
|
614
|
+
strikethrough: "\x1B[9m",
|
|
615
|
+
cyan: "\x1B[36m",
|
|
616
|
+
green: "\x1B[32m",
|
|
617
|
+
yellow: "\x1B[33m",
|
|
618
|
+
blue: "\x1B[34m",
|
|
619
|
+
magenta: "\x1B[35m",
|
|
620
|
+
gray: "\x1B[90m"
|
|
621
|
+
};
|
|
622
|
+
function createColorizer() {
|
|
623
|
+
return {
|
|
624
|
+
command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
|
|
625
|
+
arg: (text) => `${colors.green}${text}${colors.reset}`,
|
|
626
|
+
type: (text) => `${colors.yellow}${text}${colors.reset}`,
|
|
627
|
+
description: (text) => `${colors.dim}${text}${colors.reset}`,
|
|
628
|
+
label: (text) => `${colors.bold}${text}${colors.reset}`,
|
|
629
|
+
meta: (text) => `${colors.gray}${text}${colors.reset}`,
|
|
630
|
+
example: (text) => `${colors.underline}${text}${colors.reset}`,
|
|
631
|
+
exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
|
|
632
|
+
deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
//#endregion
|
|
636
|
+
//#region src/formatter.ts
|
|
637
|
+
function createTextStyler() {
|
|
638
|
+
return {
|
|
639
|
+
command: (text) => text,
|
|
640
|
+
arg: (text) => text,
|
|
641
|
+
type: (text) => text,
|
|
642
|
+
description: (text) => text,
|
|
643
|
+
label: (text) => text,
|
|
644
|
+
meta: (text) => text,
|
|
645
|
+
example: (text) => text,
|
|
646
|
+
exampleValue: (text) => text,
|
|
647
|
+
deprecated: (text) => text
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function createAnsiStyler() {
|
|
651
|
+
const colorizer = createColorizer();
|
|
652
|
+
return {
|
|
653
|
+
command: colorizer.command,
|
|
654
|
+
arg: colorizer.arg,
|
|
655
|
+
type: colorizer.type,
|
|
656
|
+
description: colorizer.description,
|
|
657
|
+
label: colorizer.label,
|
|
658
|
+
meta: colorizer.meta,
|
|
659
|
+
example: colorizer.example,
|
|
660
|
+
exampleValue: colorizer.exampleValue,
|
|
661
|
+
deprecated: colorizer.deprecated
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
function createConsoleStyler() {
|
|
665
|
+
const colors = {
|
|
666
|
+
reset: "\x1B[0m",
|
|
667
|
+
bold: "\x1B[1m",
|
|
668
|
+
dim: "\x1B[2m",
|
|
669
|
+
italic: "\x1B[3m",
|
|
670
|
+
underline: "\x1B[4m",
|
|
671
|
+
strikethrough: "\x1B[9m",
|
|
672
|
+
cyan: "\x1B[36m",
|
|
673
|
+
green: "\x1B[32m",
|
|
674
|
+
yellow: "\x1B[33m",
|
|
675
|
+
gray: "\x1B[90m"
|
|
676
|
+
};
|
|
677
|
+
return {
|
|
678
|
+
command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
|
|
679
|
+
arg: (text) => `${colors.green}${text}${colors.reset}`,
|
|
680
|
+
type: (text) => `${colors.yellow}${text}${colors.reset}`,
|
|
681
|
+
description: (text) => `${colors.dim}${text}${colors.reset}`,
|
|
682
|
+
label: (text) => `${colors.bold}${text}${colors.reset}`,
|
|
683
|
+
meta: (text) => `${colors.gray}${text}${colors.reset}`,
|
|
684
|
+
example: (text) => `${colors.underline}${text}${colors.reset}`,
|
|
685
|
+
exampleValue: (text) => `${colors.italic}${text}${colors.reset}`,
|
|
686
|
+
deprecated: (text) => `${colors.strikethrough}${colors.gray}${text}${colors.reset}`
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function createMarkdownStyler() {
|
|
690
|
+
return {
|
|
691
|
+
command: (text) => `**${text}**`,
|
|
692
|
+
arg: (text) => `\`${text}\``,
|
|
693
|
+
type: (text) => `\`${text}\``,
|
|
694
|
+
description: (text) => text,
|
|
695
|
+
label: (text) => `### ${text}`,
|
|
696
|
+
meta: (text) => `*${text}*`,
|
|
697
|
+
example: (text) => `**${text}**`,
|
|
698
|
+
exampleValue: (text) => `\`${text}\``,
|
|
699
|
+
deprecated: (text) => `~~${text}~~`
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function escapeHtml(text) {
|
|
703
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
704
|
+
}
|
|
705
|
+
function createHtmlStyler() {
|
|
706
|
+
return {
|
|
707
|
+
command: (text) => `<strong style="color: #00bcd4;">${escapeHtml(text)}</strong>`,
|
|
708
|
+
arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
|
|
709
|
+
type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
|
|
710
|
+
description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
|
|
711
|
+
label: (text) => `<h3>${escapeHtml(text)}</h3>`,
|
|
712
|
+
meta: (text) => `<span style="color: #999;">${escapeHtml(text)}</span>`,
|
|
713
|
+
example: (text) => `<strong style="text-decoration: underline;">${escapeHtml(text)}</strong>`,
|
|
714
|
+
exampleValue: (text) => `<em>${escapeHtml(text)}</em>`,
|
|
715
|
+
deprecated: (text) => `<del style="color: #999;">${escapeHtml(text)}</del>`
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function createTextLayout() {
|
|
719
|
+
return {
|
|
720
|
+
newline: "\n",
|
|
721
|
+
indent: (level) => " ".repeat(level),
|
|
722
|
+
join: (parts) => parts.filter(Boolean).join(" "),
|
|
723
|
+
usageLabel: "Usage:"
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function createMarkdownLayout() {
|
|
727
|
+
return {
|
|
728
|
+
newline: "\n\n",
|
|
729
|
+
indent: (level) => {
|
|
730
|
+
if (level === 0) return "";
|
|
731
|
+
if (level === 1) return " ";
|
|
732
|
+
return " ";
|
|
733
|
+
},
|
|
734
|
+
join: (parts) => parts.filter(Boolean).join(" "),
|
|
735
|
+
usageLabel: "Usage:"
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function createHtmlLayout() {
|
|
739
|
+
return {
|
|
740
|
+
newline: "<br>",
|
|
741
|
+
indent: (level) => " ".repeat(level),
|
|
742
|
+
join: (parts) => parts.filter(Boolean).join(" "),
|
|
743
|
+
wrapDocument: (content) => `<div style="font-family: monospace; line-height: 1.6;">${content}</div>`,
|
|
744
|
+
usageLabel: "<strong>Usage:</strong>"
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Creates a formatter that uses the given styler and layout configuration.
|
|
749
|
+
*/
|
|
750
|
+
function createGenericFormatter(styler, layout) {
|
|
751
|
+
const { newline, indent, join, wrapDocument, usageLabel } = layout;
|
|
752
|
+
function formatUsageSection(info) {
|
|
753
|
+
const usageParts = [styler.command(info.usage.command), info.usage.hasSubcommands ? styler.meta("[command]") : ""];
|
|
754
|
+
if (info.positionals && info.positionals.length > 0) for (const arg of info.positionals) {
|
|
755
|
+
const name = arg.name.startsWith("...") ? `${arg.name}` : arg.name;
|
|
756
|
+
usageParts.push(styler.meta(arg.optional ? `[${name}]` : `<${name}>`));
|
|
757
|
+
}
|
|
758
|
+
if (info.usage.hasArguments) usageParts.push(styler.meta("[options]"));
|
|
759
|
+
return [`${usageLabel} ${join(usageParts)}`];
|
|
760
|
+
}
|
|
761
|
+
function formatSubcommandsSection(info) {
|
|
762
|
+
const lines = [];
|
|
763
|
+
const subcommands = info.subcommands;
|
|
764
|
+
lines.push(styler.label("Commands:"));
|
|
765
|
+
const subcommandSuffix = (c) => c.hasSubcommands ? " <subcommand>" : "";
|
|
766
|
+
const formatAliasParts = (c) => {
|
|
767
|
+
if (!c.aliases?.length) return {
|
|
768
|
+
plain: "",
|
|
769
|
+
styled: ""
|
|
770
|
+
};
|
|
771
|
+
const realAliases = c.aliases.filter((a) => a !== "[default]");
|
|
772
|
+
const hasDefault = c.aliases.some((a) => a === "[default]");
|
|
773
|
+
const parts = [];
|
|
774
|
+
const styledParts = [];
|
|
775
|
+
if (realAliases.length) {
|
|
776
|
+
parts.push(`(${realAliases.join(", ")})`);
|
|
777
|
+
styledParts.push(`(${realAliases.join(", ")})`);
|
|
778
|
+
}
|
|
779
|
+
if (hasDefault) {
|
|
780
|
+
parts.push("[default]");
|
|
781
|
+
styledParts.push(styler.meta("[default]"));
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
plain: parts.length ? ` ${parts.join(" ")}` : "",
|
|
785
|
+
styled: styledParts.length ? ` ${styledParts.join(" ")}` : ""
|
|
786
|
+
};
|
|
787
|
+
};
|
|
788
|
+
const maxNameLength = Math.max(...subcommands.map((c) => {
|
|
789
|
+
return (c.name + subcommandSuffix(c) + formatAliasParts(c).plain).length;
|
|
790
|
+
}));
|
|
791
|
+
for (const subCmd of subcommands) {
|
|
792
|
+
const aliasParts = formatAliasParts(subCmd);
|
|
793
|
+
const suffix = subcommandSuffix(subCmd);
|
|
794
|
+
const commandDisplay = subCmd.name + suffix + aliasParts.plain;
|
|
795
|
+
const padding = " ".repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
|
|
796
|
+
const isDeprecated = !!subCmd.deprecated;
|
|
797
|
+
const isDefaultEntry = subCmd.name === "[default]";
|
|
798
|
+
const lineParts = [isDeprecated ? styler.deprecated(commandDisplay) : (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) + (suffix ? styler.meta(suffix) : "") + aliasParts.styled, padding];
|
|
799
|
+
const displayText = subCmd.title ?? subCmd.description;
|
|
800
|
+
if (displayText) lineParts.push(isDeprecated ? styler.deprecated(displayText) : styler.description(displayText));
|
|
801
|
+
if (isDeprecated) {
|
|
802
|
+
const deprecatedMeta = typeof subCmd.deprecated === "string" ? styler.meta(` (deprecated: ${subCmd.deprecated})`) : styler.meta(" (deprecated)");
|
|
803
|
+
lineParts.push(deprecatedMeta);
|
|
804
|
+
}
|
|
805
|
+
lines.push(indent(1) + lineParts.join(""));
|
|
806
|
+
}
|
|
807
|
+
lines.push("");
|
|
808
|
+
lines.push(styler.meta(`Run "${info.name} [command] --help" for more information on a command.`));
|
|
809
|
+
return lines;
|
|
810
|
+
}
|
|
811
|
+
function formatPositionalsSection(info) {
|
|
812
|
+
const lines = [];
|
|
813
|
+
const args = info.positionals;
|
|
814
|
+
lines.push(styler.label("Arguments:"));
|
|
815
|
+
const maxNameLength = Math.min(32, Math.max(...args.map((a) => a.name.length)));
|
|
816
|
+
for (const arg of args) {
|
|
817
|
+
const padding = " ".repeat(Math.max(2, maxNameLength - arg.name.length + 2));
|
|
818
|
+
const descParts = [];
|
|
819
|
+
if (arg.description) descParts.push(styler.description(arg.description));
|
|
820
|
+
if (info.usage.stdinField === arg.name) descParts.push(styler.meta("(stdin)"));
|
|
821
|
+
if (arg.default !== void 0) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
|
|
822
|
+
lines.push(indent(1) + styler.arg(arg.name) + padding + join(descParts));
|
|
823
|
+
}
|
|
824
|
+
return lines;
|
|
825
|
+
}
|
|
826
|
+
function formatArgumentsSection(info) {
|
|
827
|
+
const lines = [];
|
|
828
|
+
const argList = info.arguments || [];
|
|
829
|
+
lines.push(styler.label("Options:"));
|
|
830
|
+
const hasDefault = (value) => {
|
|
831
|
+
if (value === void 0) return false;
|
|
832
|
+
if (value === "") return false;
|
|
833
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
834
|
+
return true;
|
|
835
|
+
};
|
|
836
|
+
const argColumns = argList.map((arg) => {
|
|
837
|
+
const kebab = camelToKebab(arg.name);
|
|
838
|
+
const primaryName = kebab && arg.aliases?.includes(kebab) ? kebab : arg.name;
|
|
839
|
+
const remainingAliases = arg.aliases?.filter((a) => a !== primaryName);
|
|
840
|
+
const argName = `--${primaryName}`;
|
|
841
|
+
const shortNames = [arg.flags?.length ? arg.flags.map((f) => `-${f}`).join(", ") : "", remainingAliases?.length ? remainingAliases.map((a) => `--${a}`).join(", ") : ""].filter(Boolean).join(", ");
|
|
842
|
+
const fullArgName = shortNames ? `${argName}, ${shortNames}` : argName;
|
|
843
|
+
const isDeprecated = !!arg.deprecated;
|
|
844
|
+
const formattedArgName = isDeprecated ? styler.deprecated(fullArgName) : styler.arg(fullArgName);
|
|
845
|
+
const plainParts = [fullArgName];
|
|
846
|
+
const styledParts = [formattedArgName];
|
|
847
|
+
if (arg.type && arg.type !== "boolean") {
|
|
848
|
+
const typePart = arg.optional ? `[${arg.type}]` : `<${arg.type}>`;
|
|
849
|
+
plainParts.push(typePart);
|
|
850
|
+
styledParts.push(styler.type(typePart));
|
|
851
|
+
}
|
|
852
|
+
if (isDeprecated) {
|
|
853
|
+
const deprecatedPart = typeof arg.deprecated === "string" ? `(deprecated: ${arg.deprecated})` : "(deprecated)";
|
|
854
|
+
plainParts.push(deprecatedPart);
|
|
855
|
+
styledParts.push(styler.meta(deprecatedPart));
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
plain: plainParts.join(" "),
|
|
859
|
+
styled: join(styledParts),
|
|
860
|
+
arg
|
|
861
|
+
};
|
|
862
|
+
});
|
|
863
|
+
const maxColumnWidth = Math.min(32, Math.max(...argColumns.map((c) => c.plain.length)));
|
|
864
|
+
for (const { plain, styled, arg } of argColumns) {
|
|
865
|
+
const padding = " ".repeat(Math.max(2, maxColumnWidth - plain.length + 2));
|
|
866
|
+
const descParts = [];
|
|
867
|
+
if (arg.description) descParts.push(styler.description(arg.description));
|
|
868
|
+
if (info.usage.stdinField === arg.name) descParts.push(styler.meta("(stdin)"));
|
|
869
|
+
if (arg.enum) descParts.push(styler.meta(`(choices: ${arg.enum.join(", ")})`));
|
|
870
|
+
if (hasDefault(arg.default)) descParts.push(styler.meta(`(default: ${String(arg.default)})`));
|
|
871
|
+
lines.push(indent(1) + styled + padding + join(descParts));
|
|
872
|
+
if (arg.env) {
|
|
873
|
+
const envVars = typeof arg.env === "string" ? [arg.env] : arg.env;
|
|
874
|
+
const envParts = [styler.example("Env:"), styler.exampleValue(envVars.join(", "))];
|
|
875
|
+
lines.push(indent(3) + join(envParts));
|
|
876
|
+
}
|
|
877
|
+
if (arg.configKey) {
|
|
878
|
+
const configParts = [styler.example("Config:"), styler.exampleValue(arg.configKey)];
|
|
879
|
+
lines.push(indent(3) + join(configParts));
|
|
880
|
+
}
|
|
881
|
+
if (arg.examples && arg.examples.length > 0) {
|
|
882
|
+
const exampleValues = arg.examples.map((example) => typeof example === "string" ? example : JSON.stringify(example)).join(", ");
|
|
883
|
+
const exampleParts = [styler.example("Example:"), styler.exampleValue(exampleValues)];
|
|
884
|
+
lines.push(indent(3) + join(exampleParts));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return lines;
|
|
888
|
+
}
|
|
889
|
+
function formatBuiltinsSection(info) {
|
|
890
|
+
const lines = [];
|
|
891
|
+
const builtins = info.builtins;
|
|
892
|
+
lines.push(styler.label("Built-in:"));
|
|
893
|
+
const allLengths = [];
|
|
894
|
+
for (const entry of builtins) {
|
|
895
|
+
allLengths.push(entry.name.length);
|
|
896
|
+
if (entry.sub) for (const sub of entry.sub) allLengths.push(sub.name.length + 2);
|
|
897
|
+
}
|
|
898
|
+
const maxLen = Math.max(...allLengths);
|
|
899
|
+
for (const entry of builtins) {
|
|
900
|
+
const padding = " ".repeat(Math.max(2, maxLen - entry.name.length + 2));
|
|
901
|
+
const parts = [styler.command(entry.name)];
|
|
902
|
+
if (entry.description) parts.push(padding + styler.description(entry.description));
|
|
903
|
+
lines.push(indent(1) + parts.join(""));
|
|
904
|
+
if (entry.sub) for (const sub of entry.sub) {
|
|
905
|
+
const subPadding = " ".repeat(Math.max(2, maxLen - sub.name.length));
|
|
906
|
+
const subParts = [styler.arg(sub.name)];
|
|
907
|
+
if (sub.description) subParts.push(subPadding + styler.description(sub.description));
|
|
908
|
+
lines.push(indent(2) + subParts.join(""));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return lines;
|
|
912
|
+
}
|
|
913
|
+
return { format(info) {
|
|
914
|
+
const lines = [];
|
|
915
|
+
if (info.deprecated) {
|
|
916
|
+
const deprecationMessage = typeof info.deprecated === "string" ? `⚠️ This command is deprecated: ${info.deprecated}` : "⚠️ This command is deprecated";
|
|
917
|
+
lines.push(styler.deprecated(deprecationMessage));
|
|
918
|
+
lines.push("");
|
|
919
|
+
}
|
|
920
|
+
lines.push(...formatUsageSection(info));
|
|
921
|
+
lines.push("");
|
|
922
|
+
if (info.title) {
|
|
923
|
+
lines.push(styler.label(info.title));
|
|
924
|
+
lines.push("");
|
|
925
|
+
}
|
|
926
|
+
if (info.aliases && info.aliases.length > 0) {
|
|
927
|
+
lines.push(styler.meta(`Aliases: ${info.aliases.join(", ")}`));
|
|
928
|
+
lines.push("");
|
|
929
|
+
}
|
|
930
|
+
if (info.description) {
|
|
931
|
+
lines.push(styler.description(info.description));
|
|
932
|
+
lines.push("");
|
|
933
|
+
}
|
|
934
|
+
if (info.subcommands && info.subcommands.length > 0) {
|
|
935
|
+
lines.push(...formatSubcommandsSection(info));
|
|
936
|
+
lines.push("");
|
|
937
|
+
}
|
|
938
|
+
if (info.positionals && info.positionals.length > 0) {
|
|
939
|
+
lines.push(...formatPositionalsSection(info));
|
|
940
|
+
lines.push("");
|
|
941
|
+
}
|
|
942
|
+
if (info.arguments && info.arguments.length > 0) {
|
|
943
|
+
lines.push(...formatArgumentsSection(info));
|
|
944
|
+
lines.push("");
|
|
945
|
+
}
|
|
946
|
+
if (info.builtins && info.builtins.length > 0) {
|
|
947
|
+
lines.push(...formatBuiltinsSection(info));
|
|
948
|
+
lines.push("");
|
|
949
|
+
}
|
|
950
|
+
if (info.arguments?.some((arg) => arg.negatable && arg.default === true)) {
|
|
951
|
+
lines.push(styler.meta("Boolean options can be negated with --no-<option>."));
|
|
952
|
+
lines.push("");
|
|
953
|
+
}
|
|
954
|
+
if (info.nestedCommands?.length) {
|
|
955
|
+
lines.push(styler.label("Subcommand Details:"));
|
|
956
|
+
lines.push("");
|
|
957
|
+
for (const nestedCmd of info.nestedCommands) {
|
|
958
|
+
lines.push(styler.meta("─".repeat(60)));
|
|
959
|
+
lines.push(this.format(nestedCmd));
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
const result = lines.join(newline);
|
|
963
|
+
return wrapDocument ? wrapDocument(result) : result;
|
|
964
|
+
} };
|
|
965
|
+
}
|
|
966
|
+
function createJsonFormatter() {
|
|
967
|
+
return { format(info) {
|
|
968
|
+
return JSON.stringify(info, null, 2);
|
|
969
|
+
} };
|
|
970
|
+
}
|
|
971
|
+
function shouldUseAnsi() {
|
|
972
|
+
if (typeof process === "undefined") return false;
|
|
973
|
+
if (process.env.NO_COLOR) return false;
|
|
974
|
+
if (process.env.CI) return false;
|
|
975
|
+
if (process.stdout && typeof process.stdout.isTTY === "boolean") return process.stdout.isTTY;
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Creates a minimal formatter that outputs just a single-line usage string.
|
|
980
|
+
*/
|
|
981
|
+
function createMinimalFormatter() {
|
|
982
|
+
return { format(info) {
|
|
983
|
+
const parts = [info.usage.command];
|
|
984
|
+
if (info.usage.hasSubcommands) parts.push("[command]");
|
|
985
|
+
if (info.positionals && info.positionals.length > 0) for (const arg of info.positionals) {
|
|
986
|
+
const name = arg.name.startsWith("...") ? `${arg.name}` : arg.name;
|
|
987
|
+
parts.push(arg.optional ? `[${name}]` : `<${name}>`);
|
|
988
|
+
}
|
|
989
|
+
if (info.usage.hasArguments) parts.push("[options]");
|
|
990
|
+
return parts.join(" ");
|
|
991
|
+
} };
|
|
992
|
+
}
|
|
993
|
+
function createFormatter(format, detail = "standard") {
|
|
994
|
+
if (detail === "minimal") return createMinimalFormatter();
|
|
995
|
+
if (format === "json") return createJsonFormatter();
|
|
996
|
+
if (format === "ansi" || format === "auto" && shouldUseAnsi()) return createGenericFormatter(createAnsiStyler(), createTextLayout());
|
|
997
|
+
if (format === "console") return createGenericFormatter(createConsoleStyler(), createTextLayout());
|
|
998
|
+
if (format === "markdown") return createGenericFormatter(createMarkdownStyler(), createMarkdownLayout());
|
|
999
|
+
if (format === "html") return createGenericFormatter(createHtmlStyler(), createHtmlLayout());
|
|
1000
|
+
return createGenericFormatter(createTextStyler(), createTextLayout());
|
|
1001
|
+
}
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/help.ts
|
|
1004
|
+
/**
|
|
1005
|
+
* Extract positional arguments info from schema based on meta.positional config.
|
|
1006
|
+
*/
|
|
1007
|
+
function extractPositionalArgsInfo(schema, meta) {
|
|
1008
|
+
const args = [];
|
|
1009
|
+
const positionalNames = /* @__PURE__ */ new Set();
|
|
1010
|
+
if (!schema || !meta?.positional || meta.positional.length === 0) return {
|
|
1011
|
+
args,
|
|
1012
|
+
positionalNames
|
|
1013
|
+
};
|
|
1014
|
+
const positionalConfig = parsePositionalConfig(meta.positional);
|
|
1015
|
+
try {
|
|
1016
|
+
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1017
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
1018
|
+
const properties = jsonSchema.properties;
|
|
1019
|
+
const required = jsonSchema.required || [];
|
|
1020
|
+
for (const { name, variadic } of positionalConfig) {
|
|
1021
|
+
const prop = properties[name];
|
|
1022
|
+
if (!prop) continue;
|
|
1023
|
+
positionalNames.add(name);
|
|
1024
|
+
const optMeta = meta.fields?.[name];
|
|
1025
|
+
args.push({
|
|
1026
|
+
name: variadic ? `...${name}` : name,
|
|
1027
|
+
description: optMeta?.description ?? prop.description,
|
|
1028
|
+
optional: !required.includes(name),
|
|
1029
|
+
default: prop.default,
|
|
1030
|
+
type: variadic ? `array<${prop.items?.type || "string"}>` : prop.type
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
} catch {}
|
|
1035
|
+
return {
|
|
1036
|
+
args,
|
|
1037
|
+
positionalNames
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function extractArgsInfo(schema, meta, positionalNames) {
|
|
1041
|
+
const result = [];
|
|
1042
|
+
if (!schema) return result;
|
|
1043
|
+
if (!schema["~standard"].vendor.includes("zod")) return result;
|
|
1044
|
+
const argsMeta = meta?.fields;
|
|
1045
|
+
try {
|
|
1046
|
+
const jsonSchema = schema["~standard"].jsonSchema.input({ target: "draft-2020-12" });
|
|
1047
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
1048
|
+
const properties = jsonSchema.properties;
|
|
1049
|
+
const required = jsonSchema.required || [];
|
|
1050
|
+
const propertyNames = new Set(Object.keys(properties));
|
|
1051
|
+
const hasExplicitNegation = (key) => {
|
|
1052
|
+
const camelNegated = `no${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
1053
|
+
if (propertyNames.has(camelNegated)) return true;
|
|
1054
|
+
const kebabNegated = `no-${key}`;
|
|
1055
|
+
if (propertyNames.has(kebabNegated)) return true;
|
|
1056
|
+
return false;
|
|
1057
|
+
};
|
|
1058
|
+
const isNegationOf = (key) => {
|
|
1059
|
+
if (key.startsWith("no") && key.length > 2 && key[2] === key[2]?.toUpperCase()) {
|
|
1060
|
+
const positiveKey = key.charAt(2).toLowerCase() + key.slice(3);
|
|
1061
|
+
if (propertyNames.has(positiveKey)) return true;
|
|
1062
|
+
}
|
|
1063
|
+
if (key.startsWith("no-")) {
|
|
1064
|
+
const positiveKey = key.slice(3);
|
|
1065
|
+
if (propertyNames.has(positiveKey)) return true;
|
|
1066
|
+
}
|
|
1067
|
+
return false;
|
|
1068
|
+
};
|
|
1069
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
1070
|
+
if (positionalNames?.has(key)) continue;
|
|
1071
|
+
const isOptional = !required.includes(key);
|
|
1072
|
+
const enumValues = prop.enum ?? prop.items?.enum;
|
|
1073
|
+
const optMeta = argsMeta?.[key];
|
|
1074
|
+
const propType = prop.type;
|
|
1075
|
+
const isNegatable = propType === "boolean" && !hasExplicitNegation(key) && !isNegationOf(key);
|
|
1076
|
+
result.push({
|
|
1077
|
+
name: key,
|
|
1078
|
+
description: optMeta?.description ?? prop.description,
|
|
1079
|
+
optional: isOptional,
|
|
1080
|
+
default: prop.default,
|
|
1081
|
+
type: propType === "array" ? `${prop.items?.type || "string"}[]` : propType,
|
|
1082
|
+
enum: enumValues,
|
|
1083
|
+
deprecated: optMeta?.deprecated ?? prop?.deprecated,
|
|
1084
|
+
hidden: optMeta?.hidden ?? prop?.hidden,
|
|
1085
|
+
examples: optMeta?.examples ?? prop?.examples,
|
|
1086
|
+
variadic: propType === "array",
|
|
1087
|
+
negatable: isNegatable
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
} catch {}
|
|
1092
|
+
return result;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Builds a comprehensive HelpInfo structure from a command.
|
|
1096
|
+
* This is the single source of truth that all formatters use.
|
|
1097
|
+
* @param cmd - The command to build help info for
|
|
1098
|
+
* @param detail - The level of detail ('minimal', 'standard', or 'full')
|
|
1099
|
+
*/
|
|
1100
|
+
function getHelpInfo(cmd, detail = "standard") {
|
|
1101
|
+
const rootCmd = getRootCommand(cmd);
|
|
1102
|
+
const isDefaultCommand = cmd.parent && (!cmd.name || cmd.aliases?.includes(""));
|
|
1103
|
+
const nonEmptyAliases = cmd.aliases?.filter(Boolean);
|
|
1104
|
+
const commandName = cmd.path || cmd.name || nonEmptyAliases?.[0] || (cmd.parent ? "[default]" : "program");
|
|
1105
|
+
const remainingAliases = !cmd.name && nonEmptyAliases?.length ? nonEmptyAliases.slice(1) : nonEmptyAliases ?? [];
|
|
1106
|
+
const displayAliases = isDefaultCommand ? [...remainingAliases, "[default]"] : nonEmptyAliases;
|
|
1107
|
+
const { args: positionalArgs, positionalNames } = cmd.argsSchema ? extractPositionalArgsInfo(cmd.argsSchema, cmd.meta) : {
|
|
1108
|
+
args: [],
|
|
1109
|
+
positionalNames: /* @__PURE__ */ new Set()
|
|
1110
|
+
};
|
|
1111
|
+
const hasPositionals = positionalArgs.length > 0;
|
|
1112
|
+
const helpInfo = {
|
|
1113
|
+
name: commandName,
|
|
1114
|
+
title: cmd.title,
|
|
1115
|
+
description: cmd.description,
|
|
1116
|
+
aliases: displayAliases,
|
|
1117
|
+
deprecated: cmd.deprecated,
|
|
1118
|
+
hidden: cmd.hidden,
|
|
1119
|
+
usage: {
|
|
1120
|
+
command: rootCmd === cmd ? commandName : `${rootCmd.name} ${commandName}`,
|
|
1121
|
+
hasSubcommands: !!(cmd.commands && cmd.commands.length > 0),
|
|
1122
|
+
hasPositionals,
|
|
1123
|
+
hasArguments: false,
|
|
1124
|
+
stdinField: cmd.meta?.stdin ? parseStdinConfig(cmd.meta.stdin).field : void 0
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
if (cmd.commands && cmd.commands.length > 0) {
|
|
1128
|
+
const visibleCommands = detail === "full" ? cmd.commands : cmd.commands.filter((c) => !c.hidden);
|
|
1129
|
+
helpInfo.subcommands = [...cmd.action ? [{
|
|
1130
|
+
name: "[default]",
|
|
1131
|
+
title: cmd.title,
|
|
1132
|
+
description: cmd.description
|
|
1133
|
+
}] : [], ...visibleCommands.flatMap((c) => {
|
|
1134
|
+
const isDefault = !c.name || c.aliases?.includes("");
|
|
1135
|
+
const nonEmptyAliases = c.aliases?.filter(Boolean);
|
|
1136
|
+
const displayName = c.name || nonEmptyAliases?.[0] || "[default]";
|
|
1137
|
+
const remainingAliases = !c.name && nonEmptyAliases?.length ? nonEmptyAliases.slice(1) : nonEmptyAliases ?? [];
|
|
1138
|
+
const displayAliases = isDefault && displayName !== "[default]" ? [...remainingAliases, "[default]"] : isDefault ? remainingAliases : nonEmptyAliases;
|
|
1139
|
+
const hasSubcommands = !!(c.commands && c.commands.length > 0);
|
|
1140
|
+
const hasDefaultHandler = c.action || c.commands?.some((sub) => !sub.name || sub.aliases?.includes(""));
|
|
1141
|
+
if (hasSubcommands && hasDefaultHandler) {
|
|
1142
|
+
const defaultSub = !c.action ? c.commands?.find((sub) => !sub.name || sub.aliases?.includes("")) : void 0;
|
|
1143
|
+
const hasDefaultSubInfo = defaultSub && (defaultSub.title || defaultSub.description);
|
|
1144
|
+
return [{
|
|
1145
|
+
name: displayName,
|
|
1146
|
+
title: hasDefaultSubInfo ? defaultSub.title : c.title,
|
|
1147
|
+
description: hasDefaultSubInfo ? defaultSub.description : c.description,
|
|
1148
|
+
aliases: displayAliases?.length ? displayAliases : void 0,
|
|
1149
|
+
deprecated: c.deprecated,
|
|
1150
|
+
hidden: c.hidden
|
|
1151
|
+
}, {
|
|
1152
|
+
name: displayName,
|
|
1153
|
+
title: c.title,
|
|
1154
|
+
description: c.description,
|
|
1155
|
+
deprecated: c.deprecated,
|
|
1156
|
+
hidden: c.hidden,
|
|
1157
|
+
hasSubcommands: true
|
|
1158
|
+
}];
|
|
1159
|
+
}
|
|
1160
|
+
return [{
|
|
1161
|
+
name: displayName,
|
|
1162
|
+
title: c.title,
|
|
1163
|
+
description: c.description,
|
|
1164
|
+
aliases: displayAliases?.length ? displayAliases : void 0,
|
|
1165
|
+
deprecated: c.deprecated,
|
|
1166
|
+
hidden: c.hidden,
|
|
1167
|
+
hasSubcommands
|
|
1168
|
+
}];
|
|
1169
|
+
})];
|
|
1170
|
+
if (detail === "full") helpInfo.nestedCommands = visibleCommands.map((c) => getHelpInfo(c, "full"));
|
|
1171
|
+
}
|
|
1172
|
+
if (hasPositionals) helpInfo.positionals = positionalArgs;
|
|
1173
|
+
if (cmd.argsSchema) {
|
|
1174
|
+
const argsInfo = extractArgsInfo(cmd.argsSchema, cmd.meta, positionalNames);
|
|
1175
|
+
const argMap = Object.fromEntries(argsInfo.map((arg) => [arg.name, arg]));
|
|
1176
|
+
const { flags, aliases } = extractSchemaMetadata(cmd.argsSchema, cmd.meta?.fields, cmd.meta?.autoAlias);
|
|
1177
|
+
for (const [flag, name] of Object.entries(flags)) {
|
|
1178
|
+
const arg = argMap[name];
|
|
1179
|
+
if (!arg) continue;
|
|
1180
|
+
arg.flags = [...arg.flags || [], flag];
|
|
1181
|
+
}
|
|
1182
|
+
for (const [alias, name] of Object.entries(aliases)) {
|
|
1183
|
+
const arg = argMap[name];
|
|
1184
|
+
if (!arg) continue;
|
|
1185
|
+
arg.aliases = [...arg.aliases || [], alias];
|
|
1186
|
+
}
|
|
1187
|
+
const visibleArgs = argsInfo.filter((arg) => !arg.hidden);
|
|
1188
|
+
if (visibleArgs.length > 0) {
|
|
1189
|
+
helpInfo.arguments = visibleArgs;
|
|
1190
|
+
helpInfo.usage.hasArguments = true;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (!cmd.parent) {
|
|
1194
|
+
const builtins = [];
|
|
1195
|
+
if (!findCommandByName("help", cmd.commands)) builtins.push({
|
|
1196
|
+
name: "help [command], -h, --help",
|
|
1197
|
+
description: "Show help for a command",
|
|
1198
|
+
sub: [{
|
|
1199
|
+
name: "--detail <level>",
|
|
1200
|
+
description: "Detail level (minimal, standard, full)"
|
|
1201
|
+
}, {
|
|
1202
|
+
name: "--format <format>",
|
|
1203
|
+
description: "Output format (text, ansi, json, markdown, html)"
|
|
1204
|
+
}]
|
|
1205
|
+
});
|
|
1206
|
+
if (!findCommandByName("version", cmd.commands)) builtins.push({
|
|
1207
|
+
name: "version, -v, --version",
|
|
1208
|
+
description: "Show version information"
|
|
1209
|
+
});
|
|
1210
|
+
if (!findCommandByName("completion", cmd.commands)) builtins.push({
|
|
1211
|
+
name: "completion [shell]",
|
|
1212
|
+
description: "Generate shell completions (bash, zsh, fish, powershell)"
|
|
1213
|
+
});
|
|
1214
|
+
builtins.push({
|
|
1215
|
+
name: "[command] --repl",
|
|
1216
|
+
description: "Start interactive REPL scoped to a command"
|
|
1217
|
+
});
|
|
1218
|
+
if (builtins.length > 0) helpInfo.builtins = builtins;
|
|
1219
|
+
}
|
|
1220
|
+
return helpInfo;
|
|
1221
|
+
}
|
|
1222
|
+
function generateHelp(rootCommand, commandObj = rootCommand, prefs) {
|
|
1223
|
+
const helpInfo = getHelpInfo(commandObj, prefs?.detail);
|
|
1224
|
+
return createFormatter(prefs?.format ?? "auto", prefs?.detail).format(helpInfo);
|
|
1225
|
+
}
|
|
1226
|
+
//#endregion
|
|
1227
|
+
export { getVersion as S, warnIfUnexpectedAsync as _, commandSymbol as a, createTerminalReplSession as b, hasInteractiveConfig as c, noop as d, outputValue as f, thenMaybe as g, suggestSimilar as h, buildReplCompleter as i, isAsyncBranded as l, runPluginChain as m, getHelpInfo as n, findCommandByName as o, repathCommandTree as p, asyncSchema as r, getCommandRuntime as s, generateHelp as t, mergeCommands as u, wrapWithLifecycle as v, resolveStdin as x, REPL_SIGINT as y };
|
|
1228
|
+
|
|
1229
|
+
//# sourceMappingURL=help-CgGP7hQU.mjs.map
|