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/src/errors.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured error hierarchy for Padrone CLI framework.
|
|
3
|
+
*
|
|
4
|
+
* All Padrone errors extend `PadroneError`, which carries an exit code,
|
|
5
|
+
* optional suggestions, and context about which command/phase produced the error.
|
|
6
|
+
* This allows callers to distinguish user errors (bad input) from bugs (unexpected throws)
|
|
7
|
+
* and to present formatted, actionable error messages.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type PadroneErrorOptions = {
|
|
11
|
+
/** Process exit code. Defaults to 1. */
|
|
12
|
+
exitCode?: number;
|
|
13
|
+
/** Actionable suggestions shown to the user (e.g. "Use --env production"). */
|
|
14
|
+
suggestions?: string[];
|
|
15
|
+
/** The command path that produced the error (e.g. "deploy staging"). */
|
|
16
|
+
command?: string;
|
|
17
|
+
/** The phase where the error occurred. */
|
|
18
|
+
phase?: 'parse' | 'validate' | 'execute' | 'config';
|
|
19
|
+
/** Original cause for error chaining. */
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base error class for all Padrone errors.
|
|
25
|
+
* Carries structured metadata for user-friendly formatting and programmatic handling.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* throw new PadroneError('Something went wrong', {
|
|
30
|
+
* exitCode: 1,
|
|
31
|
+
* suggestions: ['Try --help for usage information'],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class PadroneError extends Error {
|
|
36
|
+
readonly exitCode: number;
|
|
37
|
+
readonly suggestions: string[];
|
|
38
|
+
readonly command?: string;
|
|
39
|
+
readonly phase?: 'parse' | 'validate' | 'execute' | 'config';
|
|
40
|
+
|
|
41
|
+
constructor(message: string, options?: PadroneErrorOptions) {
|
|
42
|
+
super(message, options?.cause ? { cause: options.cause } : undefined);
|
|
43
|
+
this.name = 'PadroneError';
|
|
44
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
45
|
+
this.suggestions = options?.suggestions ?? [];
|
|
46
|
+
this.command = options?.command;
|
|
47
|
+
this.phase = options?.phase;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns a serializable representation of the error,
|
|
52
|
+
* suitable for non-terminal runtimes (web UIs, APIs, etc.).
|
|
53
|
+
*/
|
|
54
|
+
toJSON(): {
|
|
55
|
+
name: string;
|
|
56
|
+
message: string;
|
|
57
|
+
exitCode: number;
|
|
58
|
+
suggestions: string[];
|
|
59
|
+
command?: string;
|
|
60
|
+
phase?: string;
|
|
61
|
+
} {
|
|
62
|
+
return {
|
|
63
|
+
name: this.name,
|
|
64
|
+
message: this.message,
|
|
65
|
+
exitCode: this.exitCode,
|
|
66
|
+
suggestions: this.suggestions,
|
|
67
|
+
command: this.command,
|
|
68
|
+
phase: this.phase,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Thrown when command routing fails — unknown command, unexpected arguments, etc.
|
|
75
|
+
*/
|
|
76
|
+
export class RoutingError extends PadroneError {
|
|
77
|
+
constructor(message: string, options?: PadroneErrorOptions) {
|
|
78
|
+
super(message, { phase: 'parse', ...options });
|
|
79
|
+
this.name = 'RoutingError';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Thrown when argument or schema validation fails.
|
|
85
|
+
* Carries the structured issues from the schema validator.
|
|
86
|
+
*/
|
|
87
|
+
export class ValidationError extends PadroneError {
|
|
88
|
+
readonly issues: readonly { path?: PropertyKey[]; message: string }[];
|
|
89
|
+
|
|
90
|
+
constructor(message: string, issues: readonly { path?: PropertyKey[]; message: string }[], options?: PadroneErrorOptions) {
|
|
91
|
+
super(message, { phase: 'validate', ...options });
|
|
92
|
+
this.name = 'ValidationError';
|
|
93
|
+
this.issues = issues;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override toJSON() {
|
|
97
|
+
return {
|
|
98
|
+
...super.toJSON(),
|
|
99
|
+
issues: this.issues.map((i) => ({ path: i.path?.map(String), message: i.message })),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Thrown when config file loading or validation fails.
|
|
106
|
+
*/
|
|
107
|
+
export class ConfigError extends PadroneError {
|
|
108
|
+
constructor(message: string, options?: PadroneErrorOptions) {
|
|
109
|
+
super(message, { phase: 'config', ...options });
|
|
110
|
+
this.name = 'ConfigError';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Thrown from user action handlers to surface structured errors with exit codes and suggestions.
|
|
116
|
+
* This is the primary error class users should throw from their command actions.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* throw new ActionError('Missing environment', {
|
|
121
|
+
* exitCode: 1,
|
|
122
|
+
* suggestions: ['Use --env production or --env staging'],
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export class ActionError extends PadroneError {
|
|
127
|
+
constructor(message: string, options?: PadroneErrorOptions) {
|
|
128
|
+
super(message, { phase: 'execute', ...options });
|
|
129
|
+
this.name = 'ActionError';
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/formatter.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type HelpDetail = 'minimal' | 'standard' | 'full';
|
|
|
10
10
|
/**
|
|
11
11
|
* Information about a single positional argument.
|
|
12
12
|
*/
|
|
13
|
-
export type
|
|
13
|
+
export type HelpPositionalInfo = {
|
|
14
14
|
name: string;
|
|
15
15
|
description?: string;
|
|
16
16
|
optional: boolean;
|
|
@@ -19,9 +19,9 @@ export type HelpArgumentInfo = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Information about a single
|
|
22
|
+
* Information about a single argument/flag.
|
|
23
23
|
*/
|
|
24
|
-
export type
|
|
24
|
+
export type HelpArgumentInfo = {
|
|
25
25
|
name: string;
|
|
26
26
|
description?: string;
|
|
27
27
|
optional: boolean;
|
|
@@ -32,13 +32,13 @@ export type HelpOptionInfo = {
|
|
|
32
32
|
deprecated?: boolean | string;
|
|
33
33
|
hidden?: boolean;
|
|
34
34
|
examples?: unknown[];
|
|
35
|
-
/** Environment variable(s) this
|
|
35
|
+
/** Environment variable(s) this arg can be set from */
|
|
36
36
|
env?: string | string[];
|
|
37
|
-
/** Whether this
|
|
37
|
+
/** Whether this arg is an array type (shown as <type...>) */
|
|
38
38
|
variadic?: boolean;
|
|
39
|
-
/** Whether this
|
|
39
|
+
/** Whether this arg is a boolean (shown as --[no-]arg) */
|
|
40
40
|
negatable?: boolean;
|
|
41
|
-
/** Config file key that maps to this
|
|
41
|
+
/** Config file key that maps to this arg */
|
|
42
42
|
configKey?: string;
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -52,6 +52,16 @@ export type HelpSubcommandInfo = {
|
|
|
52
52
|
aliases?: string[];
|
|
53
53
|
deprecated?: boolean | string;
|
|
54
54
|
hidden?: boolean;
|
|
55
|
+
hasSubcommands?: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Information about a built-in command/flag entry.
|
|
60
|
+
*/
|
|
61
|
+
export type HelpBuiltinInfo = {
|
|
62
|
+
name: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
sub?: { name: string; description?: string }[];
|
|
55
65
|
};
|
|
56
66
|
|
|
57
67
|
/**
|
|
@@ -75,15 +85,19 @@ export type HelpInfo = {
|
|
|
75
85
|
usage: {
|
|
76
86
|
command: string;
|
|
77
87
|
hasSubcommands: boolean;
|
|
88
|
+
hasPositionals: boolean;
|
|
78
89
|
hasArguments: boolean;
|
|
79
|
-
|
|
90
|
+
/** The name of the field that reads from stdin, if any. Shown as `[stdin > field]` in usage. */
|
|
91
|
+
stdinField?: string;
|
|
80
92
|
};
|
|
81
93
|
/** List of subcommands */
|
|
82
94
|
subcommands?: HelpSubcommandInfo[];
|
|
83
95
|
/** Positional arguments */
|
|
96
|
+
positionals?: HelpPositionalInfo[];
|
|
97
|
+
/** Arguments/flags (only visible ones, hidden filtered out) */
|
|
84
98
|
arguments?: HelpArgumentInfo[];
|
|
85
|
-
/**
|
|
86
|
-
|
|
99
|
+
/** Built-in commands and flags (shown only for root command) */
|
|
100
|
+
builtins?: HelpBuiltinInfo[];
|
|
87
101
|
/** Full help info for nested commands (used in 'full' detail mode) */
|
|
88
102
|
nestedCommands?: HelpInfo[];
|
|
89
103
|
};
|
|
@@ -110,7 +124,7 @@ export type Formatter = {
|
|
|
110
124
|
*/
|
|
111
125
|
type Styler = {
|
|
112
126
|
command: (text: string) => string;
|
|
113
|
-
|
|
127
|
+
arg: (text: string) => string;
|
|
114
128
|
type: (text: string) => string;
|
|
115
129
|
description: (text: string) => string;
|
|
116
130
|
label: (text: string) => string;
|
|
@@ -138,7 +152,7 @@ type LayoutConfig = {
|
|
|
138
152
|
function createTextStyler(): Styler {
|
|
139
153
|
return {
|
|
140
154
|
command: (text) => text,
|
|
141
|
-
|
|
155
|
+
arg: (text) => text,
|
|
142
156
|
type: (text) => text,
|
|
143
157
|
description: (text) => text,
|
|
144
158
|
label: (text) => text,
|
|
@@ -153,7 +167,7 @@ function createAnsiStyler(): Styler {
|
|
|
153
167
|
const colorizer = createColorizer();
|
|
154
168
|
return {
|
|
155
169
|
command: colorizer.command,
|
|
156
|
-
|
|
170
|
+
arg: colorizer.arg,
|
|
157
171
|
type: colorizer.type,
|
|
158
172
|
description: colorizer.description,
|
|
159
173
|
label: colorizer.label,
|
|
@@ -179,7 +193,7 @@ function createConsoleStyler(): Styler {
|
|
|
179
193
|
};
|
|
180
194
|
return {
|
|
181
195
|
command: (text) => `${colors.cyan}${colors.bold}${text}${colors.reset}`,
|
|
182
|
-
|
|
196
|
+
arg: (text) => `${colors.green}${text}${colors.reset}`,
|
|
183
197
|
type: (text) => `${colors.yellow}${text}${colors.reset}`,
|
|
184
198
|
description: (text) => `${colors.dim}${text}${colors.reset}`,
|
|
185
199
|
label: (text) => `${colors.bold}${text}${colors.reset}`,
|
|
@@ -193,7 +207,7 @@ function createConsoleStyler(): Styler {
|
|
|
193
207
|
function createMarkdownStyler(): Styler {
|
|
194
208
|
return {
|
|
195
209
|
command: (text) => `**${text}**`,
|
|
196
|
-
|
|
210
|
+
arg: (text) => `\`${text}\``,
|
|
197
211
|
type: (text) => `\`${text}\``,
|
|
198
212
|
description: (text) => text,
|
|
199
213
|
label: (text) => `### ${text}`,
|
|
@@ -211,7 +225,7 @@ function escapeHtml(text: string): string {
|
|
|
211
225
|
function createHtmlStyler(): Styler {
|
|
212
226
|
return {
|
|
213
227
|
command: (text) => `<strong style="color: #00bcd4;">${escapeHtml(text)}</strong>`,
|
|
214
|
-
|
|
228
|
+
arg: (text) => `<code style="color: #4caf50;">${escapeHtml(text)}</code>`,
|
|
215
229
|
type: (text) => `<code style="color: #ff9800;">${escapeHtml(text)}</code>`,
|
|
216
230
|
description: (text) => `<span style="color: #666;">${escapeHtml(text)}</span>`,
|
|
217
231
|
label: (text) => `<h3>${escapeHtml(text)}</h3>`,
|
|
@@ -269,12 +283,16 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
269
283
|
const { newline, indent, join, wrapDocument, usageLabel } = layout;
|
|
270
284
|
|
|
271
285
|
function formatUsageSection(info: HelpInfo): string[] {
|
|
272
|
-
const usageParts: string[] = [
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
286
|
+
const usageParts: string[] = [styler.command(info.usage.command), info.usage.hasSubcommands ? styler.meta('[command]') : ''];
|
|
287
|
+
// Show actual positional argument names in usage line
|
|
288
|
+
if (info.positionals && info.positionals.length > 0) {
|
|
289
|
+
for (const arg of info.positionals) {
|
|
290
|
+
const name = arg.name.startsWith('...') ? `${arg.name}` : arg.name;
|
|
291
|
+
usageParts.push(styler.meta(arg.optional ? `[${name}]` : `<${name}>`));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (info.usage.hasArguments) usageParts.push(styler.meta('[options]'));
|
|
295
|
+
if (info.usage.stdinField) usageParts.push(styler.meta(`[stdin > ${info.usage.stdinField}]`));
|
|
278
296
|
return [`${usageLabel} ${join(usageParts)}`];
|
|
279
297
|
}
|
|
280
298
|
|
|
@@ -284,18 +302,40 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
284
302
|
|
|
285
303
|
lines.push(styler.label('Commands:'));
|
|
286
304
|
|
|
305
|
+
const subcommandSuffix = (c: HelpSubcommandInfo) => (c.hasSubcommands ? ' <subcommand>' : '');
|
|
306
|
+
const formatAliasParts = (c: HelpSubcommandInfo) => {
|
|
307
|
+
if (!c.aliases?.length) return { plain: '', styled: '' };
|
|
308
|
+
const realAliases = c.aliases.filter((a) => a !== '[default]');
|
|
309
|
+
const hasDefault = c.aliases.some((a) => a === '[default]');
|
|
310
|
+
const parts: string[] = [];
|
|
311
|
+
const styledParts: string[] = [];
|
|
312
|
+
if (realAliases.length) {
|
|
313
|
+
parts.push(`(${realAliases.join(', ')})`);
|
|
314
|
+
styledParts.push(`(${realAliases.join(', ')})`);
|
|
315
|
+
}
|
|
316
|
+
if (hasDefault) {
|
|
317
|
+
parts.push('[default]');
|
|
318
|
+
styledParts.push(styler.meta('[default]'));
|
|
319
|
+
}
|
|
320
|
+
return { plain: parts.length ? ` ${parts.join(' ')}` : '', styled: styledParts.length ? ` ${styledParts.join(' ')}` : '' };
|
|
321
|
+
};
|
|
287
322
|
const maxNameLength = Math.max(
|
|
288
323
|
...subcommands.map((c) => {
|
|
289
|
-
|
|
290
|
-
return (c.name + aliases).length;
|
|
324
|
+
return (c.name + subcommandSuffix(c) + formatAliasParts(c).plain).length;
|
|
291
325
|
}),
|
|
292
326
|
);
|
|
293
327
|
for (const subCmd of subcommands) {
|
|
294
|
-
const
|
|
295
|
-
const
|
|
328
|
+
const aliasParts = formatAliasParts(subCmd);
|
|
329
|
+
const suffix = subcommandSuffix(subCmd);
|
|
330
|
+
const commandDisplay = subCmd.name + suffix + aliasParts.plain;
|
|
296
331
|
const padding = ' '.repeat(Math.max(0, maxNameLength - commandDisplay.length + 2));
|
|
297
332
|
const isDeprecated = !!subCmd.deprecated;
|
|
298
|
-
const
|
|
333
|
+
const isDefaultEntry = subCmd.name === '[default]';
|
|
334
|
+
const commandName = isDeprecated
|
|
335
|
+
? styler.deprecated(commandDisplay)
|
|
336
|
+
: (isDefaultEntry ? styler.meta(subCmd.name) : styler.command(subCmd.name)) +
|
|
337
|
+
(suffix ? styler.meta(suffix) : '') +
|
|
338
|
+
aliasParts.styled;
|
|
299
339
|
const lineParts: string[] = [commandName, padding];
|
|
300
340
|
|
|
301
341
|
// Use title if available, otherwise use description
|
|
@@ -317,14 +357,14 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
317
357
|
return lines;
|
|
318
358
|
}
|
|
319
359
|
|
|
320
|
-
function
|
|
360
|
+
function formatPositionalsSection(info: HelpInfo): string[] {
|
|
321
361
|
const lines: string[] = [];
|
|
322
|
-
const args = info.
|
|
362
|
+
const args = info.positionals!;
|
|
323
363
|
|
|
324
364
|
lines.push(styler.label('Arguments:'));
|
|
325
365
|
|
|
326
366
|
for (const arg of args) {
|
|
327
|
-
const parts: string[] = [styler.
|
|
367
|
+
const parts: string[] = [styler.arg(arg.name)];
|
|
328
368
|
if (arg.optional) parts.push(styler.meta('(optional)'));
|
|
329
369
|
if (arg.default !== undefined) parts.push(styler.meta(`(default: ${String(arg.default)})`));
|
|
330
370
|
lines.push(indent(1) + join(parts));
|
|
@@ -337,54 +377,54 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
337
377
|
return lines;
|
|
338
378
|
}
|
|
339
379
|
|
|
340
|
-
function
|
|
380
|
+
function formatArgumentsSection(info: HelpInfo): string[] {
|
|
341
381
|
const lines: string[] = [];
|
|
342
|
-
const
|
|
382
|
+
const argList = info.arguments || [];
|
|
343
383
|
|
|
344
384
|
lines.push(styler.label('Options:'));
|
|
345
385
|
|
|
346
|
-
const maxNameLength = Math.max(...
|
|
347
|
-
|
|
348
|
-
for (const
|
|
349
|
-
// Format
|
|
350
|
-
const
|
|
351
|
-
const aliasNames =
|
|
352
|
-
const
|
|
353
|
-
const padding = ' '.repeat(Math.max(0, maxNameLength -
|
|
354
|
-
const isDeprecated = !!
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
const parts: string[] = [
|
|
358
|
-
if (
|
|
359
|
-
if (
|
|
360
|
-
if (
|
|
361
|
-
if (
|
|
362
|
-
if (
|
|
386
|
+
const maxNameLength = Math.max(...argList.map((arg) => arg.name.length));
|
|
387
|
+
|
|
388
|
+
for (const arg of argList) {
|
|
389
|
+
// Format arg name: --[no-]arg for booleans, --arg otherwise
|
|
390
|
+
const argName = arg.negatable ? `--[no-]${arg.name}` : `--${arg.name}`;
|
|
391
|
+
const aliasNames = arg.aliases && arg.aliases.length > 0 ? arg.aliases.map((a) => `-${a}`).join(', ') : '';
|
|
392
|
+
const fullArgName = aliasNames ? `${argName}, ${aliasNames}` : argName;
|
|
393
|
+
const padding = ' '.repeat(Math.max(0, maxNameLength - arg.name.length + 2));
|
|
394
|
+
const isDeprecated = !!arg.deprecated;
|
|
395
|
+
const formattedArgName = isDeprecated ? styler.deprecated(fullArgName) : styler.arg(fullArgName);
|
|
396
|
+
|
|
397
|
+
const parts: string[] = [formattedArgName];
|
|
398
|
+
if (arg.type) parts.push(styler.type(`<${arg.type}>`));
|
|
399
|
+
if (arg.optional && !arg.deprecated) parts.push(styler.meta('(optional)'));
|
|
400
|
+
if (arg.default !== undefined) parts.push(styler.meta(`(default: ${String(arg.default)})`));
|
|
401
|
+
if (arg.enum) parts.push(styler.meta(`(choices: ${arg.enum.join(', ')})`));
|
|
402
|
+
if (arg.variadic) parts.push(styler.meta('(repeatable)'));
|
|
363
403
|
if (isDeprecated) {
|
|
364
404
|
const deprecatedMeta =
|
|
365
|
-
typeof
|
|
405
|
+
typeof arg.deprecated === 'string' ? styler.meta(`(deprecated: ${arg.deprecated})`) : styler.meta('(deprecated)');
|
|
366
406
|
parts.push(deprecatedMeta);
|
|
367
407
|
}
|
|
368
408
|
|
|
369
|
-
const description =
|
|
409
|
+
const description = arg.description ? styler.description(arg.description) : '';
|
|
370
410
|
lines.push(indent(1) + join(parts) + padding + description);
|
|
371
411
|
|
|
372
412
|
// Environment variable line
|
|
373
|
-
if (
|
|
374
|
-
const envVars = typeof
|
|
413
|
+
if (arg.env) {
|
|
414
|
+
const envVars = typeof arg.env === 'string' ? [arg.env] : arg.env;
|
|
375
415
|
const envParts: string[] = [styler.example('Env:'), styler.exampleValue(envVars.join(', '))];
|
|
376
416
|
lines.push(indent(3) + join(envParts));
|
|
377
417
|
}
|
|
378
418
|
|
|
379
419
|
// Config key line
|
|
380
|
-
if (
|
|
381
|
-
const configParts: string[] = [styler.example('Config:'), styler.exampleValue(
|
|
420
|
+
if (arg.configKey) {
|
|
421
|
+
const configParts: string[] = [styler.example('Config:'), styler.exampleValue(arg.configKey)];
|
|
382
422
|
lines.push(indent(3) + join(configParts));
|
|
383
423
|
}
|
|
384
424
|
|
|
385
425
|
// Examples line
|
|
386
|
-
if (
|
|
387
|
-
const exampleValues =
|
|
426
|
+
if (arg.examples && arg.examples.length > 0) {
|
|
427
|
+
const exampleValues = arg.examples.map((example) => (typeof example === 'string' ? example : JSON.stringify(example))).join(', ');
|
|
388
428
|
const exampleParts: string[] = [styler.example('Example:'), styler.exampleValue(exampleValues)];
|
|
389
429
|
lines.push(indent(3) + join(exampleParts));
|
|
390
430
|
}
|
|
@@ -393,6 +433,44 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
393
433
|
return lines;
|
|
394
434
|
}
|
|
395
435
|
|
|
436
|
+
function formatBuiltinsSection(info: HelpInfo): string[] {
|
|
437
|
+
const lines: string[] = [];
|
|
438
|
+
const builtins = info.builtins!;
|
|
439
|
+
|
|
440
|
+
lines.push(styler.label('Built-in:'));
|
|
441
|
+
|
|
442
|
+
// Compute max effective name length for alignment across main and sub entries
|
|
443
|
+
const allLengths: number[] = [];
|
|
444
|
+
for (const entry of builtins) {
|
|
445
|
+
allLengths.push(entry.name.length);
|
|
446
|
+
if (entry.sub) {
|
|
447
|
+
for (const sub of entry.sub) {
|
|
448
|
+
// Sub entries get extra indent(2) - indent(1) = 2 chars
|
|
449
|
+
allLengths.push(sub.name.length + 2);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const maxLen = Math.max(...allLengths);
|
|
454
|
+
|
|
455
|
+
for (const entry of builtins) {
|
|
456
|
+
const padding = ' '.repeat(Math.max(2, maxLen - entry.name.length + 2));
|
|
457
|
+
const parts: string[] = [styler.command(entry.name)];
|
|
458
|
+
if (entry.description) parts.push(padding + styler.description(entry.description));
|
|
459
|
+
lines.push(indent(1) + parts.join(''));
|
|
460
|
+
|
|
461
|
+
if (entry.sub) {
|
|
462
|
+
for (const sub of entry.sub) {
|
|
463
|
+
const subPadding = ' '.repeat(Math.max(2, maxLen - sub.name.length));
|
|
464
|
+
const subParts: string[] = [styler.arg(sub.name)];
|
|
465
|
+
if (sub.description) subParts.push(subPadding + styler.description(sub.description));
|
|
466
|
+
lines.push(indent(2) + subParts.join(''));
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return lines;
|
|
472
|
+
}
|
|
473
|
+
|
|
396
474
|
return {
|
|
397
475
|
format(info: HelpInfo): string {
|
|
398
476
|
const lines: string[] = [];
|
|
@@ -433,15 +511,18 @@ function createGenericFormatter(styler: Styler, layout: LayoutConfig): Formatter
|
|
|
433
511
|
lines.push('');
|
|
434
512
|
}
|
|
435
513
|
|
|
436
|
-
|
|
514
|
+
if (info.positionals && info.positionals.length > 0) {
|
|
515
|
+
lines.push(...formatPositionalsSection(info));
|
|
516
|
+
lines.push('');
|
|
517
|
+
}
|
|
518
|
+
|
|
437
519
|
if (info.arguments && info.arguments.length > 0) {
|
|
438
520
|
lines.push(...formatArgumentsSection(info));
|
|
439
521
|
lines.push('');
|
|
440
522
|
}
|
|
441
523
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
lines.push(...formatOptionsSection(info));
|
|
524
|
+
if (info.builtins && info.builtins.length > 0) {
|
|
525
|
+
lines.push(...formatBuiltinsSection(info));
|
|
445
526
|
lines.push('');
|
|
446
527
|
}
|
|
447
528
|
|
|
@@ -497,8 +578,13 @@ function createMinimalFormatter(): Formatter {
|
|
|
497
578
|
format(info: HelpInfo): string {
|
|
498
579
|
const parts: string[] = [info.usage.command];
|
|
499
580
|
if (info.usage.hasSubcommands) parts.push('[command]');
|
|
500
|
-
if (info.
|
|
501
|
-
|
|
581
|
+
if (info.positionals && info.positionals.length > 0) {
|
|
582
|
+
for (const arg of info.positionals) {
|
|
583
|
+
const name = arg.name.startsWith('...') ? `${arg.name}` : arg.name;
|
|
584
|
+
parts.push(arg.optional ? `[${name}]` : `<${name}>`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (info.usage.hasArguments) parts.push('[options]');
|
|
502
588
|
return parts.join(' ');
|
|
503
589
|
},
|
|
504
590
|
};
|