@gunshi/plugin-completion 0.27.0-alpha.7 → 0.27.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,240 @@
1
- # `@gunshi/plugin-completion`
1
+ # @gunshi/plugin-completion
2
2
 
3
- > completion plugin for gunshi
3
+ > shell completion plugin for gunshi.
4
+
5
+ This plugin provides tab completion functionality for your CLI applications, allowing users to auto-complete commands, options, and arguments in their shell. It generates shell-specific completion scripts and handles runtime completion suggestions.
6
+
7
+ <!-- eslint-disable markdown/no-missing-label-refs -->
8
+
9
+ > [!WARNING]
10
+ > This package support Node.js runtime only. Deno and Bun support are coming soon.
11
+
12
+ <!-- eslint-enable markdown/no-missing-label-refs -->
13
+
14
+ ## 💿 Installation
15
+
16
+ ```sh
17
+ # npm
18
+ npm install --save @gunshi/plugin-completion
19
+
20
+ # pnpm
21
+ pnpm add @gunshi/plugin-completion
22
+
23
+ # yarn
24
+ yarn add @gunshi/plugin-completion
25
+
26
+ # deno
27
+ deno add jsr:@gunshi/plugin-completion
28
+
29
+ # bun
30
+ bun add @gunshi/plugin-completion
31
+ ```
32
+
33
+ ## 🚀 Usage
34
+
35
+ ```ts
36
+ import { cli } from 'gunshi'
37
+ import completion from '@gunshi/plugin-completion'
38
+
39
+ const command = {
40
+ name: 'deploy',
41
+ args: {
42
+ environment: {
43
+ type: 'string',
44
+ short: 'e',
45
+ description: 'Target environment'
46
+ },
47
+ config: {
48
+ type: 'string',
49
+ short: 'c',
50
+ description: 'Config file path'
51
+ }
52
+ },
53
+ run: ctx => {
54
+ console.log(`Deploying to ${ctx.values.environment}`)
55
+ }
56
+ }
57
+
58
+ await cli(process.argv.slice(2), command, {
59
+ name: 'my-cli',
60
+ version: '1.0.0',
61
+ plugins: [
62
+ completion({
63
+ config: {
64
+ entry: {
65
+ args: {
66
+ config: {
67
+ handler: () => [
68
+ { value: 'prod.json', description: 'Production config' },
69
+ { value: 'dev.json', description: 'Development config' },
70
+ { value: 'test.json', description: 'Test config' }
71
+ ]
72
+ }
73
+ }
74
+ }
75
+ }
76
+ })
77
+ ]
78
+ })
79
+ ```
80
+
81
+ ## ✨ Features
82
+
83
+ ### Automatic Complete Command
84
+
85
+ The plugin automatically adds a `complete` subcommand to your CLI:
86
+
87
+ ```bash
88
+ # Generate shell completion script
89
+ my-cli complete bash > ~/.my-cli-completion.bash
90
+ source ~/.my-cli-completion.bash
91
+
92
+ # Now tab completion works!
93
+ my-cli dep<TAB> # Completes to: my-cli deploy
94
+ my-cli deploy --env<TAB> # Completes to: my-cli deploy --environment
95
+ ```
96
+
97
+ ### Shell Support
98
+
99
+ The `complete` command accepts the following shell types:
100
+
101
+ - `bash` - Bash shell completion
102
+ - `zsh` - Zsh shell completion
103
+ - `fish` - Fish shell completion
104
+
105
+ ### Custom Completion Handlers
106
+
107
+ You can provide custom completion handlers for specific arguments:
108
+
109
+ ```ts
110
+ completion({
111
+ config: {
112
+ entry: {
113
+ args: {
114
+ environment: {
115
+ handler: ({ locale }) => [
116
+ { value: 'production', description: 'Production environment' },
117
+ { value: 'staging', description: 'Staging environment' },
118
+ { value: 'development', description: 'Development environment' }
119
+ ]
120
+ }
121
+ }
122
+ },
123
+ subCommands: {
124
+ deploy: {
125
+ args: {
126
+ region: {
127
+ handler: ({ previousArgs }) => {
128
+ // Dynamic completions based on previous arguments
129
+ const env = previousArgs.find(arg => arg.startsWith('--environment='))
130
+ if (env?.includes('production')) {
131
+ return [
132
+ { value: 'us-east-1', description: 'US East (N. Virginia)' },
133
+ { value: 'eu-west-1', description: 'EU (Ireland)' }
134
+ ]
135
+ }
136
+ return [{ value: 'local', description: 'Local development' }]
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ })
144
+ ```
145
+
146
+ ### Internationalization Support
147
+
148
+ When used with `@gunshi/plugin-i18n`, completion descriptions are automatically localized:
149
+
150
+ ```ts
151
+ import completion from '@gunshi/plugin-completion'
152
+ import i18n from '@gunshi/plugin-i18n'
153
+
154
+ await cli(args, command, {
155
+ plugins: [
156
+ i18n({ locale: 'ja-JP' }),
157
+ completion() // Descriptions will be shown in Japanese
158
+ ]
159
+ })
160
+ ```
161
+
162
+ ## ⚙️ Plugin Options
163
+
164
+ ### `config`
165
+
166
+ - Type: `{ entry?: CompletionConfig, subCommands?: Record<string, CompletionConfig> }`
167
+ - Default: `{}`
168
+ - Description: Configuration for completion handlers
169
+
170
+ #### CompletionConfig
171
+
172
+ ```ts
173
+ interface CompletionConfig {
174
+ handler?: CompletionHandler // Handler for command-level completions
175
+ args?: Record<
176
+ string,
177
+ {
178
+ // Handlers for specific arguments
179
+ handler: CompletionHandler
180
+ }
181
+ >
182
+ }
183
+ ```
184
+
185
+ #### CompletionHandler
186
+
187
+ ```ts
188
+ type CompletionHandler = (params: {
189
+ previousArgs: string[] // Previously entered arguments
190
+ toComplete: string // Current string being completed
191
+ endWithSpace: boolean // Whether input ends with space
192
+ locale?: Intl.Locale // Current locale (if i18n is enabled)
193
+ }) => CompletionItem[]
194
+
195
+ interface CompletionItem {
196
+ value: string // The completion value
197
+ description?: string // Optional description
198
+ }
199
+ ```
200
+
201
+ ## 🔗 Plugin Dependencies
202
+
203
+ The completion plugin has an optional dependency on the i18n plugin:
204
+
205
+ - **Plugin ID**: `g:i18n` (optional)
206
+ - **Purpose**: Provides localized descriptions for completions
207
+ - **Effect**: When the i18n plugin is present, all command and argument descriptions are automatically translated to the current locale
208
+
209
+ ## 🧩 Context Extensions
210
+
211
+ When using the completion plugin, your command context is extended via `ctx.extensions['g:completion']`.
4
212
 
