@gunshi/bone 0.27.0-alpha.1 → 0.27.0-alpha.10

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.
Files changed (3) hide show
  1. package/lib/index.d.ts +204 -92
  2. package/lib/index.js +82 -20
  3. package/package.json +7 -7
package/lib/index.d.ts CHANGED
@@ -1,151 +1,186 @@
1
- //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/parser-FiQIAw-2.d.ts
1
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/parser-Cbxholql.d.ts
2
2
  //#region src/parser.d.ts
3
3
  /**
4
- * Entry point of argument parser.
5
- * @module
6
- */
7
- /**
8
- * forked from `nodejs/node` (`pkgjs/parseargs`)
9
- * repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
10
- * code url: https://github.com/nodejs/node/blob/main/lib/internal/util/parse_args/parse_args.js
11
- *
12
- * @author kazuya kawaguchi (a.k.a. kazupon)
13
- * @license MIT
14
- */
15
- /**
16
- * Argument token Kind.
17
- * - `option`: option token, support short option (e.g. `-x`) and long option (e.g. `--foo`)
18
- * - `option-terminator`: option terminator (`--`) token, see guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
19
- * - `positional`: positional token
20
- */
21
- type ArgTokenKind = "option" | "option-terminator" | "positional";
22
- /**
23
- * Argument token.
24
- */
4
+ * Entry point of argument parser.
5
+ * @module
6
+ */
7
+ /**
8
+ * forked from `nodejs/node` (`pkgjs/parseargs`)
9
+ * repository url: https://github.com/nodejs/node (https://github.com/pkgjs/parseargs)
10
+ * code url: https://github.com/nodejs/node/blob/main/lib/internal/util/parse_args/parse_args.js
11
+ *
12
+ * @author kazuya kawaguchi (a.k.a. kazupon)
13
+ * @license MIT
14
+ */
15
+ /**
16
+ * Argument token Kind.
17
+ * - `option`: option token, support short option (e.g. `-x`) and long option (e.g. `--foo`)
18
+ * - `option-terminator`: option terminator (`--`) token, see guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
19
+ * - `positional`: positional token
20
+ */
21
+ type ArgTokenKind = 'option' | 'option-terminator' | 'positional';
22
+ /**
23
+ * Argument token.
24
+ */
25
25
  interface ArgToken {
26
26
  /**
27
- * Argument token kind.
28
- */
27
+ * Argument token kind.
28
+ */
29
29
  kind: ArgTokenKind;
30
30
  /**
31
- * Argument token index, e.g `--foo bar` => `--foo` index is 0, `bar` index is 1.
32
- */
31
+ * Argument token index, e.g `--foo bar` => `--foo` index is 0, `bar` index is 1.
32
+ */
33
33
  index: number;
34
34
  /**
35
- * Option name, e.g. `--foo` => `foo`, `-x` => `x`.
36
- */
35
+ * Option name, e.g. `--foo` => `foo`, `-x` => `x`.
36
+ */
37
37
  name?: string;
38
38
  /**
39
- * Raw option name, e.g. `--foo` => `--foo`, `-x` => `-x`.
40
- */
39
+ * Raw option name, e.g. `--foo` => `--foo`, `-x` => `-x`.
40
+ */
41
41
  rawName?: string;
42
42
  /**
43
- * Option value, e.g. `--foo=bar` => `bar`, `-x=bar` => `bar`.
44
- * If the `allowCompatible` option is `true`, short option value will be same as Node.js `parseArgs` behavior.
45
- */
43
+ * Option value, e.g. `--foo=bar` => `bar`, `-x=bar` => `bar`.
44
+ * If the `allowCompatible` option is `true`, short option value will be same as Node.js `parseArgs` behavior.
45
+ */
46
46
  value?: string;
47
47
  /**
48
- * Inline value, e.g. `--foo=bar` => `true`, `-x=bar` => `true`.
49
- */
48
+ * Inline value, e.g. `--foo=bar` => `true`, `-x=bar` => `true`.
49
+ */
50
50
  inlineValue?: boolean;
51
51
  }
52
52
  /**
53
- * Parser Options.
54
- */
53
+ * Parser Options.
54
+ */
55
55
  //#endregion
56
- //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/resolver-U72Jg6Ll.d.ts
56
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/resolver-BoS-UnqX.d.ts
57
57
  //#region src/resolver.d.ts
