goke 6.1.3 → 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 +239 -3
- package/dist/__test__/index.test.js +347 -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 +149 -82
- 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 +412 -9
- package/src/coerce.ts +44 -35
- package/src/goke.ts +165 -91
- 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
|
"
|
|
@@ -837,6 +959,103 @@ describe('many commands with root command (empty string)', () => {
|
|
|
837
959
|
expect(stdout.text).toContain('Initialize a new project');
|
|
838
960
|
expect(stdout.text).toContain('Stream logs for a deployment');
|
|
839
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
|
+
});
|
|
840
1059
|
test('root help labels default command with cli name and does not duplicate global options', () => {
|
|
841
1060
|
const stdout = createTestOutputStream();
|
|
842
1061
|
const cli = goke('deploy', { stdout });
|
|
@@ -851,14 +1070,18 @@ describe('many commands with root command (empty string)', () => {
|
|
|
851
1070
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
852
1071
|
"deploy
|
|
853
1072
|
|
|
1073
|
+
|
|
854
1074
|
Usage:
|
|
855
1075
|
$ deploy [options]
|
|
856
1076
|
|
|
1077
|
+
|
|
857
1078
|
Commands:
|
|
858
1079
|
deploy Deploy the current project
|
|
859
1080
|
|
|
1081
|
+
|
|
860
1082
|
status Show deployment status
|
|
861
1083
|
|
|
1084
|
+
|
|
862
1085
|
Options:
|
|
863
1086
|
--env <env> Target environment
|
|
864
1087
|
--dry-run Preview without deploying
|
|
@@ -878,26 +1101,32 @@ describe('many commands with root command (empty string)', () => {
|
|
|
878
1101
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
879
1102
|
"mycli
|
|
880
1103
|
|
|
1104
|
+
|
|
881
1105
|
Usage:
|
|
882
1106
|
$ mycli <command> [options]
|
|
883
1107
|
|
|
1108
|
+
|
|
884
1109
|
Commands:
|
|
885
1110
|
notion-search Perform a semantic search over
|
|
886
1111
|
Notion workspace content and
|
|
887
1112
|
connected integrations with
|
|
888
1113
|
advanced filtering options, date
|
|
889
1114
|
filters, and creator filters.
|
|
1115
|
+
|
|
890
1116
|
--query <query> Natural language query text to
|
|
891
1117
|
search for
|
|
892
1118
|
--limit [limit] Maximum number of results to return
|
|
893
1119
|
(default: 10)
|
|
894
1120
|
|
|
1121
|
+
|
|
895
1122
|
notion-fetch Retrieve a Notion page or database
|
|
896
1123
|
by URL or ID and render the result
|
|
897
1124
|
in enhanced markdown format for
|
|
898
1125
|
terminal output.
|
|
1126
|
+
|
|
899
1127
|
--id <id> Notion URL or UUID to fetch
|
|
900
1128
|
|
|
1129
|
+
|
|
901
1130
|
Options:
|
|
902
1131
|
-h, --help Display this message
|
|
903
1132
|
"
|
|
@@ -915,20 +1144,28 @@ describe('many commands with root command (empty string)', () => {
|
|
|
915
1144
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
916
1145
|
"gtui
|
|
917
1146
|
|
|
1147
|
+
|
|
918
1148
|
Usage:
|
|
919
1149
|
$ gtui <command> [options]
|
|
920
1150
|
|
|
1151
|
+
|
|
921
1152
|
Commands:
|
|
922
1153
|
auth login Authenticate with Google (opens browser)
|
|
923
1154
|
|
|
1155
|
+
|
|
924
1156
|
auth logout Remove stored credentials
|
|
1157
|
+
|
|
925
1158
|
--force Skip confirmation
|
|
926
1159
|
|
|
1160
|
+
|
|
927
1161
|
mail list List email threads
|
|
1162
|
+
|
|
928
1163
|
--folder [folder] Folder to list
|
|
929
1164
|
|
|
1165
|
+
|
|
930
1166
|
attachment get <messageId> <attachmentId> Download an attachment
|
|
931
1167
|
|
|
1168
|
+
|
|
932
1169
|
Options:
|
|
933
1170
|
-h, --help Display this message
|
|
934
1171
|
"
|
|
@@ -960,14 +1197,18 @@ describe('many commands with root command (empty string)', () => {
|
|
|
960
1197
|
expect(stdout.text).toMatchInlineSnapshot(`
|
|
961
1198
|
"mycli
|
|
962
1199
|
|
|
1200
|
+
|
|
963
1201
|
Usage:
|
|
964
1202
|
$ mycli <command> [options]
|
|
965
1203
|
|
|
1204
|
+
|
|
966
1205
|
Commands:
|
|
967
1206
|
notion-search Perform a semantic search over Notion workspace content and connected integrations with advanced filtering options, date filters, and creator filters.
|
|
1207
|
+
|
|
968
1208
|
--query <query> Natural language query text to search for
|
|
969
1209
|
--limit [limit] Maximum number of results to return (default: 10)
|
|
970
1210
|
|
|
1211
|
+
|
|
971
1212
|
Options:
|
|
972
1213
|
-h, --help Display this message
|
|
973
1214
|
"
|
|
@@ -1127,4 +1368,102 @@ describe('schema description and default extraction', () => {
|
|
|
1127
1368
|
cli.parse(['node', 'bin', 'serve', '--help'], { run: false });
|
|
1128
1369
|
expect(stdout.text).toContain('(default: 3000)');
|
|
1129
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
|
+
});
|
|
1130
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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"}
|