commander-wizard 0.0.1 → 0.0.3

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 CHANGED
@@ -17,7 +17,7 @@ Save as `cli.mjs`:
17
17
 
18
18
  ```js
19
19
  import { Command } from 'commander';
20
- import { addWizard, WizardCancelledError } from 'commander-wizard';
20
+ import { addWizard } from 'commander-wizard';
21
21
 
22
22
  const program = new Command('deploy-cli');
23
23
  const deploy = program.command('deploy')
@@ -30,11 +30,7 @@ const deploy = program.command('deploy')
30
30
  // Add your commands and options before calling addWizard.
31
31
  addWizard(program, { invocation: ['node', 'cli.mjs'] });
32
32
 
33
- try {
34
- await program.parseAsync();
35
- } catch (error) {
36
- if (!(error instanceof WizardCancelledError)) throw error;
37
- }
33
+ await program.parseAsync();
38
34
  ```
39
35
 
40
36
  ```sh
@@ -68,9 +64,10 @@ After confirmation, Commander applies parsers, requirements, conflicts, and
68
64
  implications before running your action. Restart the wizard to correct invalid
69
65
  inputs; custom parsers do not run during prompting.
70
66
 
71
- Declining or pressing Ctrl-C throws `WizardCancelledError` without running your
72
- hooks or action. Catch it as in the example. For Commander errors, configure
73
- `exitOverride()` if you need to catch them instead of exiting.
67
+ Declining or pressing Ctrl-C exits cleanly with code 0 without running your
68
+ hooks or action. Wizard failures print like Commander errors and exit 1. For
69
+ tests and embedding, configure `exitOverride()` to catch everything instead
70
+ of exiting — the cancellation code is `commander-wizard.cancelled`.
74
71
 
75
72
  You keep Commander's parsing and validation for ordinary invocations. Your
76
73
  action receives no wizard-trigger option after a wizard run.
@@ -129,8 +126,9 @@ a numeric default alone does not perform that conversion.
129
126
 
130
127
  ## Compatibility limits
131
128
 
132
- Use root-only or nested leaf actions with global scalar options, short flags,
133
- positive/negative boolean pairs, positional arguments, choices, and leaf variadics.
129
+ Use global scalar options, short flags, positive/negative boolean pairs,
130
+ positional arguments, choices, and leaf variadics. Actions are optional:
131
+ commands that read `.opts()` after parsing work unchanged in wizard mode.
134
132
 
135
133
  In wizard mode, put the full command path before flags:
136
134
  `cli group command --wizard --flag=value`. Keep short flags separate; avoid `-abc` and
@@ -138,8 +136,8 @@ In wizard mode, put the full command path before flags:
138
136
 
139
137
  You cannot use these Commander features in wizard mode:
140
138
 
141
- - Executable subcommands, legacy command listeners, actions on commands with
142
- children, or implicit/default subcommands.
139
+ - Executable subcommands, implicit/default subcommands, or targeting commands
140
+ with children.
143
141
  - Ancestor positional arguments or variadic options, positional/pass-through
144
142
  option modes, or shadowed global option names/flags.
145
143
  - Environment-bound options, optional option values (`--color [value]`), presets,
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { addWizard, WizardCancelledError } from './wizard.js';
1
+ export { addWizard } from './wizard.js';
2
2
  export type { WizardOptions } from './wizard.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { addWizard, WizardCancelledError } from './wizard.js';
1
+ export { addWizard } from './wizard.js';
package/dist/wizard.d.ts CHANGED
@@ -1,7 +1,4 @@
1
1
  import type { Argument, Command, Option } from 'commander';
2
- export declare class WizardCancelledError extends Error {
3
- constructor();
4
- }
5
2
  export interface WizardOptions {
6
3
  /** Commander boolean flag declaration. Defaults to --wizard. */
7
4
  flags?: string;
package/dist/wizard.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { inspect } from 'node:util';
3
- export class WizardCancelledError extends Error {
3
+ class WizardError extends Error {
4
+ }
5
+ /** Aborts collection on cancel; surfaced to callers through Commander's exit channel. */
6
+ class WizardCancelledError extends Error {
4
7
  constructor() { super('Wizard cancelled.'); this.name = 'WizardCancelledError'; }
5
8
  }
6
9
  const installed = new WeakSet();
7
- const fail = (message) => { throw new Error(`Wizard: ${message}`); };
10
+ const fail = (message) => { throw new WizardError(`Wizard: ${message}`); };
8
11
  /** Decorate an already-configured program. No global/prototype patching; use parseAsync for wizard mode. */
9
12
  export function addWizard(program, config = {}) {
10
13
  if (installed.has(program))
@@ -37,29 +40,40 @@ export function addWizard(program, config = {}) {
37
40
  cmd.on(`option:${flagOption.name()}`, () => fail('use parseAsync() with an explicit command path and unbundled flags for wizard mode.'));
38
41
  }
39
42
  program.parseAsync = async function (argv, options) {
40
- const args = userArgs(argv, options?.from);
41
- if (!requested(args, markers))
42
- return await parseAsync.call(this, argv, options);
43
- if (options?.from === 'electron' || (!argv && process.versions.electron))
44
- fail('Electron wizard invocations are unsupported; pass explicit user arguments.');
45
- let input;
46
43
  try {
47
- input = scan(program, args, markers);
44
+ const args = userArgs(argv, options?.from);
45
+ if (!requested(args, markers))
46
+ return await parseAsync.call(this, argv, options);
47
+ if (options?.from === 'electron' || (!argv && process.versions.electron))
48
+ fail('Electron wizard invocations are unsupported; pass explicit user arguments.');
49
+ let input;
50
+ try {
51
+ input = scan(program, args, markers);
52
+ }
53
+ catch {
54
+ // Commander can distinguish reserved text used as data in grammars we do not support.
55
+ // An actual wizard option is stopped by the option listener above.
56
+ return await parseAsync.call(this, argv, options);
57
+ }
58
+ // A marker consumed as an option value is data, not a wizard request.
59
+ if (!input.wizard)
60
+ return await parseAsync.call(this, argv, options);
61
+ checkLayout(input.chain, wizardKey);
62
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
63
+ fail('interactive input requires a TTY.');
64
+ const completed = await collect(input, config, wizardKey);
65
+ // Commander alone owns coercion, validation, hooks, and action dispatch.
66
+ return await parseAsync.call(this, completed, { from: 'user' });
48
67
  }
49
- catch {
50
- // Commander can distinguish reserved text used as data in grammars we do not support.
51
- // An actual wizard option is stopped by the option listener above.
52
- return await parseAsync.call(this, argv, options);
68
+ catch (error) {
69
+ // Route our failures through Commander's own channel: stock CLI behavior by default,
70
+ // catchable via .exitOverride() for tests and embedding no library-specific catches.
71
+ if (error instanceof WizardCancelledError)
72
+ exitVia(this, 0, 'commander-wizard.cancelled', 'Wizard cancelled.');
73
+ if (error instanceof WizardError)
74
+ this.error(error.message);
75
+ throw error;
53
76
  }
54
- // A marker consumed as an option value is data, not a wizard request.
55
- if (!input.wizard)
56
- return await parseAsync.call(this, argv, options);
57
- checkLayout(input.chain, wizardKey);
58
- if (!process.stdin.isTTY || !process.stdout.isTTY)
59
- fail('interactive input requires a TTY.');
60
- const completed = await collect(input, config, wizardKey);
61
- // Commander alone owns coercion, validation, hooks, and action dispatch.
62
- return await parseAsync.call(this, completed, { from: 'user' });
63
77
  };
64
78
  installed.add(program);
65
79
  return program;
@@ -155,8 +169,9 @@ function checkLayout(chain, wizardKey) {
155
169
  }
156
170
  }
157
171
  const leaf = chain.at(-1);
158
- if (leaf.commands.length || !Reflect.get(leaf, '_actionHandler'))
159
- fail('select an explicit in-process action command. Legacy listeners are unsupported.');
172
+ // No action requirement: parse-then-.opts() CLIs work unchanged — the re-parse validates and populates.
173
+ if (leaf.commands.length)
174
+ fail('select an explicit leaf command; commands with children are ambiguous in wizard mode.');
160
175
  }
161
176
  function defaults(owner, config) {
162
177
  if (owner.defaultValue === undefined)
@@ -309,7 +324,8 @@ async function collect(input, config, wizardKey) {
309
324
  summary.push(`${arg.name()}: ${inspect(arg.variadic ? values : values[0])}`);
310
325
  }
311
326
  positional.push(...input.positionals.slice(cursor)); // let Commander report excess arguments
312
- argv.push('--', ...positional);
327
+ if (positional.length)
328
+ argv.push('--', ...positional);
313
329
  const invocation = config.invocation ?? [process.execPath, ...process.execArgv, process.argv[1] ?? fail('provide invocation.')];
314
330
  const tokens = [...invocation, ...argv];
315
331
  if (tokens.some(token => token.includes('\0')))
@@ -345,6 +361,12 @@ function unwrap(value) {
345
361
  }
346
362
  return value;
347
363
  }
364
+ /** Exits through Commander's own channel so .exitOverride() stays authoritative. */
365
+ function exitVia(program, exitCode, code, message) {
366
+ const exit = Reflect.get(program, '_exit');
367
+ exit.call(program, exitCode, code, message);
368
+ throw new Error('unreachable: _exit exits, or an exitOverride callback throws');
369
+ }
348
370
  /** POSIX shell quoting. Windows shells are not supported. */
349
371
  function shellQuote(value) {
350
372
  return /^[\w.,:/@%+=-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commander-wizard",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Interactive wizard mode for commander CLIs — collect, review, and rerun command inputs",
5
5
  "keywords": [
6
6
  "commander",