@guanghechen/commander 4.5.0 → 4.6.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 +14 -0
- package/README.md +131 -29
- package/lib/cjs/index.cjs +657 -150
- package/lib/esm/index.mjs +653 -149
- package/lib/types/index.d.ts +124 -18
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add preset-root and command-level preset resolution for commander, align control/preset pipeline
|
|
8
|
+
behavior with spec, and include updated preset parsing and validation semantics.
|
|
9
|
+
|
|
10
|
+
## 4.5.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Add built-in host and network validators (ip, domain, host), expose is helpers, and extend coerce
|
|
15
|
+
factories with port and choice support.
|
|
16
|
+
|
|
3
17
|
## 4.5.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
A minimal, type-safe command-line interface builder with fluent API. Supports subcommands, option
|
|
53
53
|
parsing, shell completion generation (bash, fish, pwsh), and built-in help/version handling.
|
|
54
54
|
|
|
55
|
+
`opts` / `args` are designed for strong type inference from the current command's own declarations.
|
|
56
|
+
|
|
55
57
|
## Install
|
|
56
58
|
|
|
57
59
|
- npm
|
|
@@ -76,7 +78,7 @@ import { Command } from '@guanghechen/commander'
|
|
|
76
78
|
const cli = new Command({
|
|
77
79
|
name: 'mycli',
|
|
78
80
|
version: '1.0.0',
|
|
79
|
-
|
|
81
|
+
desc: 'My awesome CLI tool',
|
|
80
82
|
})
|
|
81
83
|
|
|
82
84
|
cli
|
|
@@ -84,25 +86,27 @@ cli
|
|
|
84
86
|
long: 'verbose',
|
|
85
87
|
short: 'v',
|
|
86
88
|
type: 'boolean',
|
|
87
|
-
|
|
89
|
+
args: 'none',
|
|
90
|
+
desc: 'Enable verbose output',
|
|
88
91
|
})
|
|
89
92
|
.option({
|
|
90
93
|
long: 'output',
|
|
91
94
|
short: 'o',
|
|
92
95
|
type: 'string',
|
|
93
|
-
|
|
96
|
+
args: 'required',
|
|
97
|
+
desc: 'Output file path',
|
|
94
98
|
default: './output.txt',
|
|
95
99
|
})
|
|
96
100
|
.argument({
|
|
97
101
|
name: 'file',
|
|
98
102
|
kind: 'required',
|
|
99
|
-
|
|
103
|
+
desc: 'Input file to process',
|
|
100
104
|
})
|
|
101
105
|
.action(({ opts, args, ctx }) => {
|
|
102
|
-
const
|
|
106
|
+
const file = String(args.file)
|
|
103
107
|
ctx.reporter.info(`Processing ${file}...`)
|
|
104
|
-
if (opts
|
|
105
|
-
ctx.reporter.debug(`Output: ${opts
|
|
108
|
+
if (opts.verbose) {
|
|
109
|
+
ctx.reporter.debug(`Output: ${opts.output}`)
|
|
106
110
|
}
|
|
107
111
|
})
|
|
108
112
|
|
|
@@ -120,25 +124,25 @@ import { Command } from '@guanghechen/commander'
|
|
|
120
124
|
const root = new Command({
|
|
121
125
|
name: 'git',
|
|
122
126
|
version: '1.0.0',
|
|
123
|
-
|
|
127
|
+
desc: 'A simple git-like CLI',
|
|
124
128
|
})
|
|
125
129
|
|
|
126
130
|
const clone = new Command({
|
|
127
|
-
|
|
131
|
+
desc: 'Clone a repository',
|
|
128
132
|
})
|
|
129
|
-
.argument({ name: 'url', kind: 'required',
|
|
130
|
-
.option({ long: 'depth', type: 'number',
|
|
133
|
+
.argument({ name: 'url', kind: 'required', desc: 'Repository URL' })
|
|
134
|
+
.option({ long: 'depth', type: 'number', args: 'required', desc: 'Shallow clone depth' })
|
|
131
135
|
.action(({ args, opts }) => {
|
|
132
|
-
console.log(`Cloning ${args
|
|
136
|
+
console.log(`Cloning ${args.url} with depth ${opts.depth ?? 'full'}`)
|
|
133
137
|
})
|
|
134
138
|
|
|
135
139
|
const commit = new Command({
|
|
136
|
-
|
|
140
|
+
desc: 'Record changes to the repository',
|
|
137
141
|
})
|
|
138
|
-
.option({ long: 'message', short: 'm', type: 'string', required: true,
|
|
139
|
-
.option({ long: 'amend', type: 'boolean',
|
|
142
|
+
.option({ long: 'message', short: 'm', type: 'string', args: 'required', required: true, desc: 'Commit message' })
|
|
143
|
+
.option({ long: 'amend', type: 'boolean', args: 'none', desc: 'Amend previous commit' })
|
|
140
144
|
.action(({ opts }) => {
|
|
141
|
-
console.log(`Committing: ${opts
|
|
145
|
+
console.log(`Committing: ${opts.message}`)
|
|
142
146
|
})
|
|
143
147
|
|
|
144
148
|
root.subcommand('clone', clone).subcommand('commit', commit).subcommand('ci', commit)
|
|
@@ -154,7 +158,7 @@ import { Command, CompletionCommand } from '@guanghechen/commander'
|
|
|
154
158
|
const root = new Command({
|
|
155
159
|
name: 'mycli',
|
|
156
160
|
version: '1.0.0',
|
|
157
|
-
|
|
161
|
+
desc: 'My CLI with completion support',
|
|
158
162
|
})
|
|
159
163
|
|
|
160
164
|
// Add completion subcommand
|
|
@@ -171,37 +175,89 @@ root.subcommand('completion', new CompletionCommand(root))
|
|
|
171
175
|
```typescript
|
|
172
176
|
import { Command } from '@guanghechen/commander'
|
|
173
177
|
|
|
174
|
-
new Command({ name: 'example',
|
|
178
|
+
new Command({ name: 'example', desc: 'Option types demo' })
|
|
175
179
|
// Boolean (flags)
|
|
176
|
-
.option({ long: 'debug', type: 'boolean',
|
|
180
|
+
.option({ long: 'debug', type: 'boolean', args: 'none', desc: 'Enable debug mode' })
|
|
177
181
|
|
|
178
182
|
// String with choices
|
|
179
183
|
.option({
|
|
180
184
|
long: 'format',
|
|
181
185
|
type: 'string',
|
|
186
|
+
args: 'required',
|
|
182
187
|
choices: ['json', 'yaml', 'toml'],
|
|
183
188
|
default: 'json',
|
|
184
|
-
|
|
189
|
+
desc: 'Output format'
|
|
185
190
|
})
|
|
186
191
|
|
|
187
192
|
// Number
|
|
188
|
-
.option({ long: 'port', type: 'number', default: 3000,
|
|
193
|
+
.option({ long: 'port', type: 'number', args: 'required', default: 3000, desc: 'Server port' })
|
|
189
194
|
|
|
190
|
-
// Array (
|
|
191
|
-
.option({ long: 'include', type: 'string
|
|
195
|
+
// Array (generated by variadic args, not a standalone type)
|
|
196
|
+
.option({ long: 'include', type: 'string', args: 'variadic', desc: 'Files to include' })
|
|
192
197
|
|
|
193
198
|
// Required option
|
|
194
|
-
.option({ long: 'config', type: 'string', required: true,
|
|
199
|
+
.option({ long: 'config', type: 'string', args: 'required', required: true, desc: 'Config file' })
|
|
195
200
|
|
|
196
201
|
// Custom coercion
|
|
197
202
|
.option({
|
|
198
203
|
long: 'date',
|
|
199
204
|
type: 'string',
|
|
205
|
+
args: 'required',
|
|
200
206
|
coerce: (value) => new Date(value),
|
|
201
|
-
|
|
207
|
+
desc: 'Date value',
|
|
202
208
|
})
|
|
203
209
|
```
|
|
204
210
|
|
|
211
|
+
### Planned: Preset Input Files
|
|
212
|
+
|
|
213
|
+
> This is a proposed feature and not released yet.
|
|
214
|
+
> README is an overview only. Authoritative semantics are defined in `packages/commander/spec/*.md`.
|
|
215
|
+
|
|
216
|
+
The proposed `--preset-opts=<file>` and `--preset-envs=<file>` flags allow injecting preset argv and
|
|
217
|
+
env inputs before normal CLI parsing.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
mycli --preset-opts=./options.argv --preset-envs=./preset.env --log-level debug --color
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Proposed behavior:
|
|
224
|
+
|
|
225
|
+
1. Route command chain from user argv (name/alias only, no argv rewrite), then store route tokens in `sources.user.cmds`.
|
|
226
|
+
2. Run `control-scan` on user tail argv before preset merge: detect `--help` / `--version` by token scan (`--version` only when `supportsBuiltinVersion(leaf)`), detect `help` only when it is the first tail token, write `ctx.controls`, and strip control tokens from parse input.
|
|
227
|
+
3. In `run()`, execute `run-control` before preset merge: short-circuit by `help > version`. If short-circuit hits, preset files are not loaded.
|
|
228
|
+
4. Scan preset directives before `--`, remove them from control-tail argv, and store cleaned tokens in `sources.user.argv`.
|
|
229
|
+
5. Read options preset file(s) and tokenize by whitespace to `sources.preset.argv`.
|
|
230
|
+
6. Read env preset file(s) and parse via `@guanghechen/env.parse` to `sources.preset.envs`.
|
|
231
|
+
7. Build `effectiveTailArgv = [...sources.preset.argv, ...sources.user.argv]`.
|
|
232
|
+
8. Build `ctx.envs = { ...sources.user.envs, ...sources.preset.envs }`.
|
|
233
|
+
9. Expose source snapshots through `ctx.sources` and reuse existing tokenize/resolve/parse pipeline.
|
|
234
|
+
|
|
235
|
+
Proposed precedence for same option key:
|
|
236
|
+
|
|
237
|
+
1. User CLI tokens (highest)
|
|
238
|
+
2. Tokens loaded from `--preset-opts`
|
|
239
|
+
3. Option `default` / implicit defaults
|
|
240
|
+
4. `NO_COLOR` fallback for color rendering only (applies only when no explicit `--color/--no-color` token appears)
|
|
241
|
+
|
|
242
|
+
Proposed precedence for same env key:
|
|
243
|
+
|
|
244
|
+
1. Key-values loaded from `--preset-envs` (highest)
|
|
245
|
+
2. User envs (e.g. `process.env`)
|
|
246
|
+
|
|
247
|
+
Additional notes:
|
|
248
|
+
|
|
249
|
+
1. `variadic` options append in appearance order.
|
|
250
|
+
2. `NO_COLOR` is evaluated from `ctx.envs` and remains a fallback only when no color token is explicitly provided.
|
|
251
|
+
3. The `--preset-opts` file is expected to contain option fragments (`-x`/`--xxx` and their values), not command-route tokens.
|
|
252
|
+
4. The `--preset-envs` file must be parseable by `@guanghechen/env`.
|
|
253
|
+
5. Only preset flags before `--` are processed; after `--` they are treated as normal args.
|
|
254
|
+
6. Repeated preset flags are processed in appearance order.
|
|
255
|
+
7. Built-in control semantics recognize `--help` / `help` / `--version` only (no short aliases).
|
|
256
|
+
8. `long: 'help'` and `long: 'version'` are reserved and must not be user-defined in `.option()`.
|
|
257
|
+
9. `--help` / `help` / `--version` are forbidden in `--preset-opts` files; loading should fail fast.
|
|
258
|
+
10. `--` is forbidden inside `--preset-opts` files; loading should fail fast.
|
|
259
|
+
11. `parse()` never executes control handlers; it only records control hits in `ctx.controls`.
|
|
260
|
+
|
|
205
261
|
### Built-in Coerce Factories
|
|
206
262
|
|
|
207
263
|
```typescript
|
|
@@ -229,6 +285,41 @@ new Command({ name: 'example', desc: 'Coerce demo' })
|
|
|
229
285
|
coerce: Coerce.positiveNumber('--duration'),
|
|
230
286
|
desc: 'Duration in seconds',
|
|
231
287
|
})
|
|
288
|
+
.option({
|
|
289
|
+
long: 'port',
|
|
290
|
+
type: 'number',
|
|
291
|
+
args: 'required',
|
|
292
|
+
coerce: Coerce.port('--port'),
|
|
293
|
+
desc: 'Server port',
|
|
294
|
+
})
|
|
295
|
+
.option({
|
|
296
|
+
long: 'domain',
|
|
297
|
+
type: 'string',
|
|
298
|
+
args: 'required',
|
|
299
|
+
coerce: Coerce.domain('--domain'),
|
|
300
|
+
desc: 'Domain name',
|
|
301
|
+
})
|
|
302
|
+
.option({
|
|
303
|
+
long: 'ip',
|
|
304
|
+
type: 'string',
|
|
305
|
+
args: 'required',
|
|
306
|
+
coerce: Coerce.ip('--ip'),
|
|
307
|
+
desc: 'IP address',
|
|
308
|
+
})
|
|
309
|
+
.option({
|
|
310
|
+
long: 'host',
|
|
311
|
+
type: 'string',
|
|
312
|
+
args: 'required',
|
|
313
|
+
coerce: Coerce.host('--host'),
|
|
314
|
+
desc: 'Host (IP or domain)',
|
|
315
|
+
})
|
|
316
|
+
.option({
|
|
317
|
+
long: 'mode',
|
|
318
|
+
type: 'string',
|
|
319
|
+
args: 'required',
|
|
320
|
+
coerce: Coerce.choice('--mode', ['dev', 'test', 'prod'] as const),
|
|
321
|
+
desc: 'Deploy mode',
|
|
322
|
+
})
|
|
232
323
|
.option({
|
|
233
324
|
long: 'scale',
|
|
234
325
|
type: 'number',
|
|
@@ -246,6 +337,17 @@ Default error message format:
|
|
|
246
337
|
|
|
247
338
|
You can still override the message via `Coerce.xxx(name, 'custom error message')`.
|
|
248
339
|
|
|
340
|
+
### Built-in Is Helpers
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { isDomain, isIp, isIpv4, isIpv6 } from '@guanghechen/commander'
|
|
344
|
+
|
|
345
|
+
isIpv4('127.0.0.1') // true
|
|
346
|
+
isIpv6('::1') // true
|
|
347
|
+
isIp('2001:db8::1') // true
|
|
348
|
+
isDomain('example.com') // true
|
|
349
|
+
```
|
|
350
|
+
|
|
249
351
|
### Help Examples
|
|
250
352
|
|
|
251
353
|
```typescript
|
|
@@ -263,11 +365,11 @@ await cli.run({ argv: ['--help'], envs: process.env })
|
|
|
263
365
|
|
|
264
366
|
`usage` 是相对当前 command path 的片段,help 中会自动补齐前缀,例如 `mycli build --watch`。
|
|
265
367
|
|
|
266
|
-
`--color` / `--no-color` 仅控制 help 文本的终端着色;
|
|
267
|
-
|
|
368
|
+
`--color` / `--no-color` 仅控制 help 文本的终端着色; `--log-colorful` / `--no-log-colorful` 控制
|
|
369
|
+
`Reporter` 的日志着色。
|
|
268
370
|
|
|
269
|
-
当环境变量 `NO_COLOR` 存在时,help 渲染默认视为 `--no-color
|
|
270
|
-
|
|
371
|
+
当环境变量 `NO_COLOR` 存在时,help 渲染默认视为 `--no-color`;显式传入 `--color`
|
|
372
|
+
可以覆盖这个默认值。
|
|
271
373
|
|
|
272
374
|
## Reference
|
|
273
375
|
|