5
213
  <!-- eslint-disable markdown/no-missing-label-refs -->
6
214
 
7
- > ![WARNING]
8
- > This package is still work in progress
215
+ > [!IMPORTANT]
216
+ > This plugin extension is namespaced in `CommandContext.extensions` using this plugin ID `g:completion` by the gunshi plugin system.
9
217
 
10
218
  <!-- eslint-enable markdown/no-missing-label-refs -->
11
219
 
220
+ Currently, the completion plugin does not provide any context extensions for use within commands. The plugin ID can be imported for type-safe access:
221
+
222
+ ```ts
223
+ import completion, { pluginId } from '@gunshi/plugin-completion'
224
+ ```
225
+
226
+ ## 📚 API References
227
+
228
+ See the [API References](./docs/index.md)
229
+
230
+ ## 💖 Credits
231
+
232
+ This project uses and depends on:
233
+
234
+ - [`@bombsh/tab`](https://github.com/bombshell-dev/tab), created by [Bombshell](https://github.com/bombshell-dev) - Shell completion library
235
+
236
+ Thank you!
237
+
12
238
  ## ©️ License
13
239
 
14
240
  [MIT](http://opensource.org/licenses/MIT)
package/lib/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- import { PluginWithExtension } from "@gunshi/plugin";
2
- import { Handler } from "@bombsh/tab";
1
+ /*! license ISC
2
+ * @author Bombshell team and Bombshell contributors
3
+ * Bombshell related codes are forked from @bombsh/tab
4
+ */
5
+
6
+ import { PluginWithoutExtension } from "@gunshi/plugin";
3
7
 
4
8
  //#region ../shared/src/constants.d.ts
5
9
  /**
@@ -23,6 +27,15 @@ type GenerateNamespacedKey<Key extends string, Prefixed extends string = typeof
23
27
  /**
24
28
  * Command i18n built-in arguments keys.
25
29
  */
30
+
31
+ //#endregion
32
+ //#region src/bombshell/index.d.ts
33
+
34
+ type Item = {
35
+ description: string;
36
+ value: string;
37
+ };
38
+ type Handler = (previousArgs: string[], toComplete: string, endsWithSpace: boolean) => Item[] | Promise<Item[]>;
26
39
  //#endregion
27
40
  //#region src/types.d.ts
28
41
  /**
@@ -33,6 +46,19 @@ declare const pluginId: GenerateNamespacedKey<'completion', typeof PLUGIN_PREFIX
33
46
  * Type representing the unique identifier for the completion plugin.
34
47
  */
35
48
  type PluginId = typeof pluginId;
49
+ /**
50
+ * Parameters for {@link CompletionHandler | the completion handler}.
51
+ */
52
+ interface CompletionParams {
53
+ previousArgs: Parameters<Handler>[0];
54
+ toComplete: Parameters<Handler>[1];
55
+ endWithSpace: Parameters<Handler>[2];
56
+ locale?: Intl.Locale;
57
+ }
58
+ /**
59
+ * The handler for completion.
60
+ */
61
+ type CompletionHandler = (params: CompletionParams) => ReturnType<Handler>;
36
62
  /**
37
63
  * Extended command context which provides utilities via completion plugin.
38
64
  * These utilities are available via `CommandContext.extensions['g:completion']`.
@@ -42,9 +68,9 @@ interface CompletionCommandContext {}
42
68
  * Completion configuration, which structure is similar `bombsh/tab`'s `CompletionConfig`.
43
69
  */
44
70
  interface CompletionConfig {
45
- handler?: Handler;
71
+ handler?: CompletionHandler;
46
72
  args?: Record<string, {
47
- handler: Handler;
73
+ handler: CompletionHandler;
48
74
  }>;
49
75
  }
50
76
  /**
@@ -67,6 +93,6 @@ interface CompletionOptions {
67
93
  /**
68
94
  * completion plugin for gunshi
69
95
  */
70
- declare function completion(options?: CompletionOptions): PluginWithExtension<CompletionCommandContext>;
96
+ declare function completion(options?: CompletionOptions): PluginWithoutExtension;
71
97
  //#endregion
72
- export { CompletionCommandContext, CompletionConfig, CompletionOptions, PluginId, completion as default, pluginId };
98
+ export { CompletionCommandContext, CompletionConfig, CompletionHandler, CompletionOptions, CompletionParams, PluginId, completion as default, pluginId };
package/lib/index.js CHANGED
@@ -1,6 +1,195 @@
1
- import { plugin } from "@gunshi/plugin";
1
+ /*! license ISC
2
+ * @author Bombshell team and Bombshell contributors
3
+ * Bombshell related codes are forked from @bombsh/tab
4
+ */
2
5
 
3
- //#region ../../node_modules/.pnpm/tab@https+++pkg.pr.new+bombshell-dev+tab@main/node_modules/tab/dist/zsh-Bm75fLuV.js
6
+ import { CLI_OPTIONS_DEFAULT, createCommandContext, plugin } from "@gunshi/plugin";
7
+
8
+ //#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/utils-N7UlhLbz.js
9
+ /**
10
+ * Entry point of utils.
11
+ *
12
+ * Note that this entry point is used by gunshi to import utility functions.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * @author kazuya kawaguchi (a.k.a. kazupon)
18
+ * @license MIT
19
+ */
20
+ function kebabnize(str) {
21
+ return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
22
+ }
23
+
24
+ //#endregion
25
+ //#region ../gunshi/src/utils.ts
26
+ function isLazyCommand(cmd) {
27
+ return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName;
28
+ }
29
+ async function resolveLazyCommand(cmd, name, needRunResolving = false) {
30
+ let command;
31
+ if (isLazyCommand(cmd)) {
32
+ const baseCommand = {
33
+ name: cmd.commandName,
34
+ description: cmd.description,
35
+ args: cmd.args,
36
+ examples: cmd.examples,
37
+ internal: cmd.internal,
38
+ entry: cmd.entry
39
+ };
40
+ if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
41
+ command = Object.assign(create(), baseCommand);
42
+ if (needRunResolving) {
43
+ const loaded = await cmd();
44
+ if (typeof loaded === "function") command.run = loaded;
45
+ else if (typeof loaded === "object") {
46
+ if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
47
+ command.run = loaded.run;
48
+ command.name = loaded.name;
49
+ command.description = loaded.description;
50
+ command.args = loaded.args;
51
+ command.examples = loaded.examples;
52
+ command.internal = loaded.internal;
53
+ command.entry = loaded.entry;
54
+ if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
55
+ } else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
56
+ }
57
+ } else command = Object.assign(create(), cmd);
58
+ if (command.name == null && name) command.name = name;
59
+ return deepFreeze(command);
60
+ }
61
+ function create(obj = null) {
62
+ return Object.create(obj);
63
+ }
64
+ function deepFreeze(obj, ignores = []) {
65
+ if (obj === null || typeof obj !== "object") return obj;
66
+ for (const key of Object.keys(obj)) {
67
+ const value = obj[key];
68
+ if (ignores.includes(key)) continue;
69
+ if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
70
+ }
71
+ return Object.freeze(obj);
72
+ }
73
+
74
+ //#endregion
75
+ //#region ../shared/src/constants.ts
76
+ /**
77
+ * @author kazuya kawaguchi (a.k.a. kazupon)
78
+ * @license MIT
79
+ */
80
+ const BUILT_IN_PREFIX = "_";
81
+ const PLUGIN_PREFIX = "g";
82
+ const ARG_PREFIX = "arg";
83
+ const BUILT_IN_KEY_SEPARATOR = ":";
84
+ const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
85
+ const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
86
+ const ARG_NEGATABLE_PREFIX = "no-";
87
+
88
+ //#endregion
89
+ //#region ../resources/locales/en-US.json
90
+ var COMMAND = "COMMAND";
91
+ var COMMANDS = "COMMANDS";
92
+ var SUBCOMMAND = "SUBCOMMAND";
93
+ var USAGE = "USAGE";
94
+ var ARGUMENTS = "ARGUMENTS";
95
+ var OPTIONS = "OPTIONS";
96
+ var EXAMPLES = "EXAMPLES";
97
+ var FORMORE = "For more info, run any command with the `--help` flag";
98
+ var NEGATABLE = "Negatable of";
99
+ var DEFAULT = "default";
100
+ var CHOICES = "choices";
101
+ var help = "Display this help message";
102
+ var version = "Display this version";
103
+ var en_US_default = {
104
+ COMMAND,
105
+ COMMANDS,
106
+ SUBCOMMAND,
107
+ USAGE,
108
+ ARGUMENTS,
109
+ OPTIONS,
110
+ EXAMPLES,
111
+ FORMORE,
112
+ NEGATABLE,
113
+ DEFAULT,
114
+ CHOICES,
115
+ help,
116
+ version
117
+ };
118
+
119
+ //#endregion
120
+ //#region ../shared/src/utils.ts
121
+ /**
122
+ * Resolve a namespaced key for argument resources.
123
+ * Argument keys are prefixed with "arg:".
124
+ * If the command name is provided, it will be prefixed with the command name (e.g. "cmd1:arg:foo").
125
+ * @param key The argument key to resolve.
126
+ * @param ctx The command context.
127
+ * @returns Prefixed argument key.
128
+ */
129
+ function resolveArgKey(key, ctx) {
130
+ return `${ctx?.name ? `${ctx.name}${BUILT_IN_KEY_SEPARATOR}` : ""}${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
131
+ }
132
+ /**
133
+ * Resolve a namespaced key for non-built-in resources.
134
+ * Non-built-in keys are not prefixed with any special characters. If the command name is provided, it will be prefixed with the command name (e.g. "cmd1:foo").
135
+ * @param key The non-built-in key to resolve.
136
+ * @param ctx The command context.
137
+ * @returns Prefixed non-built-in key.
138
+ */
139
+ function resolveKey(key, ctx) {
140
+ return `${ctx?.name ? `${ctx.name}${BUILT_IN_KEY_SEPARATOR}` : ""}${key}`;
141
+ }
142
+ async function resolveExamples(ctx, examples) {
143
+ return typeof examples === "string" ? examples : typeof examples === "function" ? await examples(ctx) : "";
144
+ }
145
+ function namespacedId(id) {
146
+ return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
147
+ }
148
+ function makeShortLongOptionPair(schema, name, toKebab) {
149
+ const displayName = toKebab || schema.toKebab ? kebabnize(name) : name;
150
+ let key = `--${displayName}`;
151
+ if (schema.short) key = `-${schema.short}, ${key}`;
152
+ return key;
153
+ }
154
+
155
+ //#endregion
156
+ //#region ../shared/src/localization.ts
157
+ /**
158
+ * Create a localizable function for a command.
159
+ * This function will resolve the translation key based on the command context and the provided translation function.
160
+ * @param ctx Command context
161
+ * @param cmd Command
162
+ * @param translate Translation function
163
+ * @returns Localizable function
164
+ */
165
+ function localizable(ctx, cmd, translate) {
166
+ async function localize(key, values) {
167
+ if (translate) return translate(key, values);
168
+ if (key.startsWith(BUILD_IN_PREFIX_AND_KEY_SEPARATOR)) {
169
+ const resKey = key.slice(BUILD_IN_PREFIX_AND_KEY_SEPARATOR.length);
170
+ return en_US_default[resKey] || key;
171
+ }
172
+ const namaspacedArgKey = resolveKey(ARG_PREFIX_AND_KEY_SEPARATOR, ctx);
173
+ if (key.startsWith(namaspacedArgKey)) {
174
+ let argKey = key.slice(namaspacedArgKey.length);
175
+ let negatable = false;
176
+ if (argKey.startsWith(ARG_NEGATABLE_PREFIX)) {
177
+ argKey = argKey.slice(ARG_NEGATABLE_PREFIX.length);
178
+ negatable = true;
179
+ }
180
+ const schema = ctx.args[argKey];
181
+ if (!schema) return argKey;
182
+ return negatable && schema.type === "boolean" && schema.negatable ? `${en_US_default["NEGATABLE"]} ${makeShortLongOptionPair(schema, argKey, ctx.toKebab)}` : schema.description || "";
183
+ }
184
+ if (key === resolveKey("description", ctx)) return "";
185
+ else if (key === resolveKey("examples", ctx)) return await resolveExamples(ctx, cmd.examples);
186
+ else return key;
187
+ }
188
+ return localize;
189
+ }
190
+
191
+ //#endregion
192
+ //#region src/bombshell/bash.ts
4
193
  function generate$3(name, exec) {
5
194
  const nameForVar = name.replace(/[-:]/g, "_");
6
195
  const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
@@ -32,31 +221,31 @@ __${nameForVar}_complete() {
32
221
  _get_comp_words_by_ref -n "=:" cur prev words cword
33
222
 
34
223
  local requestComp out directive
35
-
224
+
36
225
  # Build the command to get completions
37
226
  requestComp="${exec} complete -- \${words[@]:1}"
38
-
227
+
39
228
  # Add an empty parameter if the last parameter is complete
40
229
  if [[ -z "$cur" ]]; then
41
230
  requestComp="$requestComp ''"
42
231
  fi
43
-
232
+
44
233
  # Get completions from the program
45
234
  out=$(eval "$requestComp" 2>/dev/null)
46
-
235
+
47
236
  # Extract directive if present
48
237
  directive=0
49
238
  if [[ "$out" == *:* ]]; then
50
239
  directive=\${out##*:}
51
240
  out=\${out%:*}
52
241
  fi
53
-
242
+
54
243
  # Process completions based on directive
55
244
  if [[ $((directive & $ShellCompDirectiveError)) -ne 0 ]]; then
56
245
  # Error, no completion
57
246
  return
58
247
  fi
59
-
248
+
60
249
  # Apply directives
61
250
  if [[ $((directive & $ShellCompDirectiveNoSpace)) -ne 0 ]]; then
62
251
  compopt -o nospace
@@ -67,7 +256,7 @@ __${nameForVar}_complete() {
67
256
  if [[ $((directive & $ShellCompDirectiveNoFileComp)) -ne 0 ]]; then
68
257
  compopt +o default
69
258
  fi
70
-
259
+
71
260
  # Handle file extension filtering
72
261
  if [[ $((directive & $ShellCompDirectiveFilterFileExt)) -ne 0 ]]; then
73
262
  local filter=""
@@ -79,18 +268,18 @@ __${nameForVar}_complete() {
79
268
  COMPREPLY=( $(compgen -f -X "!$filter" -- "$cur") )
80
269
  return
81
270
  fi
82
-
271
+
83
272
  # Handle directory filtering
84
273
  if [[ $((directive & $ShellCompDirectiveFilterDirs)) -ne 0 ]]; then
85
274
  compopt -o dirnames
86
275
  COMPREPLY=( $(compgen -d -- "$cur") )
87
276
  return
88
277
  fi
89
-
278
+
90
279
  # Process completions
91
280
  local IFS=$'\\n'
92
281
  local tab=$(printf '\\t')
93
-
282
+
94
283
  # Parse completions with descriptions
95
284
  local completions=()
96
285
  while read -r comp; do
@@ -103,7 +292,7 @@ __${nameForVar}_complete() {
103
292
  completions+=("$comp")
104
293
  fi
105
294
  done <<< "$out"
106
-
295
+
107
296
  # Return completions
108
297
  COMPREPLY=( $(compgen -W "\${completions[*]}" -- "$cur") )
109
298
  }
@@ -112,6 +301,9 @@ __${nameForVar}_complete() {
112
301
  complete -F __${nameForVar}_complete ${name}
113
302
  `;
114
303
  }
304
+
305
+ //#endregion
306
+ //#region src/bombshell/fish.ts
115
307
  function generate$2(name, exec) {
116
308
  const nameForVar = name.replace(/[-:]/g, "_");
117
309
  const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
@@ -142,16 +334,16 @@ function __${nameForVar}_perform_completion
142
334
 
143
335
  # Extract all args except the completion flag
144
336
  set -l args (string match -v -- "--completion=" (commandline -opc))
145
-
337
+
146
338
  # Extract the current token being completed
147
339
  set -l current_token (commandline -ct)
148
-
340
+
149
341
  # Check if current token starts with a dash
150
342
  set -l flag_prefix ""
151
343
  if string match -q -- "-*" $current_token
152
344
  set flag_prefix "--flag="
153
345
  end
154
-
346
+
155
347
  __${nameForVar}_debug "Current token: $current_token"
156
348
  __${nameForVar}_debug "All args: $args"
157
349
 
@@ -159,7 +351,7 @@ function __${nameForVar}_perform_completion
159
351
  set -l requestComp "${exec} complete -- $args"
160
352
  __${nameForVar}_debug "Calling $requestComp"
161
353
  set -l results (eval $requestComp 2> /dev/null)
162
-
354
+
163
355
  # Some programs may output extra empty lines after the directive.
164
356
  # Let's ignore them or else it will break completion.
165
357
  # Ref: https://github.com/spf13/cobra/issues/1279
@@ -171,12 +363,12 @@ function __${nameForVar}_perform_completion
171
363
  break
172
364
  end
173
365
  end
174
-
366
+
175
367
  # No directive specified, use default
176
368
  if not set -q directive_num
177
369
  set directive_num 0
178
370
  end
179
-
371
+
180
372
  __${nameForVar}_debug "Directive: $directive_num"
181
373
 
182
374
  # Process completions based on directive
@@ -211,14 +403,14 @@ function __${nameForVar}_perform_completion
211
403
  end
212
404
  end
213
405
  __${nameForVar}_debug "File extensions: $file_extensions"
214
-
406
+
215
407
  # Use the file extensions as completions
216
408
  set -l completions
217
409
  for ext in $file_extensions
218
410
  # Get all files matching the extension
219
411
  set -a completions (string replace -r '^.*/' '' -- $ext)
220
412
  end
221
-
413
+
222
414
  for item in $completions
223
415
  echo -e "$item\t"
224
416
  end
@@ -234,7 +426,7 @@ function __${nameForVar}_perform_completion
234
426
  set -a dirs "$item/"
235
427
  end
236
428
  end
237
-
429
+
238
430
  for item in $dirs
239
431
  echo -e "$item\t"
240
432
  end
@@ -249,7 +441,7 @@ function __${nameForVar}_perform_completion
249
441
  set -l completion_parts (string split \t -- "$item")
250
442
  set -l comp $completion_parts[1]
251
443
  set -l desc $completion_parts[2]
252
-
444
+
253
445
  # Add the completion and description
254
446
  echo -e "$comp\t$desc"
255
447
  else
@@ -258,12 +450,12 @@ function __${nameForVar}_perform_completion
258
450
  end
259
451
  end
260
452
  end
261
-
453
+
262
454
  # If directive contains NoSpace, tell fish not to add a space after completion
263
455
  if test (math "$directive_num & $ShellCompDirectiveNoSpace") -ne 0
264
456
  return 2
265
457
  end
266
-
458
+
267
459
  return 0
268
460
  end
269
461
 
@@ -271,7 +463,10 @@ end
271
463
  complete -c ${name} -f -a "(eval __${nameForVar}_perform_completion)"
272
464
  `;
273
465
  }
274
- function generate$1(name, exec, includeDesc = false) {
466
+
467
+ //#endregion
468
+ //#region src/bombshell/powershell.ts
469
+ function generate$1(name, exec, _includeDesc = false) {
275
470
  const nameForVar = name.replace(/[-:]/g, "_");
276
471
  const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
277
472
  const ShellCompDirectiveNoSpace = ShellCompDirective.ShellCompDirectiveNoSpace;
@@ -526,199 +721,9 @@ function generate$1(name, exec, includeDesc = false) {
526
721
  Register-ArgumentCompleter -CommandName '${name}' -ScriptBlock $__${nameForVar}CompleterBlock
527
722
  `;
528
723
  }
529
- const ShellCompDirective = {
530
- ShellCompDirectiveError: 1,
531
- ShellCompDirectiveNoSpace: 2,
532
- ShellCompDirectiveNoFileComp: 4,
533
- ShellCompDirectiveFilterFileExt: 8,
534
- ShellCompDirectiveFilterDirs: 16,
535
- ShellCompDirectiveKeepOrder: 32,
536
- shellCompDirectiveMaxValue: 64,
537
- ShellCompDirectiveDefault: 0
538
- };
539
- var Completion = class {
540
- commands = new Map();
541
- completions = [];
542
- directive = ShellCompDirective.ShellCompDirectiveDefault;
543
- addCommand(name, description, args, handler, parent) {
544
- const key = parent ? `${parent} ${name}` : name;
545
- this.commands.set(key, {
546
- name: key,
547
- description,
548
- args,
549
- handler,
550
- options: new Map(),
551
- parent: parent ? this.commands.get(parent) : void 0
552
- });
553
- return key;
554
- }
555
- addOption(command, option, description, handler, alias) {
556
- const cmd = this.commands.get(command);
557
- if (!cmd) throw new Error(`Command ${command} not found.`);
558
- cmd.options.set(option, {
559
- description,
560
- handler,
561
- alias
562
- });
563
- return option;
564
- }
565
- stripOptions(args) {
566
- const parts = [];
567
- let option = false;
568
- for (const k of args) {
569
- if (k.startsWith("-")) {
570
- option = true;
571
- continue;
572
- }
573
- if (option) {
574
- option = false;
575
- continue;
576
- }
577
- parts.push(k);
578
- }
579
- return parts;
580
- }
581
- matchCommand(args) {
582
- args = this.stripOptions(args);
583
- const parts = [];
584
- let remaining = [];
585
- let matched = this.commands.get("");
586
- for (let i = 0; i < args.length; i++) {
587
- const k = args[i];
588
- parts.push(k);
589
- const potential = this.commands.get(parts.join(" "));
590
- if (potential) matched = potential;
591
- else {
592
- remaining = args.slice(i, args.length);
593
- break;
594
- }
595
- }
596
- return [matched, remaining];
597
- }
598
- async parse(args) {
599
- const endsWithSpace = args[args.length - 1] === "";
600
- if (endsWithSpace) args.pop();
601
- let toComplete = args[args.length - 1] || "";
602
- const previousArgs = args.slice(0, -1);
603
- if (endsWithSpace) {
604
- previousArgs.push(toComplete);
605
- toComplete = "";
606
- }
607
- const [matchedCommand] = this.matchCommand(previousArgs);
608
- const lastPrevArg = previousArgs[previousArgs.length - 1];
609
- if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) await this.handleFlagCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace, lastPrevArg);
610
- else {
611
- if (this.shouldCompleteCommands(toComplete, endsWithSpace)) await this.handleCommandCompletion(previousArgs, toComplete);
612
- if (matchedCommand && matchedCommand.args.length > 0) await this.handlePositionalCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace);
613
- }
614
- this.complete(toComplete);
615
- }
616
- complete(toComplete) {
617
- this.directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
618
- const seen = new Set();
619
- this.completions.filter((comp) => {
620
- if (seen.has(comp.value)) return false;
621
- seen.add(comp.value);
622
- return true;
623
- }).filter((comp) => comp.value.startsWith(toComplete)).forEach((comp) => console.log(`${comp.value}\t${comp.description ?? ""}`));
624
- console.log(`:${this.directive}`);
625
- }
626
- shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace) {
627
- return lastPrevArg?.startsWith("--") || lastPrevArg?.startsWith("-") || toComplete.startsWith("--") || toComplete.startsWith("-");
628
- }
629
- shouldCompleteCommands(toComplete, endsWithSpace) {
630
- return !toComplete.startsWith("-");
631
- }
632
- async handleFlagCompletion(command, previousArgs, toComplete, endsWithSpace, lastPrevArg) {
633
- let flagName;
634
- let valueToComplete = toComplete;
635
- if (toComplete.includes("=")) {
636
- const parts = toComplete.split("=");
637
- flagName = parts[0];
638
- valueToComplete = parts[1] || "";
639
- } else if (lastPrevArg?.startsWith("-")) flagName = lastPrevArg;
640
- if (flagName) {
641
- let option = command.options.get(flagName);
642
- if (!option) {
643
- for (const [name, opt] of command.options) if (opt.alias && `-${opt.alias}` === flagName) {
644
- option = opt;
645
- flagName = name;
646
- break;
647
- }
648
- }
649
- if (option) {
650
- const suggestions = await option.handler(previousArgs, valueToComplete, endsWithSpace);
651
- if (toComplete.includes("=")) this.completions = suggestions.map((suggestion) => ({
652
- value: `${flagName}=${suggestion.value}`,
653
- description: suggestion.description
654
- }));
655
- else this.completions.push(...suggestions);
656
- }
657
- return;
658
- }
659
- if (toComplete.startsWith("-")) {
660
- const isShortFlag = toComplete.startsWith("-") && !toComplete.startsWith("--");
661
- for (const [name, option] of command.options) if (isShortFlag) {
662
- if (option.alias && `-${option.alias}`.startsWith(toComplete)) this.completions.push({
663
- value: `-${option.alias}`,
664
- description: option.description
665
- });
666
- } else if (name.startsWith(toComplete)) this.completions.push({
667
- value: name,
668
- description: option.description
669
- });
670
- }
671
- }
672
- async handleCommandCompletion(previousArgs, toComplete) {
673
- const commandParts = [...previousArgs].filter(Boolean);
674
- for (const [k, command] of this.commands) {
675
- if (k === "") continue;
676
- const parts = k.split(" ");
677
- let match = true;
678
- let i = 0;
679
- while (i < commandParts.length) {
680
- if (parts[i] !== commandParts[i]) {
681
- match = false;
682
- break;
683
- }
684
- i++;
685
- }
686
- if (match && parts[i]?.startsWith(toComplete)) this.completions.push({
687
- value: parts[i],
688
- description: command.description
689
- });
690
- }
691
- }
692
- async handlePositionalCompletion(command, previousArgs, toComplete, endsWithSpace) {
693
- const suggestions = await command.handler(previousArgs, toComplete, endsWithSpace);
694
- this.completions.push(...suggestions);
695
- }
696
- };
697
- function script(shell, name, x) {
698
- switch (shell) {
699
- case "zsh": {
700
- const script$1 = generate(name, x);
701
- console.log(script$1);
702
- break;
703
- }
704
- case "bash": {
705
- const script$1 = generate$3(name, x);
706
- console.log(script$1);
707
- break;
708
- }
709
- case "fish": {
710
- const script$1 = generate$2(name, x);
711
- console.log(script$1);
712
- break;
713
- }
714
- case "powershell": {
715
- const script$1 = generate$1(name, x);
716
- console.log(script$1);
717
- break;
718
- }
719
- default: throw new Error(`Unsupported shell: ${shell}`);
720
- }
721
- }
724
+
725
+ //#endregion
726
+ //#region src/bombshell/zsh.ts
722
727
  function generate(name, exec) {
723
728
  return `#compdef ${name}
724
729
  compdef _${name} ${name}
@@ -939,72 +944,199 @@ fi
939
944
  }
940
945
 
941
946
  //#endregion
942
- //#region ../gunshi/src/utils.ts
943
- function isLazyCommand(cmd) {
944
- return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName;
945
- }
946
- async function resolveLazyCommand(cmd, name, needRunResolving = false) {
947
- let command;
948
- if (isLazyCommand(cmd)) {
949
- const baseCommand = {
950
- name: cmd.commandName,
951
- description: cmd.description,
952
- args: cmd.args,
953
- examples: cmd.examples,
954
- internal: cmd.internal,
955
- entry: cmd.entry
956
- };
957
- if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
958
- command = Object.assign(create(), baseCommand);
959
- if (needRunResolving) {
960
- const loaded = await cmd();
961
- if (typeof loaded === "function") command.run = loaded;
962
- else if (typeof loaded === "object") {
963
- if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
964
- command.run = loaded.run;
965
- command.name = loaded.name;
966
- command.description = loaded.description;
967
- command.args = loaded.args;
968
- command.examples = loaded.examples;
969
- command.internal = loaded.internal;
970
- command.entry = loaded.entry;
971
- if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
972
- } else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
947
+ //#region src/bombshell/index.ts
948
+ const ShellCompDirective = {
949
+ ShellCompDirectiveError: Math.trunc(1),
950
+ ShellCompDirectiveNoSpace: 2,
951
+ ShellCompDirectiveNoFileComp: 4,
952
+ ShellCompDirectiveFilterFileExt: 8,
953
+ ShellCompDirectiveFilterDirs: 16,
954
+ ShellCompDirectiveKeepOrder: 32,
955
+ shellCompDirectiveMaxValue: 64,
956
+ ShellCompDirectiveDefault: 0
957
+ };
958
+ var Completion = class {
959
+ commands = new Map();
960
+ completions = [];
961
+ directive = ShellCompDirective.ShellCompDirectiveDefault;
962
+ addCommand(name, description, args, handler, parent) {
963
+ const key = parent ? `${parent} ${name}` : name;
964
+ this.commands.set(key, {
965
+ name: key,
966
+ description,
967
+ args,
968
+ handler,
969
+ options: new Map(),
970
+ parent: parent ? this.commands.get(parent) : void 0
971
+ });
972
+ return key;
973
+ }
974
+ addOption(command, option, description, handler, alias) {
975
+ const cmd = this.commands.get(command);
976
+ if (!cmd) throw new Error(`Command ${command} not found.`);
977
+ cmd.options.set(option, {
978
+ description,
979
+ handler,
980
+ alias
981
+ });
982
+ return option;
983
+ }
984
+ stripOptions(args) {
985
+ const parts = [];
986
+ let option = false;
987
+ for (const k of args) {
988
+ if (k.startsWith("-")) {
989
+ option = true;
990
+ continue;
991
+ }
992
+ if (option) {
993
+ option = false;
994
+ continue;
995
+ }
996
+ parts.push(k);
973
997
  }
974
- } else command = Object.assign(create(), cmd);
975
- if (command.name == null && name) command.name = name;
976
- return deepFreeze(command);
977
- }
978
- function create(obj = null) {
979
- return Object.create(obj);
980
- }
981
- function deepFreeze(obj, ignores = []) {
982
- if (obj === null || typeof obj !== "object") return obj;
983
- for (const key of Object.keys(obj)) {
984
- const value = obj[key];
985
- if (ignores.includes(key)) continue;
986
- if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
998
+ return parts;
999
+ }
1000
+ matchCommand(args) {
1001
+ args = this.stripOptions(args);
1002
+ const parts = [];
1003
+ let remaining = [];
1004
+ let matched = this.commands.get("");
1005
+ for (let i = 0; i < args.length; i++) {
1006
+ const k = args[i];
1007
+ parts.push(k);
1008
+ const potential = this.commands.get(parts.join(" "));
1009
+ if (potential) matched = potential;
1010
+ else {
1011
+ remaining = args.slice(i);
1012
+ break;
1013
+ }
1014
+ }
1015
+ return [matched, remaining];
1016
+ }
1017
+ async parse(args) {
1018
+ const endsWithSpace = args.at(-1) === "";
1019
+ if (endsWithSpace) args.pop();
1020
+ let toComplete = args.at(-1) || "";
1021
+ const previousArgs = args.slice(0, -1);
1022
+ if (endsWithSpace) {
1023
+ previousArgs.push(toComplete);
1024
+ toComplete = "";
1025
+ }
1026
+ const [matchedCommand] = this.matchCommand(previousArgs);
1027
+ const lastPrevArg = previousArgs.at(-1);
1028
+ if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) await this.handleFlagCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace, lastPrevArg);
1029
+ else {
1030
+ if (this.shouldCompleteCommands(toComplete, endsWithSpace)) await this.handleCommandCompletion(previousArgs, toComplete);
1031
+ if (matchedCommand && matchedCommand.args.length > 0) await this.handlePositionalCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace);
1032
+ }
1033
+ this.complete(toComplete);
1034
+ }
1035
+ complete(toComplete) {
1036
+ this.directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
1037
+ const seen = new Set();
1038
+ for (const comp of this.completions.filter((comp$1) => {
1039
+ if (seen.has(comp$1.value)) return false;
1040
+ seen.add(comp$1.value);
1041
+ return true;
1042
+ }).filter((comp$1) => comp$1.value.startsWith(toComplete))) console.log(`${comp.value}\t${comp.description ?? ""}`);
1043
+ console.log(`:${this.directive}`);
1044
+ }
1045
+ shouldCompleteFlags(lastPrevArg, toComplete, _endsWithSpace) {
1046
+ return lastPrevArg?.startsWith("--") || lastPrevArg?.startsWith("-") || toComplete.startsWith("--") || toComplete.startsWith("-");
1047
+ }
1048
+ shouldCompleteCommands(toComplete, _endsWithSpace) {
1049
+ return !toComplete.startsWith("-");
1050
+ }
1051
+ async handleFlagCompletion(command, previousArgs, toComplete, endsWithSpace, lastPrevArg) {
1052
+ let flagName;
1053
+ let valueToComplete = toComplete;
1054
+ if (toComplete.includes("=")) {
1055
+ const parts = toComplete.split("=");
1056
+ flagName = parts[0];
1057
+ valueToComplete = parts[1] || "";
1058
+ } else if (lastPrevArg?.startsWith("-")) flagName = lastPrevArg;
1059
+ if (flagName) {
1060
+ let option = command.options.get(flagName);
1061
+ if (!option) {
1062
+ for (const [name, opt] of command.options) if (opt.alias && `-${opt.alias}` === flagName) {
1063
+ option = opt;
1064
+ flagName = name;
1065
+ break;
1066
+ }
1067
+ }
1068
+ if (option) {
1069
+ const suggestions = await option.handler(previousArgs, valueToComplete, endsWithSpace);
1070
+ if (toComplete.includes("=")) this.completions = suggestions.map((suggestion) => ({
1071
+ value: `${flagName}=${suggestion.value}`,
1072
+ description: suggestion.description
1073
+ }));
1074
+ else this.completions.push(...suggestions);
1075
+ }
1076
+ return;
1077
+ }
1078
+ if (toComplete.startsWith("-")) {
1079
+ const isShortFlag = toComplete.startsWith("-") && !toComplete.startsWith("--");
1080
+ for (const [name, option] of command.options) if (isShortFlag) {
1081
+ if (option.alias && `-${option.alias}`.startsWith(toComplete)) this.completions.push({
1082
+ value: `-${option.alias}`,
1083
+ description: option.description
1084
+ });
1085
+ } else if (name.startsWith(toComplete)) this.completions.push({
1086
+ value: name,
1087
+ description: option.description
1088
+ });
1089
+ }
1090
+ }
1091
+ async handleCommandCompletion(previousArgs, toComplete) {
1092
+ const commandParts = [...previousArgs].filter(Boolean);
1093
+ for (const [k, command] of this.commands) {
1094
+ if (k === "") continue;
1095
+ const parts = k.split(" ");
1096
+ let match = true;
1097
+ let i = 0;
1098
+ while (i < commandParts.length) {
1099
+ if (parts[i] !== commandParts[i]) {
1100
+ match = false;
1101
+ break;
1102
+ }
1103
+ i++;
1104
+ }
1105
+ if (match && parts[i]?.startsWith(toComplete)) this.completions.push({
1106
+ value: parts[i],
1107
+ description: command.description
1108
+ });
1109
+ }
1110
+ }
1111
+ async handlePositionalCompletion(command, previousArgs, toComplete, endsWithSpace) {
1112
+ const suggestions = await command.handler(previousArgs, toComplete, endsWithSpace);
1113
+ this.completions.push(...suggestions);
1114
+ }
1115
+ };
1116
+ function script(shell, name, x) {
1117
+ switch (shell) {
1118
+ case "zsh": {
1119
+ const script$1 = generate(name, x);
1120
+ console.log(script$1);
1121
+ break;
1122
+ }
1123
+ case "bash": {
1124
+ const script$1 = generate$3(name, x);
1125
+ console.log(script$1);
1126
+ break;
1127
+ }
1128
+ case "fish": {
1129
+ const script$1 = generate$2(name, x);
1130
+ console.log(script$1);
1131
+ break;
1132
+ }
1133
+ case "powershell": {
1134
+ const script$1 = generate$1(name, x);
1135
+ console.log(script$1);
1136
+ break;
1137
+ }
1138
+ default: throw new Error(`Unsupported shell: ${shell}`);
987
1139
  }
988
- return Object.freeze(obj);
989
- }
990
-
991
- //#endregion
992
- //#region ../shared/src/constants.ts
993
- /**
994
- * @author kazuya kawaguchi (a.k.a. kazupon)
995
- * @license MIT
996
- */
997
- const BUILT_IN_PREFIX = "_";
998
- const PLUGIN_PREFIX = "g";
999
- const ARG_PREFIX = "arg";
1000
- const BUILT_IN_KEY_SEPARATOR = ":";
1001
- const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
1002
- const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
1003
-
1004
- //#endregion
1005
- //#region ../shared/src/utils.ts
1006
- function namespacedId(id) {
1007
- return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
1008
1140
  }
1009
1141
 
1010
1142
  //#endregion
@@ -1016,10 +1148,27 @@ const pluginId = namespacedId("completion");
1016
1148
 
1017
1149
  //#endregion
1018
1150
  //#region src/utils.ts
1019
- /**
1020
- * @author kazuya kawaguchi (a.k.a. kazupon)
1021
- * @license MIT
1022
- */
1151
+ async function createCommandContext$1(cmd, id, i18n) {
1152
+ const extensions = Object.create(null);
1153
+ if (i18n) extensions[id] = {
1154
+ key: Symbol(id),
1155
+ factory: () => i18n
1156
+ };
1157
+ return await createCommandContext({
1158
+ args: cmd.args || Object.create(null),
1159
+ values: Object.create(null),
1160
+ positionals: [],
1161
+ rest: [],
1162
+ argv: [],
1163
+ explicit: Object.create(null),
1164
+ tokens: [],
1165
+ omitted: false,
1166
+ callMode: cmd.entry ? "entry" : "subCommand",
1167
+ command: cmd,
1168
+ extensions,
1169
+ cliOptions: CLI_OPTIONS_DEFAULT
1170
+ });
1171
+ }
1023
1172
  function detectRuntime() {
1024
1173
  if (globalThis.process !== void 0 && globalThis.process.release?.name === "node") return "node";
1025
1174
  if (globalThis.Deno !== void 0) return "deno";
@@ -1052,6 +1201,7 @@ const TERMINATOR = "--";
1052
1201
  const NOOP_HANDLER = () => {
1053
1202
  return [];
1054
1203
  };
1204
+ const i18nPluginId = namespacedId("i18n");
1055
1205
  /**
1056
1206
  * completion plugin for gunshi
1057
1207
  */
@@ -1061,6 +1211,10 @@ function completion(options = {}) {
1061
1211
  return plugin({
1062
1212
  id: pluginId,
1063
1213
  name: "completion",
1214
+ dependencies: [{
1215
+ id: i18nPluginId,
1216
+ optional: true
1217
+ }],
1064
1218
  async setup(ctx) {
1065
1219
  /**
1066
1220
  * add command for completion script generation
@@ -1069,6 +1223,7 @@ function completion(options = {}) {
1069
1223
  ctx.addCommand(completeName, {
1070
1224
  name: completeName,
1071
1225
  description: "Generate shell completion script",
1226
+ rendering: { header: null },
1072
1227
  run: async (cmdCtx) => {
1073
1228
  if (!cmdCtx.env.name) throw new Error("your cli name is not defined.");
1074
1229
  let shell = cmdCtx._[1];
@@ -1079,46 +1234,63 @@ function completion(options = {}) {
1079
1234
  } else script(shell, cmdCtx.env.name, quoteExec());
1080
1235
  }
1081
1236
  });
1082
- /**
1083
- * disable header renderer
1084
- */
1085
- ctx.decorateHeaderRenderer(async (_baseRenderer, _cmdCtx) => "");
1086
1237
  },
1087
- extension: (_ctx, _cmd) => {
1088
- return {};
1089
- },
1090
- onExtension: async (ctx, _cmd) => {
1238
+ onExtension: async (ctx, cmd) => {
1239
+ const i18n = ctx.extensions?.[i18nPluginId];
1091
1240
  const subCommands = ctx.env.subCommands;
1092
- const entry = [...subCommands].map(([_, cmd]) => cmd).find((cmd) => cmd.entry);
1241
+ const entry = [...subCommands].map(([_, cmd$1]) => cmd$1).find((cmd$1) => cmd$1.entry);
1093
1242
  if (!entry) throw new Error("entry command not found.");
1243
+ const entryCtx = await createCommandContext$1(entry, i18nPluginId, i18n);
1244
+ if (i18n) {
1245
+ const ret = await i18n.loadResource(i18n.locale, entryCtx, entry);
1246
+ if (!ret) console.warn(`Failed to load i18n resources for command: ${entry.name} (${i18n.locale})`);
1247
+ }
1248
+ const localizeDescription = localizable(entryCtx, cmd, i18n ? i18n.translate : void 0);
1094
1249
  const isPositional = hasPositional(await resolveLazyCommand(entry));
1095
1250
  const root = "";
1096
- completion$1.addCommand(root, entry.description || "", isPositional ? [false] : [], NOOP_HANDLER);
1251
+ completion$1.addCommand(root, await localizeDescription(resolveKey("description", entryCtx)) || entry.description || "", isPositional ? [false] : [], NOOP_HANDLER);
1097
1252
  const args = entry.args || Object.create(null);
1098
1253
  for (const [key, schema] of Object.entries(args)) {
1099
1254
  if (schema.type === "positional") continue;
1100
- completion$1.addOption(root, `--${key}`, schema.description || "", config.entry?.args?.[key]?.handler || NOOP_HANDLER, schema.short);
1255
+ completion$1.addOption(root, `--${key}`, await localizeDescription(resolveArgKey(key, entryCtx)) || schema.description || "", toBombshellCompletionHandler(config.entry?.args?.[key]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0), schema.short);
1101
1256
  }
1102
- await handleSubCommands(completion$1, subCommands, config.subCommands);
1257
+ await handleSubCommands(completion$1, subCommands, config.subCommands, i18nPluginId, i18n);
1103
1258
  }
1104
1259
  });
1105
1260
  }
1106
- async function handleSubCommands(completion$1, subCommands, configs = {}) {
1261
+ async function handleSubCommands(completion$1, subCommands, configs = {}, i18nPluginId$1, i18n) {
1107
1262
  for (const [name, cmd] of subCommands) {
1108
1263
  if (cmd.internal || cmd.entry || name === "complete") continue;
1109
1264
  const resolvedCmd = await resolveLazyCommand(cmd);
1265
+ const ctx = await createCommandContext$1(resolvedCmd, i18nPluginId$1, i18n);
1266
+ if (i18n) {
1267
+ const ret = await i18n.loadResource(i18n.locale, ctx, resolvedCmd);
1268
+ if (!ret) console.warn(`Failed to load i18n resources for command: ${name} (${i18n.locale})`);
1269
+ }
1270
+ const localizeDescription = localizable(ctx, resolvedCmd, i18n ? i18n.translate : void 0);
1110
1271
  const isPositional = hasPositional(resolvedCmd);
1111
- const commandName = completion$1.addCommand(name, resolvedCmd.description || "", isPositional ? [false] : [], configs?.[name]?.handler || NOOP_HANDLER);
1272
+ const commandName = completion$1.addCommand(name, await localizeDescription(resolveKey("description", ctx)) || resolvedCmd.description || "", isPositional ? [false] : [], toBombshellCompletionHandler(configs?.[name]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0));
1112
1273
  const args = resolvedCmd.args || Object.create(null);
1113
1274
  for (const [key, schema] of Object.entries(args)) {
1114
1275
  if (schema.type === "positional") continue;
1115
- completion$1.addOption(commandName, `--${key}`, schema.description || "", configs[commandName]?.args?.[key]?.handler || NOOP_HANDLER, schema.short);
1276
+ completion$1.addOption(commandName, `--${key}`, await localizeDescription(resolveArgKey(key, ctx)) || schema.description || "", toBombshellCompletionHandler(configs[commandName]?.args?.[key]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0), schema.short);
1116
1277
  }
1117
1278
  }
1118
1279
  }
1119
1280
  function hasPositional(cmd) {
1120
1281
  return cmd.args && Object.values(cmd.args).some((arg) => arg.type === "positional");
1121
1282
  }
1283
+ function toLocale(locale) {
1284
+ return locale instanceof Intl.Locale ? locale : new Intl.Locale(locale);
1285
+ }
1286
+ function toBombshellCompletionHandler(handler, locale) {
1287
+ return (previousArgs, toComplete, endWithSpace) => handler({
1288
+ previousArgs,
1289
+ toComplete,
1290
+ endWithSpace,
1291
+ locale
1292
+ });
1293
+ }
1122
1294
 
1123
1295
  //#endregion
1124
1296
  export { completion as default, pluginId };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gunshi/plugin-completion",
3
3
  "description": "completion plugin for gunshi",
4
- "version": "0.27.0-alpha.7",
4
+ "version": "0.27.0-alpha.9",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -52,17 +52,21 @@
52
52
  }
53
53
  },
54
54
  "dependencies": {
55
- "@bombsh/tab": "https://pkg.pr.new/bombshell-dev/tab@main",
56
- "@gunshi/plugin": "0.27.0-alpha.7"
55
+ "@gunshi/plugin": "0.27.0-alpha.9"
56
+ },
57
+ "peerDependencies": {
58
+ "@gunshi/plugin-i18n": "0.27.0-alpha.9"
57
59
  },
58
60
  "devDependencies": {
59
- "@types/node": "^22.16.0",
60
- "deno": "^2.4.0",
61
+ "@types/node": "^22.16.5",
62
+ "deno": "^2.4.2",
61
63
  "jsr": "^0.13.5",
62
64
  "jsr-exports-lint": "^0.4.1",
63
65
  "publint": "^0.3.12",
64
- "tsdown": "^0.12.9",
65
- "@gunshi/shared": "0.27.0-alpha.7"
66
+ "rollup-plugin-license": "^3.6.0",
67
+ "tsdown": "^0.13.0",
68
+ "@gunshi/plugin-i18n": "0.27.0-alpha.9",
69
+ "@gunshi/shared": "0.27.0-alpha.9"
66
70
  },
67
71
  "scripts": {
68
72
  "build": "tsdown",