@gunshi/docs 0.27.0-beta.4

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.
@@ -0,0 +1,722 @@
1
+ # Gunshi v0.27 Release Notes
2
+
3
+ ## ๐ŸŒŸ Features
4
+
5
+ ### Plugin System
6
+
7
+ Gunshi v0.27 introduces a powerful plugin system that enables modular CLI architecture. Plugins can extend command functionality, add global options, and provide cross-cutting concerns like internationalization and completion.
8
+
9
+ ```ts
10
+ import { cli } from 'gunshi'
11
+ import i18n, { defineI18nWithTypes, pluginId as i18nId } from '@gunshi/plugin-i18n'
12
+ import completion from '@gunshi/plugin-completion'
13
+ import type { I18nExtension, PluginId as I18nPluginId } from '@gunshi/plugin-i18n'
14
+
15
+ // Type-safe command definition with plugin extensions
16
+ const command = defineI18nWithTypes<{ extensions: Record<I18nPluginId, I18nExtension> }>()({
17
+ name: 'app',
18
+ // Provide translation resources for i18n plugin
19
+ resource: locale => {
20
+ if (locale.toString() === 'ja-JP') {
21
+ return { greeting: 'ใ“ใ‚“ใซใกใฏ' }
22
+ }
23
+ return { greeting: 'Hello' }
24
+ },
25
+ run: ctx => {
26
+ // Plugins automatically extend the context
27
+ console.log(ctx.extensions[i18nId].translate('greeting'))
28
+ }
29
+ })
30
+
31
+ await cli(process.argv.slice(2), command, {
32
+ name: 'my-app',
33
+ version: '1.0.0',
34
+ plugins: [i18n(), completion()]
35
+ })
36
+ ```
37
+
38
+ Plugins can extend the `CommandContext` by adding their own properties to `ctx.extensions`. Each plugin's extension is namespaced using its plugin ID, preventing conflicts and ensuring type safety:
39
+
40
+ - Extensions are accessed via `ctx.extensions[pluginId]`
41
+ - Plugin IDs should be imported to avoid hardcoding strings
42
+ - TypeScript provides full type inference for extensions when using `define<Record<PluginId, Extension>>()`
43
+ - Multiple plugins can coexist, each providing their own extensions
44
+
45
+ In the example above, the i18n plugin extends `ctx.extensions` with internationalization functionality accessible via `ctx.extensions[i18nId]`. This provides methods like `translate()` and properties like `locale` to all commands, enabling consistent internationalization across your CLI application.
46
+
47
+ ### Official Plugin Ecosystem
48
+
49
+ Gunshi v0.27 comes with a comprehensive set of official plugins that work seamlessly with the new plugin system:
50
+
51
+ #### Built-in Plugins (included in `gunshi` package)
52
+
53
+ These plugins are automatically included when using the standard `cli()` function from gunshi:
54
+
55
+ - `@gunshi/plugin-global`: Adds `--help` and `--version` options to all commands automatically
56
+ - Provides `showVersion()`, `showHeader()`, `showUsage()`, and `showValidationErrors()` methods
57
+ - Intercepts command execution to handle help/version display
58
+
59
+ - `@gunshi/plugin-renderer`: Automatic rendering of usage, help text, and validation errors
60
+ - Integrates with i18n plugin for localized messages
61
+ - Customizable rendering for headers, usage, and error messages
62
+ - Smart formatting for arguments, options, and subcommands
63
+
64
+ #### Optional Plugins
65
+
66
+ Install these plugins separately based on your needs:
67
+
68
+ - `gunshi/plugin-i18n`: Comprehensive internationalization support
69
+
70
+ ```sh
71
+ npm install @gunshi/plugin-i18n
72
+ ```
73
+
74
+ - Type-safe translation functions with key completion
75
+ - Dynamic resource loading per command
76
+ - Custom translation adapters
77
+ - Built-in namespace management
78
+
79
+ - `@gunshi/plugin-completion`: Shell completion support
80
+
81
+ ```sh
82
+ npm install @gunshi/plugin-completion
83
+ ```
84
+
85
+ - Support for bash, zsh, fish, and PowerShell
86
+ - Custom completion handlers for dynamic suggestions
87
+ - Automatic `complete` subcommand generation
88
+ - Integration with i18n for localized descriptions
89
+
90
+ ### `@gunshi/definition` - Command Definition Utilities
91
+
92
+ New standalone package for creating reusable, distributable commands:
93
+
94
+ ```sh
95
+ npm install @gunshi/definition
96
+ ```
97
+
98
+ This package enables you to create standalone commands that can be published as libraries and shared across multiple CLI applications:
99
+
100
+ ```js
101
+ // my-shared-commands/build.js
102
+ import { define } from '@gunshi/definition'
103
+
104
+ // Create a standalone command that can be imported by any Gunshi CLI
105
+ export const buildCommand = define({
106
+ name: 'build',
107
+ args: {
108
+ watch: { type: 'boolean', default: false },
109
+ output: { type: 'string', required: true }
110
+ },
111
+ run: ctx => {
112
+ if (ctx.values.watch) {
113
+ console.log(`Watching and building to ${ctx.values.output}`)
114
+ }
115
+ }
116
+ })
117
+
118
+ // my-shared-commands/analyze.js
119
+ import { lazy } from '@gunshi/definition'
120
+
121
+ // Export a lazy-loaded command for better performance
122
+ export const analyzeCommand = lazy(async () => {
123
+ // Heavy imports only when command is actually used
124
+ const { analyze } = await import('./heavy-analyzer.js')
125
+
126
+ return {
127
+ name: 'analyze',
128
+ run: ctx => analyze(ctx.values)
129
+ }
130
+ })
131
+ ```
132
+
133
+ Now these commands can be imported and used in any Gunshi CLI:
134
+
135
+ ```js
136
+ // app-cli/index.js
137
+ import { cli } from 'gunshi'
138
+ import { buildCommand, analyzeCommand } from 'my-shared-commands'
139
+
140
+ await cli(process.argv.slice(2), buildCommand, {
141
+ name: 'app',
142
+ subCommands: {
143
+ build: buildCommand,
144
+ analyze: analyzeCommand
145
+ }
146
+ })
147
+ ```
148
+
149
+ Benefits:
150
+
151
+ - **Standalone commands**: Create reusable commands that can be published as npm packages
152
+ - **Framework-agnostic distribution**: Commands defined with `@gunshi/definition` can be imported by any Gunshi CLI
153
+ - **Smaller bundles**: Separate from main gunshi package for optimal tree-shaking
154
+ - **Full type safety**: Complete TypeScript inference for arguments and extensions (see [Type Safety Enhancements](#type-safety-enhancements) for details)
155
+ - **Mix and match**: Combine commands from multiple packages to build your CLI
156
+
157
+ ### `@gunshi/bone` - Minimal CLI Package
158
+
159
+ A lightweight alternative to the main gunshi package, without any built-in plugins:
160
+
161
+ ```sh
162
+ npm install @gunshi/bone
163
+ ```
164
+
165
+ ```js
166
+ import { cli } from '@gunshi/bone'
167
+ import customPlugin from './my-custom-plugin.js'
168
+
169
+ // No default plugins - you have full control
170
+ await cli(process.argv.slice(2), command, {
171
+ name: 'my-minimal-cli',
172
+ plugins: [customPlugin()] // Only the plugins you need
173
+ })
174
+ ```
175
+
176
+ Use cases:
177
+
178
+ - When you need complete control over plugin composition
179
+ - For minimal bundle size in size-critical applications
180
+ - When built-in plugins conflict with your custom implementation
181
+ - For embedded CLIs where every byte counts
182
+
183
+ ### Fallback to Entry Command
184
+
185
+ New `fallbackToEntry` option enables graceful handling of unknown subcommands:
186
+
187
+ ```js
188
+ const mainCommand = {
189
+ name: 'tool',
190
+ args: {
191
+ file: { type: 'string', positional: true }
192
+ },
193
+ run: ctx => {
194
+ // Process file when no subcommand is provided
195
+ console.log(`Processing ${ctx.values.file}`)
196
+ }
197
+ }
198
+
199
+ await cli(process.argv.slice(2), mainCommand, {
200
+ name: 'tool',
201
+ fallbackToEntry: true, // Enable fallback
202
+ subCommands: {
203
+ convert: convertCommand,
204
+ validate: validateCommand
205
+ }
206
+ })
207
+
208
+ // Usage:
209
+ // tool convert file.txt -> Runs convert subcommand
210
+ // tool file.txt -> Falls back to main command
211
+ // tool unknown file.txt -> Falls back to main command (with v0.27)
212
+ ```
213
+
214
+ This feature is particularly useful for:
215
+
216
+ - Git-style CLIs where the main command has its own functionality
217
+ - Backward compatibility when adding subcommands to existing CLIs
218
+ - Creating more forgiving user experiences
219
+
220
+ ### Subcommand Object Style
221
+
222
+ Subcommands can now be defined as plain objects (Record) in addition to Map, providing better organization for complex CLIs:
223
+
224
+ ```js
225
+ // Define subcommands
226
+ const addCommand = {
227
+ name: 'add',
228
+ description: 'Add file contents to the index',
229
+ run: ctx => console.log('Adding files...')
230
+ }
231
+
232
+ const commitCommand = {
233
+ name: 'commit',
234
+ description: 'Record changes to the repository',
235
+ run: ctx => console.log('Committing...')
236
+ }
237
+
238
+ // Pass subcommands as object to CLI options
239
+ await cli(process.argv.slice(2), mainCommand, {
240
+ name: 'git',
241
+ version: '1.0.0',
242
+ subCommands: {
243
+ add: addCommand,
244
+ commit: commitCommand
245
+ }
246
+ })
247
+ ```
248
+
249
+ ### Explicit Argument Detection
250
+
251
+ New feature to detect whether arguments were explicitly provided by the user or using default values:
252
+
253
+ ```js
254
+ const command = {
255
+ name: 'deploy',
256
+ args: {
257
+ verbose: {
258
+ type: 'boolean',
259
+ default: false,
260
+ description: 'Enable verbose output'
261
+ },
262
+ output: {
263
+ type: 'string',
264
+ default: 'output.txt',
265
+ description: 'Output file path'
266
+ }
267
+ },
268
+ run: ctx => {
269
+ // Check if arguments were explicitly provided
270
+ if (ctx.explicit.output) {
271
+ console.log('Output file explicitly specified:', ctx.values.output)
272
+ } else {
273
+ console.log('Using default output file:', ctx.values.output)
274
+ }
275
+
276
+ // Useful for configuration overrides
277
+ if (!ctx.explicit.verbose && ctx.values.verbose) {
278
+ console.log('Verbose mode enabled by config file')
279
+ }
280
+ }
281
+ }
282
+ ```
283
+
284
+ The `ctx.explicit` object indicates for each argument:
285
+
286
+ - `true`: The argument was explicitly provided via command line
287
+ - `false`: The argument uses a default value or is undefined
288
+
289
+ ### Command Lifecycle Hooks
290
+
291
+ New hooks for fine-grained control over command execution, available in CLI options:
292
+
293
+ ```js
294
+ const command = {
295
+ name: 'backup',
296
+ run: ctx => {
297
+ // Main backup logic
298
+ console.log('Running backup...')
299
+ }
300
+ }
301
+
302
+ await cli(process.argv.slice(2), command, {
303
+ name: 'backup-tool',
304
+ version: '1.0.0',
305
+ onBeforeCommand: async ctx => {
306
+ console.log('Preparing backup...')
307
+ },
308
+ onAfterCommand: async (ctx, result) => {
309
+ console.log('Backup completed!')
310
+ if (result) console.log('Result:', result)
311
+ },
312
+ onErrorCommand: async (ctx, error) => {
313
+ console.error('Backup failed:', error)
314
+ // Custom error handling logic
315
+ }
316
+ })
317
+ ```
318
+
319
+ ### Custom Rendering Control
320
+
321
+ New rendering options allow fine-grained control over how commands display their output. Each command can customize or disable specific parts of the UI:
322
+
323
+ ```js
324
+ const command = {
325
+ name: 'wizard',
326
+ rendering: {
327
+ // Custom header with emoji and version
328
+ header: async ctx => `๐Ÿš€ ${ctx.name} v${ctx.version}`,
329
+
330
+ // Disable default usage (set to null)
331
+ usage: null,
332
+
333
+ // Custom error formatting
334
+ validationErrors: async (ctx, error) => `โŒ Error: ${error.message}`
335
+ },
336
+ run: ctx => {
337
+ // Command logic
338
+ }
339
+ }
340
+ ```
341
+
342
+ This feature enables:
343
+
344
+ - **Per-command customization**: Each command can have its own UI style
345
+ - **Selective rendering**: Disable specific parts (header, usage, errors) by setting them to `null`
346
+ - **Async rendering**: Support for async functions to fetch dynamic content
347
+ - **Full context access**: Renderers receive the complete command context for flexible output generation
348
+
349
+ ## โšก Improvement Features
350
+
351
+ ### Type Safety Enhancements
352
+
353
+ v0.27 brings comprehensive type safety improvements to all core APIs through generic type parameters, enabling full TypeScript inference for arguments and plugin extensions.
354
+
355
+ #### Enhanced `define()` API
356
+
357
+ The `define` function now accepts type parameters for type-safe command definitions with plugin extensions:
358
+
359
+ ```ts
360
+ import { define } from 'gunshi'
361
+ import logger, { pluginId as loggerId } from '@your-company/plugin-logger'
362
+ import auth, { pluginId as authId } from '@your-company/plugin-auth'
363
+
364
+ import type { LoggerExtension, PluginId as LoggerId } from '@your-company/plugin-logger'
365
+ import type { AuthExtension, PluginId as AuthId } from '@your-company/plugin-auth'
366
+
367
+ // Type-safe command with plugin extensions
368
+ const deployCommand = define<{
369
+ extensions: Record<LoggerId, LoggerExtension> & Record<AuthId, AuthExtension>
370
+ }>({
371
+ name: 'deploy',
372
+ run: ctx => {
373
+ // Plugin extensions are fully typed
374
+ ctx.extensions[loggerId]?.log('Starting deployment...')
375
+
376
+ if (!ctx.extensions[authId]?.isAuthenticated()) {
377
+ throw new Error('Authentication required')
378
+ }
379
+
380
+ const user = ctx.extensions[authId]?.getUser()
381
+ ctx.extensions[loggerId]?.info(`Deploying as ${user?.name}`)
382
+ }
383
+ })
384
+ ```
385
+
386
+ #### New `defineWithTypes()` API
387
+
388
+ For better type inference with plugin extensions, v0.27 introduces `defineWithTypes()` - a curried function that separates type specification from command definition:
389
+
390
+ ```ts
391
+ import { defineWithTypes } from 'gunshi'
392
+
393
+ // Define your extensions type first
394
+ type MyExtensions = {
395
+ logger: { log: (msg: string) => void }
396
+ auth: { isAuthenticated: () => boolean; getUser: () => User }
397
+ }
398
+
399
+ // Use defineWithTypes - specify only extensions, args are inferred!
400
+ const command = defineWithTypes<{ extensions: MyExtensions }>()({
401
+ name: 'deploy',
402
+ args: {
403
+ env: { type: 'string', required: true },
404
+ force: { type: 'boolean', default: false }
405
+ },
406
+ run: ctx => {
407
+ // ctx.values is automatically inferred as { env: string; force?: boolean }
408
+ // ctx.extensions is typed as MyExtensions
409
+ if (!ctx.extensions.auth?.isAuthenticated()) {
410
+ throw new Error('Authentication required')
411
+ }
412
+
413
+ ctx.extensions.logger?.log(`Deploying to ${ctx.values.env}`)
414
+ }
415
+ })
416
+ ```
417
+
418
+ Benefits over standard `define()`:
419
+
420
+ - Cleaner syntax when specifying extensions - no need for complex generic type parameters
421
+ - Better IDE support with curried function pattern
422
+ - Arguments are automatically inferred from the definition
423
+
424
+ #### Enhanced `lazy()` API
425
+
426
+ The `lazy` function supports type parameters for lazy-loaded commands with extensions:
427
+
428
+ ```ts
429
+ import { lazy } from 'gunshi'
430
+ import { pluginId as dbId } from '@your-company/plugin-database'
431
+ import { pluginId as cacheId } from '@your-company/plugin-cache'
432
+
433
+ import type { DatabaseExtension, PluginId as DbId } from '@your-company/plugin-database'
434
+ import type { CacheExtension, PluginId as CacheId } from '@your-company/plugin-cache'
435
+
436
+ // Type-safe lazy loading with plugin extensions
437
+ const heavyCommand = lazy<{
438
+ extensions: Record<DbId, DatabaseExtension> & Record<CacheId, CacheExtension>
439
+ }>(async () => {
440
+ // Command is loaded only when needed
441
+ return {
442
+ name: 'process',
443
+ run: async ctx => {
444
+ // Extensions are typed even in lazy-loaded commands
445
+ const cached = await ctx.extensions[cacheId]?.get('data')
446
+ if (cached) return cached
447
+
448
+ const result = await ctx.extensions[dbId]?.query('SELECT * FROM data')
449
+ await ctx.extensions[cacheId]?.set('data', result)
450
+ return result
451
+ }
452
+ }
453
+ })
454
+ ```
455
+
456
+ #### New `lazyWithTypes()` API
457
+
458
+ Similar to `defineWithTypes()`, the new `lazyWithTypes()` function provides better type inference for lazy-loaded commands with extensions:
459
+
460
+ <!-- eslint-skip -->
461
+
462
+ ```ts
463
+ import { lazyWithTypes } from 'gunshi'
464
+
465
+ type BuildExtensions = {
466
+ logger: { log: (msg: string) => void }
467
+ metrics: { track: (event: string) => void }
468
+ }
469
+
470
+ // Define args separately for reusability
471
+ const buildArgs = {
472
+ target: { type: 'enum', required: true, choices: ['dev', 'prod'] },
473
+ minify: { type: 'boolean', default: false }
474
+ } as const
475
+
476
+ // Use lazyWithTypes with both args and extensions
477
+ const buildCommand = lazyWithTypes<{
478
+ args: typeof buildArgs
479
+ extensions: BuildExtensions
480
+ }>()(
481
+ async () => {
482
+ // Heavy dependencies loaded only when needed
483
+ const { buildProject } = await import('./heavy-build-utils')
484
+
485
+ return async ctx => {
486
+ // ctx.values inferred from args
487
+ const { target, minify } = ctx.values
488
+
489
+ // Extensions fully typed
490
+ ctx.extensions.logger?.log(`Building for ${target}...`)
491
+ ctx.extensions.metrics?.track('build.started')
492
+
493
+ await buildProject({ target, minify })
494
+ }
495
+ },
496
+ {
497
+ name: 'build',
498
+ description: 'Build the project',
499
+ args: buildArgs
500
+ }
501
+ )
502
+ ```
503
+
504
+ Benefits:
505
+
506
+ - Type-safe lazy loading with plugin extensions
507
+ - Separates type declaration from implementation
508
+ - Supports both args and extensions type parameters
509
+ - Maintains all benefits of lazy loading (performance, code splitting)
510
+
511
+ #### Enhanced `cli()` API
512
+
513
+ The `cli` function accepts type parameters for the entire CLI application's extensions:
514
+
515
+ ```ts
516
+ import { cli } from 'gunshi'
517
+ import i18n, { pluginId as i18nId } from '@gunshi/plugin-i18n'
518
+ import metrics, { pluginId as metricsId } from '@your-company/plugin-metrics'
519
+
520
+ import type { I18nExtension, PluginId as I18nId } from '@gunshi/plugin-i18n'
521
+ import type { MetricsExtension, PluginId as MetricsId } from '@your-company/plugin-metrics'
522
+
523
+ // Type-safe CLI with multiple plugin extensions
524
+ await cli<{ extensions: Record<I18nId, I18nExtension> & Record<MetricsId, MetricsExtension> }>(
525
+ process.argv.slice(2),
526
+ async ctx => {
527
+ // All plugin extensions are available and typed
528
+ const greeting = ctx.extensions[i18nId]?.translate('welcome')
529
+ console.log(greeting)
530
+
531
+ // Track CLI usage
532
+ ctx.extensions[metricsId]?.track('cli.started', {
533
+ command: ctx.name,
534
+ locale: ctx.extensions[i18nId]?.locale.toString()
535
+ })
536
+ },
537
+ {
538
+ name: 'my-cli',
539
+ plugins: [i18n(), metrics()]
540
+ }
541
+ )
542
+ ```
543
+
544
+ These type safety improvements ensure that:
545
+
546
+ - Command arguments are validated at compile time
547
+ - Plugin extensions are properly typed and accessible
548
+ - Typos and incorrect property access are caught before runtime
549
+ - IDE autocompletion works seamlessly across your CLI application
550
+
551
+ ## ๐Ÿ’ฅ Breaking Changes
552
+
553
+ ### Internationalization migration
554
+
555
+ The built-in i18n support in CLI options has been moved to the official i18n plugin:
556
+
557
+ **Before (v0.26):**
558
+
559
+ ```js
560
+ import { cli } from 'gunshi'
561
+
562
+ const command = {
563
+ name: 'app',
564
+ resource: async ctx => {
565
+ // Return translations based on locale
566
+ if (ctx.locale.toString() === 'ja-JP') {
567
+ return { greeting: 'ใ“ใ‚“ใซใกใฏ' }
568
+ }
569
+ return { greeting: 'Hello' }
570
+ },
571
+ run: ctx => {
572
+ console.log(ctx.translate('greeting'))
573
+ }
574
+ }
575
+
576
+ // locale and translationAdapterFactory were in CLI options
577
+ await cli(process.argv.slice(2), command, {
578
+ name: 'my-app',
579
+ version: '1.0.0',
580
+ locale: new Intl.Locale('en-US'),
581
+ translationAdapterFactory: customAdapter
582
+ })
583
+ ```
584
+
585
+ **After (v0.27):**
586
+
587
+ ```ts
588
+ import { cli } from 'gunshi'
589
+ import i18n, { defineI18nWithTypes, pluginId as i18nId } from '@gunshi/plugin-i18n'
590
+ import type { I18nExtension, PluginId as I18nPluginId } from '@gunshi/plugin-i18n'
591
+
592
+ // Type-safe command with i18n plugin extension
593
+ const command = defineI18nWithTypes<{ extensions: Record<I18nPluginId, I18nExtension> }>()({
594
+ name: 'app',
595
+ // resource function is still used with i18n plugin
596
+ resource: locale => {
597
+ if (locale.toString() === 'ja-JP') {
598
+ return { greeting: 'ใ“ใ‚“ใซใกใฏ' }
599
+ }
600
+ return { greeting: 'Hello' }
601
+ },
602
+ run: ctx => {
603
+ // Use the translate() function from i18n plugin extension
604
+ console.log(ctx.extensions[i18nId].translate('greeting'))
605
+ }
606
+ })
607
+
608
+ // locale and translationAdapterFactory removed from CLI options
609
+ await cli(process.argv.slice(2), command, {
610
+ name: 'my-app',
611
+ version: '1.0.0',
612
+ plugins: [
613
+ i18n({
614
+ locale: 'en-US', // Moved from CLI options
615
+ translationAdapterFactory: customAdapter // Moved from CLI options
616
+ })
617
+ ]
618
+ })
619
+ ```
620
+
621
+ **Migration steps:**
622
+
623
+ 1. Install the i18n plugin: `npm install --save @gunshi/plugin-i18n`
624
+ 2. Import the plugin and its ID: `import i18n, { pluginId as i18nId } from '@gunshi/plugin-i18n'`
625
+ 3. Move `locale` from CLI options to i18n plugin options
626
+ 4. Move `translationAdapterFactory` from CLI options to i18n plugin options
627
+ 5. Change `ctx.translate()` to `ctx.extensions[i18nId].translate()`
628
+ 6. Keep using `resource` function in commands
629
+
630
+ **Additional i18n Resources and Helpers:**
631
+
632
+ The i18n plugin comes with additional packages and helper functions to simplify internationalization:
633
+
634
+ - `@gunshi/resources`: Pre-built localization resources for common CLI terms
635
+
636
+ ```js
637
+ import resources from '@gunshi/resources'
638
+
639
+ // Use with i18n plugin
640
+ i18n({
641
+ locale: 'en-US',
642
+ builtinResources: resources
643
+ })
644
+ ```
645
+
646
+ - **Helper Functions**: Type-safe command definition with i18n
647
+
648
+ ```js
649
+ import { defineI18n, withI18nResource } from '@gunshi/plugin-i18n'
650
+
651
+ // Define new command with i18n support
652
+ const command = defineI18n({
653
+ name: 'deploy',
654
+ resource: async ctx => ({
655
+ /* translations */
656
+ }),
657
+ run: ctx => {
658
+ /* command logic */
659
+ }
660
+ })
661
+
662
+ // Add i18n to existing command
663
+ const enhancedCommand = withI18nResource(existingCommand, {
664
+ resource: async ctx => ({
665
+ /* translations */
666
+ })
667
+ })
668
+ ```
669
+
670
+ - **Key Resolution Helpers**: Proper namespace management
671
+
672
+ ```js
673
+ import { resolveKey } from '@gunshi/plugin-i18n'
674
+
675
+ // In subcommands, use `resolveKey` for proper namespacing
676
+ run: ctx => {
677
+ const key = resolveKey('customMessage', ctx.name)
678
+ const message = ctx.extensions[i18nId].translate(key)
679
+ }
680
+ ```
681
+
682
+ ## ๐Ÿ“š Documentation
683
+
684
+ ### New Plugin Guides
685
+
686
+ - **Plugin Ecosystem Guide**: Comprehensive introduction to the plugin system
687
+ - **Context Extensions Guide**: Learn how to leverage plugin extensions
688
+ - **Plugin Development Guide**: Step-by-step guide for creating custom plugins
689
+
690
+ ### Updated API Reference
691
+
692
+ - New plugin-related interfaces: `PluginOptions`, `PluginWithExtension`, `PluginWithoutExtension`
693
+ - Enhanced type definitions: `GunshiParams`, `CommandContextExtension`
694
+ - Rendering options: `RenderingOptions`, `RendererDecorator`
695
+
696
+ ### Example Projects
697
+
698
+ New playground examples demonstrating:
699
+
700
+ - Plugin usage patterns
701
+ - Context extension scenarios
702
+ - Advanced type safety with plugins
703
+
704
+ ## ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ Contributors
705
+
706
+ We'd like to thank all the contributors who made this release possible:
707
+
708
+ - [@kazupon](https://github.com/kazupon) - Core maintainer, plugin system architect, i18n extraction, lifecycle hooks
709
+ - [@yukukotani](https://github.com/yukukotani) - Fallback to entry command feature (#291)
710
+ - [@sushichan044](https://github.com/sushichan044) - Explicit argument detection feature (#232)
711
+ - [@43081j](https://github.com/43081j) - Exposed `Plugin` type (#159)
712
+ - [@theoephraim](https://github.com/theoephraim) - Documentation improvements (#249)
713
+ - [@lukekarrys](https://github.com/lukekarrys) - Documentation updates (#228)
714
+ - [@BobbieGoede](https://github.com/BobbieGoede) - Fixed FUNDING.yml (#303)
715
+
716
+ Special thanks to the community for feedback and bug reports that helped shape v0.27!
717
+
718
+ ## ๐Ÿ’– Credits
719
+
720
+ The completion plugin is powered by:
721
+
722
+ - [`@bomb.sh/tab`](https://github.com/bombshell-dev/tab) by [Bombshell](https://github.com/bombshell-dev) - Shell completion library that powers our tab completion functionality