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/README.md
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
# clap-ts
|
|
2
2
|
|
|
3
|
-
A type-safe CLI argument parser for TypeScript,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Validation** -- required, exclusive, conflictsWith, requires, requiredUnlessPresent, requiredIfEq, valueParser, numArgs, argument groups
|
|
13
|
-
- **Custom value parsers** -- function-based parsers for custom validation and type conversion
|
|
14
|
-
- **Clap-style help** -- colored, terminal-width-aware help with custom headings, templates, and styles
|
|
15
|
-
- **Clap-style errors** -- "did you mean?" typo suggestions via Levenshtein distance
|
|
16
|
-
- **Environment variable fallback** -- `env` field on args, with CLI > env > default precedence
|
|
17
|
-
- **Lifecycle hooks** -- setup/run/cleanup pattern for resource management
|
|
18
|
-
- **Actions** -- `set` (default), `append` (collect into array), `count` (e.g. `-vvv` = 3)
|
|
19
|
-
- **Value delimiters** -- `--tags=a,b,c` splits into array with `valueDelimiter`
|
|
20
|
-
- **Boolean negation** -- `--no-verbose` automatically supported for boolean flags
|
|
21
|
-
- **Global args** -- `global: true` args inherited by all subcommands
|
|
22
|
-
- **Reusable arg groups** -- `defineArgs()` + spread for sharing args across commands
|
|
23
|
-
- **Trailing var args** -- last positional consumes all remaining args
|
|
24
|
-
- **Negative numbers** -- `--offset -10` with `allowNegativeNumbers`
|
|
25
|
-
- **Hyphen values** -- `--grep -pattern` with `allowHyphenValues`
|
|
26
|
-
- **Zero dependencies** -- only uses `node:util` (parseArgs + styleText)
|
|
27
|
-
- **Bun and Node.js** -- works on both runtimes
|
|
3
|
+
A type-safe CLI argument parser for TypeScript, modelled on Rust's
|
|
4
|
+
[clap](https://docs.rs/clap/latest/clap/).
|
|
5
|
+
|
|
6
|
+
- **Full type inference.** `defineCommand` infers exact argument types; no casts, no schema to repeat.
|
|
7
|
+
- **Complete clap parity.** Every `Arg` and `Command` builder option, from `numArgs` to `overridesWith` to `multicall`.
|
|
8
|
+
- **Fast.** A single pass over argv against a spec compiled once per command: 40ns for a bare parse, 1.5us for a full pipeline.
|
|
9
|
+
- **Zero dependencies.** Only `node:util`, for colour detection.
|
|
10
|
+
- **Batteries behind subpaths.** Config files, prompts, completions for six shells, man pages, markdown docs, plugins, tables, logging and progress, none of which load unless imported.
|
|
11
|
+
- **Node and Bun.** Requires Node 20 or Bun 1.0.
|
|
28
12
|
|
|
29
13
|
## Install
|
|
30
14
|
|
|
@@ -97,7 +81,31 @@ Options:
|
|
|
97
81
|
-V, --version Print version
|
|
98
82
|
```
|
|
99
83
|
|
|
100
|
-
##
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
**Getting started:** [Install](#install) · [Quick Start](#quick-start)
|
|
87
|
+
|
|
88
|
+
**Defining commands:** [defineCommand](#definecommand) · [Arguments](#arguments) ·
|
|
89
|
+
[Constraints](#constraints) · [Positionals](#positionals) ·
|
|
90
|
+
[Subcommands](#subcommands) · [Argument Groups](#argument-groups) ·
|
|
91
|
+
[Deprecation](#deprecating-arguments-and-commands) · [Lazy Subcommands](#lazy-subcommands)
|
|
92
|
+
|
|
93
|
+
**Running:** [runMain](#runmain) · [Lifecycle Hooks](#lifecycle-hooks) ·
|
|
94
|
+
[Value Precedence](#value-precedence-and-sources) · [Exit Codes](#exit-codes) ·
|
|
95
|
+
[Error Messages](#error-messages) · [Low-Level API](#low-level-api)
|
|
96
|
+
|
|
97
|
+
**Help:** [Help Output](#help-output) · [Styles](#styles) · [Templates](#help-template)
|
|
98
|
+
|
|
99
|
+
**Optional modules:** [config](#configuration-files) · [prompt](#interactive-prompts) ·
|
|
100
|
+
[testing](#testing) · [completions](#shell-completions) · [man](#man-pages) ·
|
|
101
|
+
[markdown](#markdown-documentation) · [install](#installing-completions-and-man-pages) ·
|
|
102
|
+
[spec](#machine-readable-spec) · [plugins](#plugins) · [argfile](#response-files-and-stdin) ·
|
|
103
|
+
[output](#terminal-output) · [log and progress](#logging-and-progress)
|
|
104
|
+
|
|
105
|
+
**Reference:** [Performance](#performance) · [Comparison with clap](#comparison-with-rust-clap) ·
|
|
106
|
+
[Upgrading](#upgrading-from-02)
|
|
107
|
+
|
|
108
|
+
## Defining Commands
|
|
101
109
|
|
|
102
110
|
### defineCommand
|
|
103
111
|
|
|
@@ -116,7 +124,18 @@ const cmd = defineCommand({
|
|
|
116
124
|
beforeHelp: 'NOTE: Requires auth.', // text before help output
|
|
117
125
|
afterHelp: 'Examples:\n serve -p 8080', // text after help output
|
|
118
126
|
hidden: false, // hide from parent help
|
|
119
|
-
aliases: ['s', 'start'], // subcommand aliases
|
|
127
|
+
aliases: ['s', 'start'], // subcommand aliases, shown in help
|
|
128
|
+
hiddenAliases: ['srv'], // aliases that work but stay hidden
|
|
129
|
+
author: 'Salama Ashoush', // available to templates as {author}
|
|
130
|
+
longVersion: '1.0.0 (build abc123)', // --version; -V still shows `version`
|
|
131
|
+
propagateVersion: true, // subcommands inherit this version
|
|
132
|
+
displayOrder: 1, // position among sibling subcommands
|
|
133
|
+
shortFlag: 's', // invoke as `tool -s`
|
|
134
|
+
longFlag: 'serve', // invoke as `tool --serve`
|
|
135
|
+
shortFlagAliases: ['S'], // extra forms, hidden from help
|
|
136
|
+
longFlagAliases: ['start'],
|
|
137
|
+
visibleShortFlagAliases: ['r'], // extra forms shown in help
|
|
138
|
+
visibleLongFlagAliases: ['run'],
|
|
120
139
|
|
|
121
140
|
// Subcommand behavior
|
|
122
141
|
subcommandRequired: true, // error if no subcommand
|
|
@@ -126,10 +145,48 @@ const cmd = defineCommand({
|
|
|
126
145
|
subcommandNegatesReqs: true, // subcommand waives parent required args
|
|
127
146
|
argsConflictsWithSubcommands: true, // args and subcommands mutually exclusive
|
|
128
147
|
argRequiredElseHelp: true, // show help if no args provided
|
|
148
|
+
subcommandPrecedenceOverArg: true, // a subcommand name ends value collection
|
|
149
|
+
multicall: true, // dispatch on the invoked binary name
|
|
150
|
+
noBinaryName: true, // argv carries no binary name to strip
|
|
151
|
+
|
|
152
|
+
// Naming
|
|
153
|
+
binName: 'git stash', // shown in the usage line
|
|
154
|
+
displayName: 'git-stash', // shown in the help header
|
|
155
|
+
|
|
156
|
+
// Parsing behavior
|
|
157
|
+
allowHyphenValues: true, // every arg may take -values
|
|
158
|
+
allowNegativeNumbers: true, // every arg may take negative numbers
|
|
159
|
+
allowMissingPositional: true, // `cp DEST` fills the trailing positional
|
|
160
|
+
argsOverrideSelf: true, // repeating an arg replaces, not errors
|
|
129
161
|
|
|
130
162
|
// Help customization
|
|
131
163
|
helpTemplate: '{name} v{version}\n{usage}\n{options}',
|
|
164
|
+
beforeLongHelp: 'Shown only with --help',
|
|
165
|
+
afterLongHelp: 'Shown only with --help',
|
|
166
|
+
subcommandHelpHeading: 'Operations', // default "Commands"
|
|
167
|
+
subcommandValueName: 'OP', // usage placeholder, default "COMMAND"
|
|
168
|
+
overrideUsage: 'serve <FILE> [--port N]', // replace the usage line
|
|
169
|
+
overrideHelp: '...', // replace the whole help output
|
|
170
|
+
termWidth: 100, // fixed help width
|
|
171
|
+
maxTermWidth: 120, // cap on the detected terminal width
|
|
172
|
+
disableHelpFlag: true, // drop the built-in -h/--help
|
|
173
|
+
disableVersionFlag: true, // drop the built-in -V/--version
|
|
174
|
+
disableHelpSubcommand: true, // drop the built-in `help` subcommand
|
|
175
|
+
disableColoredHelp: true, // render help without colour
|
|
176
|
+
color: 'always', // 'auto' | 'always' | 'never'
|
|
177
|
+
flattenHelp: true, // summarise subcommand args in place
|
|
178
|
+
nextHelpHeading: 'Global', // default heading for args that set none
|
|
179
|
+
nextDisplayOrder: 100, // starting order for args that set none
|
|
180
|
+
helpExpected: true, // reject a visible arg with no description
|
|
181
|
+
|
|
182
|
+
// Error tolerance
|
|
183
|
+
ignoreErrors: true, // collect on ParseResult.errors, keep going
|
|
184
|
+
dontDelimitTrailingValues: true, // leave values after -- unsplit
|
|
132
185
|
},
|
|
186
|
+
// Built on first use instead of at definition time
|
|
187
|
+
lazySubCommands: () => ({ deploy: heavyDeployCommand }),
|
|
188
|
+
// Applied to each argument of an external subcommand
|
|
189
|
+
externalSubcommandValueParser: (v) => v.trim(),
|
|
133
190
|
args: { /* ... */ },
|
|
134
191
|
subCommands: { /* ... */ },
|
|
135
192
|
groups: [ /* ... */ ],
|
|
@@ -139,7 +196,7 @@ const cmd = defineCommand({
|
|
|
139
196
|
});
|
|
140
197
|
```
|
|
141
198
|
|
|
142
|
-
###
|
|
199
|
+
### Arguments
|
|
143
200
|
|
|
144
201
|
Each argument is defined with an `ArgDef`:
|
|
145
202
|
|
|
@@ -286,11 +343,97 @@ const cmd = defineCommand({
|
|
|
286
343
|
hidePossibleValues: true,
|
|
287
344
|
description: 'Output format',
|
|
288
345
|
},
|
|
346
|
+
|
|
347
|
+
// Multi-value option: --point 1 2 3
|
|
348
|
+
point: {
|
|
349
|
+
type: 'string',
|
|
350
|
+
numArgs: { min: 3, max: 3 },
|
|
351
|
+
valueNames: ['X', 'Y', 'Z'], // help shows --point <X> <Y> <Z>
|
|
352
|
+
description: 'Coordinates',
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// Value terminator: --cmds ls -la ; file.txt
|
|
356
|
+
cmds: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
action: 'append',
|
|
359
|
+
numArgs: { min: 1, max: 99 },
|
|
360
|
+
allowHyphenValues: true,
|
|
361
|
+
valueTerminator: ';',
|
|
362
|
+
description: 'Command to run',
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// Require the equals form: --key=value, never --key value
|
|
366
|
+
key: {
|
|
367
|
+
type: 'string',
|
|
368
|
+
requireEquals: true,
|
|
369
|
+
description: 'API key',
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
// Possible values carrying help text, aliases and hidden entries
|
|
373
|
+
mode: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
ignoreCase: true, // --mode FAST matches 'fast'
|
|
376
|
+
valueParser: [
|
|
377
|
+
{ name: 'fast', help: 'Skip the slow checks' },
|
|
378
|
+
{ name: 'thorough', aliases: ['full'] },
|
|
379
|
+
{ name: 'legacy', hidden: true },
|
|
380
|
+
],
|
|
381
|
+
description: 'Build mode',
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
// The later of the two wins
|
|
385
|
+
dev: { type: 'boolean', overridesWith: ['release'] },
|
|
386
|
+
release: { type: 'boolean', overridesWith: ['dev'] },
|
|
387
|
+
|
|
388
|
+
// Help presentation
|
|
389
|
+
bind: {
|
|
390
|
+
type: 'string',
|
|
391
|
+
longDescription: 'The longer explanation, shown with --help only',
|
|
392
|
+
displayOrder: 1, // position within its help section
|
|
393
|
+
nextLineHelp: true, // description on its own line
|
|
394
|
+
hideDefaultValue: true, // drop the [default: ...] note
|
|
395
|
+
description: 'Address to bind',
|
|
396
|
+
},
|
|
397
|
+
token: {
|
|
398
|
+
type: 'string',
|
|
399
|
+
env: 'TOOL_TOKEN',
|
|
400
|
+
hideEnvValues: true, // show [env: TOOL_TOKEN], not its value
|
|
401
|
+
description: 'API token',
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// Explicit positional position, and group membership
|
|
405
|
+
dest: { type: 'positional', index: 2 },
|
|
406
|
+
src: { type: 'positional', index: 1 },
|
|
407
|
+
yaml: { type: 'boolean', group: 'format' }, // or groups: ['format', 'output']
|
|
408
|
+
|
|
409
|
+
// Actions beyond set, append and count
|
|
410
|
+
colour: { type: 'boolean', action: 'setTrue' },
|
|
411
|
+
noColour: { type: 'boolean', long: 'no-colour', action: 'setFalse' },
|
|
412
|
+
usage: { type: 'boolean', short: '?', action: 'help' }, // also helpShort, helpLong
|
|
413
|
+
revision: { type: 'boolean', action: 'version' },
|
|
414
|
+
|
|
415
|
+
// Values for a bare multi-value flag
|
|
416
|
+
origin: {
|
|
417
|
+
type: 'string',
|
|
418
|
+
numArgs: { min: 0, max: 3 },
|
|
419
|
+
defaultMissingValues: ['0', '0', '0'],
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// Requires another arg only at a particular value
|
|
423
|
+
source: {
|
|
424
|
+
type: 'string',
|
|
425
|
+
requiresIf: ['remote', 'url'], // or requiresIfs: [[v, arg], ...]
|
|
426
|
+
},
|
|
427
|
+
url: { type: 'string' },
|
|
289
428
|
},
|
|
290
429
|
});
|
|
291
430
|
```
|
|
292
431
|
|
|
293
|
-
|
|
432
|
+
By default a value shown for `env` in help includes the variable's current value,
|
|
433
|
+
matching clap. Use `hideEnvValues` to show only the name, or `hideEnv` to drop the
|
|
434
|
+
note entirely, when the variable holds a secret.
|
|
435
|
+
|
|
436
|
+
### Constraints
|
|
294
437
|
|
|
295
438
|
```ts
|
|
296
439
|
const cmd = defineCommand({
|
|
@@ -359,7 +502,7 @@ const cmd = defineCommand({
|
|
|
359
502
|
});
|
|
360
503
|
```
|
|
361
504
|
|
|
362
|
-
###
|
|
505
|
+
### Positionals
|
|
363
506
|
|
|
364
507
|
```ts
|
|
365
508
|
// trailingVarArg: last positional consumes all remaining args
|
|
@@ -445,7 +588,7 @@ const git = defineCommand({
|
|
|
445
588
|
});
|
|
446
589
|
```
|
|
447
590
|
|
|
448
|
-
###
|
|
591
|
+
### Argument Groups
|
|
449
592
|
|
|
450
593
|
```ts
|
|
451
594
|
import { defineArgs, defineCommand } from 'clap-ts';
|
|
@@ -474,47 +617,46 @@ const cmd = defineCommand({
|
|
|
474
617
|
});
|
|
475
618
|
```
|
|
476
619
|
|
|
477
|
-
###
|
|
478
|
-
|
|
479
|
-
Commands support setup/run/cleanup lifecycle hooks. `cleanup` always runs, even if `run` throws.
|
|
620
|
+
### Deprecating Arguments and Commands
|
|
480
621
|
|
|
481
622
|
```ts
|
|
482
|
-
const
|
|
483
|
-
meta: { name: '
|
|
484
|
-
args: {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
ctx.data.db = await connectToDatabase();
|
|
488
|
-
},
|
|
489
|
-
|
|
490
|
-
async run(ctx) {
|
|
491
|
-
const db = ctx.data.db as Database;
|
|
492
|
-
await startServer(ctx.args.port, db);
|
|
623
|
+
const main = defineCommand({
|
|
624
|
+
meta: { name: 'my-tool' },
|
|
625
|
+
args: {
|
|
626
|
+
output: { type: 'string', description: 'Where to write' },
|
|
627
|
+
out: { type: 'string', deprecated: 'renamed', replacedBy: 'output' },
|
|
493
628
|
},
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const db = ctx.data.db as Database;
|
|
497
|
-
await db?.close();
|
|
629
|
+
run({ args }) {
|
|
630
|
+
console.log(args.output);
|
|
498
631
|
},
|
|
499
632
|
});
|
|
500
633
|
```
|
|
501
634
|
|
|
502
|
-
|
|
635
|
+
Using `--out` warns once on stderr, labels itself `[deprecated: renamed]` in
|
|
636
|
+
help, and forwards its value to `--output`, so a rename keeps working without
|
|
637
|
+
the handler knowing. `deprecated` works the same on `meta` for a whole command.
|
|
503
638
|
|
|
504
|
-
|
|
639
|
+
### Lazy Subcommands
|
|
505
640
|
|
|
506
|
-
|
|
507
|
-
|
|
641
|
+
`lazySubCommands` takes a thunk, so a command tree whose branches each pull in
|
|
642
|
+
heavy modules only builds the branch being run. It is merged with `subCommands`,
|
|
643
|
+
which wins on a name collision.
|
|
508
644
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
645
|
+
```ts
|
|
646
|
+
const main = defineCommand({
|
|
647
|
+
meta: { name: 'tool' },
|
|
648
|
+
lazySubCommands: () => ({
|
|
649
|
+
build: require('./commands/build').default,
|
|
650
|
+
deploy: require('./commands/deploy').default,
|
|
651
|
+
}),
|
|
515
652
|
});
|
|
516
653
|
```
|
|
517
654
|
|
|
655
|
+
The thunk runs on the first token that could be a subcommand, so `tool --version`
|
|
656
|
+
never calls it.
|
|
657
|
+
|
|
658
|
+
## Running
|
|
659
|
+
|
|
518
660
|
### runMain
|
|
519
661
|
|
|
520
662
|
Entry point for CLI applications. Handles argv parsing, subcommand resolution, validation, help/version, and error display.
|
|
@@ -532,34 +674,32 @@ runMain(rootCommand, {
|
|
|
532
674
|
});
|
|
533
675
|
```
|
|
534
676
|
|
|
535
|
-
###
|
|
677
|
+
### Lifecycle Hooks
|
|
536
678
|
|
|
537
|
-
|
|
679
|
+
Commands support setup/run/cleanup lifecycle hooks. `cleanup` always runs, even if `run` throws.
|
|
538
680
|
|
|
539
681
|
```ts
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const command = defineCommand({
|
|
543
|
-
meta: { name: 'tool' },
|
|
682
|
+
const cmd = defineCommand({
|
|
683
|
+
meta: { name: 'server' },
|
|
544
684
|
args: { port: { type: 'number', default: 3000 } },
|
|
545
|
-
});
|
|
546
685
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
// result.explicitlySet -- Set of arg keys that were explicitly provided
|
|
551
|
-
|
|
552
|
-
// Validate separately
|
|
553
|
-
validate(result, command); // throws CliParseError on failure
|
|
686
|
+
async setup(ctx) {
|
|
687
|
+
ctx.data.db = await connectToDatabase();
|
|
688
|
+
},
|
|
554
689
|
|
|
555
|
-
|
|
556
|
-
const
|
|
690
|
+
async run(ctx) {
|
|
691
|
+
const db = ctx.data.db as Database;
|
|
692
|
+
await startServer(ctx.args.port, db);
|
|
693
|
+
},
|
|
557
694
|
|
|
558
|
-
|
|
559
|
-
const
|
|
695
|
+
async cleanup(ctx) {
|
|
696
|
+
const db = ctx.data.db as Database;
|
|
697
|
+
await db?.close();
|
|
698
|
+
},
|
|
699
|
+
});
|
|
560
700
|
```
|
|
561
701
|
|
|
562
|
-
|
|
702
|
+
### Value Precedence and Sources
|
|
563
703
|
|
|
564
704
|
Arguments are resolved in this order (highest wins):
|
|
565
705
|
|
|
@@ -568,7 +708,34 @@ Arguments are resolved in this order (highest wins):
|
|
|
568
708
|
3. **Conditional defaults** -- `defaultValueIf: ['env', 'prod', 443]`
|
|
569
709
|
4. **Static defaults** -- `default: 3000`
|
|
570
710
|
|
|
571
|
-
|
|
711
|
+
Every handler gets a `valueSources` map saying where each argument's value came
|
|
712
|
+
from, which is the only way to tell `--port 3000` from a default of the same
|
|
713
|
+
number.
|
|
714
|
+
|
|
715
|
+
```ts
|
|
716
|
+
defineCommand({
|
|
717
|
+
meta: { name: 'serve' },
|
|
718
|
+
args: {
|
|
719
|
+
port: { type: 'number', default: 3000, env: 'PORT' },
|
|
720
|
+
},
|
|
721
|
+
run({ args, valueSources }) {
|
|
722
|
+
// 'cli' | 'env' | 'default', or undefined when the arg has no value
|
|
723
|
+
if (valueSources.get('port') === 'default') {
|
|
724
|
+
console.log(`Using the default port ${args.port}`);
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Exit Codes
|
|
731
|
+
|
|
732
|
+
| Code | Meaning |
|
|
733
|
+
|------|---------|
|
|
734
|
+
| 0 | Success |
|
|
735
|
+
| 1 | Runtime error (unhandled exception in run/setup/cleanup) |
|
|
736
|
+
| 2 | Usage error (parse failure, validation failure, unknown flags) |
|
|
737
|
+
|
|
738
|
+
### Error Messages
|
|
572
739
|
|
|
573
740
|
Errors match clap's format with typo suggestions:
|
|
574
741
|
|
|
@@ -600,6 +767,36 @@ Usage: my-tool [OPTIONS]
|
|
|
600
767
|
For more information, try '--help'.
|
|
601
768
|
```
|
|
602
769
|
|
|
770
|
+
### Low-Level API
|
|
771
|
+
|
|
772
|
+
For advanced use cases, you can use the parser and validator directly:
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
import {
|
|
776
|
+
parseArgs, validate, renderHelp, CliParseError,
|
|
777
|
+
subCommandsOf, hasSubCommands, possibleValues,
|
|
778
|
+
} from 'clap-ts';
|
|
779
|
+
|
|
780
|
+
const command = defineCommand({
|
|
781
|
+
meta: { name: 'tool' },
|
|
782
|
+
args: { port: { type: 'number', default: 3000 } },
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// Parse without validation
|
|
786
|
+
const result = parseArgs(['--port', '8080'], command);
|
|
787
|
+
// result.args, result.positionals, result.rest, result.unknown
|
|
788
|
+
// result.explicitlySet -- Set of arg keys that were explicitly provided
|
|
789
|
+
|
|
790
|
+
// Validate separately
|
|
791
|
+
validate(result, command); // throws CliParseError on failure
|
|
792
|
+
|
|
793
|
+
// Render help text
|
|
794
|
+
const helpText = renderHelp(command);
|
|
795
|
+
|
|
796
|
+
// Render short help (-h style, hides hideShortHelp args)
|
|
797
|
+
const shortHelp = renderHelp(command, undefined, true);
|
|
798
|
+
```
|
|
799
|
+
|
|
603
800
|
## Help Output
|
|
604
801
|
|
|
605
802
|
Help is automatically generated in clap's format, respecting `NO_COLOR`, `TERM=dumb`, and terminal width:
|
|
@@ -632,6 +829,22 @@ Examples:
|
|
|
632
829
|
my-tool -n World serve -p 8080
|
|
633
830
|
```
|
|
634
831
|
|
|
832
|
+
### Styles
|
|
833
|
+
|
|
834
|
+
Override the default terminal colors for help and error output:
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
import { runMain } from 'clap-ts';
|
|
838
|
+
|
|
839
|
+
runMain(rootCommand, {
|
|
840
|
+
styles: {
|
|
841
|
+
heading: (s) => `\x1b[35m${s}\x1b[0m`, // magenta headings
|
|
842
|
+
flag: (s) => `\x1b[36m${s}\x1b[0m`, // cyan flags
|
|
843
|
+
command: (s) => `\x1b[1m${s}\x1b[0m`, // bold commands
|
|
844
|
+
},
|
|
845
|
+
});
|
|
846
|
+
```
|
|
847
|
+
|
|
635
848
|
### Help Template
|
|
636
849
|
|
|
637
850
|
Use `helpTemplate` for full control over help layout:
|
|
@@ -655,32 +868,353 @@ const cmd = defineCommand({
|
|
|
655
868
|
|
|
656
869
|
Placeholders: `{name}`, `{version}`, `{about}`, `{usage}`, `{all-args}`, `{arguments}`, `{options}`, `{commands}`, `{before-help}`, `{after-help}`
|
|
657
870
|
|
|
658
|
-
##
|
|
871
|
+
## Optional Modules
|
|
872
|
+
|
|
873
|
+
Everything past the core parser lives behind its own entry point, so a running
|
|
874
|
+
CLI never loads a generator it does not call. `import 'clap-ts'` is 3.0ms on
|
|
875
|
+
Node; each module below is another 0.5ms to 1ms, and only when imported.
|
|
876
|
+
|
|
877
|
+
| Import | Provides |
|
|
878
|
+
|--------|----------|
|
|
879
|
+
| `clap-ts` | `defineCommand`, `runMain`, parsing, validation, help |
|
|
880
|
+
| `clap-ts/config` | Config file discovery, layered under argv and env |
|
|
881
|
+
| `clap-ts/prompt` | Ask for required arguments argv left out |
|
|
882
|
+
| `clap-ts/testing` | `runCli`, `captureArgs`, no spawning or global patching |
|
|
883
|
+
| `clap-ts/completions` | bash, zsh, fish, powershell, elvish, nushell |
|
|
884
|
+
| `clap-ts/man` | roff man pages |
|
|
885
|
+
| `clap-ts/markdown` | Markdown documentation |
|
|
886
|
+
| `clap-ts/install` | Write completions and man pages where the system finds them |
|
|
887
|
+
| `clap-ts/spec` | The command tree as plain JSON |
|
|
888
|
+
| `clap-ts/plugins` | Subcommands discovered from installed packages |
|
|
889
|
+
| `clap-ts/argfile` | `@file` response files and stdin |
|
|
890
|
+
| `clap-ts/output` | Tables, key-value blocks, trees |
|
|
891
|
+
| `clap-ts/log` | Levelled logger wired to `-v` and `--quiet` |
|
|
892
|
+
| `clap-ts/progress` | Spinners and progress bars |
|
|
893
|
+
|
|
894
|
+
### Configuration Files
|
|
659
895
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
| 1 | Runtime error (unhandled exception in run/setup/cleanup) |
|
|
664
|
-
| 2 | Usage error (parse failure, validation failure, unknown flags) |
|
|
896
|
+
```ts
|
|
897
|
+
import { runMain } from 'clap-ts';
|
|
898
|
+
import { configOptions } from 'clap-ts/config';
|
|
665
899
|
|
|
666
|
-
|
|
900
|
+
await runMain(main, { ...configOptions('mytool') });
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
Precedence becomes command line, then environment, then config file, then the
|
|
904
|
+
argument's default, and `ctx.valueSources` reports which layer won. By default
|
|
905
|
+
the search looks for `.mytoolrc`, `.mytoolrc.json`, `mytool.config.json`,
|
|
906
|
+
`.config/mytool.json` and a `mytool` key in `package.json`, walking up from the
|
|
907
|
+
working directory.
|
|
908
|
+
|
|
909
|
+
A nested object named for a subcommand scopes its contents to that command,
|
|
910
|
+
while scalar keys stay in scope all the way down:
|
|
911
|
+
|
|
912
|
+
```json
|
|
913
|
+
{
|
|
914
|
+
"verbose": true,
|
|
915
|
+
"serve": { "port": 8080 }
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
Only JSON is read out of the box, which is what keeps the package
|
|
920
|
+
dependency-free. Point `parse` at a TOML or YAML reader for those:
|
|
921
|
+
|
|
922
|
+
```ts
|
|
923
|
+
import { parse as parseToml } from 'smol-toml';
|
|
924
|
+
|
|
925
|
+
configOptions('mytool', {
|
|
926
|
+
files: ['.mytoolrc.toml', 'mytool.toml'],
|
|
927
|
+
parse: (text) => parseToml(text),
|
|
928
|
+
stopAtProjectRoot: true, // stop at the directory holding package.json or .git
|
|
929
|
+
});
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
`configOptions` returns a thunk rather than the values, so the filesystem is
|
|
933
|
+
only searched when some argument is still on its default. A fully specified
|
|
934
|
+
command line costs nothing: 1.0us against 23us when the search actually runs.
|
|
935
|
+
|
|
936
|
+
### Interactive Prompts
|
|
937
|
+
|
|
938
|
+
```ts
|
|
939
|
+
import { promptMissing } from 'clap-ts/prompt';
|
|
940
|
+
|
|
941
|
+
await runMain(main, { fillMissing: promptMissing() });
|
|
942
|
+
```
|
|
667
943
|
|
|
668
|
-
|
|
944
|
+
A required argument nobody supplied is asked for rather than rejected, and the
|
|
945
|
+
prompt comes from the definition that already exists: a boolean becomes a
|
|
946
|
+
confirm, possible values become a numbered list, an argument marked `secret` is
|
|
947
|
+
read without echoing, and the answer goes through the argument's own
|
|
948
|
+
`valueParser` before being accepted. `ctx.valueSources` reports these as
|
|
949
|
+
`'prompt'`.
|
|
669
950
|
|
|
670
|
-
|
|
951
|
+
Nothing is asked when stdin is not a terminal, so a script or a CI job still
|
|
952
|
+
fails fast with the usual error rather than waiting on input that never comes.
|
|
953
|
+
Pass `force: true` to ask anyway.
|
|
671
954
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
955
|
+
The individual prompts are available on their own, and each takes an `io` so a
|
|
956
|
+
test can script the answers without a terminal:
|
|
957
|
+
|
|
958
|
+
```ts
|
|
959
|
+
import { input, confirm, select, password, scriptedIO } from 'clap-ts/prompt';
|
|
960
|
+
|
|
961
|
+
const name = await input('Release name');
|
|
962
|
+
const mode = await select('Mode', ['fast', 'safe']);
|
|
963
|
+
const sure = await confirm('Deploy to production', { defaultValue: false });
|
|
964
|
+
const token = await password('API token');
|
|
965
|
+
|
|
966
|
+
// In a test
|
|
967
|
+
await select('Mode', ['fast', 'safe'], { io: scriptedIO(['2']) }); // 'safe'
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
### Testing
|
|
971
|
+
|
|
972
|
+
```ts
|
|
973
|
+
import { runCli, captureArgs } from 'clap-ts/testing';
|
|
974
|
+
|
|
975
|
+
const result = await runCli(main, ['serve', '--port', '8080']);
|
|
976
|
+
expect(result.exitCode).toBe(0);
|
|
977
|
+
expect(result.plainStdout).toContain('listening');
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
`runCli` returns `stdout`, `stderr`, `plainStdout`, `plainStderr`, `exitCode` and
|
|
981
|
+
`error`. No process is spawned and no global is patched: output goes to
|
|
982
|
+
collectors through `RunOptions.stdout`/`stderr` and the exit code arrives via
|
|
983
|
+
`onExit`. An error thrown by a handler comes back on `error` rather than being
|
|
984
|
+
rethrown, so one assertion style covers success and failure.
|
|
985
|
+
|
|
986
|
+
`captureArgs` reports the parsed arguments without running the handler body,
|
|
987
|
+
which works for a subcommand as well as the root:
|
|
988
|
+
|
|
989
|
+
```ts
|
|
990
|
+
const { args } = await captureArgs(main, ['serve', '--port', '9']);
|
|
991
|
+
expect(args.port).toBe(9);
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
Handlers that write through `ctx.stdout` rather than `process.stdout` are
|
|
995
|
+
captured too.
|
|
996
|
+
|
|
997
|
+
### Shell Completions
|
|
998
|
+
|
|
999
|
+
Add Tab completion for bash, zsh, fish, and powershell with one line:
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
import { defineCommand, runMain, withCompletions } from 'clap-ts';
|
|
1003
|
+
|
|
1004
|
+
const root = defineCommand({
|
|
1005
|
+
meta: { name: 'my-cli', version: '1.0.0' },
|
|
1006
|
+
args: { /* ... */ },
|
|
1007
|
+
subCommands: { /* ... */ },
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// Auto-adds a `completions` subcommand
|
|
1011
|
+
runMain(withCompletions(root));
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
Users then enable completions in their shell:
|
|
1015
|
+
|
|
1016
|
+
```bash
|
|
1017
|
+
# bash - add to ~/.bashrc
|
|
1018
|
+
eval "$(my-cli completions bash)"
|
|
1019
|
+
|
|
1020
|
+
# zsh - add to ~/.zshrc
|
|
1021
|
+
eval "$(my-cli completions zsh)"
|
|
1022
|
+
|
|
1023
|
+
# fish - save to completions dir
|
|
1024
|
+
my-cli completions fish > ~/.config/fish/completions/my-cli.fish
|
|
1025
|
+
|
|
1026
|
+
# powershell - add to $PROFILE
|
|
1027
|
+
my-cli completions powershell >> $PROFILE
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
Generated scripts support flags, subcommands, aliases, enum values, and value hints (file/dir completion):
|
|
1031
|
+
|
|
1032
|
+
```ts
|
|
1033
|
+
const cmd = defineCommand({
|
|
1034
|
+
meta: { name: 'tool' },
|
|
1035
|
+
args: {
|
|
1036
|
+
config: { type: 'string', valueHint: 'filePath' }, // Tab completes files
|
|
1037
|
+
outDir: { type: 'string', valueHint: 'dirPath' }, // Tab completes directories
|
|
1038
|
+
host: { type: 'string', valueHint: 'hostname' }, // Tab completes hostnames
|
|
1039
|
+
env: { type: 'string', valueParser: ['dev', 'prod'] }, // Tab shows dev, prod
|
|
1040
|
+
},
|
|
1041
|
+
});
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
You can also generate scripts manually without the subcommand:
|
|
1045
|
+
|
|
1046
|
+
```ts
|
|
1047
|
+
import { generateCompletions } from 'clap-ts';
|
|
1048
|
+
|
|
1049
|
+
const bashScript = generateCompletions(root, 'bash');
|
|
1050
|
+
const zshScript = generateCompletions(root, 'zsh', 'custom-binary-name');
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### Man Pages
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
import { renderManPage, generateManPages } from 'clap-ts';
|
|
1057
|
+
import { writeFileSync } from 'node:fs';
|
|
1058
|
+
|
|
1059
|
+
// One page
|
|
1060
|
+
writeFileSync('my-tool.1', renderManPage(main));
|
|
1061
|
+
|
|
1062
|
+
// One page per command, named my-tool.1, my-tool-serve.1, and so on
|
|
1063
|
+
for (const [file, roff] of generateManPages(main, { section: '1' })) {
|
|
1064
|
+
writeFileSync(`man/${file}`, roff);
|
|
1065
|
+
}
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
The output carries NAME, SYNOPSIS, DESCRIPTION, OPTIONS, SUBCOMMANDS, EXTRA,
|
|
1069
|
+
VERSION and AUTHORS, with possible values as a bullet list and notes for
|
|
1070
|
+
defaults and environment variables. Check it with `man -l my-tool.1`.
|
|
1071
|
+
|
|
1072
|
+
### Markdown Documentation
|
|
1073
|
+
|
|
1074
|
+
```ts
|
|
1075
|
+
import { renderMarkdownHelp } from 'clap-ts';
|
|
1076
|
+
|
|
1077
|
+
writeFileSync('docs/cli.md', renderMarkdownHelp(main, {
|
|
1078
|
+
title: 'CLI Reference', // optional, demotes command headings by one level
|
|
1079
|
+
footer: 'Generated from the command definitions.',
|
|
1080
|
+
}));
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
Produces one heading per command with its usage line and Commands, Arguments and
|
|
1084
|
+
Options lists, nesting subcommands as deeper headings.
|
|
1085
|
+
|
|
1086
|
+
### Installing Completions and Man Pages
|
|
1087
|
+
|
|
1088
|
+
```ts
|
|
1089
|
+
import { withInstallers } from 'clap-ts/install';
|
|
1090
|
+
|
|
1091
|
+
runMain(withInstallers(main));
|
|
1092
|
+
// my-tool completions install zsh
|
|
1093
|
+
// my-tool man install --dryRun
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
Writes to the XDG per-user locations, names the file the way each shell expects,
|
|
1097
|
+
and reports the profile line to add where sourcing is not automatic.
|
|
1098
|
+
|
|
1099
|
+
### Machine-Readable Spec
|
|
1100
|
+
|
|
1101
|
+
```ts
|
|
1102
|
+
import { toSpecJson } from 'clap-ts/spec';
|
|
1103
|
+
|
|
1104
|
+
writeFileSync('cli.json', toSpecJson(main));
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
The whole tree as plain JSON, for a docs site, editor integration, or generating
|
|
1108
|
+
tool definitions from a CLI.
|
|
1109
|
+
|
|
1110
|
+
### Plugins
|
|
1111
|
+
|
|
1112
|
+
```ts
|
|
1113
|
+
import { pluginSubCommands } from 'clap-ts/plugins';
|
|
1114
|
+
|
|
1115
|
+
const main = defineCommand({
|
|
1116
|
+
meta: { name: 'my-tool' },
|
|
1117
|
+
lazySubCommands: pluginSubCommands('my-tool'),
|
|
1118
|
+
});
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
Installing `my-tool-plugin-deploy` makes `my-tool deploy` work. Because it is a
|
|
1122
|
+
`lazySubCommands` thunk, neither the directory scan nor the module import
|
|
1123
|
+
happens until a token could be a subcommand.
|
|
1124
|
+
|
|
1125
|
+
### Response Files and stdin
|
|
1126
|
+
|
|
1127
|
+
```ts
|
|
1128
|
+
import { expandArgFiles, readPathOrStdin } from 'clap-ts/argfile';
|
|
1129
|
+
|
|
1130
|
+
await runMain(main, { argv: expandArgFiles() });
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
`@args.txt` is replaced by that file's contents, following the convention git,
|
|
1134
|
+
gcc and java use: one argument per line, `#` comments and blank lines skipped,
|
|
1135
|
+
quoted runs kept whole, `@@` for a literal at-sign, and nothing after `--`
|
|
1136
|
+
touched. `readPathOrStdin` covers the other half, where `-` means stdin.
|
|
1137
|
+
|
|
1138
|
+
### Terminal Output
|
|
1139
|
+
|
|
1140
|
+
```ts
|
|
1141
|
+
import { table, keyValue, tree } from 'clap-ts/output';
|
|
1142
|
+
|
|
1143
|
+
ctx.stdout.write(table(rows, {
|
|
1144
|
+
columns: [{ key: 'name' }, { key: 'size', align: 'right' }],
|
|
1145
|
+
rule: true,
|
|
1146
|
+
}));
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
Columns size to their widest cell, then the widest shrinks until the table fits
|
|
1150
|
+
the terminal. Padding measures visible width, so styled cells line up with plain
|
|
1151
|
+
ones.
|
|
1152
|
+
|
|
1153
|
+
### Logging and Progress
|
|
1154
|
+
|
|
1155
|
+
```ts
|
|
1156
|
+
import { loggerFrom } from 'clap-ts/log';
|
|
1157
|
+
import { spinner } from 'clap-ts/progress';
|
|
1158
|
+
|
|
1159
|
+
const main = defineCommand({
|
|
1160
|
+
meta: { name: 'my-tool' },
|
|
1161
|
+
args: {
|
|
1162
|
+
verbose: { type: 'boolean', short: 'v', action: 'count', description: 'More output' },
|
|
1163
|
+
quiet: { type: 'boolean', short: 'q', description: 'Errors only' },
|
|
1164
|
+
},
|
|
1165
|
+
async run(ctx) {
|
|
1166
|
+
const log = loggerFrom(ctx); // -v climbs a level, --quiet drops to errors
|
|
1167
|
+
log.debug('shown with -v');
|
|
1168
|
+
|
|
1169
|
+
const spin = spinner('Fetching').start();
|
|
1170
|
+
await fetchThings();
|
|
1171
|
+
spin.succeed('Fetched 12 items');
|
|
1172
|
+
},
|
|
1173
|
+
});
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
Both write to stderr, leaving stdout for real output. Spinners and bars draw
|
|
1177
|
+
only when stderr is a terminal and `CI` is unset, so a piped run never fills a
|
|
1178
|
+
log with redraw escapes, and still reports its final message once.
|
|
1179
|
+
|
|
1180
|
+
## Performance
|
|
1181
|
+
|
|
1182
|
+
Parsing is a single pass over argv against a spec compiled once per command and
|
|
1183
|
+
cached, so flag lookup, value counts and camelCase keys are all resolved ahead of
|
|
1184
|
+
time. Feature fields short-circuit on `undefined`, so an unused one costs nothing.
|
|
1185
|
+
Subcommand maps are built only when a token could actually be a subcommand, which
|
|
1186
|
+
is also what keeps `lazySubCommands` from running its thunk on a flags-only
|
|
1187
|
+
invocation.
|
|
1188
|
+
|
|
1189
|
+
Config discovery follows the same idea. The search costs one `existsSync` per
|
|
1190
|
+
candidate per directory, so it is O(directories x candidates) and independent of
|
|
1191
|
+
how large those directories are. Listing each directory once with `readdirSync`
|
|
1192
|
+
would be a single syscall per level, but it is O(entries): against a 2000-entry
|
|
1193
|
+
directory that took 117us where four `existsSync` calls took 2.4us, and walking
|
|
1194
|
+
up through a large directory is exactly the case that has to stay cheap.
|
|
1195
|
+
|
|
1196
|
+
Earlier versions delegated to `node:util parseArgs`, which re-validates its whole
|
|
1197
|
+
`options` object on every call, roughly 170ns per declared option regardless of how
|
|
1198
|
+
long argv is. A 33-option command spent 5.8us there before reading a single token.
|
|
1199
|
+
|
|
1200
|
+
Benchmarks on AMD Ryzen 9 9950X3D (Bun 1.4), against that `node:util` baseline:
|
|
1201
|
+
|
|
1202
|
+
| Scenario | Before | After | Change |
|
|
1203
|
+
|----------|--------|-------|--------|
|
|
1204
|
+
| Minimal (no args) | 1.47us | 40ns | 36x |
|
|
1205
|
+
| Simple (5 flags) | 9.01us | 494ns | 18x |
|
|
1206
|
+
| Complex (22 flags) | 14.15us | 1.80us | 8x |
|
|
1207
|
+
| Subcommand detection | 9.96us | 418ns | 24x |
|
|
1208
|
+
| Full pipeline (parse + validate) | 23.01us | 1.45us | 16x |
|
|
1209
|
+
|
|
1210
|
+
Figures are the minimum of nine pinned runs. Microbenchmarks on this machine are
|
|
1211
|
+
bimodal by roughly 1.5x depending on core placement, so a single run tells you
|
|
1212
|
+
very little.
|
|
679
1213
|
|
|
680
1214
|
To run benchmarks yourself:
|
|
681
1215
|
|
|
682
1216
|
```bash
|
|
683
|
-
bun run bench
|
|
1217
|
+
bun run bench
|
|
684
1218
|
```
|
|
685
1219
|
|
|
686
1220
|
## Comparison with Rust clap
|
|
@@ -732,20 +1266,102 @@ bun run bench/parse.bench.ts
|
|
|
732
1266
|
| hideShortHelp / hideLongHelp | Yes | Yes |
|
|
733
1267
|
| hidePossibleValues | Yes | Yes |
|
|
734
1268
|
| Hidden args/commands | Yes | Yes |
|
|
1269
|
+
| Multi-token numArgs (`--point 1 2 3`) | Yes | Yes |
|
|
1270
|
+
| requireEquals | Yes | Yes |
|
|
1271
|
+
| valueTerminator | Yes | Yes |
|
|
1272
|
+
| overridesWith | Yes | Yes |
|
|
1273
|
+
| ignoreCase | Yes | Yes |
|
|
1274
|
+
| Possible values with help/aliases | Yes | Yes |
|
|
1275
|
+
| valueNames, index, displayOrder, nextLineHelp | Yes | Yes |
|
|
1276
|
+
| hideDefaultValue / hideEnv / hideEnvValues | Yes | Yes |
|
|
1277
|
+
| defaultValueIfs, requiredIfEqAny/All | Yes | Yes |
|
|
1278
|
+
| Arg-level group membership | Yes | Yes |
|
|
1279
|
+
| Group conflictsWith / requires | Yes | Yes |
|
|
1280
|
+
| Built-in `help` subcommand | Yes | Yes |
|
|
1281
|
+
| disableHelpFlag / disableVersionFlag | Yes | Yes |
|
|
1282
|
+
| termWidth / maxTermWidth | Yes | Yes |
|
|
1283
|
+
| overrideUsage / overrideHelp | Yes | Yes |
|
|
1284
|
+
| propagateVersion / longVersion | Yes | Yes |
|
|
1285
|
+
| Flag subcommands (`pacman -S`) | Yes | Yes |
|
|
1286
|
+
| allowMissingPositional | Yes | Yes |
|
|
1287
|
+
| argsOverrideSelf | Yes | Yes |
|
|
1288
|
+
| subcommandPrecedenceOverArg | Yes | Yes |
|
|
1289
|
+
| multicall / noBinaryName | Yes | Yes |
|
|
735
1290
|
| Type-safe parsed args | derive macro | generics |
|
|
736
|
-
| Shell completions | Yes |
|
|
737
|
-
|
|
|
1291
|
+
| Shell completions (bash/zsh/fish/powershell) | Yes | Yes |
|
|
1292
|
+
| Shell completions (elvish, nushell) | Yes | Yes |
|
|
1293
|
+
| Man page generation | Yes | Yes |
|
|
1294
|
+
| Value source (CLI vs env vs default) | Yes | Yes |
|
|
1295
|
+
| setTrue / setFalse / help / version actions | Yes | Yes |
|
|
1296
|
+
| defaultMissingValues, requiresIf, singular group | Yes | Yes |
|
|
1297
|
+
| binName, displayName, color, helpExpected | Yes | Yes |
|
|
1298
|
+
| flattenHelp, ignoreErrors, nextHelpHeading | Yes | Yes |
|
|
1299
|
+
| nextDisplayOrder, dontDelimitTrailingValues | Yes | Yes |
|
|
1300
|
+
| Subcommand flag aliases (short and long) | Yes | Yes |
|
|
1301
|
+
| Lazy subcommand building (`defer`) | Yes | Yes |
|
|
1302
|
+
| externalSubcommandValueParser | Yes | Yes |
|
|
1303
|
+
| Markdown documentation | clap-markdown | Yes |
|
|
1304
|
+
| Config file layering | no | Yes (`clap-ts/config`) |
|
|
1305
|
+
| Interactive prompts for missing args | no | Yes (`clap-ts/prompt`) |
|
|
1306
|
+
| Test harness with no spawning | no | Yes (`clap-ts/testing`) |
|
|
1307
|
+
| Response files (`@file`) | no | Yes (`clap-ts/argfile`) |
|
|
1308
|
+
| Plugin subcommands from packages | no | Yes (`clap-ts/plugins`) |
|
|
1309
|
+
| Tables, logging, progress | no | Yes (`clap-ts/output`, `/log`, `/progress`) |
|
|
1310
|
+
| Derive macro | Yes | n/a in TypeScript |
|
|
1311
|
+
| dontCollapseArgsInUsage | deprecated no-op | Not implemented |
|
|
1312
|
+
|
|
1313
|
+
Two clap settings are deliberately absent. `dont_collapse_args_in_usage` is a
|
|
1314
|
+
deprecated no-op upstream, and this usage line never collapsed positionals in the
|
|
1315
|
+
first place. The derive macro has no analogue: `defineCommand` already infers the
|
|
1316
|
+
parsed argument types from the definition object.
|
|
1317
|
+
|
|
1318
|
+
## Upgrading from 0.2
|
|
1319
|
+
|
|
1320
|
+
The parser was rewritten to tokenize argv directly, which brought several defaults
|
|
1321
|
+
in line with clap. Each of these was previously wrong or silently permissive:
|
|
1322
|
+
|
|
1323
|
+
- `--version` and `-V` exist only where `meta.version` is set. A subcommand that
|
|
1324
|
+
needs the root's version should set `propagateVersion: true` on the root.
|
|
1325
|
+
- Repeating a single-value arg is an error. `argsOverrideSelf: true` restores the
|
|
1326
|
+
old behaviour of keeping the last one.
|
|
1327
|
+
- A flag missing its value is an error. `--name` with nothing after it used to
|
|
1328
|
+
yield the boolean `true` in a string-typed arg, and `--name --verbose` used to
|
|
1329
|
+
swallow the next flag as the value.
|
|
1330
|
+
- `--flag=true` is now `true`. Every `--flag=<value>` form used to yield `false`.
|
|
1331
|
+
- `-p=80` yields `80`. The leading `=` used to end up in the value.
|
|
1332
|
+
- Flags before a subcommand no longer break dispatch. `app --verbose serve` used
|
|
1333
|
+
to run nothing at all, and a subcommand's flags are now parsed against the
|
|
1334
|
+
subcommand rather than its parent.
|
|
1335
|
+
- `global: true` args declared on an intermediate command now reach its
|
|
1336
|
+
grandchildren, not just the root's.
|
|
1337
|
+
- Errors print the usage line of the command that failed rather than the root's.
|
|
1338
|
+
- Help shows `[env: VAR=value]`; see `hideEnvValues` and `hideEnv`.
|
|
1339
|
+
- The `completions` subcommand validates its shell through `valueParser`, so an
|
|
1340
|
+
unknown one now reports the accepted values and honours a caller's
|
|
1341
|
+
`exit: false` instead of calling `process.exit` from inside its handler.
|
|
1342
|
+
- `ParseResult` gained `valueSources`, `errors`, `subCommandArgs`,
|
|
1343
|
+
`subCommandIsExternal` and `versionIsShort`. Only the low-level API sees these;
|
|
1344
|
+
`defineCommand` and `runMain` are unaffected.
|
|
738
1345
|
|
|
739
1346
|
## Roadmap
|
|
740
1347
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1348
|
+
Parity with clap's builder API is complete, and the optional modules cover the
|
|
1349
|
+
ergonomics around it. What is left is polish:
|
|
1350
|
+
|
|
1351
|
+
- A `docs` subcommand helper, the way `withInstallers` wraps the generators
|
|
1352
|
+
- Arrow-key selection in `clap-ts/prompt` where the terminal supports it, keeping
|
|
1353
|
+
the numbered list as the fallback
|
|
1354
|
+
- Shell-side dynamic completion, so a value list can come from the running CLI
|
|
1355
|
+
rather than only from the definition
|
|
744
1356
|
|
|
745
1357
|
## Requirements
|
|
746
1358
|
|
|
747
1359
|
- Node.js >= 20.0.0 or Bun >= 1.0
|
|
748
|
-
- TypeScript >= 5.0
|
|
1360
|
+
- TypeScript >= 5.0, for `const` type parameter inference
|
|
1361
|
+
- ESM only. A CommonJS file has to reach it through a dynamic `import()`; the
|
|
1362
|
+
package sets `"type": "module"` and ships no CJS build.
|
|
1363
|
+
|
|
1364
|
+
Type resolution is verified against `nodenext`, `node16` and `bundler`.
|
|
749
1365
|
|
|
750
1366
|
## License
|
|
751
1367
|
|