58
58
 
59
59
  /**
60
- * An argument schema
61
- * This schema is similar to the schema of the `node:utils`.
62
- * difference is that:
63
- * - `required` property and `description` property are added
64
- * - `type` is not only 'string' and 'boolean', but also 'number', 'enum', 'positional', 'custom' too.
65
- * - `default` property type, not support multiple types
66
- */
60
+ * An argument schema
61
+ * This schema is similar to the schema of the `node:utils`.
62
+ * difference is that:
63
+ * - `required` property and `description` property are added
64
+ * - `type` is not only 'string' and 'boolean', but also 'number', 'enum', 'positional', 'custom' too.
65
+ * - `default` property type, not support multiple types
66
+ */
67
67
  interface ArgSchema {
68
68
  /**
69
- * Type of argument.
70
- */
71
- type: "string" | "boolean" | "number" | "enum" | "positional" | "custom";
69
+ * Type of argument.
70
+ */
71
+ type: 'string' | 'boolean' | 'number' | 'enum' | 'positional' | 'custom';
72
72
  /**
73
- * A single character alias for the argument.
74
- */
73
+ * A single character alias for the argument.
74
+ */
75
75
  short?: string;
76
76
  /**
77
- * A description of the argument.
78
- */
77
+ * A description of the argument.
78
+ */
79
79
  description?: string;
80
80
  /**
81
- * Whether the argument is required or not.
82
- */
81
+ * Whether the argument is required or not.
82
+ */
83
83
  required?: true;
84
84
  /**
85
- * Whether the argument allow multiple values or not.
86
- */
85
+ * Whether the argument allow multiple values or not.
86
+ */
87
87
  multiple?: true;
88
88
  /**
89
- * Whether the negatable option for `boolean` type
90
- */
89
+ * Whether the negatable option for `boolean` type
90
+ */
91
91
  negatable?: boolean;
92
92
  /**
93
- * The allowed values of the argument, and string only. This property is only used when the type is 'enum'.
94
- */
93
+ * The allowed values of the argument, and string only. This property is only used when the type is 'enum'.
94
+ */
95
95
  choices?: string[] | readonly string[];
96
96
  /**
97
- * The default value of the argument.
98
- * if the type is 'enum', the default value must be one of the allowed values.
99
- */
97
+ * The default value of the argument.
98
+ * if the type is 'enum', the default value must be one of the allowed values.
99
+ */
100
100
  default?: string | boolean | number;
101
101
  /**
102
- * Whether to convert the argument name to kebab-case.
103
- */
102
+ * Whether to convert the argument name to kebab-case.
103
+ */
104
104
  toKebab?: true;
105
105
  /**
106
- * A function to parse the value of the argument. if the type is 'custom', this function is required.
107
- * If argument value will be invalid, this function have to throw an error.
108
- * @param value
109
- * @returns Parsed value
110
- * @throws An Error, If the value is invalid. Error type should be `Error` or extends it
111
- */
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ * A function to parse the value of the argument. if the type is 'custom', this function is required.
107
+ * If argument value will be invalid, this function have to throw an error.
108
+ * @param value
109
+ * @returns Parsed value
110
+ * @throws An Error, If the value is invalid. Error type should be `Error` or extends it
111
+ */
113
112
  parse?: (value: string) => any;
114
113
  }
115
114
  /**
116
- * An object that contains {@link ArgSchema | argument schema}.
117
- */
115
+ * An object that contains {@link ArgSchema | argument schema}.
116
+ */
118
117
  interface Args {
119
118
  [option: string]: ArgSchema;
120
119
  }
121
120
  /**
122
- * An object that contains the values of the arguments.
123
- */
121
+ * An object that contains the values of the arguments.
122
+ */
124
123
  type ArgValues<T> = T extends Args ? ResolveArgValues<T, { [Arg in keyof T]: ExtractOptionValue<T[Arg]> }> : {
125
124
  [option: string]: string | boolean | number | (string | boolean | number)[] | undefined;
126
125
  };
127
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
126
  type IsFunction<T> = T extends ((...args: any[]) => any) ? true : false;
