@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,945 @@
1
+ # Custom Rendering
2
+
3
+ Gunshi provides a flexible rendering system that allows you to customize how your CLI displays help text, usage information, and error messages.
4
+
5
+ ## Overview
6
+
7
+ Gunshi provides three levels of customization, each capable of controlling all rendering components (Header, Usage/Help, and Validation Errors).
8
+
9
+ When multiple levels define the same renderer, the higher priority level wins.
10
+
11
+ Customizing rendering offers several benefits:
12
+
13
+ - **Branding**: Match your CLI's help messages to your project's style
14
+ - **Clarity**: Organize information in a way that makes sense for your users
15
+ - **Flexibility**: Add custom sections or formatting to help messages
16
+ - **Consistency**: Ensure help messages follow your organization's standards
17
+
18
+ ## Three-Level Customization System
19
+
20
+ ```mermaid
21
+ graph TB
22
+ subgraph " "
23
+ subgraph "Plugin Level"
24
+ PH[Header]
25
+ PU[Usage/Help]
26
+ PE[Validation Errors]
27
+ end
28
+ subgraph "CLI Level"
29
+ CLH[Header]
30
+ CLU[Usage/Help]
31
+ CLE[Validation Errors]
32
+ end
33
+ subgraph "Command Level"
34
+ CH[Header]
35
+ CU[Usage/Help]
36
+ CE[Validation Errors]
37
+ end
38
+ end
39
+
40
+ style CH fill:#468c56,color:white
41
+ style CU fill:#468c56,color:white
42
+ style CE fill:#468c56,color:white
43
+ style CLH fill:#468c56,color:white
44
+ style CLU fill:#468c56,color:white
45
+ style CLE fill:#468c56,color:white
46
+ style PH fill:#468c56,color:white
47
+ style PU fill:#468c56,color:white
48
+ style PE fill:#468c56,color:white
49
+ ```
50
+
51
+ The diagram above illustrates Gunshi's three-level rendering customization system.
52
+
53
+ Each level can independently control three rendering components:
54
+
55
+ - **Header**: The title section of help text, typically showing the CLI name and version
56
+ - **Usage/Help**: The main help content including command syntax, options, and arguments
57
+ - **Validation Errors**: Error messages shown when command arguments fail validation
58
+
59
+ The levels work in a priority hierarchy where:
60
+
61
+ - **Command Level** (top) has the highest priority and overrides all other levels
62
+ - **CLI Level** (middle) provides defaults for all commands in your application
63
+ - **Plugin Level** (bottom) adds reusable rendering logic through decorators
64
+
65
+ ## Renderer Resolution
66
+
67
+ Gunshi checks each customization level in priority order (Command → CLI → Plugin) and uses the first renderer it finds:
68
+
69
+ ```mermaid
70
+ graph TD
71
+ Start[Rendering Request] --> P1{Command.rendering defined?}
72
+ P1 -->|Yes| UseCommand[Use Command Rendering]
73
+ P1 -->|No| P2{CLI Options defined?}
74
+ P2 -->|Yes| UseCLI[Use CLI Rendering]
75
+ P2 -->|No| P3{Plugin Decorators?}
76
+ P3 -->|Yes| UsePlugin[Apply Plugin Decorators]
77
+ P3 -->|No| Default[Use Default]
78
+
79
+ UseCommand --> Output[Final Output]
80
+ UseCLI --> Output
81
+ UsePlugin --> Output
82
+ Default --> Output
83
+
84
+ style UseCommand fill:#468c56,color:white
85
+ style UseCLI fill:#468c56,color:white
86
+ style UsePlugin fill:#468c56,color:white
87
+ style Default fill:#468c56,color:white
88
+ ```
89
+
90
+ ## How It Works
91
+
92
+ This example shows the rendering resolution flow when a user runs a command with `--help`:
93
+
94
+ ```mermaid
95
+ sequenceDiagram
96
+ participant User
97
+ participant CLI
98
+ participant Command
99
+ participant Plugin
100
+ participant Output
101
+
102
+ User->>CLI: Run command with --help
103
+ CLI->>Command: Check command.rendering
104
+ alt Has command.rendering
105
+ Command->>Output: Use command renderer
106
+ else Has CLI options
107
+ CLI->>Output: Use CLI renderer
108
+ else Has plugins
109
+ CLI->>Plugin: Apply plugin decorators
110
+ Plugin->>Output: Use decorated renderer
111
+ else
112
+ CLI->>Output: Use default renderer
113
+ end
114
+ Output->>User: Display formatted help
115
+ ```
116
+
117
+ ## Command-Level Rendering
118
+
119
+ The most specific way to customize rendering is at the command level. Use this when:
120
+
121
+ - A specific command needs unique formatting or branding
122
+ - Different commands require different help layouts
123
+ - You want to override global settings for particular commands
124
+
125
+ ### Understanding Command Context
126
+
127
+ All renderer functions receive a Command Context object as their first parameter.
128
+
129
+ This context provides comprehensive information about the current command execution state, including configuration, arguments, environment details, and more.
130
+
131
+ ```js
132
+ // Renderer functions receive context as their first argument
133
+ renderHeader: ctx => {
134
+ // ctx contains all command execution information
135
+ return `${ctx.env.name} - ${ctx.description}`
136
+ }
137
+ ```
138
+
139
+ <!-- eslint-disable markdown/no-missing-label-refs -->
140
+
141
+ > [!NOTE]
142
+ > The Command Context (`ctx`) is a read-only object that provides access to command metadata, parsed arguments, environment configuration, and plugin extensions. For the complete CommandContext API reference including all properties and types, see the [CommandContext interface documentation](../../api/default/interfaces/CommandContext).
143
+
144
+ <!-- eslint-enable markdown/no-missing-label-refs -->
145
+
146
+ ### Basic Example
147
+
148
+ The following example demonstrates how to customize rendering at the command level for a watch command that needs specialized formatting for its interactive mode.
149
+
150
+ Notice how the renderer functions receive a Command Context (`ctx`) parameter that provides access to command information:
151
+
152
+ ```js
153
+ import { define } from 'gunshi'
154
+
155
+ const command = define({
156
+ name: 'watch',
157
+ description: 'Watch files and rebuild on changes',
158
+
159
+ // Command-specific rendering for interactive watch mode
160
+ rendering: {
161
+ header: ctx => {
162
+ return `👁️ WATCH MODE - Press Ctrl+C to stop`
163
+ },
164
+
165
+ usage: ctx => {
166
+ return `Usage: ${ctx.name} [options]
167
+
168
+ Watch Options:
169
+ --entry <file> Entry point (default: src/index.ts)
170
+ --out <dir> Output directory (default: dist)
171
+ --ignore <pattern> Ignore patterns
172
+
173
+ Keyboard Shortcuts (during watch):
174
+ r - Force rebuild
175
+ c - Clear console
176
+ q - Quit watch mode`
177
+ },
178
+
179
+ validationErrors: (ctx, error) => {
180
+ // Watch-specific error formatting
181
+ return `⚠️ Watch mode cannot start:\n${error.errors.map(err => ` • ${err.message}`).join('\n')}`
182
+ }
183
+ },
184
+
185
+ run: async ctx => {
186
+ // Command logic ...
187
+ }
188
+ })
189
+ ```
190
+
191
+ ### Practical Examples
192
+
193
+ Here's how to create custom renderers for specific use cases:
194
+
195
+ ```js
196
+ import { cli, define } from 'gunshi'
197
+
198
+ // Define a custom header renderer with fancy formatting
199
+ const customHeaderRenderer = ctx => {
200
+ const lines = []
201
+
202
+ // Add a fancy header
203
+ lines.push('╔═════════════════════════════════════════╗')
204
+ lines.push(`║ ${ctx.env.name.toUpperCase().padStart(20).padEnd(39)} ║`)
205
+ lines.push('╚═════════════════════════════════════════╝')
206
+
207
+ // Add description and version
208
+ if (ctx.env.description) {
209
+ lines.push(ctx.env.description)
210
+ }
211
+
212
+ if (ctx.env.version) {
213
+ lines.push(`Version: ${ctx.env.version}`)
214
+ }
215
+
216
+ lines.push('')
217
+
218
+ // Return the header as a string
219
+ return lines.join('\n')
220
+ }
221
+
222
+ // Define your command
223
+ const command = define({
224
+ name: 'app',
225
+ description: 'My application',
226
+ args: {
227
+ name: {
228
+ type: 'string',
229
+ short: 'n',
230
+ description: 'Name to use'
231
+ }
232
+ },
233
+ run: ctx => {
234
+ // Command implementation
235
+ }
236
+ })
237
+
238
+ // Run the command with the custom header renderer
239
+ await cli(process.argv.slice(2), command, {
240
+ name: 'my-app',
241
+ version: '1.0.0',
242
+ description: 'A CLI application with custom usage generation',
243
+ renderHeader: customHeaderRenderer
244
+ })
245
+ ```
246
+
247
+ When users run `node app.js --help`, they'll see your custom header:
248
+
249
+ ```sh
250
+ ╔═════════════════════════════════════════╗
251
+ ║ MY-APP ║
252
+ ╚═════════════════════════════════════════╝
253
+ A CLI application with custom usage generation
254
+ Version: 1.0.0
255
+
256
+
257
+ USAGE:
258
+ my-app <OPTIONS>
259
+
260
+ OPTIONS:
261
+ -n, --name <name> Name to use
262
+ -h, --help Display this help message
263
+ -v, --version Display this version
264
+ ```
265
+
266
+ ### Disabling Renderers
267
+
268
+ Set any renderer to `null` to disable it:
269
+
270
+ <!-- eslint-skip -->
271
+
272
+ ```js
273
+ rendering: {
274
+ header: null, // No header
275
+ usage: async () => '...' // Custom usage
276
+ }
277
+ ```
278
+
279
+ ## CLI-Level Rendering
280
+
281
+ Set default renderers for all commands in your CLI. Use this when:
282
+
283
+ - You want consistent formatting across your entire application
284
+ - All commands should share the same branding or style
285
+ - You need a baseline that individual commands can override
286
+
287
+ ### Basic Configuration
288
+
289
+ ```js
290
+ import { cli } from 'gunshi'
291
+
292
+ await cli(process.argv.slice(2), command, {
293
+ name: 'my-cli',
294
+ version: '1.0.0',
295
+
296
+ // Apply to all commands
297
+ renderHeader: ctx => {
298
+ return `=== ${ctx.env.name} v${ctx.env.version} ===`
299
+ },
300
+
301
+ renderUsage: ctx => {
302
+ return `${ctx.env.name} ${ctx.name || 'command'} [options]`
303
+ },
304
+
305
+ renderValidationErrors: (ctx, error) => {
306
+ return `Error: ${error.message}`
307
+ }
308
+ })
309
+ ```
310
+
311
+ ### Combined Renderers Example
312
+
313
+ You can combine all three custom renderers for a completely customized help experience:
314
+
315
+ ```js
316
+ import { cli, define } from 'gunshi'
317
+
318
+ // Define a command
319
+ const command = define({
320
+ name: 'task-manager',
321
+ description: 'A task management utility',
322
+ args: {
323
+ add: {
324
+ type: 'string',
325
+ short: 'a',
326
+ description: 'Add a new task'
327
+ },
328
+ list: {
329
+ type: 'boolean',
330
+ short: 'l',
331
+ description: 'List all tasks'
332
+ },
333
+ complete: {
334
+ type: 'string',
335
+ short: 'c',
336
+ description: 'Mark a task as complete'
337
+ },
338
+ priority: {
339
+ type: 'string',
340
+ short: 'p',
341
+ description: 'Set task priority (low, medium, high)'
342
+ },
343
+ due: {
344
+ type: 'string',
345
+ short: 'd',
346
+ description: 'Set due date in YYYY-MM-DD format'
347
+ }
348
+ },
349
+ examples: `# Add a new task
350
+ $ task-manager --add "Complete the project"
351
+
352
+ # Add a task with priority and due date
353
+ $ task-manager --add "Important meeting" --priority high --due 2023-12-31
354
+
355
+ # List all tasks
356
+ $ task-manager --list
357
+
358
+ # Mark a task as complete
359
+ $ task-manager --complete "Complete the project"`,
360
+ run: ctx => {
361
+ // Command implementation
362
+ }
363
+ })
364
+
365
+ // Define custom usage renderer
366
+ const customUsageRenderer = ctx => {
367
+ const lines = []
368
+ lines.push('📋 USAGE:')
369
+ lines.push(` ${ctx.env.name} [options]`)
370
+ lines.push('')
371
+ lines.push('⚡ QUICK START:')
372
+ lines.push(' Run with --help for detailed options')
373
+ return lines.join('\n')
374
+ }
375
+
376
+ // Define custom validation errors renderer
377
+ const customValidationErrorsRenderer = (ctx, error) => {
378
+ const lines = []
379
+ lines.push('❌ VALIDATION ERROR')
380
+ lines.push('────────────────────')
381
+
382
+ // Display each validation error
383
+ for (const e of error.errors) {
384
+ lines.push(` • ${e.message}`)
385
+ }
386
+
387
+ lines.push('')
388
+ lines.push('💡 TIP: Use --help to see valid options')
389
+ return lines.join('\n')
390
+ }
391
+
392
+ // Run the CLI with all custom renderers
393
+ await cli(process.argv.slice(2), command, {
394
+ name: 'task-manager',
395
+ version: '1.0.0',
396
+ description: 'A task management utility',
397
+ renderHeader: customHeaderRenderer,
398
+ renderUsage: customUsageRenderer,
399
+ renderValidationErrors: customValidationErrorsRenderer
400
+ })
401
+ ```
402
+
403
+ ## Plugin-Level Rendering
404
+
405
+ Plugins provide the most powerful way to customize rendering across your entire CLI.
406
+
407
+ They can decorate renderers to add consistent behavior across all commands.
408
+
409
+ <!-- eslint-disable markdown/no-missing-label-refs -->
410
+
411
+ > [!NOTE]
412
+ > Plugin-level rendering has the lowest priority in the rendering hierarchy. Command-level and global-level renderers will override plugin decorators. For detailed information on how renderer decorators work and how to implement them, see the [How Renderer Decorators Work](../plugin/decorators.md#how-renderer-decorators-work) documentation.
413
+
414
+ <!-- eslint-enable markdown/no-missing-label-refs -->
415
+
416
+ ### Using the Built-in Renderer Plugin
417
+
418
+ The `@gunshi/plugin-renderer` package provides enhanced rendering out of the box:
419
+
420
+ ```js
421
+ import { cli } from 'gunshi/bone'
422
+ import renderer from '@gunshi/plugin-renderer'
423
+
424
+ await cli(process.argv.slice(2), command, {
425
+ name: 'my-cli',
426
+ plugins: [
427
+ renderer() // Adds enhanced rendering
428
+ ]
429
+ })
430
+ ```
431
+
432
+ The built-in renderer provides complete rendering implementations with improved formatting and visual hierarchy.
433
+
434
+ Unlike decorator plugins, it replaces the default rendering entirely.
435
+
436
+ <!-- eslint-disable markdown/no-missing-label-refs -->
437
+
438
+ > [!TIP]
439
+ > For detailed features of the built-in renderer plugin, see the [documentation](https://github.com/kazupon/gunshi/blob/main/packages/plugin-renderer/README.md).
440
+
441
+ <!-- eslint-enable markdown/no-missing-label-refs -->
442
+
443
+ ### Creating a Custom Rendering Plugin
444
+
445
+ When building rendering plugins, use the decorator pattern to enhance existing output:
446
+
447
+ <!-- eslint-skip -->
448
+
449
+ ```js
450
+ import { plugin } from 'gunshi/plugin'
451
+
452
+ const color = plugin({
453
+ id: 'my:color',
454
+ name: 'Color Plugin',
455
+
456
+ setup(context) {
457
+ // Decorate header renderer
458
+ context.decorateHeaderRenderer(async (baseRenderer, ctx) => {
459
+ const output = await baseRenderer(ctx)
460
+ if (!output) {
461
+ return ''
462
+ }
463
+
464
+ return `\x1b[36m${output}\x1b[0m` // Add cyan color
465
+ })
466
+
467
+ // Decorate error renderer
468
+ context.decorateValidationErrorsRenderer(async (baseRenderer, ctx, error) => {
469
+ const output = await baseRenderer(ctx, error)
470
+ if (!output) {
471
+ return ''
472
+ }
473
+
474
+ return `\x1b[31m${output}\x1b[0m` // Add red color
475
+ })
476
+ }
477
+ })
478
+ ```
479
+
480
+ <!-- eslint-disable markdown/no-missing-label-refs -->
481
+
482
+ > [!IMPORTANT]
483
+ > Always call `baseRenderer` in your decorators to maintain the plugin chain. This ensures compatibility with other plugins.
484
+
485
+ <!-- eslint-enable markdown/no-missing-label-refs -->
486
+
487
+ ### Practical Example: Theme Plugin
488
+
489
+ Here's a complete theme plugin that enhances all rendering aspects:
490
+
491
+ <!-- eslint-skip -->
492
+
493
+ ```js
494
+ import { plugin } from 'gunshi/plugin'
495
+
496
+ const oceanTheme = plugin({
497
+ id: 'themes:ocean',
498
+ name: 'Ocean Theme',
499
+
500
+ setup(context) {
501
+ const colors = {
502
+ header: '\x1b[36m', // Cyan
503
+ usage: '\x1b[34m', // Blue
504
+ error: '\x1b[31m', // Red
505
+ reset: '\x1b[0m'
506
+ }
507
+
508
+ // Add wave decoration to headers
509
+ context.decorateHeaderRenderer(async (baseRenderer, ctx) => {
510
+ const output = await baseRenderer(ctx)
511
+ if (!output) return ''
512
+
513
+ const waves = '~~~~~'
514
+ return `${colors.header}${waves}\n${output}\n${waves}${colors.reset}`
515
+ })
516
+
517
+ // Style usage sections
518
+ context.decorateUsageRenderer(async (baseRenderer, ctx) => {
519
+ const output = await baseRenderer(ctx)
520
+ if (!output) return ''
521
+
522
+ return output.replace(/^([A-Z][A-Z\s]+:)$/gm, `${colors.usage}$1${colors.reset}`)
523
+ })
524
+
525
+ // Enhance error display
526
+ context.decorateValidationErrorsRenderer(async (baseRenderer, ctx, error) => {
527
+ const output = await baseRenderer(ctx, error)
528
+ if (!output) return ''
529
+
530
+ return `${colors.error}⚠️ ERROR\n${output}${colors.reset}`
531
+ })
532
+ }
533
+ })
534
+ ```
535
+
536
+ ### Combining Multiple Rendering Plugins
537
+
538
+ When using multiple rendering plugins, they execute in the order they are registered.
539
+
540
+ Each plugin receives the output from the previous one, creating a transformation chain.
541
+
542
+ Let's create a simple emoji plugin to demonstrate plugin combination:
543
+
544
+ ```js
545
+ import { plugin } from 'gunshi/plugin'
546
+
547
+ // Define a simple emoji plugin using correct decorator API
548
+ const emoji = plugin({
549
+ id: 'my:emoji',
550
+ name: 'Emoji Decorator',
551
+
552
+ setup(context) {
553
+ // Use the correct decorator method for usage rendering
554
+ context.decorateUsageRenderer(async (baseRenderer, ctx) => {
555
+ const output = await baseRenderer(ctx)
556
+ if (!output) return ''
557
+
558
+ // Add emoji indicators to section headers
559
+ return output
560
+ .replace(/^USAGE:/gm, '📖 USAGE:')
561
+ .replace(/^OPTIONS:/gm, '⚙️ OPTIONS:')
562
+ .replace(/^COMMANDS:/gm, '📦 COMMANDS:')
563
+ .replace(/^ARGUMENTS:/gm, '🔤 ARGUMENTS:')
564
+ .replace(/^EXAMPLES:/gm, '💡 EXAMPLES:')
565
+ })
566
+ }
567
+ })
568
+
569
+ // Combine with our ocean theme plugin
570
+ await cli(process.argv.slice(2), command, {
571
+ name: 'my-cli',
572
+ plugins: [
573
+ renderer(), // Base renderer
574
+ oceanTheme(), // Apply ocean theme colors
575
+ emoji() // Add emoji indicators
576
+ ]
577
+ })
578
+ ```
579
+
580
+ #### Understanding the Transformation Chain
581
+
582
+ Each plugin transforms the output sequentially:
583
+
584
+ ```sh
585
+ 1. Base renderer output:
586
+ "Usage: my-cli [options]"
587
+
588
+ 2. After oceanThemePlugin:
589
+ "\x1b[38;5;39mUsage: my-cli [options]\x1b[0m" (colored)
590
+
591
+ 3. After emojiPlugin:
592
+ "📖 \x1b[38;5;39mUsage: my-cli [options]\x1b[0m" (emoji + colored)
593
+ ```
594
+
595
+ <!-- eslint-disable markdown/no-missing-label-refs -->
596
+
597
+ > [!IMPORTANT]
598
+ > Plugin order significantly affects the final output. Color plugins should typically come before text modification plugins to ensure ANSI codes don't interfere with pattern matching.
599
+
600
+ <!-- eslint-enable markdown/no-missing-label-refs -->
601
+
602
+ #### Guidelines for Plugin Composition
603
+
604
+ - **Keep plugins focused**: Each plugin should handle one specific aspect of rendering
605
+ - **Consider dependencies**: Some plugins may require others to run first
606
+ - **Test combinations**: Verify that your plugins work well together
607
+ - **Document requirements**: Clearly state if your plugin expects specific input formats
608
+
609
+ ## Advanced Customization
610
+
611
+ ### Using Colors
612
+
613
+ You can enhance your custom renderers with colors using libraries like:
614
+
615
+ - [chalk](https://github.com/chalk/chalk)
616
+ - [kleur](https://github.com/lukeed/kleur)
617
+ - [picocolors](https://github.com/alexeyraspopov/picocolors)
618
+
619
+ The following example demonstrates using chalk for colored output:
620
+
621
+ ```js
622
+ import { cli } from 'gunshi'
623
+ import chalk from 'chalk'
624
+
625
+ // Custom header renderer with colors
626
+ const coloredHeaderRenderer = ctx => {
627
+ const lines = []
628
+ lines.push(chalk.blue('╔═════════════════════════════════════════╗'))
629
+ lines.push(chalk.blue(`║ ${chalk.bold(ctx.env.name.toUpperCase())} ║`))
630
+ lines.push(chalk.blue('╚═════════════════════════════════════════╝'))
631
+
632
+ if (ctx.env.description) {
633
+ lines.push(chalk.white(ctx.env.description))
634
+ }
635
+
636
+ if (ctx.env.version) {
637
+ lines.push(chalk.gray(`Version: ${ctx.env.version}`))
638
+ }
639
+
640
+ lines.push('')
641
+ return lines.join('\n')
642
+ }
643
+
644
+ // Custom usage renderer with colors
645
+ const coloredUsageRenderer = ctx => {
646
+ const lines = []
647
+
648
+ lines.push(chalk.yellow.bold('📋 COMMAND USAGE'))
649
+ lines.push(chalk.yellow('═══════════════'))
650
+ lines.push('')
651
+
652
+ lines.push(chalk.white.bold('BASIC USAGE:'))
653
+ lines.push(chalk.white(` $ ${ctx.env.name} [options]`))
654
+ lines.push('')
655
+
656
+ if (ctx.args && Object.keys(ctx.args).length > 0) {
657
+ lines.push(chalk.white.bold('OPTIONS:'))
658
+
659
+ for (const [key, option] of Object.entries(ctx.args)) {
660
+ const shortFlag = option.short ? chalk.green(`-${option.short}, `) : ' '
661
+ const longFlag = chalk.green(`--${key}`)
662
+ const type = chalk.blue(`[${option.type}]`)
663
+ const required = option.required ? chalk.red(' (required)') : ''
664
+
665
+ lines.push(
666
+ ` ${shortFlag}${longFlag.padEnd(15)} ${type.padEnd(10)} ${option.description || key}${required}`
667
+ )
668
+ }
669
+
670
+ lines.push('')
671
+ }
672
+
673
+ return lines.join('\n')
674
+ }
675
+
676
+ // Run the CLI with colored renderers
677
+ await cli(process.argv.slice(2), command, {
678
+ name: 'task-manager',
679
+ version: '1.0.0',
680
+ description: 'A task management utility',
681
+ renderHeader: coloredHeaderRenderer,
682
+ renderUsage: coloredUsageRenderer
683
+ })
684
+ ```
685
+
686
+ ### Working with Command Context
687
+
688
+ The Command Context object (`ctx`) passed to renderer functions provides access to all information needed to customize your output.
689
+
690
+ Here are the key property groups and their practical uses:
691
+
692
+ #### Environment and Metadata
693
+
694
+ These properties provide information about the CLI and current command:
695
+
696
+ ```js
697
+ renderHeader: ctx => {
698
+ // Access CLI-level configuration
699
+ const appName = ctx.env.name // CLI application name
700
+ const version = ctx.env.version // Application version
701
+
702
+ // Access command-specific metadata
703
+ const cmdName = ctx.name // Current command name
704
+ const description = ctx.description // Command description
705
+
706
+ return `${appName} v${version} - ${cmdName || 'main'}`
707
+ }
708
+ ```
709
+
710
+ #### Arguments and Values
711
+
712
+ Access parsed command-line arguments and their values:
713
+
714
+ ```js
715
+ renderUsage: ctx => {
716
+ // ctx.args - Command argument schemas
717
+ // ctx.values - Parsed argument values
718
+ // ctx.explicit - Which args were explicitly provided
719
+
720
+ const lines = []
721
+
722
+ // Show which arguments have default values
723
+ for (const [key, schema] of Object.entries(ctx.args)) {
724
+ const wasProvided = ctx.explicit[key]
725
+ const currentValue = ctx.values[key]
726
+
727
+ if (!wasProvided && currentValue !== undefined) {
728
+ lines.push(` --${key} (default: ${currentValue})`)
729
+ }
730
+ }
731
+
732
+ return lines.join('\n')
733
+ }
734
+ ```
735
+
736
+ #### Positional and Rest Arguments
737
+
738
+ Handle non-flag arguments:
739
+
740
+ ```js
741
+ renderValidationErrors: (ctx, error) => {
742
+ // ctx.positionals - Positional arguments
743
+ // ctx.rest - Arguments after '--' delimiter
744
+
745
+ if (ctx.positionals.length > 0) {
746
+ return `Unexpected arguments: ${ctx.positionals.join(', ')}\n${error.message}`
747
+ }
748
+
749
+ return error.message
750
+ }
751
+ ```
752
+
753
+ #### Plugin Extensions
754
+
755
+ Plugins extend the command context with additional functionality through namespaced extensions.
756
+
757
+ Each plugin registers its capabilities under a unique plugin ID to prevent conflicts.
758
+
759
+ When accessing plugin-provided data in custom renderers, you must use the plugin's ID as the namespace key:
760
+
761
+ ```js
762
+ import { pluginId as themeId } from '@my/plugin-theme' // Import plugin ID
763
+
764
+ renderHeader: ctx => {
765
+ // Access plugin extension using its ID as namespace
766
+ const themeExtension = ctx.extensions[themeId]
767
+
768
+ // Defensive check for extension availability, if the plugin is optional
769
+ if (themeExtension) {
770
+ return themeExtension.formatHeader(ctx.env.name)
771
+ }
772
+
773
+ // Fallback when plugin is not available
774
+ return ctx.env.name
775
+ }
776
+ ```
777
+
778
+ <!-- eslint-disable markdown/no-missing-label-refs -->
779
+
780
+ > [!TIP]
781
+ > For comprehensive guidance on working with plugin extensions including type safety, composition patterns, and troubleshooting, see the [Context Extensions](./context-extensions.md) guide.
782
+
783
+ <!-- eslint-enable markdown/no-missing-label-refs -->
784
+
785
+ For the complete list of available properties and their types, refer to the [CommandContext API reference](../../api/default/interfaces/CommandContext).
786
+
787
+ ### Complete Implementation Example
788
+
789
+ Here's a comprehensive example of a task manager CLI with fully customized rendering:
790
+
791
+ ```js [cli.js]
792
+ import { cli, define } from 'gunshi'
793
+
794
+ // Custom header renderer
795
+ const customHeaderRenderer = ctx => {
796
+ const lines = []
797
+ lines.push('╔═════════════════════════════════════════╗')
798
+ lines.push('║ TASK MANAGER ║')
799
+ lines.push('╚═════════════════════════════════════════╝')
800
+
801
+ if (ctx.env.description) {
802
+ lines.push(ctx.env.description)
803
+ }
804
+
805
+ if (ctx.env.version) {
806
+ lines.push(`Version: ${ctx.env.version}`)
807
+ }
808
+
809
+ lines.push('')
810
+ return lines.join('\n')
811
+ }
812
+
813
+ // Custom usage renderer
814
+ const customUsageRenderer = ctx => {
815
+ const lines = []
816
+
817
+ // Add a custom title
818
+ lines.push('📋 COMMAND USAGE')
819
+ lines.push('═══════════════')
820
+ lines.push('')
821
+
822
+ // Add basic usage
823
+ lines.push('BASIC USAGE:')
824
+ lines.push(` $ ${ctx.env.name} [options]`)
825
+ lines.push('')
826
+
827
+ // Add options section with custom formatting
828
+ if (ctx.args && Object.keys(ctx.args).length > 0) {
829
+ lines.push('OPTIONS:')
830
+
831
+ for (const [key, option] of Object.entries(ctx.args)) {
832
+ const shortFlag = option.short ? `-${option.short}, ` : ' '
833
+ const longFlag = `--${key}`
834
+ const type = `[${option.type}]`
835
+
836
+ // Format the option with custom styling
837
+ const formattedOption = ` ${shortFlag}${longFlag.padEnd(15)} ${type.padEnd(10)} ${option.description || key}`
838
+ lines.push(formattedOption)
839
+ }
840
+
841
+ lines.push('')
842
+ }
843
+
844
+ // Add footer
845
+ lines.push('NOTE: This is a demo application with custom usage formatting.')
846
+ lines.push('For more information, visit: https://github.com/kazupon/gunshi')
847
+
848
+ return lines.join('\n')
849
+ }
850
+
851
+ // Custom validation errors renderer
852
+ const customValidationErrorsRenderer = (ctx, error) => {
853
+ const lines = []
854
+
855
+ lines.push('❌ ERROR:')
856
+ lines.push('═════════')
857
+
858
+ for (const err of error.errors) {
859
+ lines.push(` • ${err.message}`)
860
+ }
861
+
862
+ lines.push('')
863
+ lines.push('Please correct the above errors and try again.')
864
+ lines.push(`Run '${ctx.env.name} --help' for usage information.`)
865
+
866
+ return lines.join('\n')
867
+ }
868
+
869
+ // Define a command
870
+ const command = define({
871
+ name: 'task-manager',
872
+ description: 'A task management utility with custom usage generation',
873
+ args: {
874
+ add: {
875
+ type: 'string',
876
+ short: 'a',
877
+ description: 'Add a new task'
878
+ },
879
+ list: {
880
+ type: 'boolean',
881
+ short: 'l',
882
+ description: 'List all tasks'
883
+ },
884
+ complete: {
885
+ type: 'string',
886
+ short: 'c',
887
+ description: 'Mark a task as complete'
888
+ },
889
+ priority: {
890
+ type: 'string',
891
+ short: 'p',
892
+ description: 'Set task priority (low, medium, high)'
893
+ },
894
+ due: {
895
+ type: 'string',
896
+ short: 'd',
897
+ description: 'Set due date in YYYY-MM-DD format'
898
+ }
899
+ },
900
+ examples: `# Add a new task
901
+ $ task-manager --add "Complete the project"
902
+
903
+ # Add a task with priority and due date
904
+ $ task-manager --add "Important meeting" --priority high --due 2023-12-31
905
+
906
+ # List all tasks
907
+ $ task-manager --list
908
+
909
+ # Mark a task as complete
910
+ $ task-manager --complete "Complete the project"`,
911
+ run: ctx => {
912
+ const { add, list, complete, priority, due } = ctx.values
913
+
914
+ if (add) {
915
+ console.log(`Adding task: "${add}"`)
916
+ if (priority) console.log(`Priority: ${priority}`)
917
+ if (due) console.log(`Due date: ${due}`)
918
+ } else if (list) {
919
+ console.log('Listing all tasks...')
920
+ } else if (complete) {
921
+ console.log(`Marking task as complete: "${complete}"`)
922
+ } else {
923
+ console.log('No action specified. Run with --help to see usage information.')
924
+ }
925
+ }
926
+ })
927
+
928
+ // Run the command with custom usage generation
929
+ await cli(process.argv.slice(2), command, {
930
+ name: 'task-manager',
931
+ version: '1.0.0',
932
+ description: 'A task management utility with custom usage generation',
933
+ // Custom renderers
934
+ renderHeader: customHeaderRenderer,
935
+ renderUsage: customUsageRenderer,
936
+ renderValidationErrors: customValidationErrorsRenderer
937
+ })
938
+ ```
939
+
940
+ <!-- eslint-disable markdown/no-missing-label-refs -->
941
+
942
+ > [!TIP]
943
+ > The example fully code is [here](https://github.com/kazupon/gunshi/tree/main/playground/advanced/custom-rendering).
944
+
945
+ <!-- eslint-enable markdown/no-missing-label-refs -->