commander-wizard 0.0.1 → 0.0.2

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.
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;
@@ -345,6 +359,12 @@ function unwrap(value) {
345
359
  }
346
360
  return value;
347
361
  }
362
+ /** Exits through Commander's own channel so .exitOverride() stays authoritative. */
363
+ function exitVia(program, exitCode, code, message) {
364
+ const exit = Reflect.get(program, '_exit');
365
+ exit.call(program, exitCode, code, message);
366
+ throw new Error('unreachable: _exit exits, or an exitOverride callback throws');
367
+ }
348
368
  /** POSIX shell quoting. Windows shells are not supported. */
349
369
  function shellQuote(value) {
350
370
  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.2",
4
4
  "description": "Interactive wizard mode for commander CLIs — collect, review, and rerun command inputs",
5
5
  "keywords": [
6
6
  "commander",