@guanghechen/commander 4.5.1 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## 4.5.1
4
11
 
5
12
  ### Patch 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
- description: 'My awesome CLI tool',
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
- description: 'Enable verbose output',
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
- description: 'Output file path',
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
- description: 'Input file to process',
103
+ desc: 'Input file to process',
100
104
  })
101
105
  .action(({ opts, args, ctx }) => {
102
- const [file] = args
106
+ const file = String(args.file)
103
107
  ctx.reporter.info(`Processing ${file}...`)
104
- if (opts['verbose']) {
105
- ctx.reporter.debug(`Output: ${opts['output']}`)
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
- description: 'A simple git-like CLI',
127
+ desc: 'A simple git-like CLI',
124
128
  })
125
129
 
126
130
  const clone = new Command({
127
- description: 'Clone a repository',
131
+ desc: 'Clone a repository',
128
132
  })
129
- .argument({ name: 'url', kind: 'required', description: 'Repository URL' })
130
- .option({ long: 'depth', type: 'number', description: 'Shallow clone depth' })
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[0]} with depth ${opts['depth'] ?? 'full'}`)
136
+ console.log(`Cloning ${args.url} with depth ${opts.depth ?? 'full'}`)
133
137
  })
134
138
 
135
139
  const commit = new Command({
136
- description: 'Record changes to the repository',
140
+ desc: 'Record changes to the repository',
137
141
  })
138
- .option({ long: 'message', short: 'm', type: 'string', required: true, description: 'Commit message' })
139
- .option({ long: 'amend', type: 'boolean', description: 'Amend previous commit' })
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['message']}`)
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
- description: 'My CLI with completion support',
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', description: 'Option types demo' })
178
+ new Command({ name: 'example', desc: 'Option types demo' })
175
179
  // Boolean (flags)
176
- .option({ long: 'debug', type: 'boolean', description: 'Enable debug mode' })
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
- description: 'Output format'
189
+ desc: 'Output format'
185
190
  })
186
191
 
187
192
  // Number
188
- .option({ long: 'port', type: 'number', default: 3000, description: 'Server port' })
193
+ .option({ long: 'port', type: 'number', args: 'required', default: 3000, desc: 'Server port' })
189
194
 
190
- // Array (can be specified multiple times)
191
- .option({ long: 'include', type: 'string[]', description: 'Files to include' })
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, description: 'Config file' })
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
- description: 'Date value',
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