goke 6.1.2 → 6.2.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 +298 -3
- package/dist/__test__/index.test.js +404 -8
- package/dist/coerce.d.ts +15 -19
- package/dist/coerce.d.ts.map +1 -1
- package/dist/coerce.js +39 -33
- package/dist/goke.d.ts +25 -2
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +199 -92
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/__test__/index.test.ts +483 -9
- package/src/coerce.ts +44 -35
- package/src/goke.ts +221 -101
- package/src/index.ts +1 -1
|
@@ -16,6 +16,119 @@ function createTestOutputStream() {
|
|
|
16
16
|
write(data) { lines.push(data); },
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Helper: creates a goke instance with exit overridden to a no-op.
|
|
21
|
+
* This prevents process.exit(1) from killing the test runner while
|
|
22
|
+
* still allowing the original error to propagate (the framework
|
|
23
|
+
* re-throws after calling exit when exit doesn't halt execution).
|
|
24
|
+
*
|
|
25
|
+
* Tests can still use .toThrow() to assert CLI errors normally.
|
|
26
|
+
*/
|
|
27
|
+
function gokeTestable(name = '', options) {
|
|
28
|
+
return goke(name, {
|
|
29
|
+
...options,
|
|
30
|
+
exit: () => { },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Strip stack trace lines for stable snapshots.
|
|
35
|
+
* Keeps the error message and help hint, removes all " at ..." lines
|
|
36
|
+
* and the blank line before them, since those contain machine-specific paths.
|
|
37
|
+
*/
|
|
38
|
+
function stripStackTrace(text) {
|
|
39
|
+
return text
|
|
40
|
+
.split('\n')
|
|
41
|
+
.filter(line => !line.match(/^\s+at /))
|
|
42
|
+
.join('\n')
|
|
43
|
+
.replace(/\n{2,}/g, '\n')
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
describe('error formatting', () => {
|
|
47
|
+
test('unknown option prints formatted error to stderr', () => {
|
|
48
|
+
const stderr = createTestOutputStream();
|
|
49
|
+
const cli = goke('mycli', { stderr, exit: () => { } });
|
|
50
|
+
cli
|
|
51
|
+
.command('build', 'Build your app')
|
|
52
|
+
.option('--port <port>', 'Port')
|
|
53
|
+
.action(() => { });
|
|
54
|
+
try {
|
|
55
|
+
cli.parse('node bin build --unknown'.split(' '));
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
expect(stripStackTrace(stderr.text)).toMatchInlineSnapshot(`"error: Unknown option \`--unknown\`"`);
|
|
59
|
+
});
|
|
60
|
+
test('missing required option value prints formatted error to stderr', () => {
|
|
61
|
+
const stderr = createTestOutputStream();
|
|
62
|
+
const cli = goke('mycli', { stderr, exit: () => { } });
|
|
63
|
+
cli
|
|
64
|
+
.command('serve', 'Start server')
|
|
65
|
+
.option('--port <port>', 'Port')
|
|
66
|
+
.action(() => { });
|
|
67
|
+
try {
|
|
68
|
+
cli.parse('node bin serve --port'.split(' '));
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
expect(stripStackTrace(stderr.text)).toMatchInlineSnapshot(`"error: option \`--port <port>\` value is missing"`);
|
|
72
|
+
});
|
|
73
|
+
test('schema coercion error prints formatted error to stderr', () => {
|
|
74
|
+
const stderr = createTestOutputStream();
|
|
75
|
+
const cli = goke('mycli', { stderr, exit: () => { } });
|
|
76
|
+
cli.option('--port <port>', z.number().describe('Port'));
|
|
77
|
+
try {
|
|
78
|
+
cli.parse('node bin --port abc'.split(' '));
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
expect(stripStackTrace(stderr.text)).toMatchInlineSnapshot(`"error: Invalid value for --port: expected number, got "abc""`);
|
|
82
|
+
});
|
|
83
|
+
test('error includes help hint when help is enabled', () => {
|
|
84
|
+
const stderr = createTestOutputStream();
|
|
85
|
+
const cli = goke('mycli', { stderr, exit: () => { } });
|
|
86
|
+
cli.help();
|
|
87
|
+
cli
|
|
88
|
+
.command('serve', 'Start server')
|
|
89
|
+
.option('--port <port>', 'Port')
|
|
90
|
+
.action(() => { });
|
|
91
|
+
try {
|
|
92
|
+
cli.parse('node bin serve --port'.split(' '));
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
expect(stripStackTrace(stderr.text)).toMatchInlineSnapshot(`
|
|
96
|
+
"error: option \`--port <port>\` value is missing
|
|
97
|
+
Run "mycli serve --help" for usage information."
|
|
98
|
+
`);
|
|
99
|
+
});
|
|
100
|
+
test('async action error prints formatted error to stderr', async () => {
|
|
101
|
+
const stderr = createTestOutputStream();
|
|
102
|
+
let exitCode;
|
|
103
|
+
const cli = goke('mycli', { stderr, exit: (code) => { exitCode = code; } });
|
|
104
|
+
cli
|
|
105
|
+
.command('deploy', 'Deploy app')
|
|
106
|
+
.action(async () => {
|
|
107
|
+
throw new Error('connection refused');
|
|
108
|
+
});
|
|
109
|
+
cli.parse('node bin deploy'.split(' '));
|
|
110
|
+
// Wait for the async rejection to be handled
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
112
|
+
expect(exitCode).toBe(1);
|
|
113
|
+
expect(stripStackTrace(stderr.text)).toMatchInlineSnapshot(`"error: connection refused"`);
|
|
114
|
+
});
|
|
115
|
+
test('error output includes stack trace', () => {
|
|
116
|
+
const stderr = createTestOutputStream();
|
|
117
|
+
const cli = goke('mycli', { stderr, exit: () => { } });
|
|
118
|
+
cli
|
|
119
|
+
.command('build', 'Build app')
|
|
120
|
+
.action(() => { });
|
|
121
|
+
try {
|
|
122
|
+
cli.parse('node bin build --unknown'.split(' '));
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
// Verify that stderr contains "error:" prefix and a stack trace with "at" lines
|
|
126
|
+
const text = stderr.text;
|
|
127
|
+
expect(text).toContain('error:');
|
|
128
|
+
expect(text).toContain('Unknown option `--unknown`');
|
|
129
|
+
expect(text).toMatch(/at /);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
19
132
|
test('double dashes', () => {
|
|
20
133
|
const cli = goke();
|
|
21
134
|
const { args, options } = cli.parse([
|
|
@@ -73,7 +186,7 @@ describe('schema-based options', () => {
|
|
|
73
186
|
expect(options.items).toEqual([1, 2, 3]);
|
|
74
187
|
});
|
|
75
188
|
test('schema throws on invalid number', () => {
|
|
76
|
-
const cli =
|
|
189
|
+
const cli = gokeTestable();
|
|
77
190
|
cli.option('--port <port>', z.number().describe('Port number'));
|
|
78
191
|
expect(() => cli.parse('node bin --port abc'.split(' ')))
|
|
79
192
|
.toThrow('expected number, got "abc"');
|
|
@@ -253,7 +366,7 @@ describe('typical CLI usage examples', () => {
|
|
|
253
366
|
});
|
|
254
367
|
describe('regression: oracle-found issues', () => {
|
|
255
368
|
test('required option with schema still throws when value missing', () => {
|
|
256
|
-
const cli =
|
|
369
|
+
const cli = gokeTestable();
|
|
257
370
|
let actionCalled = false;
|
|
258
371
|
cli
|
|
259
372
|
.command('serve', 'Start server')
|
|
@@ -266,13 +379,13 @@ describe('regression: oracle-found issues', () => {
|
|
|
266
379
|
expect(actionCalled).toBe(false);
|
|
267
380
|
});
|
|
268
381
|
test('repeated flags with non-array schema throws', () => {
|
|
269
|
-
const cli =
|
|
382
|
+
const cli = gokeTestable();
|
|
270
383
|
cli.option('--tag <tag>', z.string().describe('Tags'));
|
|
271
384
|
expect(() => cli.parse('node bin --tag foo --tag bar'.split(' ')))
|
|
272
385
|
.toThrow('does not accept multiple values');
|
|
273
386
|
});
|
|
274
387
|
test('repeated flags with number schema throws', () => {
|
|
275
|
-
const cli =
|
|
388
|
+
const cli = gokeTestable();
|
|
276
389
|
cli.option('--id <id>', z.number().describe('ID'));
|
|
277
390
|
expect(() => cli.parse('node bin --id 1 --id 2'.split(' ')))
|
|
278
391
|
.toThrow('does not accept multiple values');
|
|
@@ -398,7 +511,7 @@ describe('edge cases: boolean flags + schema', () => {
|
|
|
398
511
|
expect(opts2.flag).toBe(false);
|
|
399
512
|
});
|
|
400
513
|
test('invalid boolean string with boolean schema throws', () => {
|
|
401
|
-
const cli =
|
|
514
|
+
const cli = gokeTestable();
|
|
402
515
|
cli.option('--flag <flag>', z.boolean().describe('A flag'));
|
|
403
516
|
expect(() => cli.parse('node bin --flag yes'.split(' ')))
|
|
404
517
|
.toThrow('expected true or false');
|
|
@@ -435,7 +548,7 @@ describe('edge cases: empty string values', () => {
|
|
|
435
548
|
expect(options.name).toBe('');
|
|
436
549
|
});
|
|
437
550
|
test('empty string with number schema throws', () => {
|
|
438
|
-
const cli =
|
|
551
|
+
const cli = gokeTestable();
|
|
439
552
|
cli.option('--port <port>', z.number().describe('Port'));
|
|
440
553
|
expect(() => cli.parse(['node', 'bin', '--port', '']))
|
|
441
554
|
.toThrow('expected number, got empty string');
|
|
@@ -482,14 +595,14 @@ describe('edge cases: short alias + schema', () => {
|
|
|
482
595
|
expect(options.p).toBe(8080);
|
|
483
596
|
});
|
|
484
597
|
test('short alias repeated with non-array schema throws', () => {
|
|
485
|
-
const cli =
|
|
598
|
+
const cli = gokeTestable();
|
|
486
599
|
cli.option('-p, --port <port>', z.number().describe('Port'));
|
|
487
600
|
expect(() => cli.parse('node bin -p 3000 -p 4000'.split(' ')))
|
|
488
601
|
.toThrow('does not accept multiple values');
|
|
489
602
|
});
|
|
490
603
|
});
|
|
491
604
|
test('throw on unknown options', () => {
|
|
492
|
-
const cli =
|
|
605
|
+
const cli = gokeTestable();
|
|
493
606
|
cli
|
|
494
607
|
.command('build [entry]', 'Build your app')
|
|
495
608
|
.option('--foo-bar', 'foo bar')
|
|
@@ -623,23 +736,32 @@ describe('space-separated subcommands', () => {
|
|
|
623
736
|
expect(stripAnsi(output)).toMatchInlineSnapshot(`
|
|
624
737
|
"mycli
|
|
625
738
|
|
|
739
|
+
|
|
626
740
|
Usage:
|
|
627
741
|
$ mycli <command> [options]
|
|
628
742
|
|
|
743
|
+
|
|
629
744
|
Commands:
|
|
630
745
|
mcp login <url> Login to MCP server
|
|
631
746
|
|
|
747
|
+
|
|
632
748
|
mcp logout Logout from MCP server
|
|
633
749
|
|
|
750
|
+
|
|
634
751
|
mcp status Show connection status
|
|
635
752
|
|
|
753
|
+
|
|
636
754
|
git remote add <name> <url> Add a git remote
|
|
637
755
|
|
|
756
|
+
|
|
638
757
|
git remote remove <name> Remove a git remote
|
|
639
758
|
|
|
759
|
+
|
|
640
760
|
build Build the project
|
|
761
|
+
|
|
641
762
|
--watch Watch mode
|
|
642
763
|
|
|
764
|
+
|
|
643
765
|
Options:
|
|
644
766
|
-h, --help Display this message
|
|
645
767
|
"
|
|
@@ -679,6 +801,34 @@ describe('space-separated subcommands', () => {
|
|
|
679
801
|
// Should not show filtered help since "foo" is not a prefix of any command
|
|
680
802
|
expect(stripAnsi(output)).not.toContain('Available "foo" commands');
|
|
681
803
|
});
|
|
804
|
+
test('unknown command without prefix outputs root help', () => {
|
|
805
|
+
let output = '';
|
|
806
|
+
const cli = goke('mycli', {
|
|
807
|
+
stdout: { write(data) { output += data; } },
|
|
808
|
+
});
|
|
809
|
+
cli.command('mcp login', 'Login to MCP');
|
|
810
|
+
cli.command('build', 'Build project');
|
|
811
|
+
cli.help();
|
|
812
|
+
// User types an unknown command that does not match any prefix group
|
|
813
|
+
cli.parse(['node', 'bin', 'something'], { run: true });
|
|
814
|
+
expect(cli.matchedCommand).toBeUndefined();
|
|
815
|
+
expect(stripAnsi(output)).toContain('Usage:');
|
|
816
|
+
expect(stripAnsi(output)).toContain('$ mycli <command> [options]');
|
|
817
|
+
expect(stripAnsi(output)).toContain('mcp login');
|
|
818
|
+
expect(stripAnsi(output)).toContain('build');
|
|
819
|
+
});
|
|
820
|
+
test('no args without default command outputs root help', () => {
|
|
821
|
+
const stdout = createTestOutputStream();
|
|
822
|
+
const cli = goke('mycli', { stdout });
|
|
823
|
+
cli.command('mcp login', 'Login to MCP');
|
|
824
|
+
cli.command('build', 'Build project');
|
|
825
|
+
cli.help();
|
|
826
|
+
cli.parse(['node', 'bin'], { run: true });
|
|
827
|
+
expect(stdout.text).toContain('Usage:');
|
|
828
|
+
expect(stdout.text).toContain('$ mycli <command> [options]');
|
|
829
|
+
expect(stdout.text).toContain('mcp login');
|
|
830
|
+
expect(stdout.text).toContain('build');
|
|
831
|
+
});
|
|
682
832
|
test('prefix --help shows filtered help for matching command group', () => {
|
|
683
833
|
let output = '';
|
|
684
834
|
const cli = goke('mycli', {
|
|
@@ -809,6 +959,136 @@ describe('many commands with root command (empty string)', () => {
|
|
|
809
959
|
expect(stdout.text).toContain('Initialize a new project');
|
|
810
960
|
expect(stdout.text).toContain('Stream logs for a deployment');
|
|
811
961
|
});
|
|
962
|
+
test('root help with many commands renders examples section after options', () => {
|
|
963
|
+
const stdout = createTestOutputStream();
|
|
964
|
+
const cli = goke('deploy', { stdout });
|
|
965
|
+
cli
|
|
966
|
+
.command('', 'Deploy the current project')
|
|
967
|
+
.option('--env <env>', 'Target environment')
|
|
968
|
+
.option('--dry-run', 'Preview without deploying')
|
|
969
|
+
.example('# Deploy to staging first')
|
|
970
|
+
.example('deploy --env staging --dry-run');
|
|
971
|
+
cli.command('init', 'Initialize a new project');
|
|
972
|
+
cli.command('login', 'Authenticate with the server');
|
|
973
|
+
cli.command('logout', 'Clear saved credentials');
|
|
974
|
+
cli.command('status', 'Show deployment status');
|
|
975
|
+
cli.command('logs <deploymentId>', 'Stream logs for a deployment');
|
|
976
|
+
cli.help();
|
|
977
|
+
cli.parse(['node', 'bin', '--help'], { run: false });
|
|
978
|
+
expect(stdout.text).toMatchInlineSnapshot(`
|
|
979
|
+
"deploy
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
Usage:
|
|
983
|
+
$ deploy [options]
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
Commands:
|
|
987
|
+
deploy Deploy the current project
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
init Initialize a new project
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
login Authenticate with the server
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
logout Clear saved credentials
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
status Show deployment status
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
logs <deploymentId> Stream logs for a deployment
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
Options:
|
|
1006
|
+
--env <env> Target environment
|
|
1007
|
+
--dry-run Preview without deploying
|
|
1008
|
+
-h, --help Display this message
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
Examples:
|
|
1012
|
+
# Deploy to staging first
|
|
1013
|
+
deploy --env staging --dry-run
|
|
1014
|
+
"
|
|
1015
|
+
`);
|
|
1016
|
+
});
|
|
1017
|
+
test('subcommand help renders command examples at the end', () => {
|
|
1018
|
+
const stdout = createTestOutputStream();
|
|
1019
|
+
const cli = goke('deploy', { stdout, columns: 80 });
|
|
1020
|
+
cli.command('', 'Deploy the current project');
|
|
1021
|
+
cli.command('init', 'Initialize a new project');
|
|
1022
|
+
cli.command('login', 'Authenticate with the server');
|
|
1023
|
+
cli
|
|
1024
|
+
.command('logs <deploymentId>', 'Stream logs for a deployment')
|
|
1025
|
+
.option('--follow', 'Follow log output')
|
|
1026
|
+
.option('--lines <n>', z.number().default(100).describe('Number of lines'))
|
|
1027
|
+
.example('# Stream last 200 lines for a deployment')
|
|
1028
|
+
.example('deploy logs dep_123 --lines 200')
|
|
1029
|
+
.example('# Keep following new log lines')
|
|
1030
|
+
.example('deploy logs dep_123 --follow');
|
|
1031
|
+
cli.help();
|
|
1032
|
+
cli.parse(['node', 'bin', 'logs', '--help'], { run: false });
|
|
1033
|
+
expect(stdout.text).toMatchInlineSnapshot(`
|
|
1034
|
+
"deploy
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
Usage:
|
|
1038
|
+
$ deploy logs <deploymentId>
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
Options:
|
|
1042
|
+
--follow Follow log output
|
|
1043
|
+
--lines <n> Number of lines (default: 100)
|
|
1044
|
+
-h, --help Display this message
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
Description:
|
|
1048
|
+
Stream logs for a deployment
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
Examples:
|
|
1052
|
+
# Stream last 200 lines for a deployment
|
|
1053
|
+
deploy logs dep_123 --lines 200
|
|
1054
|
+
# Keep following new log lines
|
|
1055
|
+
deploy logs dep_123 --follow
|
|
1056
|
+
"
|
|
1057
|
+
`);
|
|
1058
|
+
});
|
|
1059
|
+
test('root help labels default command with cli name and does not duplicate global options', () => {
|
|
1060
|
+
const stdout = createTestOutputStream();
|
|
1061
|
+
const cli = goke('deploy', { stdout });
|
|
1062
|
+
cli.option('--env <env>', 'Target environment');
|
|
1063
|
+
cli
|
|
1064
|
+
.command('', 'Deploy the current project')
|
|
1065
|
+
.option('--env <env>', 'Target environment')
|
|
1066
|
+
.option('--dry-run', 'Preview without deploying');
|
|
1067
|
+
cli.command('status', 'Show deployment status');
|
|
1068
|
+
cli.help();
|
|
1069
|
+
cli.parse(['node', 'bin', '--help'], { run: false });
|
|
1070
|
+
expect(stdout.text).toMatchInlineSnapshot(`
|
|
1071
|
+
"deploy
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
Usage:
|
|
1075
|
+
$ deploy [options]
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
Commands:
|
|
1079
|
+
deploy Deploy the current project
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
status Show deployment status
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
Options:
|
|
1086
|
+
--env <env> Target environment
|
|
1087
|
+
--dry-run Preview without deploying
|
|
1088
|
+
-h, --help Display this message
|
|
1089
|
+
"
|
|
1090
|
+
`);
|
|
1091
|
+
});
|
|
812
1092
|
test('root help wraps long command descriptions snapshot', () => {
|
|
813
1093
|
const stdout = createTestOutputStream();
|
|
814
1094
|
const cli = goke('mycli', { stdout, columns: 56 });
|
|
@@ -821,26 +1101,32 @@ describe('many commands with root command (empty string)', () => {
|
|
|
821
1101
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
822
1102
|
"mycli
|
|
823
1103
|
|
|
1104
|
+
|
|
824
1105
|
Usage:
|
|
825
1106
|
$ mycli <command> [options]
|
|
826
1107
|
|
|
1108
|
+
|
|
827
1109
|
Commands:
|
|
828
1110
|
notion-search Perform a semantic search over
|
|
829
1111
|
Notion workspace content and
|
|
830
1112
|
connected integrations with
|
|
831
1113
|
advanced filtering options, date
|
|
832
1114
|
filters, and creator filters.
|
|
1115
|
+
|
|
833
1116
|
--query <query> Natural language query text to
|
|
834
1117
|
search for
|
|
835
1118
|
--limit [limit] Maximum number of results to return
|
|
836
1119
|
(default: 10)
|
|
837
1120
|
|
|
1121
|
+
|
|
838
1122
|
notion-fetch Retrieve a Notion page or database
|
|
839
1123
|
by URL or ID and render the result
|
|
840
1124
|
in enhanced markdown format for
|
|
841
1125
|
terminal output.
|
|
1126
|
+
|
|
842
1127
|
--id <id> Notion URL or UUID to fetch
|
|
843
1128
|
|
|
1129
|
+
|
|
844
1130
|
Options:
|
|
845
1131
|
-h, --help Display this message
|
|
846
1132
|
"
|
|
@@ -858,20 +1144,28 @@ describe('many commands with root command (empty string)', () => {
|
|
|
858
1144
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
859
1145
|
"gtui
|
|
860
1146
|
|
|
1147
|
+
|
|
861
1148
|
Usage:
|
|
862
1149
|
$ gtui <command> [options]
|
|
863
1150
|
|
|
1151
|
+
|
|
864
1152
|
Commands:
|
|
865
1153
|
auth login Authenticate with Google (opens browser)
|
|
866
1154
|
|
|
1155
|
+
|
|
867
1156
|
auth logout Remove stored credentials
|
|
1157
|
+
|
|
868
1158
|
--force Skip confirmation
|
|
869
1159
|
|
|
1160
|
+
|
|
870
1161
|
mail list List email threads
|
|
1162
|
+
|
|
871
1163
|
--folder [folder] Folder to list
|
|
872
1164
|
|
|
1165
|
+
|
|
873
1166
|
attachment get <messageId> <attachmentId> Download an attachment
|
|
874
1167
|
|
|
1168
|
+
|
|
875
1169
|
Options:
|
|
876
1170
|
-h, --help Display this message
|
|
877
1171
|
"
|
|
@@ -903,14 +1197,18 @@ describe('many commands with root command (empty string)', () => {
|
|
|
903
1197
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
904
1198
|
"mycli
|
|
905
1199
|
|
|
1200
|
+
|
|
906
1201
|
Usage:
|
|
907
1202
|
$ mycli <command> [options]
|
|
908
1203
|
|
|
1204
|
+
|
|
909
1205
|
Commands:
|
|
910
1206
|
notion-search Perform a semantic search over Notion workspace content and connected integrations with advanced filtering options, date filters, and creator filters.
|
|
1207
|
+
|
|
911
1208
|
--query <query> Natural language query text to search for
|
|
912
1209
|
--limit [limit] Maximum number of results to return (default: 10)
|
|
913
1210
|
|
|
1211
|
+
|
|
914
1212
|
Options:
|
|
915
1213
|
-h, --help Display this message
|
|
916
1214
|
"
|
|
@@ -1070,4 +1368,102 @@ describe('schema description and default extraction', () => {
|
|
|
1070
1368
|
cli.parse(['node', 'bin', 'serve', '--help'], { run: false });
|
|
1071
1369
|
expect(stdout.text).toContain('(default: 3000)');
|
|
1072
1370
|
});
|
|
1371
|
+
test('deprecated options are hidden from help output', () => {
|
|
1372
|
+
const stdout = createTestOutputStream();
|
|
1373
|
+
const cli = goke('mycli', { stdout });
|
|
1374
|
+
cli
|
|
1375
|
+
.command('serve', 'Start server')
|
|
1376
|
+
.option('--old <value>', z.string().meta({ deprecated: true, description: 'Old option' }))
|
|
1377
|
+
.option('--new <value>', z.string().describe('Normal option'));
|
|
1378
|
+
cli.help();
|
|
1379
|
+
cli.parse(['node', 'bin', 'serve', '--help'], { run: false });
|
|
1380
|
+
// Normal option should be visible
|
|
1381
|
+
expect(stdout.text).toContain('--new');
|
|
1382
|
+
expect(stdout.text).toContain('Normal option');
|
|
1383
|
+
// Deprecated option should be hidden
|
|
1384
|
+
expect(stdout.text).not.toContain('--old');
|
|
1385
|
+
expect(stdout.text).not.toContain('Old option');
|
|
1386
|
+
});
|
|
1387
|
+
test('deprecated option still works for parsing (just hidden from help)', () => {
|
|
1388
|
+
const cli = gokeTestable('mycli');
|
|
1389
|
+
let result = {};
|
|
1390
|
+
cli
|
|
1391
|
+
.command('serve', 'Start server')
|
|
1392
|
+
.option('--old <value>', z.string().meta({ deprecated: true, description: 'Old option' }))
|
|
1393
|
+
.action((options) => { result = options; });
|
|
1394
|
+
cli.parse(['node', 'bin', 'serve', '--old', 'legacy-value']);
|
|
1395
|
+
// Deprecated option should still be parsed and usable
|
|
1396
|
+
expect(result.old).toBe('legacy-value');
|
|
1397
|
+
});
|
|
1398
|
+
test('deprecated options hidden from global help', () => {
|
|
1399
|
+
const stdout = createTestOutputStream();
|
|
1400
|
+
const cli = goke('mycli', { stdout });
|
|
1401
|
+
cli.option('--legacy [value]', z.string().meta({ deprecated: true, description: 'Deprecated global' }));
|
|
1402
|
+
cli.option('--current [value]', z.string().describe('Current option'));
|
|
1403
|
+
cli.help();
|
|
1404
|
+
cli.parse(['node', 'bin', '--help'], { run: false });
|
|
1405
|
+
expect(stdout.text).toContain('--current');
|
|
1406
|
+
expect(stdout.text).toContain('Current option');
|
|
1407
|
+
expect(stdout.text).not.toContain('--legacy');
|
|
1408
|
+
expect(stdout.text).not.toContain('Deprecated global');
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1411
|
+
describe('helpText()', () => {
|
|
1412
|
+
test('returns help string without printing', () => {
|
|
1413
|
+
const stdout = createTestOutputStream();
|
|
1414
|
+
const cli = goke('mycli', { stdout });
|
|
1415
|
+
cli.command('serve', 'Start server');
|
|
1416
|
+
cli.option('--port <port>', 'Port number');
|
|
1417
|
+
cli.help();
|
|
1418
|
+
// parse a known command so help is not auto-triggered
|
|
1419
|
+
cli.parse(['node', 'bin', 'serve'], { run: false });
|
|
1420
|
+
// reset stdout after parse
|
|
1421
|
+
stdout.lines.length = 0;
|
|
1422
|
+
const text = stripAnsi(cli.helpText());
|
|
1423
|
+
expect(text).toContain('mycli');
|
|
1424
|
+
expect(text).toContain('serve');
|
|
1425
|
+
expect(text).toContain('Start server');
|
|
1426
|
+
expect(text).toContain('--port');
|
|
1427
|
+
// helpText() does not print to stdout
|
|
1428
|
+
expect(stdout.text).toBe('');
|
|
1429
|
+
});
|
|
1430
|
+
test('returns same content as outputHelp', () => {
|
|
1431
|
+
const stdout = createTestOutputStream();
|
|
1432
|
+
const cli = goke('mycli', { stdout });
|
|
1433
|
+
cli.command('build', 'Build project');
|
|
1434
|
+
cli.option('--watch [watch]', 'Watch mode');
|
|
1435
|
+
cli.help();
|
|
1436
|
+
// parse a known command so help is not auto-triggered
|
|
1437
|
+
cli.parse(['node', 'bin', 'build'], { run: false });
|
|
1438
|
+
// reset stdout after parse
|
|
1439
|
+
stdout.lines.length = 0;
|
|
1440
|
+
const helpTextResult = stripAnsi(cli.helpText());
|
|
1441
|
+
cli.outputHelp();
|
|
1442
|
+
// outputHelp adds a trailing newline via console.log
|
|
1443
|
+
const outputHelpResult = stdout.text.replace(/\n$/, '');
|
|
1444
|
+
expect(helpTextResult).toBe(outputHelpResult);
|
|
1445
|
+
});
|
|
1446
|
+
test('returns subcommand help when command is matched', () => {
|
|
1447
|
+
const cli = goke('mycli');
|
|
1448
|
+
cli.command('deploy <env>', 'Deploy to environment')
|
|
1449
|
+
.option('--force', 'Force deploy');
|
|
1450
|
+
cli.help();
|
|
1451
|
+
cli.parse(['node', 'bin', 'deploy', '--help'], { run: false });
|
|
1452
|
+
const text = stripAnsi(cli.helpText());
|
|
1453
|
+
expect(text).toContain('deploy');
|
|
1454
|
+
expect(text).toContain('--force');
|
|
1455
|
+
expect(text).toContain('Force deploy');
|
|
1456
|
+
});
|
|
1457
|
+
test('works without calling parse', () => {
|
|
1458
|
+
const cli = goke('mycli');
|
|
1459
|
+
cli.command('test', 'Run tests');
|
|
1460
|
+
cli.option('--coverage', 'Enable coverage');
|
|
1461
|
+
cli.help();
|
|
1462
|
+
// helpText() works even without parse
|
|
1463
|
+
const text = stripAnsi(cli.helpText());
|
|
1464
|
+
expect(text).toContain('mycli');
|
|
1465
|
+
expect(text).toContain('test');
|
|
1466
|
+
expect(text).toContain('Run tests');
|
|
1467
|
+
expect(text).toContain('--coverage');
|
|
1468
|
+
});
|
|
1073
1469
|
});
|
package/dist/coerce.d.ts
CHANGED
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
* Union types (e.g. ["number", "string"]):
|
|
29
29
|
* Tries each type in order, returns first successful coercion.
|
|
30
30
|
*/
|
|
31
|
+
/**
|
|
32
|
+
* Custom error class for CLI usage errors (unknown options, missing values,
|
|
33
|
+
* invalid types, etc.). Used by both the coercion layer and the framework
|
|
34
|
+
* to distinguish user-facing errors from unexpected failures.
|
|
35
|
+
*/
|
|
36
|
+
export declare class GokeError extends Error {
|
|
37
|
+
constructor(message: string);
|
|
38
|
+
}
|
|
31
39
|
/** The Standard Typed interface. Base type extended by other specs. */
|
|
32
40
|
export interface StandardTypedV1<Input = unknown, Output = Input> {
|
|
33
41
|
readonly "~standard": StandardTypedV1.Props<Input, Output>;
|
|
@@ -70,23 +78,8 @@ export declare namespace StandardJSONSchemaV1 {
|
|
|
70
78
|
/**
|
|
71
79
|
* Wraps a plain JSON Schema object into a StandardJSONSchemaV1-compatible object.
|
|
72
80
|
*
|
|
73
|
-
* This is
|
|
74
|
-
*
|
|
75
|
-
* schema option which expects StandardJSONSchemaV1.
|
|
76
|
-
*
|
|
77
|
-
* @example
|
|
78
|
-
* ```ts
|
|
79
|
-
* import { wrapJsonSchema } from 'goke'
|
|
80
|
-
*
|
|
81
|
-
* // Wrap a plain JSON Schema for use with Goke options
|
|
82
|
-
* const schema = wrapJsonSchema({ type: "number" })
|
|
83
|
-
* cmd.option('--port <port>', 'Port', { schema })
|
|
84
|
-
*
|
|
85
|
-
* // Wrap MCP tool property schemas
|
|
86
|
-
* for (const [name, propSchema] of Object.entries(tool.inputSchema.properties)) {
|
|
87
|
-
* cmd.option(`--${name} <${name}>`, desc, { schema: wrapJsonSchema(propSchema) })
|
|
88
|
-
* }
|
|
89
|
-
* ```
|
|
81
|
+
* @internal This is an internal helper used by @goke/mcp to wrap MCP tool schemas.
|
|
82
|
+
* Users should pass Zod or other StandardSchema-compatible schemas to `.option()`.
|
|
90
83
|
*
|
|
91
84
|
* @param jsonSchema - A plain JSON Schema object (e.g. `{ type: "number" }`)
|
|
92
85
|
* @returns A StandardJSONSchemaV1-compatible object that Goke can use for coercion
|
|
@@ -106,6 +99,8 @@ export interface JsonSchema {
|
|
|
106
99
|
additionalProperties?: boolean | JsonSchema;
|
|
107
100
|
default?: unknown;
|
|
108
101
|
description?: string;
|
|
102
|
+
/** JSON Schema deprecated annotation (draft 2019-09+) */
|
|
103
|
+
deprecated?: boolean;
|
|
109
104
|
}
|
|
110
105
|
/**
|
|
111
106
|
* Coerce a raw CLI value to the type declared in a JSON Schema.
|
|
@@ -128,11 +123,12 @@ export declare function extractJsonSchema(schema: unknown): Record<string, unkno
|
|
|
128
123
|
*/
|
|
129
124
|
export declare function isStandardSchema(value: unknown): value is StandardJSONSchemaV1;
|
|
130
125
|
/**
|
|
131
|
-
* Extract description
|
|
132
|
-
* Calls extractJsonSchema() internally and pulls `description` and `
|
|
126
|
+
* Extract description, default value, and deprecated flag from a StandardJSONSchemaV1-compatible schema.
|
|
127
|
+
* Calls extractJsonSchema() internally and pulls `description`, `default`, and `deprecated` fields.
|
|
133
128
|
*/
|
|
134
129
|
export declare function extractSchemaMetadata(schema: StandardJSONSchemaV1): {
|
|
135
130
|
description?: string;
|
|
136
131
|
default?: unknown;
|
|
132
|
+
deprecated?: boolean;
|
|
137
133
|
};
|
|
138
134
|
//# sourceMappingURL=coerce.d.ts.map
|
package/dist/coerce.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../src/coerce.ts"],"names":[],"mappings":"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"}
|