goke 6.3.1 → 6.4.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 CHANGED
@@ -15,6 +15,7 @@
15
15
  - **Yet so powerful**. Enable features like default command, git-like subcommands, validation for required arguments and options, variadic arguments, dot-nested options, automated help message generation and so on.
16
16
  - **Space-separated subcommands**: Support multi-word commands like `mcp login`, `git remote add`.
17
17
  - **Schema-based type coercion**: Use Zod, Valibot, ArkType, or plain JSON Schema for automatic type coercion and TypeScript type inference. Description and default values are extracted from the schema automatically.
18
+ - **Injected execution context**: Prefer `{ console, process }` in actions and middleware for portable output, simpler tests, and alternate runtimes like JustBash.
18
19
  - **Type-safe middleware**: Register `.use()` callbacks that run before commands with full type inference from global options.
19
20
  - **Developer friendly**. Written in TypeScript.
20
21
 
@@ -43,7 +44,7 @@ cli.option(
43
44
  )
44
45
  cli.option('--name <name>', 'Provide your name')
45
46
 
46
- cli.command('lint [...files]', 'Lint files').action((files, options) => {
47
+ cli.command('lint [...files]', 'Lint files').action((files, options, { console }) => {
47
48
  console.log(files, options)
48
49
  })
49
50
 
@@ -52,7 +53,7 @@ cli
52
53
  .option('--minify', 'Minify output')
53
54
  .example('build src/index.ts')
54
55
  .example('build src/index.ts --minify')
55
- .action(async (entry, options) => { // options is type safe! no need to type it
56
+ .action(async (entry, options, { console }) => { // options is type safe! no need to type it
56
57
  console.log(entry, options)
57
58
  })
58
59
 
@@ -132,7 +133,7 @@ cli
132
133
  .option('--channel <name>', 'Target channel: stable, beta, alpha')
133
134
  .option('--notes-file <path>', 'Markdown file used as release notes')
134
135
  .option('--dry-run', 'Preview every step without publishing')
135
- .action((version, options) => {
136
+ .action((version, options, { console }) => {
136
137
  console.log('release', version, options)
137
138
  })
138
139
 
@@ -158,7 +159,7 @@ cli
158
159
  .option('--target <migration>', 'Apply up to a specific migration id')
159
160
  .option('--dry-run', 'Print plan only, do not execute SQL')
160
161
  .option('--verbose', 'Show each executed statement')
161
- .action((options) => {
162
+ .action((options, { console }) => {
162
163
  console.log('migrate', options)
163
164
  })
164
165
 
@@ -298,7 +299,7 @@ cli
298
299
  z.string().default('production').describe('Target environment'),
299
300
  )
300
301
  .option('--dry-run', 'Preview without deploying')
301
- .action((options) => {
302
+ .action((options, { console }) => {
302
303
  console.log(`Deploying to ${options.env}...`)
303
304
  })
304
305
 
@@ -306,22 +307,22 @@ cli
306
307
  cli
307
308
  .command('init', 'Initialize a new project')
308
309
  .option('--template <template>', 'Project template')
309
- .action((options) => {
310
+ .action((options, { console }) => {
310
311
  console.log('Initializing project...')
311
312
  })
312
313
 
313
- cli.command('login', 'Authenticate with the server').action(() => {
314
+ cli.command('login', 'Authenticate with the server').action((options, { console }) => {
314
315
  console.log('Opening browser for login...')
315
316
  })
316
317
 
317
- cli.command('logout', 'Clear saved credentials').action(() => {
318
+ cli.command('logout', 'Clear saved credentials').action((options, { console }) => {
318
319
  console.log('Logged out')
319
320
  })
320
321
 
321
322
  cli
322
323
  .command('status', 'Show deployment status')
323
324
  .option('--json', 'Output as JSON')
324
- .action((options) => {
325
+ .action((options, { console }) => {
325
326
  console.log('Fetching status...')
326
327
  })
327
328
 
@@ -329,7 +330,7 @@ cli
329
330
  .command('logs <deploymentId>', 'Stream logs for a deployment')
330
331
  .option('--follow', 'Follow log output')
331
332
  .option('--lines <n>', z.number().default(100).describe('Number of lines'))
332
- .action((deploymentId, options) => {
333
+ .action((deploymentId, options, { console }) => {
333
334
  console.log(`Streaming logs for ${deploymentId}...`)
334
335
  })
335
336
 
@@ -351,6 +352,8 @@ deploy --help # shows all commands
351
352
 
352
353
  Global options are defined on the CLI instance and apply to all commands. Use `.use()` to register middleware that runs before any command action — useful for reacting to global options like setting up logging, initializing state, or configuring services.
353
354
 
355
+ Prefer the injected `{ console, process }` argument over global `console` and `process.exit`. It keeps commands easier to test and lets the same command code run inside alternate runtimes like JustBash.
356
+
354
357
  Middleware runs in registration order, after option parsing and validation, but before the matched command's `.action()` callback.
355
358
 
356
359
  ```ts
@@ -362,24 +365,24 @@ const cli = goke('mycli')
362
365
  cli
363
366
  .option('--verbose', z.boolean().default(false).describe('Enable verbose logging'))
364
367
  .option('--api-url [url]', z.string().default('https://api.example.com').describe('API base URL'))
365
- .use((options) => {
368
+ .use((options, { console }) => {
366
369
  // options.verbose and options.apiUrl are fully typed here
367
370
  if (options.verbose) {
368
- process.env.LOG_LEVEL = 'debug'
371
+ console.log('verbose mode enabled')
369
372
  }
370
373
  })
371
374
 
372
375
  cli
373
376
  .command('deploy <env>', 'Deploy to an environment')
374
377
  .option('--dry-run', 'Preview without deploying')
375
- .action((env, options) => {
378
+ .action((env, options, { console }) => {
376
379
  // options includes both command options (dryRun) and global options (verbose, apiUrl)
377
380
  console.log(`Deploying to ${env} via ${options.apiUrl}`)
378
381
  })
379
382
 
380
383
  cli
381
384
  .command('status', 'Show deployment status')
382
- .action((options) => {
385
+ .action((options, { console }) => {
383
386
  console.log('Checking status...')
384
387
  })
385
388
 
@@ -392,14 +395,16 @@ Type safety is positional — each `.use()` callback only sees options declared
392
395
  ```ts
393
396
  cli
394
397
  .option('--verbose', z.boolean().default(false).describe('Verbose'))
395
- .use((options) => {
398
+ .use((options, { process }) => {
396
399
  options.verbose // boolean — typed
400
+ process.argv // string[] — typed
397
401
  options.port // TypeScript error — not declared yet
398
402
  })
399
403
  .option('--port <port>', z.number().describe('Port'))
400
- .use((options) => {
404
+ .use((options, { console }) => {
401
405
  options.verbose // boolean — still visible
402
406
  options.port // number — now visible
407
+ console.error('ready')
403
408
  })
404
409
  ```
405
410
 
@@ -408,9 +413,10 @@ Middleware supports async functions. If any middleware is async, the remaining m
408
413
  ```ts
409
414
  cli
410
415
  .option('--token <token>', z.string().describe('API token'))
411
- .use(async (options) => {
416
+ .use(async (options, { console }) => {
412
417
  const client = await connectToApi(options.token)
413
418
  globalState.client = client
419
+ console.log('connected')
414
420
  })
415
421
  ```
416
422
 
@@ -426,7 +432,7 @@ const cli = goke()
426
432
  cli
427
433
  .command('rm <dir>', 'Remove a dir')
428
434
  .option('-r, --recursive', 'Remove recursively')
429
- .action((dir, options) => {
435
+ .action((dir, options, { console }) => {
430
436
  console.log('remove ' + dir + (options.recursive ? ' recursively' : ''))
431
437
  })
432
438
 
@@ -444,17 +450,17 @@ import { goke } from 'goke'
444
450
 
445
451
  const cli = goke('mycli')
446
452
 
447
- cli.command('mcp login <url>', 'Login to MCP server').action((url) => {
453
+ cli.command('mcp login <url>', 'Login to MCP server').action((url, options, { console }) => {
448
454
  console.log('Logging in to', url)
449
455
  })
450
456
 
451
- cli.command('mcp logout', 'Logout from MCP server').action(() => {
457
+ cli.command('mcp logout', 'Logout from MCP server').action((options, { console }) => {
452
458
  console.log('Logged out')
453
459
  })
454
460
 
455
461
  cli
456
462
  .command('git remote add <name> <url>', 'Add a git remote')
457
- .action((name, url) => {
463
+ .action((name, url, options, { console }) => {
458
464
  console.log('Adding remote', name, url)
459
465
  })
460
466
 
@@ -479,7 +485,7 @@ cli
479
485
  .option('--workers <workers>', z.int().describe('Worker count'))
480
486
  .option('--tags <tag>', z.array(z.string()).describe('Tags (repeatable)'))
481
487
  .option('--verbose', 'Verbose output')
482
- .action((options) => {
488
+ .action((options, { console }) => {
483
489
  // options.port is number, options.host is string, etc.
484
490
  console.log(options)
485
491
  })
@@ -510,7 +516,7 @@ cli
510
516
  .option('--old-port <port>', z.number().meta({ deprecated: true, description: 'Use --port instead' }))
511
517
  // Current option: visible in help
512
518
  .option('--port <port>', z.number().describe('Port number'))
513
- .action((options) => {
519
+ .action((options, { console }) => {
514
520
  const port = options.port ?? options.oldPort
515
521
  console.log('Starting on port', port)
516
522
  })
@@ -525,7 +531,9 @@ When users run `--help`, deprecated options won't appear, but `--old-port 3000`
525
531
 
526
532
  When using brackets in command name, angled brackets indicate required command arguments, while square brackets indicate optional arguments.
527
533
 
528
- When using brackets in option name, angled brackets indicate that a string / number value is required, while square brackets indicate that the value can also be `true`.
534
+ When using brackets in option name, angled brackets indicate that a string / number value is required, while square brackets indicate that the value is optional.
535
+
536
+ **Optionality is determined solely by bracket syntax, not by the schema.** `[square brackets]` makes an option optional regardless of whether the schema is `z.string()` or `z.string().optional()`. The schema's `.optional()` is never consulted for this — it only affects type coercion. This means `z.string()` with `[--name]` is treated as optional: if the flag is omitted, `options.name` is `undefined` even though the schema has no `.optional()`.
529
537
 
530
538
  ### Negated Options
531
539
 
@@ -546,7 +554,7 @@ The last argument of a command can be variadic. To make an argument variadic you
546
554
  cli
547
555
  .command('build <entry> [...otherFiles]', 'Build your app')
548
556
  .option('--foo', 'Foo option')
549
- .action((entry, otherFiles, options) => {
557
+ .action((entry, otherFiles, options, { console }) => {
550
558
  console.log(entry)
551
559
  console.log(otherFiles)
552
560
  console.log(options)
@@ -604,7 +612,7 @@ cli
604
612
  .command('build', 'desc')
605
613
  .option('--env <env>', 'Set envs')
606
614
  .example('--env.API_SECRET xxx')
607
- .action((options) => {
615
+ .action((options, { console }) => {
608
616
  console.log(options)
609
617
  })
610
618
  ```
@@ -617,7 +625,7 @@ Register a command that will be used when no other command is matched.
617
625
  cli
618
626
  .command('[...files]', 'Build files')
619
627
  .option('--minimize', 'Minimize output')
620
- .action((files, options) => {
628
+ .action((files, options, { console }) => {
621
629
  console.log(files)
622
630
  console.log(options.minimize)
623
631
  })
@@ -632,11 +640,46 @@ try {
632
640
  cli.parse(process.argv, { run: false })
633
641
  await cli.runMatchedCommand()
634
642
  } catch (error) {
635
- console.error(error.stack)
643
+ const message = error instanceof Error ? error.stack : String(error)
644
+ process.stderr.write(String(message) + '\n')
636
645
  process.exit(1)
637
646
  }
638
647
  ```
639
648
 
649
+ ### Testing with mocked console and exit
650
+
651
+ Because goke derives its injected `{ console, process }` from the CLI's configured output streams and `exit` function, tests can override them directly and assert on the calls.
652
+
653
+ ```ts
654
+ import { describe, expect, test, vi } from 'vitest'
655
+ import { goke, GokeProcessExit } from 'goke'
656
+
657
+ describe('deploy command', () => {
658
+ test('writes output and exits with injected mocks', () => {
659
+ const stdout = { write: vi.fn<(data: string) => void>() }
660
+ const stderr = { write: vi.fn<(data: string) => void>() }
661
+ const exit = vi.fn<(code: number) => void>()
662
+
663
+ const cli = goke('acme', { stdout, stderr, exit })
664
+
665
+ cli
666
+ .command('deploy', 'Deploy the project')
667
+ .action((options, { console, process }) => {
668
+ console.log('deploying')
669
+ process.exit(2)
670
+ })
671
+
672
+ expect(() => {
673
+ cli.parse(['node', 'acme', 'deploy'], { run: true })
674
+ }).toThrow(GokeProcessExit)
675
+
676
+ expect(stdout.write).toHaveBeenCalledWith('deploying\n')
677
+ expect(exit).toHaveBeenCalledWith(2)
678
+ expect(stderr.write).not.toHaveBeenCalled()
679
+ })
680
+ })
681
+ ```
682
+
640
683
  ### With TypeScript
641
684
 
642
685
  ```ts
@@ -657,7 +700,7 @@ cli
657
700
  .command('serve <entry>', 'Start the app')
658
701
  .option('--port <port>', z.number().default(3000).describe('Port number'))
659
702
  .option('--watch', 'Watch files')
660
- .action((entry, options) => {
703
+ .action((entry, options, { console }) => {
661
704
  // entry: string
662
705
  // options.port: number
663
706
  // options.watch: boolean
@@ -675,6 +718,33 @@ import { openInBrowser } from 'goke'
675
718
  openInBrowser('https://example.com/dashboard')
676
719
  ```
677
720
 
721
+ ### Expose a goke CLI to JustBash
722
+
723
+ Use `cli.createJustBashCommand()` to expose a goke CLI as a single JustBash custom command. JustBash command names are single-token executables, but the goke CLI behind them can still use multi-word subcommands like `child commandwithspaces`.
724
+
725
+ ```ts
726
+ import { goke } from 'goke'
727
+ import { z } from 'zod'
728
+ import { Bash } from 'just-bash'
729
+
730
+ const cli = goke('parent')
731
+
732
+ cli
733
+ .command('child commandwithspaces', 'Run nested command')
734
+ .option('--name <name>', z.string().describe('Name'))
735
+ .action((options, { console }) => {
736
+ console.log(`hello ${options.name}`)
737
+ })
738
+
739
+ const bash = new Bash({
740
+ customCommands: [await cli.createJustBashCommand()],
741
+ })
742
+
743
+ await bash.exec('parent child commandwithspaces --name Tommy')
744
+ ```
745
+
746
+ Prefer the injected `{ console, process }` helpers in command implementations so the same command code works cleanly both in the regular CLI runtime and through the JustBash bridge.
747
+
678
748
  ## References
679
749
 
680
750
  ### CLI Instance
@@ -710,9 +780,9 @@ Add a global option. The second argument is either:
710
780
 
711
781
  #### cli.use(callback)
712
782
 
713
- - Type: `(callback: (options: Opts) => void | Promise<void>) => CLI`
783
+ - Type: `(callback: (options: Opts, { console, process }) => void | Promise<void>) => CLI`
714
784
 
715
- Register a middleware function that runs before the matched command action. Middleware runs in registration order, after option parsing and validation. The callback receives the parsed global options, typed according to all `.option()` calls that precede the `.use()` in the chain.
785
+ Register a middleware function that runs before the matched command action. Middleware runs in registration order, after option parsing and validation. The callback receives the parsed global options, typed according to all `.option()` calls that precede the `.use()` in the chain, plus an injected `{ console, process }` helper object.
716
786
 
717
787
  #### cli.parse(argv?)
718
788
 
@@ -766,6 +836,8 @@ Basically the same as `cli.option` but this adds the option to specific command.
766
836
 
767
837
  - Type: `(callback: ActionCallback) => Command`
768
838
 
839
+ Command callbacks receive positional args first, then parsed options, then an injected `{ console, process }` object. Prefer those injected helpers over global `console` and `process.exit` so commands stay easier to test and can run inside alternate runtimes like JustBash.
840
+
769
841
  #### command.alias(name)
770
842
 
771
843
  - Type: `(name: string) => Command`
@@ -796,7 +868,7 @@ cli.on('command:!', () => {
796
868
  })
797
869
 
798
870
  cli.on('command:*', () => {
799
- console.error('Invalid command: %s', cli.args.join(' '))
871
+ process.stderr.write(`Invalid command: ${cli.args.join(' ')}\n`)
800
872
  process.exit(1)
801
873
  })
802
874
  ```
@@ -1436,6 +1436,29 @@ describe('schema description and default extraction', () => {
1436
1436
  expect(stdout.text).not.toContain('--legacy');
1437
1437
  expect(stdout.text).not.toContain('Deprecated global');
1438
1438
  });
1439
+ test('hidden commands are not shown in help output', () => {
1440
+ const stdout = createTestOutputStream();
1441
+ const cli = goke('mycli', { stdout });
1442
+ cli.command('visible', 'A visible command');
1443
+ cli.command('secret', 'A hidden command').hidden();
1444
+ cli.help();
1445
+ cli.parse(['node', 'bin', '--help'], { run: false });
1446
+ expect(stdout.text).toContain('visible');
1447
+ expect(stdout.text).toContain('A visible command');
1448
+ expect(stdout.text).not.toContain('secret');
1449
+ expect(stdout.text).not.toContain('A hidden command');
1450
+ });
1451
+ test('hidden command still parses and runs', () => {
1452
+ const cli = gokeTestable('mycli');
1453
+ let result = {};
1454
+ cli
1455
+ .command('secret', 'A hidden command')
1456
+ .hidden()
1457
+ .option('--value <v>', z.string().describe('some value'))
1458
+ .action((options) => { result = options; });
1459
+ cli.parse(['node', 'bin', 'secret', '--value', 'hello']);
1460
+ expect(result.value).toBe('hello');
1461
+ });
1439
1462
  });
1440
1463
  describe('helpText()', () => {
1441
1464
  test('returns help string without printing', () => {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for injected execution context, clone isolation, and the JustBash bridge.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=just-bash.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"just-bash.test.d.ts","sourceRoot":"","sources":["../../src/__test__/just-bash.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Tests for injected execution context, clone isolation, and the JustBash bridge.
3
+ */
4
+ import { describe, expect, test } from 'vitest';
5
+ import { z } from 'zod';
6
+ import goke from '../index.js';
7
+ const ANSI_RE = /\x1B\[[0-9;]*m/g;
8
+ const stripAnsi = (text) => text.replace(ANSI_RE, '');
9
+ function createTestOutputStream() {
10
+ const lines = [];
11
+ return {
12
+ lines,
13
+ get text() { return stripAnsi(lines.join('')); },
14
+ write(data) { lines.push(data); },
15
+ };
16
+ }
17
+ function gokeTestable(name = '', options) {
18
+ return goke(name, {
19
+ ...options,
20
+ exit: () => { },
21
+ });
22
+ }
23
+ describe('injected execution context', () => {
24
+ test('command action receives injected console and process', () => {
25
+ const stdout = createTestOutputStream();
26
+ const cli = gokeTestable('mycli', { stdout });
27
+ let seenArgv;
28
+ cli
29
+ .command('status', 'Show status')
30
+ .action((options, { console, process }) => {
31
+ console.log('ready');
32
+ seenArgv = process.argv;
33
+ });
34
+ cli.parse(['node', 'bin', 'status'], { run: true });
35
+ expect(stdout.text).toBe('ready\n');
36
+ expect(seenArgv).toEqual(['node', 'bin', 'status']);
37
+ });
38
+ test('middleware receives injected console and process', () => {
39
+ const stdout = createTestOutputStream();
40
+ const cli = gokeTestable('mycli', { stdout });
41
+ let seenArgv;
42
+ cli
43
+ .use((options, { console, process }) => {
44
+ console.log('middleware');
45
+ seenArgv = process.argv;
46
+ })
47
+ .command('build', 'Build')
48
+ .action(() => { });
49
+ cli.parse(['node', 'bin', 'build'], { run: true });
50
+ expect(stdout.text).toBe('middleware\n');
51
+ expect(seenArgv).toEqual(['node', 'bin', 'build']);
52
+ });
53
+ });
54
+ describe('clone', () => {
55
+ test('clone creates isolated parse state', () => {
56
+ const cli = gokeTestable('mycli');
57
+ cli.command('build', 'Build').action(() => { });
58
+ const cloned = cli.clone({ exit: () => { } });
59
+ cloned.parse(['node', 'bin', 'build'], { run: false });
60
+ expect(cloned).not.toBe(cli);
61
+ expect(cloned.matchedCommandName).toBe('build');
62
+ expect(cli.matchedCommandName).toBeUndefined();
63
+ });
64
+ });
65
+ describe('createJustBashCommand', () => {
66
+ test('runs multi-word goke subcommands through one just-bash command', async () => {
67
+ const cli = gokeTestable('parent');
68
+ cli
69
+ .command('child commandwithspaces', 'Run nested command')
70
+ .option('--name <name>', z.string().describe('Name'))
71
+ .action((options, { console }) => {
72
+ console.log(`hello ${options.name}`);
73
+ });
74
+ const customCommand = await cli.createJustBashCommand();
75
+ const result = await customCommand.execute(['child', 'commandwithspaces', '--name', 'Tommy']);
76
+ expect(result).toEqual({
77
+ stdout: 'hello Tommy\n',
78
+ stderr: '',
79
+ exitCode: 0,
80
+ });
81
+ });
82
+ test('maps injected process.exit to a command exit code', async () => {
83
+ const cli = gokeTestable('parent');
84
+ cli
85
+ .command('fail', 'Exit with custom code')
86
+ .action((options, { process }) => {
87
+ process.exit(7);
88
+ });
89
+ const customCommand = await cli.createJustBashCommand();
90
+ const result = await customCommand.execute(['fail']);
91
+ expect(result).toEqual({
92
+ stdout: '',
93
+ stderr: '',
94
+ exitCode: 7,
95
+ });
96
+ });
97
+ });
@@ -73,9 +73,12 @@ describe('type-level: middleware use() callback inference', () => {
73
73
  goke('test')
74
74
  .option('--port <port>', schema1)
75
75
  .option('--host <host>', schema2)
76
- .use((options) => {
76
+ .use((options, { console, process }) => {
77
77
  expectTypeOf(options.port).toEqualTypeOf();
78
78
  expectTypeOf(options.host).toEqualTypeOf();
79
+ expectTypeOf(process.argv).toEqualTypeOf();
80
+ expectTypeOf(process.stdout.write).toEqualTypeOf();
81
+ expectTypeOf(console.log).toBeFunction();
79
82
  });
80
83
  });
81
84
  test('use() only sees options declared before it', () => {
@@ -83,23 +86,26 @@ describe('type-level: middleware use() callback inference', () => {
83
86
  const schema2 = {};
84
87
  goke('test')
85
88
  .option('--verbose', schema1)
86
- .use((options) => {
89
+ .use((options, { process }) => {
87
90
  expectTypeOf(options.verbose).toEqualTypeOf();
91
+ expectTypeOf(process.exit).toEqualTypeOf();
88
92
  // @ts-expect-error port is not declared yet
89
93
  options.port;
90
94
  })
91
95
  .option('--port <port>', schema2)
92
- .use((options) => {
96
+ .use((options, { console }) => {
93
97
  // Now both are visible
94
98
  expectTypeOf(options.verbose).toEqualTypeOf();
95
99
  expectTypeOf(options.port).toEqualTypeOf();
100
+ expectTypeOf(console.error).toBeFunction();
96
101
  });
97
102
  });
98
103
  test('accessing a non-existent option is a type error', () => {
99
104
  const schema = {};
100
105
  goke('test')
101
106
  .option('--port <port>', schema)
102
- .use((options) => {
107
+ .use((options, { process }) => {
108
+ expectTypeOf(process.stderr.write).toEqualTypeOf();
103
109
  // @ts-expect-error nonExistent was never defined
104
110
  options.nonExistent;
105
111
  });
package/dist/goke.d.ts CHANGED
@@ -9,8 +9,8 @@
9
9
  * - createConsole: factory for console-like objects from output streams
10
10
  * - Utility functions: string helpers, bracket parsing, dot-prop access
11
11
  */
12
- import { EventEmitter } from 'events';
13
12
  import type { StandardJSONSchemaV1 } from "./coerce.js";
13
+ import { EventEmitter, openInBrowser } from '#runtime';
14
14
  declare class Option {
15
15
  rawName: string;
16
16
  /** Option name */
@@ -34,6 +34,7 @@ declare class Option {
34
34
  * When a schema is provided, description and default are extracted from the JSON Schema.
35
35
  */
36
36
  constructor(rawName: string, descriptionOrSchema?: string | StandardJSONSchemaV1);
37
+ clone(): Option;
37
38
  }
38
39
  /**
39
40
  * Converts a kebab-case string to camelCase at the type level.
@@ -101,6 +102,7 @@ declare class Command {
101
102
  examples: CommandExample[];
102
103
  helpCallback?: HelpCallback;
103
104
  globalCommand?: GlobalCommand;
105
+ _hidden?: boolean;
104
106
  constructor(rawName: string, description: string, config: CommandConfig | undefined, cli: Goke<any>);
105
107
  usage(text: string): this;
106
108
  allowUnknownOptions(): this;
@@ -128,6 +130,7 @@ declare class Command {
128
130
  };
129
131
  option(rawName: string, descriptionOrSchema?: string | StandardJSONSchemaV1): this;
130
132
  alias(name: string): this;
133
+ hidden(): this;
131
134
  action(callback: (...args: any[]) => any): this;
132
135
  isMatched(args: string[]): {
133
136
  matched: boolean;
@@ -177,6 +180,22 @@ interface GokeOutputStream {
177
180
  interface GokeConsole {
178
181
  log(...args: unknown[]): void;
179
182
  error(...args: unknown[]): void;
183
+ warn(...args: unknown[]): void;
184
+ info(...args: unknown[]): void;
185
+ }
186
+ interface GokeProcess {
187
+ argv: string[];
188
+ stdout: GokeOutputStream;
189
+ stderr: GokeOutputStream;
190
+ exit(code: number): never | void;
191
+ }
192
+ interface GokeExecutionContext {
193
+ console: GokeConsole;
194
+ process: GokeProcess;
195
+ }
196
+ declare class GokeProcessExit extends Error {
197
+ code: number;
198
+ constructor(code: number);
180
199
  }
181
200
  /**
182
201
  * Options for configuring a Goke CLI instance.
@@ -217,7 +236,7 @@ declare class Goke<Opts extends Record<string, any> = {}> extends EventEmitter {
217
236
  commands: Command[];
218
237
  /** Middleware functions that run before the matched command action, in registration order */
219
238
  middlewares: Array<{
220
- action: (options: any) => void | Promise<void>;
239
+ action: (options: any, context: GokeExecutionContext) => void | Promise<void>;
221
240
  }>;
222
241
  globalCommand: GlobalCommand;
223
242
  matchedCommand?: Command;
@@ -251,6 +270,11 @@ declare class Goke<Opts extends Record<string, any> = {}> extends EventEmitter {
251
270
  * @param options Configuration for stdout, stderr, and argv
252
271
  */
253
272
  constructor(name?: string, options?: GokeOptions);
273
+ clone(options?: GokeOptions): Goke<Opts>;
274
+ private createExecutionContext;
275
+ createJustBashCommand(options?: {
276
+ name?: string;
277
+ }): Promise<import("./just-bash.js").JustBashCommand>;
254
278
  /**
255
279
  * Add a global usage text.
256
280
  *
@@ -279,20 +303,21 @@ declare class Goke<Opts extends Record<string, any> = {}> extends EventEmitter {
279
303
  * options (e.g. setting up logging, initializing state).
280
304
  *
281
305
  * The callback receives the parsed options object, typed according to all
282
- * `.option()` calls that precede this `.use()` in the chain.
306
+ * `.option()` calls that precede this `.use()` in the chain, plus an injected
307
+ * execution context with `{ console, process }` for portable output and exits.
283
308
  *
284
309
  * @example
285
310
  * ```ts
286
311
  * cli
287
312
  * .option('--verbose', z.boolean().default(false).describe('Verbose'))
288
- * .use((options) => {
313
+ * .use((options, { console }) => {
289
314
  * if (options.verbose) {
290
- * process.env.LOG_LEVEL = 'debug'
315
+ * console.log('verbose mode enabled')
291
316
  * }
292
317
  * })
293
318
  * ```
294
319
  */
295
- use(callback: (options: Opts) => void | Promise<void>): this;
320
+ use(callback: (options: Opts, context: GokeExecutionContext) => void | Promise<void>): this;
296
321
  /**
297
322
  * Show help message when `-h, --help` flags appear.
298
323
  *
@@ -349,12 +374,7 @@ declare class Goke<Opts extends Record<string, any> = {}> extends EventEmitter {
349
374
  private mri;
350
375
  runMatchedCommand(): any;
351
376
  }
352
- /**
353
- * Open a URL in the default browser.
354
- * In non-TTY environments (CI, piped output, agents), prints the URL to stdout instead.
355
- */
356
- declare function openInBrowser(url: string): void;
357
- export type { GokeOutputStream, GokeConsole, GokeOptions };
358
- export { createConsole, Command, openInBrowser };
377
+ export type { GokeOutputStream, GokeConsole, GokeOptions, GokeProcess, GokeExecutionContext };
378
+ export { createConsole, Command, GokeProcessExit, openInBrowser };
359
379
  export default Goke;
360
380
  //# sourceMappingURL=goke.d.ts.map