clap-ts 0.2.0 → 0.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/README.md +718 -102
- package/dist/argfile.d.ts +55 -0
- package/dist/argfile.js +155 -0
- package/dist/completions.d.ts +4 -1
- package/dist/completions.js +199 -16
- package/dist/config.d.ts +75 -0
- package/dist/config.js +134 -0
- package/dist/help.d.ts +4 -4
- package/dist/help.js +276 -81
- package/dist/index.d.ts +2 -3
- package/dist/index.js +3 -3
- package/dist/install.d.ts +55 -0
- package/dist/install.js +185 -0
- package/dist/log.d.ts +78 -0
- package/dist/log.js +164 -0
- package/dist/man.d.ts +28 -0
- package/dist/man.js +234 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.js +165 -0
- package/dist/output.d.ts +111 -0
- package/dist/output.js +356 -0
- package/dist/parser.d.ts +40 -19
- package/dist/parser.js +789 -461
- package/dist/plugins.d.ts +58 -0
- package/dist/plugins.js +145 -0
- package/dist/progress.d.ts +89 -0
- package/dist/progress.js +205 -0
- package/dist/prompt.d.ts +99 -0
- package/dist/prompt.js +299 -0
- package/dist/runner.d.ts +5 -2
- package/dist/runner.js +341 -135
- package/dist/spec.d.ts +83 -0
- package/dist/spec.js +124 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +113 -0
- package/dist/types.d.ts +279 -11
- package/dist/validation.js +191 -69
- package/package.json +63 -6
package/dist/help.js
CHANGED
|
@@ -7,25 +7,58 @@
|
|
|
7
7
|
* and custom styles.
|
|
8
8
|
*/
|
|
9
9
|
import { styleText } from 'node:util';
|
|
10
|
+
import { hasSubCommands, possibleValues, subCommandsOf } from './parser.js';
|
|
10
11
|
// ---- Color Support ----
|
|
11
|
-
/** Get terminal width, defaulting to 80
|
|
12
|
-
function getTerminalWidth() {
|
|
12
|
+
/** Get terminal width, honouring termWidth and maxTermWidth, defaulting to 80. */
|
|
13
|
+
function getTerminalWidth(meta) {
|
|
14
|
+
if (meta.termWidth !== undefined && meta.termWidth > 0) {
|
|
15
|
+
return meta.termWidth;
|
|
16
|
+
}
|
|
17
|
+
let width = 80;
|
|
13
18
|
if (typeof process.stdout?.columns === 'number' && process.stdout.columns > 0) {
|
|
14
|
-
|
|
19
|
+
width = process.stdout.columns;
|
|
20
|
+
}
|
|
21
|
+
if (meta.maxTermWidth !== undefined && meta.maxTermWidth > 0) {
|
|
22
|
+
width = Math.min(width, meta.maxTermWidth);
|
|
15
23
|
}
|
|
16
|
-
return
|
|
24
|
+
return width;
|
|
17
25
|
}
|
|
18
26
|
/** Create style functions, merging optional user overrides. */
|
|
19
|
-
|
|
27
|
+
const NO_STYLE = {
|
|
28
|
+
bold: (s) => s,
|
|
29
|
+
yellow: (s) => s,
|
|
30
|
+
green: (s) => s,
|
|
31
|
+
cyan: (s) => s,
|
|
32
|
+
heading: (s) => s,
|
|
33
|
+
flag: (s) => s,
|
|
34
|
+
value: (s) => s,
|
|
35
|
+
command: (s) => s,
|
|
36
|
+
};
|
|
37
|
+
/** Whether colour is on for this command: 'never' off, 'always' forced, else auto. */
|
|
38
|
+
function colourEnabled(meta) {
|
|
39
|
+
if (meta.color === 'never' || meta.disableColoredHelp === true) {
|
|
40
|
+
return 'off';
|
|
41
|
+
}
|
|
42
|
+
return meta.color === 'always' ? 'force' : 'auto';
|
|
43
|
+
}
|
|
44
|
+
function createStyles(overrides, mode = 'auto') {
|
|
45
|
+
if (mode === 'off') {
|
|
46
|
+
return overrides ? { ...NO_STYLE, ...overrides } : NO_STYLE;
|
|
47
|
+
}
|
|
48
|
+
// styleText only colourises when it judges the stream capable; `always` goes
|
|
49
|
+
// around that check with the codes it would have used.
|
|
50
|
+
const paint = mode === 'force'
|
|
51
|
+
? (codes, text) => styleText(codes, text, { validateStream: false })
|
|
52
|
+
: (codes, text) => styleText(codes, text);
|
|
20
53
|
const defaults = {
|
|
21
|
-
bold: (s) =>
|
|
22
|
-
yellow: (s) =>
|
|
23
|
-
green: (s) =>
|
|
24
|
-
cyan: (s) =>
|
|
25
|
-
heading: (s) =>
|
|
26
|
-
flag: (s) =>
|
|
27
|
-
value: (s) =>
|
|
28
|
-
command: (s) =>
|
|
54
|
+
bold: (s) => paint('bold', s),
|
|
55
|
+
yellow: (s) => paint('yellow', s),
|
|
56
|
+
green: (s) => paint('green', s),
|
|
57
|
+
cyan: (s) => paint('cyan', s),
|
|
58
|
+
heading: (s) => paint(['bold', 'yellow'], s),
|
|
59
|
+
flag: (s) => paint('green', s),
|
|
60
|
+
value: (s) => paint('cyan', s),
|
|
61
|
+
command: (s) => paint('bold', s),
|
|
29
62
|
};
|
|
30
63
|
if (!overrides) {
|
|
31
64
|
return defaults;
|
|
@@ -35,7 +68,8 @@ function createStyles(overrides) {
|
|
|
35
68
|
// ---- Help Text Helpers ----
|
|
36
69
|
/** Wrap text to fit within a given width, preserving leading indent. */
|
|
37
70
|
function wrapText(text, maxWidth, indent) {
|
|
38
|
-
|
|
71
|
+
const available = Math.max(MIN_DESC_WIDTH, maxWidth - indent);
|
|
72
|
+
if (text.length <= available) {
|
|
39
73
|
return text;
|
|
40
74
|
}
|
|
41
75
|
const words = text.split(/\s+/);
|
|
@@ -46,7 +80,7 @@ function wrapText(text, maxWidth, indent) {
|
|
|
46
80
|
if (currentLine.length === 0) {
|
|
47
81
|
currentLine = word;
|
|
48
82
|
}
|
|
49
|
-
else if (currentLine.length + 1 + word.length
|
|
83
|
+
else if (currentLine.length + 1 + word.length <= available) {
|
|
50
84
|
currentLine += ` ${word}`;
|
|
51
85
|
}
|
|
52
86
|
else {
|
|
@@ -93,15 +127,14 @@ function formatArgFlag(key, def, styles) {
|
|
|
93
127
|
}
|
|
94
128
|
}
|
|
95
129
|
// Value placeholder
|
|
96
|
-
if (def.type !== 'boolean' || def.valueName) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
rawParts.push(` <${valueName}>`);
|
|
130
|
+
if (def.type !== 'boolean' || def.valueName || def.valueNames) {
|
|
131
|
+
const optional = def.numArgs !== undefined && def.numArgs.min === 0;
|
|
132
|
+
const open = optional ? '[' : '<';
|
|
133
|
+
const close = optional ? ']' : '>';
|
|
134
|
+
const names = def.valueNames ?? [def.valueName ?? def.type.toUpperCase()];
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
parts.push(` ${styles.value(`${open}${name}${close}`)}`);
|
|
137
|
+
rawParts.push(` ${open}${name}${close}`);
|
|
105
138
|
}
|
|
106
139
|
}
|
|
107
140
|
return {
|
|
@@ -112,27 +145,57 @@ function formatArgFlag(key, def, styles) {
|
|
|
112
145
|
/** Build the description suffix: [default: x] [env: VAR] [possible values: a, b] */
|
|
113
146
|
function formatArgSuffix(def) {
|
|
114
147
|
const suffixes = [];
|
|
115
|
-
if (def.default !== undefined && def.type !== 'boolean') {
|
|
148
|
+
if (def.default !== undefined && def.type !== 'boolean' && !def.hideDefaultValue) {
|
|
116
149
|
const defaultStr = Array.isArray(def.default) ? def.default.join(', ') : String(def.default);
|
|
117
150
|
suffixes.push(`[default: ${defaultStr}]`);
|
|
118
151
|
}
|
|
119
|
-
if (def.env) {
|
|
120
|
-
|
|
152
|
+
if (def.env && !def.hideEnv) {
|
|
153
|
+
const current = def.hideEnvValues || def.secret ? undefined : process.env[def.env];
|
|
154
|
+
suffixes.push(current ? `[env: ${def.env}=${current}]` : `[env: ${def.env}]`);
|
|
121
155
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
156
|
+
if (!def.hidePossibleValues) {
|
|
157
|
+
const visible = possibleValues(def).filter((v) => !v.hidden);
|
|
158
|
+
if (visible.length > 0) {
|
|
159
|
+
suffixes.push(`[possible values: ${visible.map((v) => v.name).join(', ')}]`);
|
|
160
|
+
}
|
|
124
161
|
}
|
|
125
162
|
if (def.required) {
|
|
126
163
|
suffixes.push('[required]');
|
|
127
164
|
}
|
|
165
|
+
if (def.deprecated !== undefined && def.deprecated !== false) {
|
|
166
|
+
suffixes.push(typeof def.deprecated === 'string' ? `[deprecated: ${def.deprecated}]` : '[deprecated]');
|
|
167
|
+
}
|
|
128
168
|
return suffixes.length > 0 ? ` ${suffixes.join(' ')}` : '';
|
|
129
169
|
}
|
|
170
|
+
/** Description for an arg, preferring the long form in long help. */
|
|
171
|
+
function argDescription(def, isShortHelp) {
|
|
172
|
+
const base = (!isShortHelp && def.longDescription) || def.description || '';
|
|
173
|
+
return base + formatArgSuffix(def);
|
|
174
|
+
}
|
|
175
|
+
/** Sort help entries by displayOrder, keeping declaration order as the tiebreak. */
|
|
176
|
+
function byDisplayOrder(entries, nextDisplayOrder) {
|
|
177
|
+
const hasExplicit = entries.some(([, def]) => def.displayOrder !== undefined);
|
|
178
|
+
if (!hasExplicit && nextDisplayOrder === undefined) {
|
|
179
|
+
return entries;
|
|
180
|
+
}
|
|
181
|
+
// Args without an explicit order fall in after nextDisplayOrder, keeping
|
|
182
|
+
// declaration order among themselves.
|
|
183
|
+
const implicitBase = nextDisplayOrder ?? Number.MAX_SAFE_INTEGER;
|
|
184
|
+
return entries
|
|
185
|
+
.map((entry, index) => ({
|
|
186
|
+
entry,
|
|
187
|
+
order: entry[1].displayOrder ?? implicitBase + index,
|
|
188
|
+
index,
|
|
189
|
+
}))
|
|
190
|
+
.sort((a, b) => a.order - b.order || a.index - b.index)
|
|
191
|
+
.map((wrapped) => wrapped.entry);
|
|
192
|
+
}
|
|
130
193
|
/** Append usage parts for options, positionals, and subcommands. */
|
|
131
194
|
function appendUsageParts(usageParts, command) {
|
|
132
195
|
const argsDef = command.args ?? {};
|
|
133
196
|
const hasOptions = Object.values(argsDef).some((d) => d.type !== 'positional' && !d.hidden);
|
|
134
197
|
const positionals = Object.entries(argsDef).filter(([_, d]) => d.type === 'positional');
|
|
135
|
-
const hasSubcommands =
|
|
198
|
+
const hasSubcommands = hasSubCommands(command);
|
|
136
199
|
if (hasOptions) {
|
|
137
200
|
usageParts.push('[OPTIONS]');
|
|
138
201
|
}
|
|
@@ -146,7 +209,7 @@ function appendUsageParts(usageParts, command) {
|
|
|
146
209
|
}
|
|
147
210
|
}
|
|
148
211
|
if (hasSubcommands) {
|
|
149
|
-
usageParts.push('
|
|
212
|
+
usageParts.push(`[${command.meta.subcommandValueName ?? 'COMMAND'}]`);
|
|
150
213
|
}
|
|
151
214
|
}
|
|
152
215
|
/** Check if an arg should be hidden based on help mode. */
|
|
@@ -162,18 +225,60 @@ function isArgHiddenForMode(def, isShortHelp) {
|
|
|
162
225
|
}
|
|
163
226
|
return false;
|
|
164
227
|
}
|
|
228
|
+
/** Below this many columns beside a flag, help drops to next-line layout. */
|
|
229
|
+
const MIN_DESC_WIDTH = 20;
|
|
165
230
|
/** Render aligned entries (flag + description with padding). */
|
|
166
231
|
function renderAlignedEntries(entries, termWidth, lines) {
|
|
167
232
|
if (entries.length === 0) {
|
|
168
233
|
return;
|
|
169
234
|
}
|
|
170
|
-
const
|
|
235
|
+
const inline = entries.filter((e) => !e.nextLine);
|
|
236
|
+
const maxLen = inline.length > 0 ? Math.max(...inline.map((e) => e.rawLen)) : 0;
|
|
171
237
|
const descIndent = maxLen + 4;
|
|
238
|
+
// A narrow terminal leaves too little room beside the flag to wrap into, so
|
|
239
|
+
// the whole section drops to next-line help rather than overflowing.
|
|
240
|
+
const forceNextLine = termWidth - (descIndent + 2) < MIN_DESC_WIDTH;
|
|
172
241
|
for (const entry of entries) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
242
|
+
if (entry.nextLine || forceNextLine) {
|
|
243
|
+
lines.push(entry.label);
|
|
244
|
+
if (entry.desc) {
|
|
245
|
+
lines.push(` ${wrapText(entry.desc, termWidth, 6)}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (entry.desc) {
|
|
249
|
+
const padding = ' '.repeat(Math.max(2, descIndent - entry.rawLen));
|
|
250
|
+
lines.push(`${entry.label}${padding}${wrapText(entry.desc, termWidth, descIndent)}`);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
lines.push(entry.label);
|
|
254
|
+
}
|
|
255
|
+
if (entry.detail) {
|
|
256
|
+
for (const line of entry.detail) {
|
|
257
|
+
lines.push(line);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Per-value help block, as clap renders under an option in long help:
|
|
264
|
+
*
|
|
265
|
+
* Possible values:
|
|
266
|
+
* - fast: skip the slow checks
|
|
267
|
+
*/
|
|
268
|
+
function possibleValueDetail(def, styles, isShortHelp) {
|
|
269
|
+
if (isShortHelp || def.hidePossibleValues) {
|
|
270
|
+
return undefined;
|
|
176
271
|
}
|
|
272
|
+
const visible = possibleValues(def).filter((v) => !v.hidden);
|
|
273
|
+
if (!visible.some((v) => v.help)) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
const detail = ['', ` ${styles.heading('Possible values:')}`];
|
|
277
|
+
for (const value of visible) {
|
|
278
|
+
detail.push(` - ${styles.value(value.name)}${value.help ? `: ${value.help}` : ''}`);
|
|
279
|
+
}
|
|
280
|
+
detail.push('');
|
|
281
|
+
return detail;
|
|
177
282
|
}
|
|
178
283
|
// ---- Template Rendering ----
|
|
179
284
|
/**
|
|
@@ -189,19 +294,20 @@ function renderHelpTemplate(template, command, styles, termWidth, fullName, isSh
|
|
|
189
294
|
appendUsageParts(usageParts, command);
|
|
190
295
|
const usageStr = usageParts.join(' ');
|
|
191
296
|
const argsLines = [];
|
|
192
|
-
renderPositionalSection(argsLines, argsDef, styles, termWidth, isShortHelp);
|
|
297
|
+
renderPositionalSection(argsLines, argsDef, styles, termWidth, isShortHelp, meta.nextDisplayOrder);
|
|
193
298
|
const argumentsStr = argsLines.join('\n');
|
|
194
299
|
const optLines = [];
|
|
195
300
|
renderOptionsSection(optLines, argsDef, meta, styles, termWidth, isShortHelp);
|
|
196
301
|
const optionsStr = optLines.join('\n');
|
|
197
302
|
const cmdLines = [];
|
|
198
|
-
if (
|
|
303
|
+
if (hasSubCommands(command)) {
|
|
199
304
|
renderSubcommandSection(cmdLines, command, styles, termWidth, fullName);
|
|
200
305
|
}
|
|
201
306
|
const commandsStr = cmdLines.join('\n');
|
|
202
307
|
return template
|
|
203
308
|
.replaceAll('{name}', meta.name)
|
|
204
309
|
.replaceAll('{version}', meta.version ?? '')
|
|
310
|
+
.replaceAll('{author}', meta.author ?? '')
|
|
205
311
|
.replaceAll('{about}', meta.about ?? meta.description ?? '')
|
|
206
312
|
.replaceAll('{usage}', usageStr)
|
|
207
313
|
.replaceAll('{all-args}', [argumentsStr, optionsStr].filter(Boolean).join('\n'))
|
|
@@ -213,7 +319,7 @@ function renderHelpTemplate(template, command, styles, termWidth, fullName, isSh
|
|
|
213
319
|
}
|
|
214
320
|
// ---- Section Renderers (extracted for reuse) ----
|
|
215
321
|
/** Render positional arguments section. */
|
|
216
|
-
function renderPositionalSection(lines, argsDef, styles, termWidth, isShortHelp) {
|
|
322
|
+
function renderPositionalSection(lines, argsDef, styles, termWidth, isShortHelp, nextDisplayOrder) {
|
|
217
323
|
const positionals = Object.entries(argsDef).filter(([_, d]) => d.type === 'positional');
|
|
218
324
|
const visiblePositionals = positionals.filter(([_, d]) => !isArgHiddenForMode(d, isShortHelp));
|
|
219
325
|
if (visiblePositionals.length === 0) {
|
|
@@ -221,12 +327,17 @@ function renderPositionalSection(lines, argsDef, styles, termWidth, isShortHelp)
|
|
|
221
327
|
}
|
|
222
328
|
lines.push(styles.heading('Arguments:'));
|
|
223
329
|
const entries = [];
|
|
224
|
-
for (const [key, def] of visiblePositionals) {
|
|
330
|
+
for (const [key, def] of byDisplayOrder(visiblePositionals, nextDisplayOrder)) {
|
|
225
331
|
const name = def.valueName ?? key.toUpperCase();
|
|
226
332
|
const label = ` ${styles.value(`<${name}>`)}`;
|
|
227
333
|
const rawLen = name.length + 4;
|
|
228
|
-
|
|
229
|
-
|
|
334
|
+
entries.push({
|
|
335
|
+
label,
|
|
336
|
+
rawLen,
|
|
337
|
+
desc: argDescription(def, isShortHelp),
|
|
338
|
+
nextLine: def.nextLineHelp,
|
|
339
|
+
detail: possibleValueDetail(def, styles, isShortHelp),
|
|
340
|
+
});
|
|
230
341
|
}
|
|
231
342
|
renderAlignedEntries(entries, termWidth, lines);
|
|
232
343
|
}
|
|
@@ -238,7 +349,7 @@ function renderOptionsSection(lines, argsDef, meta, styles, termWidth, isShortHe
|
|
|
238
349
|
}
|
|
239
350
|
// Group options by helpHeading
|
|
240
351
|
const groups = new Map();
|
|
241
|
-
const defaultHeading = 'Options';
|
|
352
|
+
const defaultHeading = meta.nextHelpHeading ?? 'Options';
|
|
242
353
|
for (const entry of options) {
|
|
243
354
|
const heading = entry[1].helpHeading ?? defaultHeading;
|
|
244
355
|
let group = groups.get(heading);
|
|
@@ -252,10 +363,15 @@ function renderOptionsSection(lines, argsDef, meta, styles, termWidth, isShortHe
|
|
|
252
363
|
lines.push('');
|
|
253
364
|
lines.push(styles.heading(`${heading}:`));
|
|
254
365
|
const entries = [];
|
|
255
|
-
for (const [key, def] of groupOptions) {
|
|
366
|
+
for (const [key, def] of byDisplayOrder(groupOptions, meta.nextDisplayOrder)) {
|
|
256
367
|
const { flag, rawLen } = formatArgFlag(key, def, styles);
|
|
257
|
-
|
|
258
|
-
|
|
368
|
+
entries.push({
|
|
369
|
+
label: ` ${flag}`,
|
|
370
|
+
rawLen: rawLen + 2,
|
|
371
|
+
desc: argDescription(def, isShortHelp),
|
|
372
|
+
nextLine: def.nextLineHelp,
|
|
373
|
+
detail: possibleValueDetail(def, styles, isShortHelp),
|
|
374
|
+
});
|
|
259
375
|
// Boolean negation: --no-flag
|
|
260
376
|
if (def.type === 'boolean' && def.negativeDescription) {
|
|
261
377
|
const longName = def.long ?? key;
|
|
@@ -270,11 +386,17 @@ function renderOptionsSection(lines, argsDef, meta, styles, termWidth, isShortHe
|
|
|
270
386
|
}
|
|
271
387
|
// Add built-in --help and --version to the default "Options" group
|
|
272
388
|
if (heading === defaultHeading) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
389
|
+
if (!meta.disableHelpFlag) {
|
|
390
|
+
const helpFlag = ` ${styles.flag('-h')}, ${styles.flag('--help')}`;
|
|
391
|
+
entries.push({ label: helpFlag, rawLen: ' -h, --help'.length, desc: 'Print help' });
|
|
392
|
+
}
|
|
393
|
+
if (meta.version && !meta.disableVersionFlag) {
|
|
276
394
|
const versionFlag = ` ${styles.flag('-V')}, ${styles.flag('--version')}`;
|
|
277
|
-
entries.push({
|
|
395
|
+
entries.push({
|
|
396
|
+
label: versionFlag,
|
|
397
|
+
rawLen: ' -V, --version'.length,
|
|
398
|
+
desc: 'Print version',
|
|
399
|
+
});
|
|
278
400
|
}
|
|
279
401
|
}
|
|
280
402
|
renderAlignedEntries(entries, termWidth, lines);
|
|
@@ -288,21 +410,27 @@ function renderOptionsSection(lines, argsDef, meta, styles, termWidth, isShortHe
|
|
|
288
410
|
*/
|
|
289
411
|
export function renderHelp(command, parentNames, isShortHelp = false, styleOverrides) {
|
|
290
412
|
const { meta } = command;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
413
|
+
if (meta.overrideHelp !== undefined) {
|
|
414
|
+
return meta.overrideHelp.endsWith('\n') ? meta.overrideHelp : `${meta.overrideHelp}\n`;
|
|
415
|
+
}
|
|
416
|
+
const styles = createStyles(styleOverrides, colourEnabled(meta));
|
|
417
|
+
const termWidth = getTerminalWidth(meta);
|
|
418
|
+
const usageName = meta.binName ?? meta.name;
|
|
419
|
+
const fullName = parentNames ? [...parentNames, usageName].join(' ') : usageName;
|
|
294
420
|
// Custom template override
|
|
295
421
|
if (meta.helpTemplate) {
|
|
296
422
|
return renderHelpTemplate(meta.helpTemplate, command, styles, termWidth, fullName, isShortHelp);
|
|
297
423
|
}
|
|
298
424
|
const lines = [];
|
|
299
425
|
// Before help text
|
|
300
|
-
|
|
301
|
-
|
|
426
|
+
const beforeHelp = (!isShortHelp && meta.beforeLongHelp) || meta.beforeHelp;
|
|
427
|
+
if (beforeHelp) {
|
|
428
|
+
lines.push(beforeHelp);
|
|
302
429
|
lines.push('');
|
|
303
430
|
}
|
|
304
431
|
// Header: "Description (name vX.Y.Z)"
|
|
305
|
-
const
|
|
432
|
+
const headerName = meta.displayName ?? meta.name;
|
|
433
|
+
const nameVersion = meta.version ? `${headerName} v${meta.version}` : headerName;
|
|
306
434
|
const headerDesc = meta.about ?? meta.description ?? '';
|
|
307
435
|
if (headerDesc) {
|
|
308
436
|
lines.push(`${headerDesc} (${nameVersion})`);
|
|
@@ -317,13 +445,18 @@ export function renderHelp(command, parentNames, isShortHelp = false, styleOverr
|
|
|
317
445
|
}
|
|
318
446
|
lines.push('');
|
|
319
447
|
// Usage line
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
448
|
+
if (meta.overrideUsage !== undefined) {
|
|
449
|
+
lines.push(`${styles.heading('Usage:')} ${meta.overrideUsage}`);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
const usageParts = [styles.heading('Usage:'), styles.command(fullName)];
|
|
453
|
+
appendUsageParts(usageParts, command);
|
|
454
|
+
lines.push(usageParts.join(' '));
|
|
455
|
+
}
|
|
323
456
|
// Positional arguments
|
|
324
457
|
const argsDef = command.args ?? {};
|
|
325
458
|
const posLines = [];
|
|
326
|
-
renderPositionalSection(posLines, argsDef, styles, termWidth, isShortHelp);
|
|
459
|
+
renderPositionalSection(posLines, argsDef, styles, termWidth, isShortHelp, meta.nextDisplayOrder);
|
|
327
460
|
if (posLines.length > 0) {
|
|
328
461
|
lines.push('');
|
|
329
462
|
lines.push(...posLines);
|
|
@@ -333,25 +466,55 @@ export function renderHelp(command, parentNames, isShortHelp = false, styleOverr
|
|
|
333
466
|
renderOptionsSection(optLines, argsDef, meta, styles, termWidth, isShortHelp);
|
|
334
467
|
lines.push(...optLines);
|
|
335
468
|
// Subcommands
|
|
336
|
-
const hasSubcommands =
|
|
469
|
+
const hasSubcommands = hasSubCommands(command);
|
|
337
470
|
if (hasSubcommands) {
|
|
338
471
|
renderSubcommandSection(lines, command, styles, termWidth, fullName);
|
|
339
472
|
}
|
|
340
473
|
// After help
|
|
341
|
-
|
|
474
|
+
const afterHelp = (!isShortHelp && meta.afterLongHelp) || meta.afterHelp;
|
|
475
|
+
if (afterHelp) {
|
|
342
476
|
lines.push('');
|
|
343
|
-
lines.push(
|
|
477
|
+
lines.push(afterHelp);
|
|
344
478
|
}
|
|
345
479
|
lines.push('');
|
|
346
480
|
return lines.join('\n');
|
|
347
481
|
}
|
|
348
482
|
/** Render the subcommands section of help output. */
|
|
483
|
+
/**
|
|
484
|
+
* One indented line per visible arg of a subcommand, as clap's flatten_help
|
|
485
|
+
* shows so `git stash --help` can summarise `push` and `pop` in place.
|
|
486
|
+
*/
|
|
487
|
+
function flattenedSubcommandArgs(sub, styles) {
|
|
488
|
+
const argsDef = sub.args ?? {};
|
|
489
|
+
const visible = Object.entries(argsDef).filter(([, def]) => !def.hidden);
|
|
490
|
+
if (visible.length === 0) {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
const named = visible.map(([key, def]) => ({
|
|
494
|
+
raw: def.type === 'positional'
|
|
495
|
+
? `<${def.valueName ?? key.toUpperCase()}>`
|
|
496
|
+
: `--${def.long ?? key}`,
|
|
497
|
+
def,
|
|
498
|
+
}));
|
|
499
|
+
const width = Math.max(...named.map((n) => n.raw.length));
|
|
500
|
+
return named.map(({ raw, def }) => {
|
|
501
|
+
const painted = def.type === 'positional' ? styles.value(raw) : styles.flag(raw);
|
|
502
|
+
const padding = ' '.repeat(width - raw.length + 2);
|
|
503
|
+
return ` ${painted}${def.description ? `${padding}${def.description}` : ''}`;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
349
506
|
function renderSubcommandSection(lines, command, styles, termWidth, fullName) {
|
|
507
|
+
const flatten = command.meta.flattenHelp === true;
|
|
350
508
|
lines.push('');
|
|
351
|
-
lines.push(styles.heading('Commands
|
|
509
|
+
lines.push(styles.heading(`${command.meta.subcommandHelpHeading ?? 'Commands'}:`));
|
|
352
510
|
const subEntries = [];
|
|
353
511
|
const rendered = new Set();
|
|
354
|
-
|
|
512
|
+
const subs = Object.entries(subCommandsOf(command));
|
|
513
|
+
if (subs.some(([, def]) => def.meta.displayOrder !== undefined)) {
|
|
514
|
+
subs.sort((a, b) => (a[1].meta.displayOrder ?? Number.MAX_SAFE_INTEGER) -
|
|
515
|
+
(b[1].meta.displayOrder ?? Number.MAX_SAFE_INTEGER));
|
|
516
|
+
}
|
|
517
|
+
for (const [name, def] of subs) {
|
|
355
518
|
if (def.meta.hidden) {
|
|
356
519
|
continue;
|
|
357
520
|
}
|
|
@@ -359,11 +522,24 @@ function renderSubcommandSection(lines, command, styles, termWidth, fullName) {
|
|
|
359
522
|
continue;
|
|
360
523
|
}
|
|
361
524
|
rendered.add(name);
|
|
525
|
+
// Visible aliases and any flag form share the parenthesised suffix.
|
|
526
|
+
const extras = [...(def.meta.aliases ?? [])];
|
|
527
|
+
if (def.meta.shortFlag !== undefined) {
|
|
528
|
+
extras.push(`-${def.meta.shortFlag}`);
|
|
529
|
+
}
|
|
530
|
+
if (def.meta.longFlag !== undefined) {
|
|
531
|
+
extras.push(`--${def.meta.longFlag}`);
|
|
532
|
+
}
|
|
533
|
+
for (const alias of def.meta.visibleShortFlagAliases ?? []) {
|
|
534
|
+
extras.push(`-${alias}`);
|
|
535
|
+
}
|
|
536
|
+
for (const alias of def.meta.visibleLongFlagAliases ?? []) {
|
|
537
|
+
extras.push(`--${alias}`);
|
|
538
|
+
}
|
|
362
539
|
let label;
|
|
363
540
|
let rawLen;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const aliasStr = aliases.join(', ');
|
|
541
|
+
if (extras.length > 0) {
|
|
542
|
+
const aliasStr = extras.join(', ');
|
|
367
543
|
label = ` ${styles.command(name)} (${aliasStr})`;
|
|
368
544
|
rawLen = name.length + aliasStr.length + 5;
|
|
369
545
|
}
|
|
@@ -371,8 +547,25 @@ function renderSubcommandSection(lines, command, styles, termWidth, fullName) {
|
|
|
371
547
|
label = ` ${styles.command(name)}`;
|
|
372
548
|
rawLen = name.length + 2;
|
|
373
549
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
550
|
+
const deprecated = def.meta.deprecated;
|
|
551
|
+
const note = deprecated === undefined || deprecated === false
|
|
552
|
+
? ''
|
|
553
|
+
: typeof deprecated === 'string'
|
|
554
|
+
? ` [deprecated: ${deprecated}]`
|
|
555
|
+
: ' [deprecated]';
|
|
556
|
+
subEntries.push({
|
|
557
|
+
label,
|
|
558
|
+
rawLen,
|
|
559
|
+
desc: (def.meta.description ?? '') + note,
|
|
560
|
+
detail: flatten ? flattenedSubcommandArgs(def, styles) : undefined,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
if (!command.meta.disableHelpSubcommand) {
|
|
564
|
+
subEntries.push({
|
|
565
|
+
label: ` ${styles.command('help')}`,
|
|
566
|
+
rawLen: 6,
|
|
567
|
+
desc: 'Print this message or the help of the given subcommand(s)',
|
|
568
|
+
});
|
|
376
569
|
}
|
|
377
570
|
renderAlignedEntries(subEntries, termWidth, lines);
|
|
378
571
|
lines.push('');
|
|
@@ -383,8 +576,12 @@ function renderSubcommandSection(lines, command, styles, termWidth, fullName) {
|
|
|
383
576
|
*/
|
|
384
577
|
export function renderUsage(command, parentNames, styleOverrides) {
|
|
385
578
|
const { meta } = command;
|
|
386
|
-
const styles = createStyles(styleOverrides);
|
|
387
|
-
|
|
579
|
+
const styles = createStyles(styleOverrides, colourEnabled(meta));
|
|
580
|
+
if (meta.overrideUsage !== undefined) {
|
|
581
|
+
return `${styles.heading('Usage:')} ${meta.overrideUsage}`;
|
|
582
|
+
}
|
|
583
|
+
const usageName = meta.binName ?? meta.name;
|
|
584
|
+
const fullName = parentNames ? [...parentNames, usageName].join(' ') : usageName;
|
|
388
585
|
const usageParts = [styles.heading('Usage:'), styles.command(fullName)];
|
|
389
586
|
appendUsageParts(usageParts, command);
|
|
390
587
|
return usageParts.join(' ');
|
|
@@ -392,23 +589,21 @@ export function renderUsage(command, parentNames, styleOverrides) {
|
|
|
392
589
|
/**
|
|
393
590
|
* Print help to stdout.
|
|
394
591
|
*/
|
|
395
|
-
export function showHelp(command, parentNames, isShortHelp = false, styleOverrides) {
|
|
396
|
-
|
|
397
|
-
process.stdout.write(text);
|
|
592
|
+
export function showHelp(command, parentNames, isShortHelp = false, styleOverrides, out = process.stdout) {
|
|
593
|
+
out.write(renderHelp(command, parentNames, isShortHelp, styleOverrides));
|
|
398
594
|
}
|
|
399
595
|
/**
|
|
400
596
|
* Print version to stdout.
|
|
401
597
|
*/
|
|
402
|
-
export function showVersion(meta) {
|
|
403
|
-
const version = meta.version
|
|
404
|
-
|
|
598
|
+
export function showVersion(meta, isShort = false, out = process.stdout) {
|
|
599
|
+
const version = (!isShort && meta.longVersion) || meta.version || '0.0.0';
|
|
600
|
+
out.write(`${meta.displayName ?? meta.name} ${version}\n`);
|
|
405
601
|
}
|
|
406
602
|
/**
|
|
407
603
|
* Print an error message with usage hint.
|
|
408
604
|
*/
|
|
409
|
-
export function showError(message, command, parentNames, styleOverrides) {
|
|
410
|
-
const styles = createStyles(styleOverrides);
|
|
605
|
+
export function showError(message, command, parentNames, styleOverrides, out = process.stderr) {
|
|
606
|
+
const styles = createStyles(styleOverrides, colourEnabled(command.meta));
|
|
411
607
|
const usage = renderUsage(command, parentNames, styleOverrides);
|
|
412
|
-
|
|
413
|
-
process.stderr.write(output);
|
|
608
|
+
out.write(`${styles.bold('error:')} ${message}\n\n${usage}\n\nFor more information, try '${styles.flag('--help')}'.\n`);
|
|
414
609
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports all public API.
|
|
5
5
|
*/
|
|
6
|
-
export type { ArgType, ArgAction, NumArgs, ValueParserFn, ValueHint, Shell, ArgDef, ArgsDef, StyleFn, StylesDef, CommandMeta, ArgGroup, ParsedArgs, CommandContext, CommandDef, RunOptions, ParseResult, InferArgValue, InferArgOptional, } from './types.js';
|
|
7
|
-
export { parseArgs, getRawArgs, collectGlobalArgs, mergeGlobalArgs, CliParseError, } from './parser.js';
|
|
6
|
+
export type { ArgType, ArgAction, NumArgs, ValueParserFn, PossibleValue, ValueHint, ColorChoice, ValueSource, Shell, OutputSink, MissingArg, ArgDef, ArgsDef, StyleFn, StylesDef, CommandMeta, ArgGroup, ParsedArgs, CommandContext, CommandDef, RunOptions, ParseResult, InferArgValue, InferArgOptional, } from './types.js';
|
|
7
|
+
export { parseArgs, getRawArgs, collectGlobalArgs, mergeGlobalArgs, subCommandsOf, hasSubCommands, possibleValues, matchesPossibleValue, CliParseError, } from './parser.js';
|
|
8
8
|
export { validate } from './validation.js';
|
|
9
9
|
export { renderHelp, renderUsage, showHelp, showVersion, showError } from './help.js';
|
|
10
10
|
export { defineCommand, defineArgs, defineArg, runCommand, runMain } from './runner.js';
|
|
11
|
-
export { generateCompletions, withCompletions } from './completions.js';
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Re-exports all public API.
|
|
5
5
|
*/
|
|
6
6
|
// Parser
|
|
7
|
-
export { parseArgs, getRawArgs, collectGlobalArgs, mergeGlobalArgs, CliParseError, } from './parser.js';
|
|
7
|
+
export { parseArgs, getRawArgs, collectGlobalArgs, mergeGlobalArgs, subCommandsOf, hasSubCommands, possibleValues, matchesPossibleValue, CliParseError, } from './parser.js';
|
|
8
8
|
// Validation
|
|
9
9
|
export { validate } from './validation.js';
|
|
10
10
|
// Help renderer
|
|
11
11
|
export { renderHelp, renderUsage, showHelp, showVersion, showError } from './help.js';
|
|
12
12
|
// Runner (main API)
|
|
13
13
|
export { defineCommand, defineArgs, defineArg, runCommand, runMain } from './runner.js';
|
|
14
|
-
//
|
|
15
|
-
|
|
14
|
+
// Generators live behind subpaths so a running CLI never loads them:
|
|
15
|
+
// clap-ts/completions, clap-ts/man, clap-ts/markdown
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put generated completions and man pages where the system will find them.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { withInstallers } from 'clap-ts/install';
|
|
6
|
+
*
|
|
7
|
+
* runMain(withInstallers(main));
|
|
8
|
+
* // my-tool completions install zsh
|
|
9
|
+
* // my-tool man install
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Everything respects `XDG_DATA_HOME`, and nothing is written until the target
|
|
13
|
+
* directory is created, so a dry run can report the path without touching disk.
|
|
14
|
+
*/
|
|
15
|
+
import type { CommandDef, Shell } from './types.js';
|
|
16
|
+
/** Where a shell looks for user completions, and what the file must be called. */
|
|
17
|
+
export interface CompletionTarget {
|
|
18
|
+
readonly dir: string;
|
|
19
|
+
readonly file: string;
|
|
20
|
+
/** A line the user must add themselves, when sourcing is not automatic. */
|
|
21
|
+
readonly manualStep?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The per-user completion path for a shell.
|
|
25
|
+
*
|
|
26
|
+
* bash, zsh and fish load these directories on their own. powershell, elvish
|
|
27
|
+
* and nushell have no drop-in directory, so those report a line to add to the
|
|
28
|
+
* profile instead.
|
|
29
|
+
*/
|
|
30
|
+
export declare function completionTarget(shell: Shell, binaryName: string): CompletionTarget;
|
|
31
|
+
/** What an install did, or would have done. */
|
|
32
|
+
export interface InstallResult {
|
|
33
|
+
readonly paths: readonly string[];
|
|
34
|
+
readonly manualStep?: string;
|
|
35
|
+
readonly dryRun: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface InstallOptions {
|
|
38
|
+
/** Report the paths without writing anything. */
|
|
39
|
+
readonly dryRun?: boolean;
|
|
40
|
+
/** Write here instead of the per-user location. */
|
|
41
|
+
readonly dir?: string;
|
|
42
|
+
/** Binary name; defaults to the command's binName or name. */
|
|
43
|
+
readonly binaryName?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Write the completion script for one shell to its per-user location. */
|
|
46
|
+
export declare function installCompletions(command: CommandDef, shell: Shell, opts?: InstallOptions): InstallResult;
|
|
47
|
+
/** Write a man page per command into the per-user man directory. */
|
|
48
|
+
export declare function installManPages(command: CommandDef, opts?: InstallOptions): InstallResult;
|
|
49
|
+
/**
|
|
50
|
+
* Add `completions` and `man` subcommands that both print and install.
|
|
51
|
+
*
|
|
52
|
+
* `tool completions bash` writes the script to stdout as before;
|
|
53
|
+
* `tool completions install bash` puts it where the shell will find it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function withInstallers(rootCommand: CommandDef<any>): CommandDef<any>;
|