goke 6.5.2 → 6.6.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 +1 -1
- package/dist/__test__/index.test.js +16 -6
- package/dist/__test__/types.test-d.js +5 -1
- package/dist/goke.d.ts +21 -5
- package/dist/goke.d.ts.map +1 -1
- package/dist/goke.js +14 -4
- package/package.json +1 -1
- package/src/__test__/index.test.ts +16 -6
- package/src/__test__/types.test-d.ts +21 -12
- package/src/goke.ts +42 -9
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ cli.parse()
|
|
|
47
47
|
- **JustBash support** — `cli.createJustBashCommand()` exposes your CLI as a sandboxed JustBash command. Same action code, no changes needed.
|
|
48
48
|
- **Space-separated subcommands** — `git remote add`, `mcp login`, `db migrate` — multi-word commands work out of the box.
|
|
49
49
|
- **Injected `{ fs, console, process }`** — commands receive a portable runtime context. Swap it in tests, or let JustBash replace it with a sandbox. No global side effects.
|
|
50
|
-
- **Zero dependencies** —
|
|
50
|
+
- **Zero runtime dependencies** — install `goke` without pulling extra runtime packages into your CLI.
|
|
51
51
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
@@ -230,7 +230,9 @@ test('dot-nested options', () => {
|
|
|
230
230
|
.option('--scale [level]', 'Scaling level');
|
|
231
231
|
const { options: options1 } = cli.parse(`node bin --externals.env.prod production --scale`.split(' '));
|
|
232
232
|
expect(options1.externals).toEqual({ env: { prod: 'production' } });
|
|
233
|
-
|
|
233
|
+
// Bare `--scale` normalizes to `''` (new uniform string-or-undefined shape
|
|
234
|
+
// for untyped optional-value flags).
|
|
235
|
+
expect(options1.scale).toEqual('');
|
|
234
236
|
});
|
|
235
237
|
describe('schema-based options', () => {
|
|
236
238
|
test('schema coerces string to number', () => {
|
|
@@ -334,11 +336,14 @@ describe('no-schema behavior (mri no longer auto-converts)', () => {
|
|
|
334
336
|
const { options } = cli.parse('node bin --verbose'.split(' '));
|
|
335
337
|
expect(options.verbose).toBe(true);
|
|
336
338
|
});
|
|
337
|
-
test('optional value flag returns
|
|
339
|
+
test('optional value flag returns empty string when no value given', () => {
|
|
340
|
+
// Bare `--format` is normalized from the mri `true` sentinel to `''` so
|
|
341
|
+
// callers see a uniform `string | undefined` shape. `''` still lets them
|
|
342
|
+
// distinguish "flag present but no value" from "flag omitted entirely".
|
|
338
343
|
const cli = goke();
|
|
339
344
|
cli.option('--format [fmt]', 'Format');
|
|
340
345
|
const { options } = cli.parse('node bin --format'.split(' '));
|
|
341
|
-
expect(options.format).toBe(
|
|
346
|
+
expect(options.format).toBe('');
|
|
342
347
|
});
|
|
343
348
|
test('optional value flag returns string when value given', () => {
|
|
344
349
|
const cli = goke();
|
|
@@ -545,12 +550,17 @@ describe('regression: oracle-found issues', () => {
|
|
|
545
550
|
const { options } = cli.parse('node bin --count'.split(' '));
|
|
546
551
|
expect(options.count).toBe(undefined);
|
|
547
552
|
});
|
|
548
|
-
test('optional value option without schema
|
|
553
|
+
test('optional value option without schema normalizes bare flag to empty string', () => {
|
|
549
554
|
const cli = goke();
|
|
550
555
|
cli.option('--count [count]', 'Count');
|
|
551
|
-
//
|
|
556
|
+
// Untyped optional-value flags uniformly expose `string | undefined`:
|
|
557
|
+
// - `--count` → '' (flag present, no value)
|
|
558
|
+
// - `--count 42` → '42' (flag present, with value)
|
|
559
|
+
// - (omitted) → undefined (flag absent)
|
|
560
|
+
// This lets callers use a single `typeof options.count === 'string'`
|
|
561
|
+
// check and distinguish the three cases via `=== ''` if they need to.
|
|
552
562
|
const { options } = cli.parse('node bin --count'.split(' '));
|
|
553
|
-
expect(options.count).toBe(
|
|
563
|
+
expect(options.count).toBe('');
|
|
554
564
|
});
|
|
555
565
|
test('optional value option with schema coerces when value given', () => {
|
|
556
566
|
const cli = goke();
|
|
@@ -274,12 +274,16 @@ describe('type-level: command() .action() option inference', () => {
|
|
|
274
274
|
expectTypeOf(options.port).toEqualTypeOf();
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
-
test('untyped optional value options
|
|
277
|
+
test('untyped optional value options surface as string | undefined', () => {
|
|
278
278
|
goke('test')
|
|
279
279
|
.command('serve', 'Start server')
|
|
280
280
|
.option('--host [host]', 'Optional host override')
|
|
281
281
|
.option('--verbose', 'Verbose output')
|
|
282
282
|
.action((options) => {
|
|
283
|
+
// `[value]` options always resolve to `string | undefined`:
|
|
284
|
+
// - omitted → undefined
|
|
285
|
+
// - `--host` → '' (flag present, no value)
|
|
286
|
+
// - `--host example` → 'example'
|
|
283
287
|
expectTypeOf(options.host).toEqualTypeOf();
|
|
284
288
|
expectTypeOf(options.verbose).toEqualTypeOf();
|
|
285
289
|
});
|
package/dist/goke.d.ts
CHANGED
|
@@ -77,11 +77,15 @@ type OptionEntry<RawName extends string, Schema> = IsOptionalOption<RawName> ext
|
|
|
77
77
|
* Infer the raw runtime value shape for an option declared without a schema.
|
|
78
78
|
*
|
|
79
79
|
* Required value options (`--port <port>`) always reach actions as strings.
|
|
80
|
-
* Optional value options (`--host [host]`)
|
|
81
|
-
*
|
|
80
|
+
* Optional value options (`--host [host]`) reach actions as strings: the
|
|
81
|
+
* empty string `''` when the flag is passed bare (`--host`), the given
|
|
82
|
+
* value when passed with one (`--host example.com`), and `undefined` when
|
|
83
|
+
* the flag is omitted entirely. This lets callers use a single `typeof`
|
|
84
|
+
* check and, if they really care, distinguish "omitted" from "present but
|
|
85
|
+
* empty" via `=== undefined` vs `=== ''`.
|
|
82
86
|
* Plain flags (`--verbose`) are booleans.
|
|
83
87
|
*/
|
|
84
|
-
type UntypedOptionValue<RawName extends string> = RawName extends `${string}<${string}>` ? string : RawName extends `${string}[${string}]` ? string
|
|
88
|
+
type UntypedOptionValue<RawName extends string> = RawName extends `${string}<${string}>` ? string : RawName extends `${string}[${string}]` ? string : boolean | undefined;
|
|
85
89
|
/**
|
|
86
90
|
* Build the option type entry for a `.option()` call that uses a plain
|
|
87
91
|
* description (no schema).
|
|
@@ -123,6 +127,15 @@ type ExtractCommandArgs<T extends readonly string[]> = T extends readonly [infer
|
|
|
123
127
|
* "deploy" → []
|
|
124
128
|
*/
|
|
125
129
|
type ExtractPositionalArgs<RawName extends string> = ExtractCommandArgs<TokenizeName<RawName>>;
|
|
130
|
+
/**
|
|
131
|
+
* Everything after a literal `--` on the command line is collected into
|
|
132
|
+
* `options['--']` as a string array (empty when `--` is absent). This key
|
|
133
|
+
* is always present at runtime, so it's merged into every action's options
|
|
134
|
+
* type regardless of which options the user declared.
|
|
135
|
+
*/
|
|
136
|
+
type DoubleDashOptions = {
|
|
137
|
+
'--': string[];
|
|
138
|
+
};
|
|
126
139
|
/**
|
|
127
140
|
* Build the full argument tuple passed to a command's action callback.
|
|
128
141
|
*
|
|
@@ -131,10 +144,13 @@ type ExtractPositionalArgs<RawName extends string> = ExtractCommandArgs<Tokenize
|
|
|
131
144
|
* This matches the runtime behavior in Goke.runMatchedCommand(): the action
|
|
132
145
|
* is called with positional args from the parsed command, then the parsed
|
|
133
146
|
* options object, then the injected GokeExecutionContext.
|
|
147
|
+
*
|
|
148
|
+
* The options type is always extended with `{ '--': string[] }` because the
|
|
149
|
+
* parser always populates that key (see `Goke.parse()`).
|
|
134
150
|
*/
|
|
135
151
|
type ActionArgs<RawName extends string, Opts> = [
|
|
136
152
|
...ExtractPositionalArgs<RawName>,
|
|
137
|
-
Opts,
|
|
153
|
+
Opts & DoubleDashOptions,
|
|
138
154
|
GokeExecutionContext
|
|
139
155
|
];
|
|
140
156
|
interface CommandArg {
|
|
@@ -427,7 +443,7 @@ declare class Goke<Opts = {}> extends EventEmitter {
|
|
|
427
443
|
* })
|
|
428
444
|
* ```
|
|
429
445
|
*/
|
|
430
|
-
use(callback: (options: Opts, context: GokeExecutionContext) => void | Promise<void>): this;
|
|
446
|
+
use(callback: (options: Opts & DoubleDashOptions, context: GokeExecutionContext) => void | Promise<void>): this;
|
|
431
447
|
/**
|
|
432
448
|
* Show help message when `-h, --help` flags appear.
|
|
433
449
|
*
|
package/dist/goke.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goke.d.ts","sourceRoot":"","sources":["../src/goke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAmB,aAAa,EAAW,MAAM,UAAU,CAAA;AAmNhF,cAAM,MAAM;IAwBD,OAAO,EAAE,MAAM;IAvBxB,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,8BAA8B;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IAEnB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,qEAAqE;IACrE,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAA;IAEpB;;;;;OAKG;gBAEM,OAAO,EAAE,MAAM,EACtB,mBAAmB,CAAC,EAAE,MAAM,GAAG,oBAAoB;IA0CrD,KAAK;CAGN;AAMD;;;GAGG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,IAC7B,CAAC,SAAS,GAAG,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,GAC7B,GAAG,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,GACjC,CAAC,CAAA;AAEP;;;;;GAKG;AACH,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,IAErC,CAAC,SAAS,GAAG,MAAM,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAClE,CAAC,SAAS,GAAG,MAAM,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAClE,CAAC,SAAS,GAAG,MAAM,KAAK,MAAM,IAAI,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,GACtD,MAAM,CAAA;AAER;;GAEG;AACH,KAAK,gBAAgB,CAAC,CAAC,SAAS,MAAM,IACpC,CAAC,SAAS,GAAG,MAAM,IAAI,MAAM,GAAG,GAAG,KAAK,GACxC,IAAI,CAAA;AAEN;;GAEG;AACH,KAAK,iBAAiB,CAAC,CAAC,IACtB,CAAC,SAAS;IAAE,QAAQ,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,GAAG,CAAC,GAAG,OAAO,CAAA;AAErG;;;;GAIG;AACH,KAAK,WAAW,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,IAC7C,gBAAgB,CAAC,OAAO,CAAC,SAAS,IAAI,GAClC;KAAG,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,iBAAiB,CAAC,MAAM,CAAC;CAAE,GACjE;KAAG,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC;CAAE,CAAA;AAEtE
|
|
1
|
+
{"version":3,"file":"goke.d.ts","sourceRoot":"","sources":["../src/goke.ts"],"names":[],"mappings":"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"}
|
package/dist/goke.js
CHANGED
|
@@ -1102,7 +1102,9 @@ class Goke extends EventEmitter {
|
|
|
1102
1102
|
//
|
|
1103
1103
|
// When mri returns `true` for value-taking options, it means "flag present, no value given".
|
|
1104
1104
|
// For required options (<...>), the sentinel is preserved so checkOptionValue() throws.
|
|
1105
|
-
// For optional options ([...])
|
|
1105
|
+
// For optional options ([...]) we want a single, uniform shape: `string`
|
|
1106
|
+
// with `''` meaning "flag present but no value" — callers get clean
|
|
1107
|
+
// `string | undefined` types instead of `string | boolean | undefined`.
|
|
1106
1108
|
const requiredValueOptions = new Set();
|
|
1107
1109
|
const optionalValueOptions = new Set();
|
|
1108
1110
|
for (const cliOption of cliOptions) {
|
|
@@ -1127,21 +1129,29 @@ class Goke extends EventEmitter {
|
|
|
1127
1129
|
// When value is boolean `true` and the option takes a value, it's mri's sentinel
|
|
1128
1130
|
// for "flag present, no value given":
|
|
1129
1131
|
// - Required options (<...>): preserve `true` so checkOptionValue() throws
|
|
1130
|
-
// - Optional options ([...]) with schema: replace with `undefined`
|
|
1131
|
-
//
|
|
1132
|
+
// - Optional options ([...]) with schema: replace with `undefined` so
|
|
1133
|
+
// any `.default(...)` on the schema kicks in (e.g. z.number().default(30)
|
|
1134
|
+
// should produce 30, not try to coerce the `''` empty-string sentinel).
|
|
1132
1135
|
const schemaInfo = schemaMap.get(key);
|
|
1133
1136
|
if (schemaInfo && value !== undefined) {
|
|
1134
1137
|
if (value === true && requiredValueOptions.has(key)) {
|
|
1135
1138
|
// Keep sentinel for checkOptionValue() to detect
|
|
1136
1139
|
}
|
|
1137
1140
|
else if (value === true && optionalValueOptions.has(key)) {
|
|
1138
|
-
// Optional value not given — schema expects a typed value, so return undefined
|
|
1139
1141
|
value = undefined;
|
|
1140
1142
|
}
|
|
1141
1143
|
else {
|
|
1142
1144
|
value = coerceBySchema(value, schemaInfo.jsonSchema, schemaInfo.optionName);
|
|
1143
1145
|
}
|
|
1144
1146
|
}
|
|
1147
|
+
else if (value === true && optionalValueOptions.has(key)) {
|
|
1148
|
+
// Untyped optional-value flag with no schema: normalize bare `true`
|
|
1149
|
+
// to `''` so callers get a clean `string | undefined` shape. `''`
|
|
1150
|
+
// means "flag passed with no argument", distinct from `undefined`
|
|
1151
|
+
// (flag omitted). This matches the new type inference that treats
|
|
1152
|
+
// `[value]` as `string` instead of `string | boolean`.
|
|
1153
|
+
value = '';
|
|
1154
|
+
}
|
|
1145
1155
|
setDotProp(options, keys, value);
|
|
1146
1156
|
}
|
|
1147
1157
|
}
|
package/package.json
CHANGED
|
@@ -278,7 +278,9 @@ test('dot-nested options', () => {
|
|
|
278
278
|
`node bin --externals.env.prod production --scale`.split(' ')
|
|
279
279
|
)
|
|
280
280
|
expect(options1.externals).toEqual({ env: { prod: 'production' } })
|
|
281
|
-
|
|
281
|
+
// Bare `--scale` normalizes to `''` (new uniform string-or-undefined shape
|
|
282
|
+
// for untyped optional-value flags).
|
|
283
|
+
expect(options1.scale).toEqual('')
|
|
282
284
|
})
|
|
283
285
|
|
|
284
286
|
describe('schema-based options', () => {
|
|
@@ -418,11 +420,14 @@ describe('no-schema behavior (mri no longer auto-converts)', () => {
|
|
|
418
420
|
expect(options.verbose).toBe(true)
|
|
419
421
|
})
|
|
420
422
|
|
|
421
|
-
test('optional value flag returns
|
|
423
|
+
test('optional value flag returns empty string when no value given', () => {
|
|
424
|
+
// Bare `--format` is normalized from the mri `true` sentinel to `''` so
|
|
425
|
+
// callers see a uniform `string | undefined` shape. `''` still lets them
|
|
426
|
+
// distinguish "flag present but no value" from "flag omitted entirely".
|
|
422
427
|
const cli = goke()
|
|
423
428
|
cli.option('--format [fmt]', 'Format')
|
|
424
429
|
const { options } = cli.parse('node bin --format'.split(' '))
|
|
425
|
-
expect(options.format).toBe(
|
|
430
|
+
expect(options.format).toBe('')
|
|
426
431
|
})
|
|
427
432
|
|
|
428
433
|
test('optional value flag returns string when value given', () => {
|
|
@@ -699,14 +704,19 @@ describe('regression: oracle-found issues', () => {
|
|
|
699
704
|
expect(options.count).toBe(undefined)
|
|
700
705
|
})
|
|
701
706
|
|
|
702
|
-
test('optional value option without schema
|
|
707
|
+
test('optional value option without schema normalizes bare flag to empty string', () => {
|
|
703
708
|
const cli = goke()
|
|
704
709
|
|
|
705
710
|
cli.option('--count [count]', 'Count')
|
|
706
711
|
|
|
707
|
-
//
|
|
712
|
+
// Untyped optional-value flags uniformly expose `string | undefined`:
|
|
713
|
+
// - `--count` → '' (flag present, no value)
|
|
714
|
+
// - `--count 42` → '42' (flag present, with value)
|
|
715
|
+
// - (omitted) → undefined (flag absent)
|
|
716
|
+
// This lets callers use a single `typeof options.count === 'string'`
|
|
717
|
+
// check and distinguish the three cases via `=== ''` if they need to.
|
|
708
718
|
const { options } = cli.parse('node bin --count'.split(' '))
|
|
709
|
-
expect(options.count).toBe(
|
|
719
|
+
expect(options.count).toBe('')
|
|
710
720
|
})
|
|
711
721
|
|
|
712
722
|
test('optional value option with schema coerces when value given', () => {
|
|
@@ -76,6 +76,11 @@ describe('type-level: IsOptionalOption', () => {
|
|
|
76
76
|
})
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
+
// Every action callback's options param is extended with `{ '--': string[] }`
|
|
80
|
+
// because the runtime always populates that key. Use this alias everywhere
|
|
81
|
+
// we used to write `{}` to mean "no user-declared options".
|
|
82
|
+
type Base = { '--': string[] }
|
|
83
|
+
|
|
79
84
|
describe('type-level: InferSchemaOutput', () => {
|
|
80
85
|
test('infers output from StandardTypedV1', () => {
|
|
81
86
|
type Schema = StandardTypedV1<unknown, number>
|
|
@@ -177,7 +182,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
177
182
|
goke('test')
|
|
178
183
|
.command('deploy', 'Deploy the app')
|
|
179
184
|
.action((options, ctx) => {
|
|
180
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
185
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
181
186
|
expectTypeOf(ctx).toEqualTypeOf<GokeExecutionContext>()
|
|
182
187
|
})
|
|
183
188
|
})
|
|
@@ -187,7 +192,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
187
192
|
.command('get <id>', 'Fetch a resource by id')
|
|
188
193
|
.action((id, options, ctx) => {
|
|
189
194
|
expectTypeOf(id).toEqualTypeOf<string>()
|
|
190
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
195
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
191
196
|
expectTypeOf(ctx).toEqualTypeOf<GokeExecutionContext>()
|
|
192
197
|
})
|
|
193
198
|
})
|
|
@@ -198,7 +203,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
198
203
|
.action((input, output, options) => {
|
|
199
204
|
expectTypeOf(input).toEqualTypeOf<string>()
|
|
200
205
|
expectTypeOf(output).toEqualTypeOf<string>()
|
|
201
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
206
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
202
207
|
})
|
|
203
208
|
})
|
|
204
209
|
|
|
@@ -207,7 +212,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
207
212
|
.command('run [script]', 'Run a script')
|
|
208
213
|
.action((script, options) => {
|
|
209
214
|
expectTypeOf(script).toEqualTypeOf<string | undefined>()
|
|
210
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
215
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
211
216
|
})
|
|
212
217
|
})
|
|
213
218
|
|
|
@@ -216,7 +221,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
216
221
|
.command('exec <...args>', 'Run a binary with args')
|
|
217
222
|
.action((args, options) => {
|
|
218
223
|
expectTypeOf(args).toEqualTypeOf<string[]>()
|
|
219
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
224
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
220
225
|
})
|
|
221
226
|
})
|
|
222
227
|
|
|
@@ -225,7 +230,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
225
230
|
.command('run [...rest]', 'Variadic optional')
|
|
226
231
|
.action((rest, options) => {
|
|
227
232
|
expectTypeOf(rest).toEqualTypeOf<string[]>()
|
|
228
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
233
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
229
234
|
})
|
|
230
235
|
})
|
|
231
236
|
|
|
@@ -234,7 +239,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
234
239
|
.command('mcp getNodeXml <id>', 'Get XML for a node')
|
|
235
240
|
.action((id, options) => {
|
|
236
241
|
expectTypeOf(id).toEqualTypeOf<string>()
|
|
237
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
242
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
238
243
|
})
|
|
239
244
|
})
|
|
240
245
|
|
|
@@ -243,7 +248,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
243
248
|
.command('<file>', 'Default command')
|
|
244
249
|
.action((file, options) => {
|
|
245
250
|
expectTypeOf(file).toEqualTypeOf<string>()
|
|
246
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
251
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
247
252
|
})
|
|
248
253
|
})
|
|
249
254
|
|
|
@@ -253,7 +258,7 @@ describe('type-level: command() .action() positional args inference', () => {
|
|
|
253
258
|
.action((to, cc, options) => {
|
|
254
259
|
expectTypeOf(to).toEqualTypeOf<string>()
|
|
255
260
|
expectTypeOf(cc).toEqualTypeOf<string | undefined>()
|
|
256
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
261
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
257
262
|
})
|
|
258
263
|
})
|
|
259
264
|
})
|
|
@@ -342,13 +347,17 @@ describe('type-level: command() .action() option inference', () => {
|
|
|
342
347
|
})
|
|
343
348
|
})
|
|
344
349
|
|
|
345
|
-
test('untyped optional value options
|
|
350
|
+
test('untyped optional value options surface as string | undefined', () => {
|
|
346
351
|
goke('test')
|
|
347
352
|
.command('serve', 'Start server')
|
|
348
353
|
.option('--host [host]', 'Optional host override')
|
|
349
354
|
.option('--verbose', 'Verbose output')
|
|
350
355
|
.action((options) => {
|
|
351
|
-
|
|
356
|
+
// `[value]` options always resolve to `string | undefined`:
|
|
357
|
+
// - omitted → undefined
|
|
358
|
+
// - `--host` → '' (flag present, no value)
|
|
359
|
+
// - `--host example` → 'example'
|
|
360
|
+
expectTypeOf(options.host).toEqualTypeOf<string | undefined>()
|
|
352
361
|
expectTypeOf(options.verbose).toEqualTypeOf<boolean | undefined>()
|
|
353
362
|
})
|
|
354
363
|
})
|
|
@@ -369,7 +378,7 @@ describe('type-level: command() .action() option inference', () => {
|
|
|
369
378
|
.command('get <id>', 'Fetch resource')
|
|
370
379
|
.action((id, options, ctx, ...rest) => {
|
|
371
380
|
expectTypeOf(id).toEqualTypeOf<string>()
|
|
372
|
-
expectTypeOf(options).toEqualTypeOf<
|
|
381
|
+
expectTypeOf(options).toEqualTypeOf<Base>()
|
|
373
382
|
expectTypeOf(ctx).toEqualTypeOf<GokeExecutionContext>()
|
|
374
383
|
// No more positional slots — rest should be empty
|
|
375
384
|
expectTypeOf(rest).toEqualTypeOf<[]>()
|
package/src/goke.ts
CHANGED
|
@@ -352,13 +352,17 @@ type OptionEntry<RawName extends string, Schema> =
|
|
|
352
352
|
* Infer the raw runtime value shape for an option declared without a schema.
|
|
353
353
|
*
|
|
354
354
|
* Required value options (`--port <port>`) always reach actions as strings.
|
|
355
|
-
* Optional value options (`--host [host]`)
|
|
356
|
-
*
|
|
355
|
+
* Optional value options (`--host [host]`) reach actions as strings: the
|
|
356
|
+
* empty string `''` when the flag is passed bare (`--host`), the given
|
|
357
|
+
* value when passed with one (`--host example.com`), and `undefined` when
|
|
358
|
+
* the flag is omitted entirely. This lets callers use a single `typeof`
|
|
359
|
+
* check and, if they really care, distinguish "omitted" from "present but
|
|
360
|
+
* empty" via `=== undefined` vs `=== ''`.
|
|
357
361
|
* Plain flags (`--verbose`) are booleans.
|
|
358
362
|
*/
|
|
359
363
|
type UntypedOptionValue<RawName extends string> =
|
|
360
364
|
RawName extends `${string}<${string}>` ? string :
|
|
361
|
-
RawName extends `${string}[${string}]` ? string
|
|
365
|
+
RawName extends `${string}[${string}]` ? string :
|
|
362
366
|
boolean | undefined
|
|
363
367
|
|
|
364
368
|
/**
|
|
@@ -422,6 +426,14 @@ type ExtractCommandArgs<T extends readonly string[]> =
|
|
|
422
426
|
type ExtractPositionalArgs<RawName extends string> =
|
|
423
427
|
ExtractCommandArgs<TokenizeName<RawName>>
|
|
424
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Everything after a literal `--` on the command line is collected into
|
|
431
|
+
* `options['--']` as a string array (empty when `--` is absent). This key
|
|
432
|
+
* is always present at runtime, so it's merged into every action's options
|
|
433
|
+
* type regardless of which options the user declared.
|
|
434
|
+
*/
|
|
435
|
+
type DoubleDashOptions = { '--': string[] }
|
|
436
|
+
|
|
425
437
|
/**
|
|
426
438
|
* Build the full argument tuple passed to a command's action callback.
|
|
427
439
|
*
|
|
@@ -430,9 +442,16 @@ type ExtractPositionalArgs<RawName extends string> =
|
|
|
430
442
|
* This matches the runtime behavior in Goke.runMatchedCommand(): the action
|
|
431
443
|
* is called with positional args from the parsed command, then the parsed
|
|
432
444
|
* options object, then the injected GokeExecutionContext.
|
|
445
|
+
*
|
|
446
|
+
* The options type is always extended with `{ '--': string[] }` because the
|
|
447
|
+
* parser always populates that key (see `Goke.parse()`).
|
|
433
448
|
*/
|
|
434
449
|
type ActionArgs<RawName extends string, Opts> =
|
|
435
|
-
[
|
|
450
|
+
[
|
|
451
|
+
...ExtractPositionalArgs<RawName>,
|
|
452
|
+
Opts & DoubleDashOptions,
|
|
453
|
+
GokeExecutionContext,
|
|
454
|
+
]
|
|
436
455
|
|
|
437
456
|
interface CommandArg {
|
|
438
457
|
required: boolean
|
|
@@ -1239,7 +1258,12 @@ class Goke<Opts = {}> extends EventEmitter {
|
|
|
1239
1258
|
* })
|
|
1240
1259
|
* ```
|
|
1241
1260
|
*/
|
|
1242
|
-
use(
|
|
1261
|
+
use(
|
|
1262
|
+
callback: (
|
|
1263
|
+
options: Opts & DoubleDashOptions,
|
|
1264
|
+
context: GokeExecutionContext,
|
|
1265
|
+
) => void | Promise<void>,
|
|
1266
|
+
): this {
|
|
1243
1267
|
this.middlewares.push({ action: callback })
|
|
1244
1268
|
return this
|
|
1245
1269
|
}
|
|
@@ -1592,7 +1616,9 @@ class Goke<Opts = {}> extends EventEmitter {
|
|
|
1592
1616
|
//
|
|
1593
1617
|
// When mri returns `true` for value-taking options, it means "flag present, no value given".
|
|
1594
1618
|
// For required options (<...>), the sentinel is preserved so checkOptionValue() throws.
|
|
1595
|
-
// For optional options ([...])
|
|
1619
|
+
// For optional options ([...]) we want a single, uniform shape: `string`
|
|
1620
|
+
// with `''` meaning "flag present but no value" — callers get clean
|
|
1621
|
+
// `string | undefined` types instead of `string | boolean | undefined`.
|
|
1596
1622
|
const requiredValueOptions = new Set<string>()
|
|
1597
1623
|
const optionalValueOptions = new Set<string>()
|
|
1598
1624
|
for (const cliOption of cliOptions) {
|
|
@@ -1618,18 +1644,25 @@ class Goke<Opts = {}> extends EventEmitter {
|
|
|
1618
1644
|
// When value is boolean `true` and the option takes a value, it's mri's sentinel
|
|
1619
1645
|
// for "flag present, no value given":
|
|
1620
1646
|
// - Required options (<...>): preserve `true` so checkOptionValue() throws
|
|
1621
|
-
// - Optional options ([...]) with schema: replace with `undefined`
|
|
1622
|
-
//
|
|
1647
|
+
// - Optional options ([...]) with schema: replace with `undefined` so
|
|
1648
|
+
// any `.default(...)` on the schema kicks in (e.g. z.number().default(30)
|
|
1649
|
+
// should produce 30, not try to coerce the `''` empty-string sentinel).
|
|
1623
1650
|
const schemaInfo = schemaMap.get(key)
|
|
1624
1651
|
if (schemaInfo && value !== undefined) {
|
|
1625
1652
|
if (value === true && requiredValueOptions.has(key)) {
|
|
1626
1653
|
// Keep sentinel for checkOptionValue() to detect
|
|
1627
1654
|
} else if (value === true && optionalValueOptions.has(key)) {
|
|
1628
|
-
// Optional value not given — schema expects a typed value, so return undefined
|
|
1629
1655
|
value = undefined
|
|
1630
1656
|
} else {
|
|
1631
1657
|
value = coerceBySchema(value, schemaInfo.jsonSchema, schemaInfo.optionName)
|
|
1632
1658
|
}
|
|
1659
|
+
} else if (value === true && optionalValueOptions.has(key)) {
|
|
1660
|
+
// Untyped optional-value flag with no schema: normalize bare `true`
|
|
1661
|
+
// to `''` so callers get a clean `string | undefined` shape. `''`
|
|
1662
|
+
// means "flag passed with no argument", distinct from `undefined`
|
|
1663
|
+
// (flag omitted). This matches the new type inference that treats
|
|
1664
|
+
// `[value]` as `string` instead of `string | boolean`.
|
|
1665
|
+
value = ''
|
|
1633
1666
|
}
|
|
1634
1667
|
|
|
1635
1668
|
setDotProp(options, keys, value)
|