129
127
  /**
130
- * @internal
131
- */
132
- type ExtractOptionValue<A extends ArgSchema> = A["type"] extends "string" ? ResolveOptionValue<A, string> : A["type"] extends "boolean" ? ResolveOptionValue<A, boolean> : A["type"] extends "number" ? ResolveOptionValue<A, number> : A["type"] extends "positional" ? ResolveOptionValue<A, string> : A["type"] extends "enum" ? A["choices"] extends string[] | readonly string[] ? ResolveOptionValue<A, A["choices"][number]> : never : A["type"] extends "custom" ? IsFunction<A["parse"]> extends true ? ResolveOptionValue<A, ReturnType<NonNullable<A["parse"]>>> : never : ResolveOptionValue<A, string | boolean | number>;
133
- type ResolveOptionValue<A extends ArgSchema, T> = A["multiple"] extends true ? T[] : T;
128
+ * @internal
129
+ */
130
+ type ExtractOptionValue<A extends ArgSchema> = A['type'] extends 'string' ? ResolveOptionValue<A, string> : A['type'] extends 'boolean' ? ResolveOptionValue<A, boolean> : A['type'] extends 'number' ? ResolveOptionValue<A, number> : A['type'] extends 'positional' ? ResolveOptionValue<A, string> : A['type'] extends 'enum' ? A['choices'] extends string[] | readonly string[] ? ResolveOptionValue<A, A['choices'][number]> : never : A['type'] extends 'custom' ? IsFunction<A['parse']> extends true ? ResolveOptionValue<A, ReturnType<NonNullable<A['parse']>>> : never : ResolveOptionValue<A, string | boolean | number>;
131
+ type ResolveOptionValue<A extends ArgSchema, T> = A['multiple'] extends true ? T[] : T;
134
132
  /**
135
- * @internal
136
- */
137
- type ResolveArgValues<A extends Args, V extends Record<keyof A, unknown>> = { -readonly [Arg in keyof A]?: V[Arg] } & FilterArgs<A, V, "default"> & FilterArgs<A, V, "required"> & FilterPositionalArgs<A, V> extends infer P ? { [K in keyof P]: P[K] } : never;
133
+ * @internal
134
+ */
135
+ type ResolveArgValues<A extends Args, V extends Record<keyof A, unknown>> = { -readonly [Arg in keyof A]?: V[Arg] } & FilterArgs<A, V, 'default'> & FilterArgs<A, V, 'required'> & FilterPositionalArgs<A, V> extends infer P ? { [K in keyof P]: P[K] } : never;
138
136
  /**
139
- * @internal
140
- */
137
+ * @internal
138
+ */
141
139
  type FilterArgs<A extends Args, V extends Record<keyof A, unknown>, K extends keyof ArgSchema> = { [Arg in keyof A as A[Arg][K] extends {} ? Arg : never]: V[Arg] };
142
140
  /**
143
- * @internal
144
- */
145
- type FilterPositionalArgs<A extends Args, V extends Record<keyof A, unknown>> = { [Arg in keyof A as A[Arg]["type"] extends "positional" ? Arg : never]: V[Arg] };
141
+ * @internal
142
+ */
143
+ type FilterPositionalArgs<A extends Args, V extends Record<keyof A, unknown>> = { [Arg in keyof A as A[Arg]['type'] extends 'positional' ? Arg : never]: V[Arg] };
146
144
  /**
147
- * An arguments for {@link resolveArgs | resolve arguments}.
148
- */
145
+ * An arguments for {@link resolveArgs | resolve arguments}.
146
+ */
147
+
148
+ /**
149
+ * Tracks which arguments were explicitly provided by the user.
150
+ *
151
+ * Each property indicates whether the corresponding argument was explicitly
152
+ * provided (true) or is using a default value or not provided (false).
153
+ */
154
+ type ArgExplicitlyProvided<A extends Args> = { [K in keyof A]: boolean };
155
+ /**
156
+ * Resolve command line arguments.
157
+ * @param args - An arguments that contains {@link ArgSchema | arguments schema}.
158
+ * @param tokens - An array of {@link ArgToken | tokens}.
159
+ * @param resolveArgs - An arguments that contains {@link ResolveArgs | resolve arguments}.
160
+ * @returns An object that contains the values of the arguments, positional arguments, rest arguments, {@link AggregateError | validation errors}, and explicit provision status.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * // passed tokens: --port 3000
165
+ *
166
+ * const { values, explicit } = resolveArgs({
167
+ * port: {
168
+ * type: 'number',
169
+ * default: 8080
170
+ * },
171
+ * host: {
172
+ * type: 'string',
173
+ * default: 'localhost'
174
+ * }
175
+ * }, parsedTokens)
176
+ *
177
+ * values.port // 3000
178
+ * values.host // 'localhost'
179
+ *
180
+ * explicit.port // true (explicitly provided)
181
+ * explicit.host // false (not provided, fallback to default)
182
+ * ```
183
+ */
149
184
  //#endregion
