@gunshi/plugin-renderer 0.26.3
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/LICENSE +20 -0
- package/README.md +289 -0
- package/lib/index.d.ts +123 -0
- package/lib/index.js +488 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 kazuya kawaguchi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# @gunshi/plugin-renderer
|
|
2
|
+
|
|
3
|
+
> usage renderer plugin for gunshi.
|
|
4
|
+
|
|
5
|
+
This plugin provides customizable rendering for CLI help messages, usage information, and validation errors. It automatically formats command descriptions, arguments, options, examples, and error messages in a consistent and readable format.
|
|
6
|
+
|
|
7
|
+
## 💿 Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
# npm
|
|
11
|
+
npm install --save @gunshi/plugin-renderer
|
|
12
|
+
|
|
13
|
+
# pnpm
|
|
14
|
+
pnpm add @gunshi/plugin-renderer
|
|
15
|
+
|
|
16
|
+
# yarn
|
|
17
|
+
yarn add @gunshi/plugin-renderer
|
|
18
|
+
|
|
19
|
+
# deno
|
|
20
|
+
deno add jsr:@gunshi/plugin-renderer
|
|
21
|
+
|
|
22
|
+
# bun
|
|
23
|
+
bun add @gunshi/plugin-renderer
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 🚀 Usage
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import renderer from '@gunshi/plugin-renderer'
|
|
30
|
+
import { cli, define } from 'gunshi'
|
|
31
|
+
|
|
32
|
+
const command = define({
|
|
33
|
+
name: 'deploy',
|
|
34
|
+
description: 'Deploy your application',
|
|
35
|
+
args: {
|
|
36
|
+
environment: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
description: 'Target environment',
|
|
39
|
+
required: true
|
|
40
|
+
},
|
|
41
|
+
force: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
short: 'f',
|
|
44
|
+
description: 'Force deployment without confirmation'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
examples: '$ deploy production --force',
|
|
48
|
+
run: async ctx => {
|
|
49
|
+
console.log(`Deploying to ${ctx.values.environment}...`)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
await cli(process.argv.slice(2), command, {
|
|
54
|
+
name: 'deploy-cli',
|
|
55
|
+
version: '1.0.0',
|
|
56
|
+
plugins: [
|
|
57
|
+
renderer() // Adds automatic help/usage rendering
|
|
58
|
+
]
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
63
|
+
|
|
64
|
+
> [!TIP]
|
|
65
|
+
> The renderer plugin automatically decorates the header, usage, and validation error renderers. When users run `--help` or encounter validation errors, the plugin displays the information in a clean, readable format.
|
|
66
|
+
|
|
67
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
68
|
+
|
|
69
|
+
## ✨ Features
|
|
70
|
+
|
|
71
|
+
### Automatic Rendering
|
|
72
|
+
|
|
73
|
+
This plugin automatically handles rendering for:
|
|
74
|
+
|
|
75
|
+
- **Command Headers**: Displays command name and description
|
|
76
|
+
- **Usage Information**: Shows usage syntax, arguments, options, examples, and subcommands
|
|
77
|
+
- **Validation Errors**: Formats validation errors in a user-friendly way
|
|
78
|
+
|
|
79
|
+
### Internationalization Text Rendering
|
|
80
|
+
|
|
81
|
+
The plugin provides smart text rendering with automatic fallback:
|
|
82
|
+
|
|
83
|
+
- **With i18n plugin**: Uses translations from the i18n plugin
|
|
84
|
+
- **Without i18n plugin**: Falls back to default English messages and descriptions
|
|
85
|
+
|
|
86
|
+
### Rendered Example
|
|
87
|
+
|
|
88
|
+
When a user runs `--help`, the output looks like:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
deploy - Deploy your application
|
|
92
|
+
|
|
93
|
+
USAGE
|
|
94
|
+
$ deploy [options] <environment>
|
|
95
|
+
|
|
96
|
+
ARGUMENTS
|
|
97
|
+
environment Target environment
|
|
98
|
+
|
|
99
|
+
OPTIONS
|
|
100
|
+
-f, --force Force deployment without confirmation
|
|
101
|
+
-h, --help Display this help message
|
|
102
|
+
|
|
103
|
+
EXAMPLES
|
|
104
|
+
$ deploy production --force
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Exported Functions
|
|
108
|
+
|
|
109
|
+
- **`renderHeader(ctx: CommandContext): Promise<string>`**: Renders the command header section
|
|
110
|
+
- **`renderUsage(ctx: CommandContext): Promise<string>`**: Renders the complete usage/help information
|
|
111
|
+
- **`renderValidationErrors(ctx: CommandContext, error: AggregateError): Promise<string>`**: Renders validation errors
|
|
112
|
+
|
|
113
|
+
## 🧩 Context Extensions
|
|
114
|
+
|
|
115
|
+
When using the renderer plugin, your command context is extended via `ctx.extensions['g:renderer']`.
|
|
116
|
+
|
|
117
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
118
|
+
|
|
119
|
+
> [!IMPORTANT]
|
|
120
|
+
> This plugin extension is namespaced in `CommandContext.extensions` using this plugin ID `g:renderer` by the gunshi plugin system.
|
|
121
|
+
|
|
122
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
123
|
+
|
|
124
|
+
Available extensions:
|
|
125
|
+
|
|
126
|
+
- **`text<K>(key: K, values?: Record<string, unknown>): Promise<string>`**: Render text with optional i18n support. Handles built-in keys, argument descriptions, and custom keys intelligently.
|
|
127
|
+
|
|
128
|
+
- **`loadCommands<G>(): Promise<Command<G>[]>`**: Load and cache subcommands for rendering command lists. Results are cached after the first call for performance.
|
|
129
|
+
|
|
130
|
+
### Usage Example
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import renderer from '@gunshi/plugin-renderer'
|
|
134
|
+
import { cli, define } from 'gunshi'
|
|
135
|
+
|
|
136
|
+
const deploy = define({
|
|
137
|
+
name: 'deploy',
|
|
138
|
+
description: 'Deploy the application',
|
|
139
|
+
run: async ctx => {
|
|
140
|
+
console.log('Deploying...')
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const test = define({
|
|
145
|
+
name: 'test',
|
|
146
|
+
description: 'Run tests',
|
|
147
|
+
run: async ctx => {
|
|
148
|
+
console.log('Running tests...')
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const entry = define({
|
|
153
|
+
name: 'tools',
|
|
154
|
+
run: async ctx => {
|
|
155
|
+
// Access renderer extensions
|
|
156
|
+
const { text, loadCommands } = ctx.extensions['g:renderer']
|
|
157
|
+
|
|
158
|
+
// Render built-in message
|
|
159
|
+
const usageHeader = await text('_:USAGE') // "USAGE" or translated
|
|
160
|
+
console.log(usageHeader)
|
|
161
|
+
|
|
162
|
+
// Load and display subcommands
|
|
163
|
+
const subCommands = await loadCommands()
|
|
164
|
+
console.log('\nAvailable commands:')
|
|
165
|
+
for (const cmd of subCommands) {
|
|
166
|
+
console.log(` ${cmd.name}: ${cmd.description}`)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Create subCommands Map
|
|
172
|
+
const subCommands = new Map()
|
|
173
|
+
subCommands.set(deploy.name, deploy)
|
|
174
|
+
subCommands.set(test.name, test)
|
|
175
|
+
|
|
176
|
+
await cli(process.argv.slice(2), command, {
|
|
177
|
+
name: 'tools-cli',
|
|
178
|
+
version: '1.0.0',
|
|
179
|
+
subCommands,
|
|
180
|
+
plugins: [renderer()],
|
|
181
|
+
|
|
182
|
+
// Optional: Custom renderers
|
|
183
|
+
renderHeader: async ctx => {
|
|
184
|
+
return `=== ${ctx.env.name} v${ctx.env.version} ===`
|
|
185
|
+
},
|
|
186
|
+
renderUsage: async ctx => {
|
|
187
|
+
// Your custom usage renderer
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Integration with i18n Plugin
|
|
193
|
+
|
|
194
|
+
The renderer plugin has an optional dependency on the i18n plugin. When both plugins are used together, all rendered text automatically uses translations:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import renderer from '@gunshi/plugin-renderer'
|
|
198
|
+
import i18n from '@gunshi/plugin-i18n'
|
|
199
|
+
import resources from '@gunshi/resources'
|
|
200
|
+
import { cli } from 'gunshi'
|
|
201
|
+
|
|
202
|
+
await cli(args, command, {
|
|
203
|
+
plugins: [
|
|
204
|
+
i18n({
|
|
205
|
+
locale: 'ja-JP',
|
|
206
|
+
resources // Uses built-in resources from @gunshi/resources
|
|
207
|
+
}),
|
|
208
|
+
renderer() // Will use Japanese translations
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### With Custom Resources
|
|
214
|
+
|
|
215
|
+
You can extend the built-in resources with your own translations:
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import renderer from '@gunshi/plugin-renderer'
|
|
219
|
+
import i18n from '@gunshi/plugin-i18n'
|
|
220
|
+
import resources from '@gunshi/resources'
|
|
221
|
+
import { cli } from 'gunshi'
|
|
222
|
+
|
|
223
|
+
// Extend built-in resources with custom messages
|
|
224
|
+
const customResources = {
|
|
225
|
+
'en-US': {
|
|
226
|
+
...resources['en-US'],
|
|
227
|
+
// Custom messages for your app
|
|
228
|
+
APP_WELCOME: 'Welcome to My CLI Tool!',
|
|
229
|
+
APP_PROCESSING: 'Processing your request...'
|
|
230
|
+
},
|
|
231
|
+
'ja-JP': {
|
|
232
|
+
...resources['ja-JP'],
|
|
233
|
+
// Custom messages in Japanese
|
|
234
|
+
APP_WELCOME: '私のCLIツールへようこそ!',
|
|
235
|
+
APP_PROCESSING: 'リクエストを処理しています...'
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await cli(args, command, {
|
|
240
|
+
plugins: [
|
|
241
|
+
i18n({
|
|
242
|
+
locale: 'ja-JP',
|
|
243
|
+
resources: customResources
|
|
244
|
+
}),
|
|
245
|
+
renderer() // Will use Japanese translations including custom messages
|
|
246
|
+
]
|
|
247
|
+
})
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Custom Rendering
|
|
251
|
+
|
|
252
|
+
You can create custom plugins that use the renderer functions while adding your own branding or logic:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { plugin } from '@gunshi/plugin'
|
|
256
|
+
import { renderUsage } from '@gunshi/plugin-renderer'
|
|
257
|
+
|
|
258
|
+
const customPlugin = plugin({
|
|
259
|
+
id: 'my:custom',
|
|
260
|
+
name: 'Custom Plugin',
|
|
261
|
+
|
|
262
|
+
setup: ctx => {
|
|
263
|
+
// Decorate with custom logic while using renderer functions
|
|
264
|
+
ctx.decorateUsageRenderer(async (baseRenderer, cmdCtx) => {
|
|
265
|
+
// Render usage via built-in usage renderer
|
|
266
|
+
const standardUsage = await renderUsage(cmdCtx)
|
|
267
|
+
|
|
268
|
+
// Add custom branding
|
|
269
|
+
return `
|
|
270
|
+
╔══════════════════════════════════╗
|
|
271
|
+
║ MY AWESOME CLI ║
|
|
272
|
+
╚══════════════════════════════════╝
|
|
273
|
+
|
|
274
|
+
${standardUsage}
|
|
275
|
+
|
|
276
|
+
© 2025 Your Company
|
|
277
|
+
`
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## 📚 API References
|
|
284
|
+
|
|
285
|
+
See the [API References](./docs/index.md)
|
|
286
|
+
|
|
287
|
+
## ©️ License
|
|
288
|
+
|
|
289
|
+
[MIT](http://opensource.org/licenses/MIT)T)
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Command, CommandContext, DefaultGunshiParams, GunshiParams, PluginWithExtension } from "@gunshi/plugin";
|
|
2
|
+
import { Args } from "args-tokens";
|
|
3
|
+
|
|
4
|
+
//#region rolldown:runtime
|
|
5
|
+
declare namespace constants_d_exports {
|
|
6
|
+
export { ARG_NEGATABLE_PREFIX, ARG_PREFIX, ARG_PREFIX_AND_KEY_SEPARATOR, BUILD_IN_PREFIX_AND_KEY_SEPARATOR, BUILT_IN_KEY_SEPARATOR, BUILT_IN_PREFIX, COMMAND_BUILTIN_RESOURCE_KEYS, COMMON_ARGS, PLUGIN_PREFIX };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
10
|
+
* @license MIT
|
|
11
|
+
*/
|
|
12
|
+
declare const BUILT_IN_PREFIX = "_";
|
|
13
|
+
declare const PLUGIN_PREFIX = "g";
|
|
14
|
+
declare const ARG_PREFIX = "arg";
|
|
15
|
+
declare const BUILT_IN_KEY_SEPARATOR = ":";
|
|
16
|
+
declare const BUILD_IN_PREFIX_AND_KEY_SEPARATOR: string;
|
|
17
|
+
declare const ARG_PREFIX_AND_KEY_SEPARATOR: string;
|
|
18
|
+
declare const ARG_NEGATABLE_PREFIX = "no-";
|
|
19
|
+
type CommonArgType = {
|
|
20
|
+
readonly help: {
|
|
21
|
+
readonly type: 'boolean';
|
|
22
|
+
readonly short: 'h';
|
|
23
|
+
readonly description: string;
|
|
24
|
+
};
|
|
25
|
+
readonly version: {
|
|
26
|
+
readonly type: 'boolean';
|
|
27
|
+
readonly short: 'v';
|
|
28
|
+
readonly description: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
declare const COMMON_ARGS: CommonArgType;
|
|
32
|
+
declare const COMMAND_BUILTIN_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "ARGUMENTS", "OPTIONS", "EXAMPLES", "FORMORE", "NEGATABLE", "DEFAULT", "CHOICES"];
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region ../shared/src/types.d.ts
|
|
36
|
+
type RemoveIndexSignature<T> = { [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K] };
|
|
37
|
+
/**
|
|
38
|
+
* Remove index signature from object or record type.
|
|
39
|
+
*/
|
|
40
|
+
type RemovedIndex<T> = RemoveIndexSignature<{ [K in keyof T]: T[K] }>;
|
|
41
|
+
type KeyOfArgs<A extends Args> = keyof A | { [K in keyof A]: A[K]['type'] extends 'boolean' ? A[K]['negatable'] extends true ? `no-${Extract<K, string>}` : never : never }[keyof A];
|
|
42
|
+
/**
|
|
43
|
+
* Generate a namespaced key.
|
|
44
|
+
*/
|
|
45
|
+
type GenerateNamespacedKey<Key extends string, Prefixed extends string = typeof BUILT_IN_PREFIX> = `${Prefixed}${typeof BUILT_IN_KEY_SEPARATOR}${Key}`;
|
|
46
|
+
/**
|
|
47
|
+
* Command i18n built-in arguments keys.
|
|
48
|
+
*/
|
|
49
|
+
type CommandBuiltinArgsKeys = keyof (typeof constants_d_exports)['COMMON_ARGS'];
|
|
50
|
+
/**
|
|
51
|
+
* Command i18n built-in resource keys.
|
|
52
|
+
*/
|
|
53
|
+
type CommandBuiltinResourceKeys = (typeof constants_d_exports)['COMMAND_BUILTIN_RESOURCE_KEYS'][number];
|
|
54
|
+
/**
|
|
55
|
+
* i18n built-in resource keys.
|
|
56
|
+
*/
|
|
57
|
+
type BuiltinResourceKeys = CommandBuiltinArgsKeys | CommandBuiltinResourceKeys;
|
|
58
|
+
/**
|
|
59
|
+
* Command i18n built-in keys.
|
|
60
|
+
* The command i18n built-in keys are used by the i18n plugin for translation.
|
|
61
|
+
*/
|
|
62
|
+
type CommandBuiltinKeys = GenerateNamespacedKey<BuiltinResourceKeys> | 'description' | 'examples';
|
|
63
|
+
/**
|
|
64
|
+
* Command i18n option keys.
|
|
65
|
+
* The command i18n option keys are used by the i18n plugin for translation.
|
|
66
|
+
*/
|
|
67
|
+
type CommandArgKeys<A extends Args> = GenerateNamespacedKey<KeyOfArgs<RemovedIndex<A>>, typeof ARG_PREFIX>;
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/types.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Extended command context which provides utilities via usage renderer plugin.
|
|
73
|
+
* These utilities are available via `CommandContext.extensions['g:renderer']`.
|
|
74
|
+
*/
|
|
75
|
+
interface UsageRendererCommandContext<G extends GunshiParams<any> = DefaultGunshiParams> {
|
|
76
|
+
/**
|
|
77
|
+
* Render the text message
|
|
78
|
+
*/
|
|
79
|
+
text: <T extends string = CommandBuiltinKeys, O = CommandArgKeys<G['args']>, K = CommandBuiltinKeys | O | T>(key: K, values?: Record<string, unknown>) => Promise<string>;
|
|
80
|
+
/**
|
|
81
|
+
* Load commands
|
|
82
|
+
* @returns A list of commands loaded from the command loader plugin.
|
|
83
|
+
*/
|
|
84
|
+
loadCommands: <G extends GunshiParams = DefaultGunshiParams>() => Promise<Command<G>[]>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/header.d.ts
|
|
89
|
+
/**
|
|
90
|
+
* Render the header.
|
|
91
|
+
* @param ctx A {@link CommandContext | command context}
|
|
92
|
+
* @returns A rendered header.
|
|
93
|
+
*/
|
|
94
|
+
declare function renderHeader<G extends GunshiParams = DefaultGunshiParams>(ctx: Readonly<CommandContext<G>>): Promise<string>;
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/usage.d.ts
|
|
98
|
+
/**
|
|
99
|
+
* Render the usage.
|
|
100
|
+
* @param ctx A {@link CommandContext | command context}
|
|
101
|
+
* @returns A rendered usage.
|
|
102
|
+
*/
|
|
103
|
+
declare function renderUsage<G extends GunshiParams = DefaultGunshiParams>(ctx: Readonly<CommandContext<G>>): Promise<string>;
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/validation.d.ts
|
|
107
|
+
/**
|
|
108
|
+
* Render the validation errors.
|
|
109
|
+
* @param ctx A {@link CommandContext | command context}
|
|
110
|
+
* @param error An {@link AggregateError} of option in `args-token` validation
|
|
111
|
+
* @returns A rendered validation error.
|
|
112
|
+
*/
|
|
113
|
+
declare function renderValidationErrors<G extends GunshiParams = DefaultGunshiParams>(_ctx: CommandContext<G>, error: AggregateError): Promise<string>;
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/index.d.ts
|
|
117
|
+
/**
|
|
118
|
+
* usage renderer plugin
|
|
119
|
+
*/
|
|
120
|
+
declare function renderer(): PluginWithExtension<UsageRendererCommandContext>;
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { UsageRendererCommandContext, renderer as default, renderHeader, renderUsage, renderValidationErrors };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { plugin } from "@gunshi/plugin";
|
|
2
|
+
|
|
3
|
+
//#region ../../node_modules/.pnpm/args-tokens@0.20.1/node_modules/args-tokens/lib/utils-N7UlhLbz.js
|
|
4
|
+
/**
|
|
5
|
+
* Entry point of utils.
|
|
6
|
+
*
|
|
7
|
+
* Note that this entry point is used by gunshi to import utility functions.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
function kebabnize(str) {
|
|
16
|
+
return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region ../gunshi/src/utils.ts
|
|
21
|
+
function isLazyCommand(cmd) {
|
|
22
|
+
return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName;
|
|
23
|
+
}
|
|
24
|
+
async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
25
|
+
let command;
|
|
26
|
+
if (isLazyCommand(cmd)) {
|
|
27
|
+
const baseCommand = {
|
|
28
|
+
name: cmd.commandName,
|
|
29
|
+
description: cmd.description,
|
|
30
|
+
args: cmd.args,
|
|
31
|
+
examples: cmd.examples
|
|
32
|
+
};
|
|
33
|
+
if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
|
|
34
|
+
command = Object.assign(create(), baseCommand);
|
|
35
|
+
if (needRunResolving) {
|
|
36
|
+
const loaded = await cmd();
|
|
37
|
+
if (typeof loaded === "function") command.run = loaded;
|
|
38
|
+
else if (typeof loaded === "object") {
|
|
39
|
+
if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
|
|
40
|
+
command.run = loaded.run;
|
|
41
|
+
command.name = loaded.name;
|
|
42
|
+
command.description = loaded.description;
|
|
43
|
+
command.args = loaded.args;
|
|
44
|
+
command.examples = loaded.examples;
|
|
45
|
+
if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
|
|
46
|
+
} else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
|
|
47
|
+
}
|
|
48
|
+
} else command = Object.assign(create(), cmd);
|
|
49
|
+
if (command.name == null && name) command.name = name;
|
|
50
|
+
return deepFreeze(command);
|
|
51
|
+
}
|
|
52
|
+
function create(obj = null) {
|
|
53
|
+
return Object.create(obj);
|
|
54
|
+
}
|
|
55
|
+
function deepFreeze(obj, ignores = []) {
|
|
56
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
57
|
+
for (const key of Object.keys(obj)) {
|
|
58
|
+
const value = obj[key];
|
|
59
|
+
if (ignores.includes(key)) continue;
|
|
60
|
+
if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze(obj);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region ../shared/src/constants.ts
|
|
67
|
+
/**
|
|
68
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
69
|
+
* @license MIT
|
|
70
|
+
*/
|
|
71
|
+
const BUILT_IN_PREFIX = "_";
|
|
72
|
+
const PLUGIN_PREFIX = "g";
|
|
73
|
+
const ARG_PREFIX = "arg";
|
|
74
|
+
const BUILT_IN_KEY_SEPARATOR = ":";
|
|
75
|
+
const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
76
|
+
const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
77
|
+
const ARG_NEGATABLE_PREFIX = "no-";
|
|
78
|
+
const COMMON_ARGS = {
|
|
79
|
+
help: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
short: "h",
|
|
82
|
+
description: "Display this help message"
|
|
83
|
+
},
|
|
84
|
+
version: {
|
|
85
|
+
type: "boolean",
|
|
86
|
+
short: "v",
|
|
87
|
+
description: "Display this version"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region ../resources/locales/en-US.json
|
|
93
|
+
var COMMAND = "COMMAND";
|
|
94
|
+
var COMMANDS = "COMMANDS";
|
|
95
|
+
var SUBCOMMAND = "SUBCOMMAND";
|
|
96
|
+
var USAGE = "USAGE";
|
|
97
|
+
var ARGUMENTS = "ARGUMENTS";
|
|
98
|
+
var OPTIONS = "OPTIONS";
|
|
99
|
+
var EXAMPLES = "EXAMPLES";
|
|
100
|
+
var FORMORE = "For more info, run any command with the `--help` flag";
|
|
101
|
+
var NEGATABLE = "Negatable of";
|
|
102
|
+
var DEFAULT = "default";
|
|
103
|
+
var CHOICES = "choices";
|
|
104
|
+
var help = "Display this help message";
|
|
105
|
+
var version = "Display this version";
|
|
106
|
+
var en_US_default = {
|
|
107
|
+
COMMAND,
|
|
108
|
+
COMMANDS,
|
|
109
|
+
SUBCOMMAND,
|
|
110
|
+
USAGE,
|
|
111
|
+
ARGUMENTS,
|
|
112
|
+
OPTIONS,
|
|
113
|
+
EXAMPLES,
|
|
114
|
+
FORMORE,
|
|
115
|
+
NEGATABLE,
|
|
116
|
+
DEFAULT,
|
|
117
|
+
CHOICES,
|
|
118
|
+
help,
|
|
119
|
+
version
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region ../shared/src/utils.ts
|
|
124
|
+
function resolveBuiltInKey(key) {
|
|
125
|
+
return `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
|
|
126
|
+
}
|
|
127
|
+
function resolveArgKey(key) {
|
|
128
|
+
return `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
|
|
129
|
+
}
|
|
130
|
+
async function resolveExamples$1(ctx, examples) {
|
|
131
|
+
return typeof examples === "string" ? examples : typeof examples === "function" ? await examples(ctx) : "";
|
|
132
|
+
}
|
|
133
|
+
function namespacedId(id) {
|
|
134
|
+
return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/header.ts
|
|
139
|
+
/**
|
|
140
|
+
* Render the header.
|
|
141
|
+
* @param ctx A {@link CommandContext | command context}
|
|
142
|
+
* @returns A rendered header.
|
|
143
|
+
*/
|
|
144
|
+
function renderHeader(ctx) {
|
|
145
|
+
const title = ctx.env.description || ctx.env.name || "";
|
|
146
|
+
return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/types.ts
|
|
151
|
+
/**
|
|
152
|
+
* The unique identifier for usage renderer plugin.
|
|
153
|
+
*/
|
|
154
|
+
const pluginId = namespacedId("renderer");
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/usage.ts
|
|
158
|
+
const COMMON_ARGS_KEYS = Object.keys(COMMON_ARGS);
|
|
159
|
+
/**
|
|
160
|
+
* Render the usage.
|
|
161
|
+
* @param ctx A {@link CommandContext | command context}
|
|
162
|
+
* @returns A rendered usage.
|
|
163
|
+
*/
|
|
164
|
+
async function renderUsage(ctx) {
|
|
165
|
+
const messages = [];
|
|
166
|
+
if (!ctx.omitted) {
|
|
167
|
+
const description = await resolveDescription(ctx);
|
|
168
|
+
if (description) messages.push(description, "");
|
|
169
|
+
}
|
|
170
|
+
messages.push(...await renderUsageSection(ctx), "");
|
|
171
|
+
if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
|
|
172
|
+
if (hasPositionalArgs(ctx)) messages.push(...await renderPositionalArgsSection(ctx), "");
|
|
173
|
+
if (hasOptionalArgs(ctx)) messages.push(...await renderOptionalArgsSection(ctx), "");
|
|
174
|
+
const examples = await renderExamplesSection(ctx);
|
|
175
|
+
if (examples.length > 0) messages.push(...examples, "");
|
|
176
|
+
return messages.join("\n");
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Render the positional arguments section
|
|
180
|
+
* @param ctx A {@link CommandContext | command context}
|
|
181
|
+
* @returns A rendered arguments section
|
|
182
|
+
*/
|
|
183
|
+
async function renderPositionalArgsSection(ctx) {
|
|
184
|
+
const messages = [];
|
|
185
|
+
messages.push(`${await ctx.extensions[pluginId].text(resolveBuiltInKey("ARGUMENTS"))}:`);
|
|
186
|
+
messages.push(await generatePositionalArgsUsage(ctx));
|
|
187
|
+
return messages;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Render the optional arguments section
|
|
191
|
+
* @param ctx A {@link CommandContext | command context}
|
|
192
|
+
* @returns A rendered options section
|
|
193
|
+
*/
|
|
194
|
+
async function renderOptionalArgsSection(ctx) {
|
|
195
|
+
const messages = [];
|
|
196
|
+
messages.push(`${await ctx.extensions[pluginId].text(resolveBuiltInKey("OPTIONS"))}:`);
|
|
197
|
+
messages.push(await generateOptionalArgsUsage(ctx, getOptionalArgsPairs(ctx)));
|
|
198
|
+
return messages;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Render the examples section
|
|
202
|
+
* @param ctx A {@link CommandContext | command context}
|
|
203
|
+
* @returns A rendered examples section
|
|
204
|
+
*/
|
|
205
|
+
async function renderExamplesSection(ctx) {
|
|
206
|
+
const messages = [];
|
|
207
|
+
const resolvedExamples = await resolveExamples(ctx);
|
|
208
|
+
if (resolvedExamples) {
|
|
209
|
+
const examples = resolvedExamples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
|
|
210
|
+
messages.push(`${await ctx.extensions[pluginId].text(resolveBuiltInKey("EXAMPLES"))}:`, ...examples);
|
|
211
|
+
}
|
|
212
|
+
return messages;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Render the usage section
|
|
216
|
+
* @param ctx A {@link CommandContext | command context}
|
|
217
|
+
* @returns A rendered usage section
|
|
218
|
+
*/
|
|
219
|
+
async function renderUsageSection(ctx) {
|
|
220
|
+
const messages = [`${await ctx.extensions[pluginId].text(resolveBuiltInKey("USAGE"))}:`];
|
|
221
|
+
if (ctx.omitted) {
|
|
222
|
+
const defaultCommand = `${await resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${await resolveSubCommand(ctx)}]` : ""} ${[await generateOptionsSymbols(ctx), generatePositionalSymbols(ctx)].filter(Boolean).join(" ")}`;
|
|
223
|
+
messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
|
|
224
|
+
if (await hasCommands(ctx)) {
|
|
225
|
+
const commandsUsage = `${await resolveEntry(ctx)} <${await ctx.extensions[pluginId].text(resolveBuiltInKey("COMMANDS"))}>`;
|
|
226
|
+
messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const usageStr = `${await resolveEntry(ctx)} ${await resolveSubCommand(ctx)} ${[await generateOptionsSymbols(ctx), generatePositionalSymbols(ctx)].filter(Boolean).join(" ")}`;
|
|
230
|
+
messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
|
|
231
|
+
}
|
|
232
|
+
return messages;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Render the commands section
|
|
236
|
+
* @param ctx A {@link CommandContext | command context}
|
|
237
|
+
* @returns A rendered commands section
|
|
238
|
+
*/
|
|
239
|
+
async function renderCommandsSection(ctx) {
|
|
240
|
+
const messages = [`${await ctx.extensions[pluginId].text(resolveBuiltInKey("COMMANDS"))}:`];
|
|
241
|
+
const loadedCommands = await ctx.extensions?.[pluginId].loadCommands() || [];
|
|
242
|
+
const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
|
|
243
|
+
const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
|
|
244
|
+
const key = cmd.name || "";
|
|
245
|
+
const desc = cmd.description || "";
|
|
246
|
+
const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
|
|
247
|
+
return `${command.padStart(ctx.env.leftMargin + command.length)} `;
|
|
248
|
+
}));
|
|
249
|
+
messages.push(...commandsStr, "", `${await ctx.extensions[pluginId].text(resolveBuiltInKey("FORMORE"))}:`);
|
|
250
|
+
messages.push(...loadedCommands.map((cmd) => {
|
|
251
|
+
const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
|
|
252
|
+
return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
|
|
253
|
+
}));
|
|
254
|
+
return messages;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Resolve the entry command name
|
|
258
|
+
* @param ctx A {@link CommandContext | command context}
|
|
259
|
+
* @returns The entry command name
|
|
260
|
+
*/
|
|
261
|
+
async function resolveEntry(ctx) {
|
|
262
|
+
return ctx.env.name || await ctx.extensions[pluginId].text(resolveBuiltInKey("COMMAND"));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Resolve the sub command name
|
|
266
|
+
* @param ctx A {@link CommandContext | command context}
|
|
267
|
+
* @returns The sub command name
|
|
268
|
+
*/
|
|
269
|
+
async function resolveSubCommand(ctx) {
|
|
270
|
+
return ctx.name || await ctx.extensions[pluginId].text(resolveBuiltInKey("SUBCOMMAND"));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Resolve the command description
|
|
274
|
+
* @param ctx A {@link CommandContext | command context}
|
|
275
|
+
* @returns resolved command description
|
|
276
|
+
*/
|
|
277
|
+
async function resolveDescription(ctx) {
|
|
278
|
+
return await ctx.extensions[pluginId].text("description") || ctx.description || "";
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Resolve the command examples
|
|
282
|
+
* @param ctx A {@link CommandContext | command context}
|
|
283
|
+
* @returns resolved command examples, if not resolved, return empty string
|
|
284
|
+
*/
|
|
285
|
+
async function resolveExamples(ctx) {
|
|
286
|
+
const ret = await ctx.extensions[pluginId].text("examples");
|
|
287
|
+
if (ret) return ret;
|
|
288
|
+
const command = ctx.env.subCommands?.get(ctx.name || "");
|
|
289
|
+
return await resolveExamples$1(ctx, command?.examples);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Check if the command has sub commands
|
|
293
|
+
* @param ctx A {@link CommandContext | command context}
|
|
294
|
+
* @returns True if the command has sub commands
|
|
295
|
+
*/
|
|
296
|
+
async function hasCommands(ctx) {
|
|
297
|
+
const loadedCommands = await ctx.extensions?.[pluginId].loadCommands() || [];
|
|
298
|
+
return loadedCommands.length > 1;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Check if the command has optional arguments
|
|
302
|
+
* @param ctx A {@link CommandContext | command context}
|
|
303
|
+
* @returns True if the command has options
|
|
304
|
+
*/
|
|
305
|
+
function hasOptionalArgs(ctx) {
|
|
306
|
+
return !!(ctx.args && Object.values(ctx.args).some((arg) => arg.type !== "positional"));
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Check if the command has positional arguments
|
|
310
|
+
* @param ctx A {@link CommandContext | command context}
|
|
311
|
+
* @returns True if the command has options
|
|
312
|
+
*/
|
|
313
|
+
function hasPositionalArgs(ctx) {
|
|
314
|
+
return !!(ctx.args && Object.values(ctx.args).some((arg) => arg.type === "positional"));
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check if all options have default values
|
|
318
|
+
* @param ctx A {@link CommandContext | command context}
|
|
319
|
+
* @returns True if all options have default values
|
|
320
|
+
*/
|
|
321
|
+
function hasAllDefaultOptions(ctx) {
|
|
322
|
+
return !!(ctx.args && Object.values(ctx.args).every((arg) => arg.default));
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Generate options symbols for usage
|
|
326
|
+
* @param ctx A {@link CommandContext | command context}
|
|
327
|
+
* @returns Options symbols for usage
|
|
328
|
+
*/
|
|
329
|
+
async function generateOptionsSymbols(ctx) {
|
|
330
|
+
return hasOptionalArgs(ctx) ? hasAllDefaultOptions(ctx) ? `[${await ctx.extensions[pluginId].text(resolveBuiltInKey("OPTIONS"))}]` : `<${await ctx.extensions[pluginId].text(resolveBuiltInKey("OPTIONS"))}>` : "";
|
|
331
|
+
}
|
|
332
|
+
function makeShortLongOptionPair(schema, name, toKebab) {
|
|
333
|
+
const displayName = toKebab || schema.toKebab ? kebabnize(name) : name;
|
|
334
|
+
let key = `--${displayName}`;
|
|
335
|
+
if (schema.short) key = `-${schema.short}, ${key}`;
|
|
336
|
+
return key;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Get optional arguments pairs for usage
|
|
340
|
+
* @param ctx A {@link CommandContext | command context}
|
|
341
|
+
* @returns Options pairs for usage
|
|
342
|
+
*/
|
|
343
|
+
function getOptionalArgsPairs(ctx) {
|
|
344
|
+
return Object.entries(ctx.args).reduce((acc, [name, schema]) => {
|
|
345
|
+
if (schema.type === "positional") return acc;
|
|
346
|
+
let key = makeShortLongOptionPair(schema, name, ctx.toKebab);
|
|
347
|
+
if (schema.type !== "boolean") {
|
|
348
|
+
const displayName = ctx.toKebab || schema.toKebab ? kebabnize(name) : name;
|
|
349
|
+
key = schema.default ? `${key} [${displayName}]` : `${key} <${displayName}>`;
|
|
350
|
+
}
|
|
351
|
+
acc[name] = key;
|
|
352
|
+
if (schema.type === "boolean" && schema.negatable && !COMMON_ARGS_KEYS.includes(name)) {
|
|
353
|
+
const displayName = ctx.toKebab || schema.toKebab ? kebabnize(name) : name;
|
|
354
|
+
acc[`${ARG_NEGATABLE_PREFIX}${name}`] = `--${ARG_NEGATABLE_PREFIX}${displayName}`;
|
|
355
|
+
}
|
|
356
|
+
return acc;
|
|
357
|
+
}, Object.create(null));
|
|
358
|
+
}
|
|
359
|
+
const resolveNegatableKey = (key) => key.split(ARG_NEGATABLE_PREFIX)[1];
|
|
360
|
+
function resolveNegatableType(key, ctx) {
|
|
361
|
+
return ctx.args[key.startsWith(ARG_NEGATABLE_PREFIX) ? resolveNegatableKey(key) : key].type;
|
|
362
|
+
}
|
|
363
|
+
async function generateDefaultDisplayValue(ctx, schema) {
|
|
364
|
+
return `${await ctx.extensions[pluginId].text(resolveBuiltInKey("DEFAULT"))}: ${schema.default}`;
|
|
365
|
+
}
|
|
366
|
+
async function resolveDisplayValue(ctx, key) {
|
|
367
|
+
if (COMMON_ARGS_KEYS.includes(key)) return "";
|
|
368
|
+
const schema = ctx.args[key];
|
|
369
|
+
if ((schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "custom") && schema.default !== void 0) return `(${await generateDefaultDisplayValue(ctx, schema)})`;
|
|
370
|
+
if (schema.type === "enum") {
|
|
371
|
+
const _default = schema.default !== void 0 ? await generateDefaultDisplayValue(ctx, schema) : "";
|
|
372
|
+
const choices = `${await ctx.extensions[pluginId].text(resolveBuiltInKey("CHOICES"))}: ${schema.choices.join(" | ")}`;
|
|
373
|
+
return `(${_default ? `${_default}, ${choices}` : choices})`;
|
|
374
|
+
}
|
|
375
|
+
return "";
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Generate optional arguments usage
|
|
379
|
+
* @param ctx A {@link CommandContext | command context}
|
|
380
|
+
* @param optionsPairs Options pairs for usage
|
|
381
|
+
* @returns Generated options usage
|
|
382
|
+
*/
|
|
383
|
+
async function generateOptionalArgsUsage(ctx, optionsPairs) {
|
|
384
|
+
const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
|
|
385
|
+
const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key]) => resolveNegatableType(key, ctx).length)) : 0;
|
|
386
|
+
const usages = await Promise.all(Object.entries(optionsPairs).map(async ([key, value]) => {
|
|
387
|
+
let rawDesc = await ctx.extensions[pluginId].text(resolveArgKey(key));
|
|
388
|
+
if (!rawDesc && key.startsWith(ARG_NEGATABLE_PREFIX)) {
|
|
389
|
+
const name = resolveNegatableKey(key);
|
|
390
|
+
const schema = ctx.args[name];
|
|
391
|
+
const optionKey = makeShortLongOptionPair(schema, name, ctx.toKebab);
|
|
392
|
+
rawDesc = `${await ctx.extensions[pluginId].text(resolveBuiltInKey("NEGATABLE"))} ${optionKey}`;
|
|
393
|
+
}
|
|
394
|
+
const optionsSchema = ctx.env.usageOptionType ? `[${resolveNegatableType(key, ctx)}] ` : "";
|
|
395
|
+
const valueDesc = key.startsWith(ARG_NEGATABLE_PREFIX) ? "" : await resolveDisplayValue(ctx, key);
|
|
396
|
+
const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
|
|
397
|
+
const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}${valueDesc ? ` ${valueDesc}` : ""}`;
|
|
398
|
+
return `${option.padStart(ctx.env.leftMargin + option.length)}`;
|
|
399
|
+
}));
|
|
400
|
+
return usages.join("\n");
|
|
401
|
+
}
|
|
402
|
+
function getPositionalArgs(ctx) {
|
|
403
|
+
return Object.entries(ctx.args).filter(([_, schema]) => schema.type === "positional");
|
|
404
|
+
}
|
|
405
|
+
async function generatePositionalArgsUsage(ctx) {
|
|
406
|
+
const positionals = getPositionalArgs(ctx);
|
|
407
|
+
const argsMaxLength = Math.max(...positionals.map(([name]) => name.length));
|
|
408
|
+
const usages = await Promise.all(positionals.map(async ([name]) => {
|
|
409
|
+
const desc = await ctx.extensions[pluginId].text(resolveArgKey(name)) || ctx.args[name].description || "";
|
|
410
|
+
const arg = `${name.padEnd(argsMaxLength + ctx.env.middleMargin)} ${desc}`;
|
|
411
|
+
return `${arg.padStart(ctx.env.leftMargin + arg.length)}`;
|
|
412
|
+
}));
|
|
413
|
+
return usages.join("\n");
|
|
414
|
+
}
|
|
415
|
+
function generatePositionalSymbols(ctx) {
|
|
416
|
+
return hasPositionalArgs(ctx) ? getPositionalArgs(ctx).map(([name]) => `<${name}>`).join(" ") : "";
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/validation.ts
|
|
421
|
+
/**
|
|
422
|
+
* Render the validation errors.
|
|
423
|
+
* @param ctx A {@link CommandContext | command context}
|
|
424
|
+
* @param error An {@link AggregateError} of option in `args-token` validation
|
|
425
|
+
* @returns A rendered validation error.
|
|
426
|
+
*/
|
|
427
|
+
function renderValidationErrors(_ctx, error) {
|
|
428
|
+
const messages = [];
|
|
429
|
+
for (const err of error.errors) messages.push(err.message);
|
|
430
|
+
return Promise.resolve(messages.join("\n"));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/index.ts
|
|
435
|
+
const i18nPluginId = namespacedId("i18n");
|
|
436
|
+
/**
|
|
437
|
+
* usage renderer plugin
|
|
438
|
+
*/
|
|
439
|
+
function renderer() {
|
|
440
|
+
return plugin({
|
|
441
|
+
id: pluginId,
|
|
442
|
+
name: "usage renderer",
|
|
443
|
+
dependencies: [{
|
|
444
|
+
id: i18nPluginId,
|
|
445
|
+
optional: true
|
|
446
|
+
}],
|
|
447
|
+
extension: (ctx, cmd) => {
|
|
448
|
+
const { extensions: { [i18nPluginId]: i18n } } = ctx;
|
|
449
|
+
let cachedCommands;
|
|
450
|
+
async function loadCommands() {
|
|
451
|
+
if (cachedCommands) return cachedCommands;
|
|
452
|
+
const subCommands = [...ctx.env.subCommands || []];
|
|
453
|
+
cachedCommands = await Promise.all(subCommands.map(async ([name, cmd$1]) => await resolveLazyCommand(cmd$1, name)));
|
|
454
|
+
return cachedCommands;
|
|
455
|
+
}
|
|
456
|
+
async function text(key, values = Object.create(null)) {
|
|
457
|
+
if (i18n) return i18n.translate(key, values);
|
|
458
|
+
else if (key.startsWith(BUILD_IN_PREFIX_AND_KEY_SEPARATOR)) {
|
|
459
|
+
const resKey = key.slice(BUILD_IN_PREFIX_AND_KEY_SEPARATOR.length);
|
|
460
|
+
return en_US_default[resKey] || key;
|
|
461
|
+
} else if (key.startsWith(ARG_PREFIX_AND_KEY_SEPARATOR)) {
|
|
462
|
+
let argKey = key.slice(ARG_PREFIX_AND_KEY_SEPARATOR.length);
|
|
463
|
+
let negatable = false;
|
|
464
|
+
if (argKey.startsWith(ARG_NEGATABLE_PREFIX)) {
|
|
465
|
+
argKey = argKey.slice(ARG_NEGATABLE_PREFIX.length);
|
|
466
|
+
negatable = true;
|
|
467
|
+
}
|
|
468
|
+
const schema = ctx.args[argKey];
|
|
469
|
+
return negatable && schema.type === "boolean" && schema.negatable ? `${en_US_default["NEGATABLE"]} ${makeShortLongOptionPair(schema, argKey, ctx.toKebab)}` : schema.description || "";
|
|
470
|
+
} else if (key === "description") return "";
|
|
471
|
+
else if (key === "examples") return await resolveExamples$1(ctx, cmd.examples);
|
|
472
|
+
else return key;
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
text,
|
|
476
|
+
loadCommands
|
|
477
|
+
};
|
|
478
|
+
},
|
|
479
|
+
setup: (ctx) => {
|
|
480
|
+
ctx.decorateHeaderRenderer(async (_baseRenderer, cmdCtx) => await renderHeader(cmdCtx));
|
|
481
|
+
ctx.decorateUsageRenderer(async (_baseRenderer, cmdCtx) => await renderUsage(cmdCtx));
|
|
482
|
+
ctx.decorateValidationErrorsRenderer(async (_baseRenderer, cmdCtx, error) => await renderValidationErrors(cmdCtx, error));
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
//#endregion
|
|
488
|
+
export { renderer as default, renderHeader, renderUsage, renderValidationErrors };
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gunshi/plugin-renderer",
|
|
3
|
+
"description": "usage renderer plugin for gunshi",
|
|
4
|
+
"version": "0.26.3",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "kazuya kawaguchi",
|
|
7
|
+
"email": "kawakazu80@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"funding": "https://github.com/sponsors/kazupon",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/kazupon/gunshi/issues"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/kazupon/gunshi.git",
|
|
17
|
+
"directory": "packages/plugin-renderer"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"gunshi",
|
|
21
|
+
"usage",
|
|
22
|
+
"renderer",
|
|
23
|
+
"plugin",
|
|
24
|
+
"cli"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">= 20"
|
|
31
|
+
},
|
|
32
|
+
"type": "module",
|
|
33
|
+
"files": [
|
|
34
|
+
"lib"
|
|
35
|
+
],
|
|
36
|
+
"module": "lib/index.js",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./lib/index.d.ts",
|
|
40
|
+
"import": "./lib/index.js",
|
|
41
|
+
"require": "./lib/index.js",
|
|
42
|
+
"default": "./lib/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"types": "lib/index.d.ts",
|
|
47
|
+
"typesVersions": {
|
|
48
|
+
"*": {
|
|
49
|
+
"*": [
|
|
50
|
+
"./lib/*",
|
|
51
|
+
"./*"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@gunshi/plugin": "0.26.3"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@gunshi/plugin-i18n": "0.26.3"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"deno": "^2.3.3",
|
|
63
|
+
"jsr": "^0.13.4",
|
|
64
|
+
"jsr-exports-lint": "^0.4.1",
|
|
65
|
+
"publint": "^0.3.12",
|
|
66
|
+
"tsdown": "^0.12.3",
|
|
67
|
+
"typedoc": "^0.28.4",
|
|
68
|
+
"typedoc-plugin-markdown": "^4.6.3",
|
|
69
|
+
"@gunshi/shared": "0.26.3"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsdown",
|
|
73
|
+
"build:docs": "typedoc --excludeInternal",
|
|
74
|
+
"lint:jsr": "jsr publish --dry-run --allow-dirty",
|
|
75
|
+
"typecheck:deno": "deno check --import-map=../../importmap.json ./src"
|
|
76
|
+
}
|
|
77
|
+
}
|