150
185
  //#region ../gunshi/src/plugin/context.d.ts
151
186
  /**
@@ -166,12 +201,29 @@ interface PluginContext<G extends GunshiParamsConstraint = DefaultGunshiParams>
166
201
  * @returns A map of global options.
167
202
  */
168
203
  readonly globalOptions: Map<string, ArgSchema>;
204
+ /**
205
+ * Get the registered sub commands
206
+ * @returns A map of sub commands.
207
+ */
208
+ readonly subCommands: ReadonlyMap<string, Command<G> | LazyCommand<G>>;
169
209
  /**
170
210
  * Add a global option.
171
211
  * @param name An option name
172
212
  * @param schema An {@link ArgSchema} for the option
173
213
  */
174
214
  addGlobalOption(name: string, schema: ArgSchema): void;
215
+ /**
216
+ * Add a sub command.
217
+ * @param name Command name
218
+ * @param command Command definition
219
+ */
220
+ addCommand(name: string, command: Command<G> | LazyCommand<G>): void;
221
+ /**
222
+ * Check if a command exists.
223
+ * @param name Command name
224
+ * @returns True if the command exists, false otherwise
225
+ */
226
+ hasCommand(name: string): boolean;
175
227
  /**
176
228
  * Decorate the header renderer.
177
229
  * @param decorator - A decorator function that wraps the base header renderer.
@@ -197,6 +249,7 @@ interface PluginContext<G extends GunshiParamsConstraint = DefaultGunshiParams>
197
249
  /**
198
250
  * Factory function for creating a plugin context.
199
251
  * @param decorators - A {@link Decorators} instance.
252
+ * @param initialSubCommands - Initial sub commands map.
200
253
  * @returns A new {@link PluginContext} instance.
201
254
  */
202
255
  //#endregion
@@ -293,6 +346,11 @@ type GunshiParamsConstraint = GunshiParams<any> | {
293
346
  * @internal
294
347
  */
295
348
  type ExtractArgs<G> = G extends GunshiParams<any> ? G['args'] : Args;
349
+ /**
350
+ * Type helper to extract explicitly provided argument flags from G
351
+ * @internal
352
+ */
353
+ type ExtractArgExplicitlyProvided<G> = ArgExplicitlyProvided<ExtractArgs<G>>;
296
354
  /**
297
355
  * Type helper to extract extensions from G
298
356
  * @internal
@@ -513,6 +571,15 @@ interface CommandContext<G extends GunshiParamsConstraint = DefaultGunshiParams>
513
571
  * The command arguments is same {@link Command.args}.
514
572
  */
515
573
  args: ExtractArgs<G>;
574
+ /**
575
+ * Whether arguments were explicitly provided by the user.
576
+ *
577
+ * - `true`: The argument was explicitly provided via command line
578
+ * - `false`: The argument was not explicitly provided. This means either:
579
+ * - The value comes from a default value defined in the argument schema
580
+ * - The value is `undefined` (no explicit input and no default value)
581
+ */
582
+ explicit: ExtractArgExplicitlyProvided<G>;
516
583
  /**
517
584
  * Command values, that is the values of the command that is executed.
518
585
  * Resolve values with `resolveArgs` from command arguments and {@link Command.args}.
@@ -583,6 +650,33 @@ interface CommandContextExtension<E extends GunshiParams['extensions'] = Default
583
650
  readonly factory: (ctx: CommandContextCore, cmd: Command) => Awaitable<E>;
584
651
  readonly onFactory?: (ctx: Readonly<CommandContext>, cmd: Readonly<Command>) => Awaitable<void>;
585
652
  }
653
+ /**
654
+ * Rendering control options
655
+ * @since v0.27.0
656
+ */
657
+ interface RenderingOptions<G extends GunshiParamsConstraint = DefaultGunshiParams> {
658
+ /**
659
+ * Header rendering configuration
660
+ * - `null`: Disable rendering
661
+ * - `function`: Use custom renderer
662
+ * - `undefined` (when omitted): Use default renderer
663
+ */
664
+ header?: ((ctx: Readonly<CommandContext<G>>) => Promise<string>) | null;
665
+ /**
666
+ * Usage rendering configuration
667
+ * - `null`: Disable rendering
668
+ * - `function`: Use custom renderer
669
+ * - `undefined` (when omitted): Use default renderer
670
+ */
671
+ usage?: ((ctx: Readonly<CommandContext<G>>) => Promise<string>) | null;
672
+ /**
673
+ * Validation errors rendering configuration
674
+ * - `null`: Disable rendering
675
+ * - `function`: Use custom renderer
676
+ * - `undefined` (when omitted): Use default renderer
677
+ */
678
+ validationErrors?: ((ctx: Readonly<CommandContext<G>>, error: AggregateError) => Promise<string>) | null;
679
+ }
586
680
  /**
587
681
  * Command interface.
588
682
  */
@@ -616,6 +710,24 @@ interface Command<G extends GunshiParamsConstraint = DefaultGunshiParams> {
616
710
  * If you will set to `true`, All {@link Command.args} names will be converted to kebab-case.
617
711
  */
618
712
  toKebab?: boolean;
713
+ /**
714
+ * Whether this is an internal command.
715
+ * Internal commands are not shown in help usage.
716
+ * @default false
717
+ * @since v0.27.0
718
+ */
719
+ internal?: boolean;
720
+ /**
721
+ * Whether this command is an entry command.
722
+ * @default undefined
723
+ * @since v0.27.0
724
+ */
725
+ entry?: boolean;
726
+ /**
727
+ * Rendering control options
728
+ * @since v0.27.0
729
+ */
730
+ rendering?: RenderingOptions<G>;
619
731
  }
620
732
  /**
621
733
  * Lazy command interface.
@@ -714,4 +826,4 @@ declare function cli<E extends ExtendContext = ExtendContext, G extends GunshiPa
714
826
  */
715
827
  declare function cli<G extends GunshiParams = DefaultGunshiParams>(argv: string[], entry: Command<G> | CommandRunner<G> | LazyCommand<G>, options?: CliOptions<G>): Promise<string | undefined>;
716
828
  //#endregion
717
- export { ArgSchema, ArgToken, ArgValues, Args, Awaitable, CliOptions, Command, CommandCallMode, CommandContext, CommandContextCore, CommandContextExtension, CommandDecorator, CommandEnvironment, CommandExamplesFetcher, CommandLoader, CommandRunner, Commandable, DefaultGunshiParams, ExtendContext, ExtractArgs, ExtractExtensions, GunshiParams, GunshiParamsConstraint, LazyCommand, NormalizeToGunshiParams, RendererDecorator, ValidationErrorsDecorator, cli };
829
+ export { type ArgSchema, type ArgToken, type ArgValues, type Args, Awaitable, CliOptions, Command, CommandCallMode, CommandContext, CommandContextCore, CommandContextExtension, CommandDecorator, CommandEnvironment, CommandExamplesFetcher, CommandLoader, CommandRunner, Commandable, DefaultGunshiParams, ExtendContext, ExtractArgExplicitlyProvided, ExtractArgs, ExtractExtensions, GunshiParams, GunshiParamsConstraint, LazyCommand, NormalizeToGunshiParams, RendererDecorator, RenderingOptions, ValidationErrorsDecorator, cli };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/parser-Dr4iAGaX.js
1
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/parser-Dr4iAGaX.js
2
2
  const HYPHEN_CHAR = "-";
3
3
  const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
4
4
  const EQUAL_CHAR = "=";
@@ -188,7 +188,7 @@ function hasOptionValue(value) {
188
188
  }
189
189
 
190
190
  //#endregion
191
- //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/utils-N7UlhLbz.js
191
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/utils-N7UlhLbz.js
192
192
  /**
193
193
  * Entry point of utils.
194
194
  *
@@ -205,14 +205,36 @@ function kebabnize(str) {
205
205
  }
206
206
 
207
207
  //#endregion
208
- //#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/resolver-Q4k2fgTW.js
208
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/resolver-Bcd8oyPt.js
209
209
  const SKIP_POSITIONAL_DEFAULT = -1;
210
210
  /**
211
211
  * Resolve command line arguments.
212
212
  * @param args - An arguments that contains {@link ArgSchema | arguments schema}.
213
213
  * @param tokens - An array of {@link ArgToken | tokens}.
214
214
  * @param resolveArgs - An arguments that contains {@link ResolveArgs | resolve arguments}.
215
- * @returns An object that contains the values of the arguments, positional arguments, rest arguments, and {@link AggregateError | validation errors}.
215
+ * @returns An object that contains the values of the arguments, positional arguments, rest arguments, {@link AggregateError | validation errors}, and explicit provision status.
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * // passed tokens: --port 3000
220
+ *
221
+ * const { values, explicit } = resolveArgs({
222
+ * port: {
223
+ * type: 'number',
224
+ * default: 8080
225
+ * },
226
+ * host: {
227
+ * type: 'string',
228
+ * default: 'localhost'
229
+ * }
230
+ * }, parsedTokens)
231
+ *
232
+ * values.port // 3000
233
+ * values.host // 'localhost'
234
+ *
235
+ * explicit.port // true (explicitly provided)
236
+ * explicit.host // false (not provided, fallback to default)
237
+ * ```
216
238
  */
217
239
  function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKIP_POSITIONAL_DEFAULT, toKebab = false } = {}) {
218
240
  const skipPositionalIndex = typeof skipPositional === "number" ? Math.max(skipPositional, SKIP_POSITIONAL_DEFAULT) : SKIP_POSITIONAL_DEFAULT;
@@ -312,6 +334,7 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
312
334
  */
313
335
  const values = Object.create(null);
314
336
  const errors = [];
337
+ const explicit = Object.create(null);
315
338
  function checkTokenName(option, schema, token) {
316
339
  return token.name === (schema.type === "boolean" ? schema.negatable && token.name?.startsWith("no-") ? `no-${option}` : option : option);
317
340
  }
@@ -322,6 +345,7 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
322
345
  let positionalsCount = 0;
323
346
  for (const [rawArg, schema] of Object.entries(args)) {
324
347
  const arg = toKebab || schema.toKebab ? kebabnize(rawArg) : rawArg;
348
+ explicit[rawArg] = false;
325
349
  if (schema.required) {
326
350
  const found = optionTokens.find((token) => {
327
351
  return schema.short && token.name === schema.short || token.rawName && hasLongOptionPrefix(token.rawName) && token.name === arg;
@@ -347,6 +371,7 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
347
371
  errors.push(invalid);
348
372
  continue;
349
373
  }
374
+ explicit[rawArg] = true;
350
375
  if (schema.type === "boolean") token.value = void 0;
351
376
  const [parsedValue, error] = parse(token, arg, schema);
352
377
  if (error) errors.push(error);
@@ -362,7 +387,8 @@ function resolveArgs(args, tokens, { shortGrouping = false, skipPositional = SKI
362
387
  values,
363
388
  positionals: positionalTokens.map((token) => token.value),
364
389
  rest,
365
- error: errors.length > 0 ? new AggregateError(errors) : void 0
390
+ error: errors.length > 0 ? new AggregateError(errors) : void 0,
391
+ explicit
366
392
  };
367
393
  }
368
394
  function parse(token, option, schema) {
@@ -421,7 +447,7 @@ function createTypeError(option, schema) {
421
447
  //#region ../gunshi/src/constants.ts
422
448
  const ANONYMOUS_COMMAND_NAME = "(anonymous)";
423
449
  const NOOP = () => {};
424
- const COMMAND_OPTIONS_DEFAULT = {
450
+ const CLI_OPTIONS_DEFAULT = {
425
451
  name: void 0,
426
452
  description: void 0,
427
453
  version: void 0,
@@ -450,7 +476,9 @@ async function resolveLazyCommand(cmd, name, needRunResolving = false) {
450
476
  name: cmd.commandName,
451
477
  description: cmd.description,
452
478
  args: cmd.args,
453
- examples: cmd.examples
479
+ examples: cmd.examples,
480
+ internal: cmd.internal,
481
+ entry: cmd.entry
454
482
  };
455
483
  if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
456
484
  command = Object.assign(create(), baseCommand);
@@ -464,6 +492,8 @@ async function resolveLazyCommand(cmd, name, needRunResolving = false) {
464
492
  command.description = loaded.description;
465
493
  command.args = loaded.args;
466
494
  command.examples = loaded.examples;
495
+ command.internal = loaded.internal;
496
+ command.entry = loaded.entry;
467
497
  if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
468
498
  } else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
469
499
  }
@@ -494,7 +524,7 @@ function deepFreeze(obj, ignores = []) {
494
524
  * @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context}
495
525
  * @returns A {@link CommandContext | command context}, which is readonly
496
526
  */
497
- async function createCommandContext({ args, values, positionals, rest, argv, tokens, command, extensions = {}, cliOptions, callMode = "entry", omitted = false, validationError }) {
527
+ async function createCommandContext({ args, explicit, values, positionals, rest, argv, tokens, command, extensions = {}, cliOptions, callMode = "entry", omitted = false, validationError }) {
498
528
  /**
499
529
  * normailize the options schema and values, to avoid prototype pollution
500
530
  */
@@ -505,7 +535,16 @@ async function createCommandContext({ args, values, positionals, rest, argv, tok
505
535
  /**
506
536
  * setup the environment
507
537
  */
508
- const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, cliOptions);
538
+ const env = Object.assign(create(), CLI_OPTIONS_DEFAULT, cliOptions);
539
+ /**
540
+ * apply Command definition's rendering option with highest priority
541
+ */
542
+ if (command.rendering) {
543
+ const { header, usage, validationErrors } = command.rendering;
544
+ if (header !== void 0) env.renderHeader = header;
545
+ if (usage !== void 0) env.renderUsage = usage;
546
+ if (validationErrors !== void 0) env.renderValidationErrors = validationErrors;
547
+ }
509
548
  /**
510
549
  * create the command context
511
550
  */
@@ -516,6 +555,7 @@ async function createCommandContext({ args, values, positionals, rest, argv, tok
516
555
  callMode,
517
556
  env,
518
557
  args: _args,
558
+ explicit,
519
559
  values,
520
560
  positionals,
521
561
  rest,
@@ -619,13 +659,15 @@ function createDecorators() {
619
659
  /**
620
660
  * Factory function for creating a plugin context.
621
661
  * @param decorators - A {@link Decorators} instance.
662
+ * @param initialSubCommands - Initial sub commands map.
622
663
  * @returns A new {@link PluginContext} instance.
623
664
  */
624
- function createPluginContext(decorators) {
665
+ function createPluginContext(decorators, initialSubCommands) {
625
666
  /**
626
667
  * private states
627
668
  */
628
669
  const globalOptions = new Map();
670
+ const subCommands = new Map(initialSubCommands || []);
629
671
  /**
630
672
  * public interfaces
631
673
  */
@@ -638,6 +680,17 @@ function createPluginContext(decorators) {
638
680
  if (globalOptions.has(name)) throw new Error(`Global option '${name}' is already registered`);
639
681
  globalOptions.set(name, schema);
640
682
  },
683
+ get subCommands() {
684
+ return new Map(subCommands);
685
+ },
686
+ addCommand(name, command) {
687
+ if (!name) throw new Error("Command name must be a non-empty string");
688
+ if (subCommands.has(name)) throw new Error(`Command '${name}' is already registered`);
689
+ subCommands.set(name, command);
690
+ },
691
+ hasCommand(name) {
692
+ return subCommands.has(name);
693
+ },
641
694
  decorateHeaderRenderer(decorator) {
642
695
  decorators.addHeaderDecorator(decorator);
643
696
  },
@@ -695,15 +748,16 @@ function resolveDependencies(plugins) {
695
748
  //#region ../gunshi/src/cli/core.ts
696
749
  async function cliCore(argv, entry, options, plugins) {
697
750
  const decorators = createDecorators();
698
- const pluginContext = createPluginContext(decorators);
751
+ const initialSubCommands = createInitialSubCommands(options, entry);
752
+ const pluginContext = createPluginContext(decorators, initialSubCommands);
699
753
  const resolvedPlugins = await applyPlugins(pluginContext, [...plugins, ...options.plugins || []]);
700
- const cliOptions = normalizeCliOptions(options, entry, decorators);
754
+ const cliOptions = normalizeCliOptions(options, decorators, pluginContext);
701
755
  const tokens = parseArgs(argv);
702
756
  const subCommand = getSubCommand(tokens);
703
757
  const { commandName: name, command, callMode } = await resolveCommand(subCommand, entry, cliOptions);
704
758
  if (!command) throw new Error(`Command not found: ${name || ""}`);
705
759
  const args = resolveArguments(pluginContext, getCommandArgs(command));
706
- const { values, positionals, rest, error } = resolveArgs(args, tokens, {
760
+ const { explicit, values, positionals, rest, error } = resolveArgs(args, tokens, {
707
761
  shortGrouping: true,
708
762
  toKebab: command.toKebab,
709
763
  skipPositional: cliOptions.subCommands.size > 0 ? 0 : -1
@@ -711,6 +765,7 @@ async function cliCore(argv, entry, options, plugins) {
711
765
  const omitted = !subCommand;
712
766
  const commandContext = await createCommandContext({
713
767
  args,
768
+ explicit,
714
769
  values,
715
770
  positionals,
716
771
  rest,
@@ -749,13 +804,17 @@ function getCommandArgs(cmd) {
749
804
  function resolveArguments(pluginContext, args) {
750
805
  return Object.assign(create(), Object.fromEntries(pluginContext.globalOptions), args);
751
806
  }
752
- function normalizeCliOptions(options, entry, decorators) {
807
+ function createInitialSubCommands(options, entryCmd) {
753
808
  const subCommands = new Map(options.subCommands);
754
- if (options.subCommands) {
755
- if (isLazyCommand(entry)) subCommands.set(entry.commandName, entry);
756
- else if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
809
+ if ((options.subCommands || subCommands.size > 0) && (isLazyCommand(entryCmd) || typeof entryCmd === "object")) {
810
+ entryCmd.entry = true;
811
+ subCommands.set(resolveEntryName(entryCmd), entryCmd);
757
812
  }
758
- const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
813
+ return subCommands;
814
+ }
815
+ function normalizeCliOptions(options, decorators, pluginContext) {
816
+ const subCommands = new Map(pluginContext.subCommands);
817
+ const resolvedOptions = Object.assign(create(), CLI_OPTIONS_DEFAULT, options, { subCommands });
759
818
  if (resolvedOptions.renderHeader === void 0) resolvedOptions.renderHeader = decorators.getHeaderRenderer();
760
819
  if (resolvedOptions.renderUsage === void 0) resolvedOptions.renderUsage = decorators.getUsageRenderer();
761
820
  if (resolvedOptions.renderValidationErrors === void 0) resolvedOptions.renderValidationErrors = decorators.getValidationErrorsRenderer();
@@ -775,7 +834,10 @@ async function resolveCommand(sub, entry, options) {
775
834
  callMode: "entry"
776
835
  };
777
836
  else return {
778
- command: { run: entry },
837
+ command: {
838
+ run: entry,
839
+ entry: true
840
+ },
779
841
  callMode: "entry"
780
842
  };
781
843
  else if (typeof entry === "object") return {
@@ -800,7 +862,7 @@ async function resolveCommand(sub, entry, options) {
800
862
  };
801
863
  }
802
864
  function resolveEntryName(entry) {
803
- return entry.name || ANONYMOUS_COMMAND_NAME;
865
+ return isLazyCommand(entry) ? entry.commandName || ANONYMOUS_COMMAND_NAME : entry.name || ANONYMOUS_COMMAND_NAME;
804
866
  }
805
867
  function getPluginExtensions(plugins) {
806
868
  const extensions = create();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/bone",
3
3
  "description": "gunshi minimum",
4
- "version": "0.27.0-alpha.1",
4
+ "version": "0.27.0-alpha.10",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -51,15 +51,15 @@
51
51
  }
52
52
  },
53
53
  "devDependencies": {
54
- "deno": "^2.4.0",
54
+ "deno": "^2.4.2",
55
55
  "jsr": "^0.13.5",
56
56
  "jsr-exports-lint": "^0.4.1",
57
57
  "publint": "^0.3.12",
58
- "tsdown": "^0.12.9",
59
- "@gunshi/definition": "0.27.0-alpha.1",
60
- "gunshi": "0.27.0-alpha.1",
61
- "@gunshi/plugin-global": "0.27.0-alpha.1",
62
- "@gunshi/plugin-renderer": "0.27.0-alpha.1"
58
+ "tsdown": "^0.13.0",
59
+ "@gunshi/definition": "0.27.0-alpha.10",
60
+ "@gunshi/plugin-global": "0.27.0-alpha.10",
61
+ "@gunshi/plugin-renderer": "0.27.0-alpha.10",
62
+ "gunshi": "0.27.0-alpha.10"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsdown",