gunshi 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +330 -9
- package/lib/{context-BQKZW5bg.js → context-B5z7lnoV.js} +25 -3
- package/lib/context.d.ts +42 -9
- package/lib/context.js +1 -2
- package/lib/index.d.ts +4 -4
- package/lib/index.js +191 -4
- package/lib/{types.d-B3YGxDV6.d.ts → types.d-00BVt8hZ.d.ts} +60 -68
- package/package.json +5 -5
- package/lib/renderer-Bo0DibAK.js +0 -115
- package/lib/renderer.d.ts +0 -8
- package/lib/renderer.js +0 -4
- package/lib/utils-NHs5DuHk.js +0 -24
package/README.md
CHANGED
|
@@ -5,22 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
[![Version][npm-version-src]][npm-version-href]
|
|
7
7
|
[![CI][ci-src]][ci-href]
|
|
8
|
+
[![InstallSize][install-size-src]][install-size-src]
|
|
8
9
|
|
|
9
10
|
<!--
|
|
10
11
|
[![JSR][jsr-src]][jsr-href]
|
|
11
|
-
[![InstallSize][install-size-src]][install-size-src]
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
14
|
Gunshi is a modern javascript command-line library
|
|
15
15
|
|
|
16
16
|
> [!TIP]
|
|
17
|
-
> gunshi (軍師) is a position in ancient Japanese samurai battle in which a amurai devised strategies and gave orders. That name is inspired by the word "command
|
|
17
|
+
> gunshi (軍師) is a position in ancient Japanese samurai battle in which a amurai devised strategies and gave orders. That name is inspired by the word "command".
|
|
18
18
|
|
|
19
19
|
## ✨ Features
|
|
20
20
|
|
|
21
21
|
Gunshi is designed to simplify the creation of modern command-line interfaces:
|
|
22
22
|
|
|
23
|
-
- 📏 **
|
|
23
|
+
- 📏 **Simple**: Run the commands with a simple API.
|
|
24
24
|
- 🛡️ **Type Safe**: Arguments parsing and options value resolution type-safely by [args-tokens](https://github.com/kazupon/args-tokens)
|
|
25
25
|
- ⚙️ **Declarative configuration**: Configure the command modules declaratively.
|
|
26
26
|
- 🧩 **Composable**: Sub-commands that can be composed with modularized commands.
|
|
@@ -40,13 +40,338 @@ pnpm add gunshi
|
|
|
40
40
|
|
|
41
41
|
## yarn
|
|
42
42
|
yarn add gunshi
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🚀 Usage
|
|
46
|
+
|
|
47
|
+
### Simple API
|
|
48
|
+
|
|
49
|
+
Gunshi has a simple API that is a facade:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { cli } from 'gunshi'
|
|
53
|
+
|
|
54
|
+
// Run a simple command
|
|
55
|
+
cli(process.argv.slice(2), () => {
|
|
56
|
+
console.log('Hello from Gunshi!')
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Type-Safe Arguments
|
|
61
|
+
|
|
62
|
+
Gunshi provides type-safe argument parsing with TypeScript:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { cli } from 'gunshi'
|
|
66
|
+
import type { ArgOptions, Command, CommandContext } from 'gunshi'
|
|
67
|
+
|
|
68
|
+
// Define interfaces for options and values
|
|
69
|
+
interface UserOptions extends ArgOptions {
|
|
70
|
+
name: { type: 'string'; short: 'n' }
|
|
71
|
+
age: { type: 'number'; short: 'a'; default: number }
|
|
72
|
+
verbose: { type: 'boolean'; short: 'v' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface UserValues {
|
|
76
|
+
name?: string
|
|
77
|
+
age: number
|
|
78
|
+
verbose?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create a type-safe command
|
|
82
|
+
const command: Command<UserOptions> = {
|
|
83
|
+
name: 'type-safe',
|
|
84
|
+
options: {
|
|
85
|
+
name: { type: 'string', short: 'n' },
|
|
86
|
+
age: { type: 'number', short: 'a', default: 25 },
|
|
87
|
+
verbose: { type: 'boolean', short: 'v' }
|
|
88
|
+
},
|
|
89
|
+
run: (ctx: CommandContext<UserOptions, UserValues>) => {
|
|
90
|
+
const { name, age, verbose } = ctx.values
|
|
91
|
+
console.log(`Hello, ${name || 'World'}! You are ${age} years old.`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await cli(process.argv.slice(2), command)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For more detailed examples, check out the [playground/type-safe](https://github.com/kazupon/gunshi/tree/main/playground/type-safe) in the repository.
|
|
99
|
+
|
|
100
|
+
### Declarative Configuration
|
|
43
101
|
|
|
102
|
+
Configure commands declaratively:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { cli } from 'gunshi'
|
|
106
|
+
|
|
107
|
+
// Define a command with declarative configuration
|
|
108
|
+
const command = {
|
|
109
|
+
name: 'greet',
|
|
110
|
+
description: 'A greeting command',
|
|
111
|
+
options: {
|
|
112
|
+
name: { type: 'string', short: 'n' },
|
|
113
|
+
greeting: { type: 'string', short: 'g', default: 'Hello' },
|
|
114
|
+
times: { type: 'number', short: 't', default: 1 }
|
|
115
|
+
},
|
|
116
|
+
usage: {
|
|
117
|
+
options: {
|
|
118
|
+
name: 'Name to greet',
|
|
119
|
+
greeting: 'Greeting to use (default: "Hello")',
|
|
120
|
+
times: 'Number of times to repeat the greeting (default: 1)'
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
run: ctx => {
|
|
124
|
+
const { name = 'World', greeting, times } = ctx.values
|
|
125
|
+
for (let i = 0; i < times; i++) {
|
|
126
|
+
console.log(`${greeting}, ${name}!`)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
cli(process.argv.slice(2), command, {
|
|
132
|
+
name: 'my-app',
|
|
133
|
+
version: '1.0.0',
|
|
134
|
+
description: 'My CLI application'
|
|
135
|
+
})
|
|
44
136
|
```
|
|
45
137
|
|
|
138
|
+
For more detailed examples, check out the [playground/declarative](https://github.com/kazupon/gunshi/tree/main/playground/declarative) in the repository.
|
|
139
|
+
|
|
140
|
+
### Composable Sub-commands
|
|
141
|
+
|
|
142
|
+
Create a CLI with composable sub-commands:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
import { cli } from 'gunshi'
|
|
146
|
+
|
|
147
|
+
// Define sub-commands
|
|
148
|
+
const createCommand = {
|
|
149
|
+
name: 'create',
|
|
150
|
+
description: 'Create a new resource',
|
|
151
|
+
options: {
|
|
152
|
+
name: { type: 'string', short: 'n' }
|
|
153
|
+
},
|
|
154
|
+
run: ctx => {
|
|
155
|
+
console.log(`Creating resource: ${ctx.values.name}`)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const listCommand = {
|
|
160
|
+
name: 'list',
|
|
161
|
+
description: 'List all resources',
|
|
162
|
+
run: () => {
|
|
163
|
+
console.log('Listing all resources...')
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Create a Map of sub-commands
|
|
168
|
+
const subCommands = new Map()
|
|
169
|
+
subCommands.set('create', createCommand)
|
|
170
|
+
subCommands.set('list', listCommand)
|
|
171
|
+
|
|
172
|
+
// Define the main command
|
|
173
|
+
const mainCommand = {
|
|
174
|
+
name: 'resource-manager',
|
|
175
|
+
description: 'Manage resources',
|
|
176
|
+
run: () => {
|
|
177
|
+
console.log('Use one of the sub-commands: create, list')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Run the CLI with composable sub-commands
|
|
182
|
+
cli(process.argv.slice(2), mainCommand, {
|
|
183
|
+
name: 'my-app',
|
|
184
|
+
version: '1.0.0',
|
|
185
|
+
subCommands
|
|
186
|
+
})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For more detailed examples, check out the [playground/composable](https://github.com/kazupon/gunshi/tree/main/playground/composable) in the repository.
|
|
190
|
+
|
|
191
|
+
### Lazy & Async Command Loading
|
|
192
|
+
|
|
193
|
+
Load commands lazily and execute them asynchronously:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
import { cli } from 'gunshi'
|
|
197
|
+
|
|
198
|
+
// Define a command that will be loaded lazily
|
|
199
|
+
const lazyCommand = async () => {
|
|
200
|
+
// Simulate async loading
|
|
201
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
202
|
+
|
|
203
|
+
// Return the actual command
|
|
204
|
+
return {
|
|
205
|
+
name: 'lazy',
|
|
206
|
+
description: 'A command that is loaded lazily',
|
|
207
|
+
run: async ctx => {
|
|
208
|
+
// Async execution
|
|
209
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
210
|
+
console.log('Command executed!')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Create a Map of sub-commands with lazy-loaded commands
|
|
216
|
+
const subCommands = new Map()
|
|
217
|
+
subCommands.set('lazy', lazyCommand)
|
|
218
|
+
|
|
219
|
+
// Run the CLI with lazy-loaded commands
|
|
220
|
+
cli(
|
|
221
|
+
process.argv.slice(2),
|
|
222
|
+
{ name: 'main', run: () => {} },
|
|
223
|
+
{
|
|
224
|
+
name: 'my-app',
|
|
225
|
+
subCommands
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
For more detailed examples, check out the [playground/lazy-async](https://github.com/kazupon/gunshi/tree/main/playground/lazy-async) in the repository.
|
|
231
|
+
|
|
232
|
+
### Auto Usage Generation
|
|
233
|
+
|
|
234
|
+
Gunshi automatically generates usage information:
|
|
235
|
+
|
|
236
|
+
```js
|
|
237
|
+
import { cli } from 'gunshi'
|
|
238
|
+
|
|
239
|
+
const command = {
|
|
240
|
+
name: 'app',
|
|
241
|
+
description: 'My application',
|
|
242
|
+
options: {
|
|
243
|
+
path: { type: 'string', short: 'p' },
|
|
244
|
+
recursive: { type: 'boolean', short: 'r' },
|
|
245
|
+
operation: { type: 'string', short: 'o', required: true }
|
|
246
|
+
},
|
|
247
|
+
usage: {
|
|
248
|
+
options: {
|
|
249
|
+
path: 'File or directory path',
|
|
250
|
+
recursive: 'Operate recursively on directories',
|
|
251
|
+
operation: 'Operation to perform (list, copy, move, delete)'
|
|
252
|
+
},
|
|
253
|
+
examples: '# Example\n$ my-app --operation list --path ./src'
|
|
254
|
+
},
|
|
255
|
+
run: ctx => {
|
|
256
|
+
// Command implementation
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Run with --help to see the automatically generated usage information
|
|
261
|
+
cli(process.argv.slice(2), command, {
|
|
262
|
+
name: 'my-app',
|
|
263
|
+
version: '1.0.0'
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
For more detailed examples, check out the [playground/auto-usage](https://github.com/kazupon/gunshi/tree/main/playground/auto-usage) in the repository.
|
|
268
|
+
|
|
269
|
+
### Custom Usage Generation
|
|
270
|
+
|
|
271
|
+
Customize the usage message generation:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
import { cli } from 'gunshi'
|
|
275
|
+
|
|
276
|
+
// Custom header renderer
|
|
277
|
+
const customHeaderRenderer = ctx => {
|
|
278
|
+
return Promise.resolve(`
|
|
279
|
+
╔═══════════════════════╗
|
|
280
|
+
║ ${ctx.env.name.toUpperCase()} ║
|
|
281
|
+
╚═══════════════════════╝
|
|
282
|
+
${ctx.env.description}
|
|
283
|
+
Version: ${ctx.env.version}
|
|
284
|
+
`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Custom usage renderer
|
|
288
|
+
const customUsageRenderer = ctx => {
|
|
289
|
+
const lines = []
|
|
290
|
+
lines.push('USAGE:')
|
|
291
|
+
lines.push(` $ ${ctx.env.name} [options]`)
|
|
292
|
+
lines.push('')
|
|
293
|
+
lines.push('OPTIONS:')
|
|
294
|
+
|
|
295
|
+
for (const [key, option] of Object.entries(ctx.options || {})) {
|
|
296
|
+
const shortFlag = option.short ? `-${option.short}, ` : ' '
|
|
297
|
+
lines.push(` ${shortFlag}--${key.padEnd(10)} ${ctx.translation(key)}`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return Promise.resolve(lines.join('\n'))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Run with custom renderers
|
|
304
|
+
cli(
|
|
305
|
+
process.argv.slice(2),
|
|
306
|
+
{ name: 'app', run: () => {} },
|
|
307
|
+
{
|
|
308
|
+
name: 'my-app',
|
|
309
|
+
version: '1.0.0',
|
|
310
|
+
description: 'My application',
|
|
311
|
+
renderHeader: customHeaderRenderer,
|
|
312
|
+
renderUsage: customUsageRenderer
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
For more detailed examples, check out the [playground/custom-usage](https://github.com/kazupon/gunshi/tree/main/playground/custom-usage) in the repository.
|
|
318
|
+
|
|
319
|
+
### Internationalization
|
|
320
|
+
|
|
321
|
+
Support internationalization:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { cli } from 'gunshi'
|
|
325
|
+
import enUS from './locales/en-US.json' with { type: 'json' }
|
|
326
|
+
|
|
327
|
+
const command = {
|
|
328
|
+
name: 'greeter',
|
|
329
|
+
options: {
|
|
330
|
+
name: { type: 'string', short: 'n' },
|
|
331
|
+
formal: { type: 'boolean', short: 'f' }
|
|
332
|
+
},
|
|
333
|
+
// Resource fetcher for translations
|
|
334
|
+
resource: async ctx => {
|
|
335
|
+
if (ctx.locale.toString() === 'ja-JP') {
|
|
336
|
+
const resource = await import('./locales/ja-JP.json', { with: { type: 'json' } })
|
|
337
|
+
return resource.default
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Default to English
|
|
341
|
+
return enUS
|
|
342
|
+
},
|
|
343
|
+
run: ctx => {
|
|
344
|
+
const { name = 'World', formal } = ctx.values
|
|
345
|
+
const greeting = formal ? ctx.translation('formal') : ctx.translation('informal')
|
|
346
|
+
console.log(`${greeting}, ${name}!`)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Run with locale support
|
|
351
|
+
cli(process.argv.slice(2), command, {
|
|
352
|
+
name: 'my-app',
|
|
353
|
+
version: '1.0.0',
|
|
354
|
+
// Set the locale via an environment variable
|
|
355
|
+
// If Node v21 or later is used, you can use the built-in `navigator.language` instead)
|
|
356
|
+
locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US')
|
|
357
|
+
})
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
For more detailed examples, check out the [playground/i18n](https://github.com/kazupon/gunshi/tree/main/playground/i18n) in the repository.
|
|
361
|
+
|
|
46
362
|
## 🙌 Contributing guidelines
|
|
47
363
|
|
|
48
364
|
If you are interested in contributing to `gunshi`, I highly recommend checking out [the contributing guidelines](/CONTRIBUTING.md) here. You'll find all the relevant information such as [how to make a PR](/CONTRIBUTING.md#pull-request-guidelines), [how to setup development](/CONTRIBUTING.md#development-setup)) etc., there.
|
|
49
365
|
|
|
366
|
+
## 💖 Credits
|
|
367
|
+
|
|
368
|
+
This project is inspired by:
|
|
369
|
+
|
|
370
|
+
- [`citty`](https://github.com/unjs/citty), created by UnJS team and contributors
|
|
371
|
+
- cline and claude 3.7 sonnet, examples and docs is generated
|
|
372
|
+
|
|
373
|
+
Thank you!
|
|
374
|
+
|
|
50
375
|
## ©️ License
|
|
51
376
|
|
|
52
377
|
[MIT](http://opensource.org/licenses/MIT)
|
|
@@ -57,11 +382,7 @@ If you are interested in contributing to `gunshi`, I highly recommend checking o
|
|
|
57
382
|
[npm-version-href]: https://npmjs.com/package/gunshi
|
|
58
383
|
[jsr-src]: https://jsr.io/badges/@kazupon/gunishi
|
|
59
384
|
[jsr-href]: https://jsr.io/@kazupon/gunshi
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
[install-size-src]: https://pkg-size.dev/badge/install/35082
|
|
63
|
-
[install-size-href]: https://pkg-size.dev/gunishi
|
|
64
|
-
-->
|
|
65
|
-
|
|
385
|
+
[install-size-src]: https://pkg-size.dev/badge/install/72346
|
|
386
|
+
[install-size-href]: https://pkg-size.dev/gunshi
|
|
66
387
|
[ci-src]: https://github.com/kazupon/gunshi/actions/workflows/ci.yml/badge.svg
|
|
67
388
|
[ci-href]: https://github.com/kazupon/gunshi/actions/workflows/ci.yml
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { create, deepFreeze, resolveLazyCommand } from "./utils-NHs5DuHk.js";
|
|
2
1
|
|
|
3
2
|
//#region locales/en-US.json
|
|
4
3
|
var COMMAND = "COMMAND";
|
|
@@ -57,6 +56,28 @@ const COMMAND_I18N_RESOURCE_KEYS = [
|
|
|
57
56
|
"FORMORE"
|
|
58
57
|
];
|
|
59
58
|
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/utils.ts
|
|
61
|
+
async function resolveLazyCommand(cmd, name, entry = false) {
|
|
62
|
+
const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
|
|
63
|
+
if (resolved.name == null && name) resolved.name = name;
|
|
64
|
+
return deepFreeze(resolved);
|
|
65
|
+
}
|
|
66
|
+
function create(obj = null) {
|
|
67
|
+
return Object.create(obj);
|
|
68
|
+
}
|
|
69
|
+
function log(...args) {
|
|
70
|
+
console.log(...args);
|
|
71
|
+
}
|
|
72
|
+
function deepFreeze(obj) {
|
|
73
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
74
|
+
for (const key of Object.keys(obj)) {
|
|
75
|
+
const value = obj[key];
|
|
76
|
+
if (typeof value === "object" && value !== null) deepFreeze(value);
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze(obj);
|
|
79
|
+
}
|
|
80
|
+
|
|
60
81
|
//#endregion
|
|
61
82
|
//#region src/context.ts
|
|
62
83
|
const DEFAULT_LOCALE = "en-US";
|
|
@@ -95,7 +116,8 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
95
116
|
localeResources.set(locale.toString(), builtInLoadedResources);
|
|
96
117
|
} catch {}
|
|
97
118
|
/**
|
|
98
|
-
* define the translation function
|
|
119
|
+
* define the translation function, which is used to {@link CommandContext.translation}.
|
|
120
|
+
*
|
|
99
121
|
*/
|
|
100
122
|
function translation(key) {
|
|
101
123
|
if (COMMAND_I18N_RESOURCE_KEYS.includes(key)) {
|
|
@@ -174,4 +196,4 @@ async function loadCommandResource(ctx, command) {
|
|
|
174
196
|
}
|
|
175
197
|
|
|
176
198
|
//#endregion
|
|
177
|
-
export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, createCommandContext };
|
|
199
|
+
export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, create, createCommandContext, log, resolveLazyCommand };
|
package/lib/context.d.ts
CHANGED
|
@@ -1,17 +1,50 @@
|
|
|
1
1
|
import { ArgOptions, ArgValues } from 'args-tokens';
|
|
2
|
-
import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-
|
|
2
|
+
import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-00BVt8hZ.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The default locale string, which format is BCP 47 language tag
|
|
6
|
+
*/
|
|
4
7
|
declare const DEFAULT_LOCALE = "en-US";
|
|
8
|
+
/**
|
|
9
|
+
* Parameters of {@link createCommandContext}
|
|
10
|
+
*/
|
|
11
|
+
interface CommandContextParams<
|
|
12
|
+
Options extends ArgOptions,
|
|
13
|
+
Values
|
|
14
|
+
> {
|
|
15
|
+
/**
|
|
16
|
+
* An options of target command
|
|
17
|
+
*/
|
|
18
|
+
options: Options | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* A values of target command
|
|
21
|
+
*/
|
|
22
|
+
values: Values;
|
|
23
|
+
/**
|
|
24
|
+
* A positionals arguments, which passed to the target command
|
|
25
|
+
*/
|
|
26
|
+
positionals: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Whether the command is omitted
|
|
29
|
+
*/
|
|
30
|
+
omitted: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* A target {@link Command | command}
|
|
33
|
+
*/
|
|
34
|
+
command: Command<Options>;
|
|
35
|
+
/**
|
|
36
|
+
* A command options, which is spicialized from `cli` function
|
|
37
|
+
*/
|
|
38
|
+
commandOptions: CommandOptions<Options>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a {@link CommandContext | command context}
|
|
42
|
+
* @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context}
|
|
43
|
+
* @returns A {@link CommandContext | command context}, which is readonly
|
|
44
|
+
*/
|
|
5
45
|
declare function createCommandContext<
|
|
6
46
|
Options extends ArgOptions,
|
|
7
47
|
Values = ArgValues<Options>
|
|
8
|
-
>({ options, values, positionals, command, commandOptions, omitted }:
|
|
9
|
-
options: Options | undefined
|
|
10
|
-
values: Values
|
|
11
|
-
positionals: string[]
|
|
12
|
-
omitted: boolean
|
|
13
|
-
command: Command<Options>
|
|
14
|
-
commandOptions: CommandOptions<Options>
|
|
15
|
-
}): Promise<Readonly<CommandContext<Options, Values>>>;
|
|
48
|
+
>({ options, values, positionals, command, commandOptions, omitted }: CommandContextParams<Options, Values>): Promise<Readonly<CommandContext<Options, Values>>>;
|
|
16
49
|
|
|
17
50
|
export { DEFAULT_LOCALE, createCommandContext };
|
package/lib/context.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ArgOptions } from 'args-tokens';
|
|
2
2
|
export { ArgOptionSchema, ArgOptions, ArgValues } from 'args-tokens';
|
|
3
|
-
import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-
|
|
4
|
-
export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment,
|
|
3
|
+
import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-00BVt8hZ.js';
|
|
4
|
+
export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment, h as CommandResource, i as CommandResourceFetcher, L as LazyCommand } from './types.d-00BVt8hZ.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Run the command
|
|
8
8
|
* @param args - command line arguments
|
|
9
|
-
* @param entry -
|
|
10
|
-
* @param opts -
|
|
9
|
+
* @param entry - A {@link Command | entry command} or an {@link CommandRunner | inline command runner}
|
|
10
|
+
* @param opts - A {@link CommandOptions | command options}
|
|
11
11
|
*/
|
|
12
12
|
declare function cli<Options extends ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<void>;
|
|
13
13
|
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,195 @@
|
|
|
1
|
-
import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, createCommandContext } from "./context-
|
|
2
|
-
import { create, log, resolveLazyCommand } from "./utils-NHs5DuHk.js";
|
|
3
|
-
import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-Bo0DibAK.js";
|
|
1
|
+
import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, createCommandContext, log, resolveLazyCommand } from "./context-B5z7lnoV.js";
|
|
4
2
|
import { parseArgs, resolveArgs } from "args-tokens";
|
|
5
3
|
|
|
4
|
+
//#region src/renderer/header.ts
|
|
5
|
+
function renderHeader(ctx) {
|
|
6
|
+
const title = ctx.env.description || ctx.env.name || "";
|
|
7
|
+
return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/renderer/usage.ts
|
|
12
|
+
async function renderUsage(ctx) {
|
|
13
|
+
const messages = [];
|
|
14
|
+
if (!ctx.omitted && hasDescription(ctx)) messages.push(ctx.description, "");
|
|
15
|
+
messages.push(...await renderUsageSection(ctx), "");
|
|
16
|
+
if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
|
|
17
|
+
if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
|
|
18
|
+
if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
|
|
19
|
+
return messages.join("\n");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render the options section
|
|
23
|
+
* @param ctx A {@link CommandContext | command context}
|
|
24
|
+
* @returns A rendered options section
|
|
25
|
+
*/
|
|
26
|
+
async function renderOptionsSection(ctx) {
|
|
27
|
+
const messages = [];
|
|
28
|
+
messages.push(`${ctx.translation("OPTIONS")}:`);
|
|
29
|
+
const optionsPairs = getOptionsPairs(ctx);
|
|
30
|
+
messages.push(await generateOptionsUsage(ctx, optionsPairs));
|
|
31
|
+
return messages;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render the examples section
|
|
35
|
+
* @param ctx A {@link CommandContext | command context}
|
|
36
|
+
* @returns A rendered examples section
|
|
37
|
+
*/
|
|
38
|
+
function renderExamplesSection(ctx) {
|
|
39
|
+
const messages = [];
|
|
40
|
+
const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
|
|
41
|
+
messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
|
|
42
|
+
return messages;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render the usage section
|
|
46
|
+
* @param ctx A {@link CommandContext | command context}
|
|
47
|
+
* @returns A rendered usage section
|
|
48
|
+
*/
|
|
49
|
+
async function renderUsageSection(ctx) {
|
|
50
|
+
const messages = [`${ctx.translation("USAGE")}:`];
|
|
51
|
+
if (ctx.omitted) {
|
|
52
|
+
const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
|
|
53
|
+
messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
|
|
54
|
+
if (await hasCommands(ctx)) {
|
|
55
|
+
const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
|
|
56
|
+
messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${generateOptionsSymbols(ctx)}`;
|
|
60
|
+
messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
|
|
61
|
+
}
|
|
62
|
+
return messages;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Render the commands section
|
|
66
|
+
* @param ctx A {@link CommandContext | command context}
|
|
67
|
+
* @returns A rendered commands section
|
|
68
|
+
*/
|
|
69
|
+
async function renderCommandsSection(ctx) {
|
|
70
|
+
const messages = [`${ctx.translation("COMMANDS")}:`];
|
|
71
|
+
const loadedCommands = await ctx.loadCommands();
|
|
72
|
+
const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
|
|
73
|
+
const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
|
|
74
|
+
const key = cmd.name || "";
|
|
75
|
+
const desc = cmd.description || "";
|
|
76
|
+
const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
|
|
77
|
+
return `${command.padStart(ctx.env.leftMargin + command.length)} `;
|
|
78
|
+
}));
|
|
79
|
+
messages.push(...commandsStr, "", ctx.translation("FORMORE"));
|
|
80
|
+
messages.push(...loadedCommands.map((cmd) => {
|
|
81
|
+
const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
|
|
82
|
+
return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
|
|
83
|
+
}));
|
|
84
|
+
return messages;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the entry command name
|
|
88
|
+
* @param ctx A {@link CommandContext | command context}
|
|
89
|
+
* @returns The entry command name
|
|
90
|
+
*/
|
|
91
|
+
function resolveEntry(ctx) {
|
|
92
|
+
return ctx.env.name || ctx.translation("COMMAND");
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the sub command name
|
|
96
|
+
* @param ctx A {@link CommandContext | command context}
|
|
97
|
+
* @returns The sub command name
|
|
98
|
+
*/
|
|
99
|
+
function resolveSubCommand(ctx) {
|
|
100
|
+
return ctx.name || ctx.translation("SUBCOMMAND");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if the command has a description
|
|
104
|
+
* @param ctx A {@link CommandContext | command context}
|
|
105
|
+
* @returns True if the command has a description
|
|
106
|
+
*/
|
|
107
|
+
function hasDescription(ctx) {
|
|
108
|
+
return !!ctx.description;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if the command has sub commands
|
|
112
|
+
* @param ctx A {@link CommandContext | command context}
|
|
113
|
+
* @returns True if the command has sub commands
|
|
114
|
+
*/
|
|
115
|
+
async function hasCommands(ctx) {
|
|
116
|
+
const loadedCommands = await ctx.loadCommands();
|
|
117
|
+
return loadedCommands.length > 1;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if the command has options
|
|
121
|
+
* @param ctx A {@link CommandContext | command context}
|
|
122
|
+
* @returns True if the command has options
|
|
123
|
+
*/
|
|
124
|
+
function hasOptions(ctx) {
|
|
125
|
+
return !!(ctx.options && Object.keys(ctx.options).length > 0);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if the command has examples
|
|
129
|
+
* @param ctx A {@link CommandContext | command context}
|
|
130
|
+
* @returns True if the command has examples
|
|
131
|
+
*/
|
|
132
|
+
function hasExamples(ctx) {
|
|
133
|
+
return !!ctx.usage.examples;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Check if all options have default values
|
|
137
|
+
* @param ctx A {@link CommandContext | command context}
|
|
138
|
+
* @returns True if all options have default values
|
|
139
|
+
*/
|
|
140
|
+
function hasAllDefaultOptions(ctx) {
|
|
141
|
+
return !!(ctx.options && Object.values(ctx.options).every((opt) => opt.default));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Generate options symbols for usage
|
|
145
|
+
* @param ctx A {@link CommandContext | command context}
|
|
146
|
+
* @returns Options symbols for usage
|
|
147
|
+
*/
|
|
148
|
+
function generateOptionsSymbols(ctx) {
|
|
149
|
+
return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get options pairs for usage
|
|
153
|
+
* @param ctx A {@link CommandContext | command context}
|
|
154
|
+
* @returns Options pairs for usage
|
|
155
|
+
*/
|
|
156
|
+
function getOptionsPairs(ctx) {
|
|
157
|
+
return Object.entries(ctx.options).reduce((acc, [name, value]) => {
|
|
158
|
+
let key = `--${name}`;
|
|
159
|
+
if (value.short) key = `-${value.short}, ${key}`;
|
|
160
|
+
if (value.type !== "boolean") key = value.default ? `${key} [${name}]` : `${key} <${name}>`;
|
|
161
|
+
acc[name] = key;
|
|
162
|
+
return acc;
|
|
163
|
+
}, create());
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Generate options usage
|
|
167
|
+
* @param ctx A {@link CommandContext | command context}
|
|
168
|
+
* @param optionsPairs Options pairs for usage
|
|
169
|
+
* @returns Generated options usage
|
|
170
|
+
*/
|
|
171
|
+
async function generateOptionsUsage(ctx, optionsPairs) {
|
|
172
|
+
const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
|
|
173
|
+
const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
|
|
174
|
+
const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
|
|
175
|
+
const rawDesc = ctx.translation(key);
|
|
176
|
+
const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
|
|
177
|
+
const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
|
|
178
|
+
const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
|
|
179
|
+
return `${option.padStart(ctx.env.leftMargin + option.length)}`;
|
|
180
|
+
}));
|
|
181
|
+
return usages.join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/renderer/validation.ts
|
|
186
|
+
function renderValidationErrors(_ctx, error) {
|
|
187
|
+
const messages = [];
|
|
188
|
+
for (const err of error.errors) messages.push(err.message);
|
|
189
|
+
return Promise.resolve(messages.join("\n"));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
6
193
|
//#region src/cli.ts
|
|
7
194
|
async function cli(args, entry, opts = {}) {
|
|
8
195
|
const tokens = parseArgs(args);
|
|
@@ -33,7 +220,7 @@ async function cli(args, entry, opts = {}) {
|
|
|
33
220
|
}
|
|
34
221
|
if (error) {
|
|
35
222
|
await showValidationErrors(ctx, error);
|
|
36
|
-
|
|
223
|
+
return;
|
|
37
224
|
}
|
|
38
225
|
await command.run(ctx);
|
|
39
226
|
}
|
|
@@ -25,54 +25,54 @@ declare namespace __constants {
|
|
|
25
25
|
*/
|
|
26
26
|
type Awaitable<T> = T | Promise<T>;
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Command i18n built-in options keys
|
|
29
29
|
* @experimental
|
|
30
30
|
*/
|
|
31
31
|
type CommandBuiltinOptionsKeys = keyof (typeof __constants)["COMMON_OPTIONS"];
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Command i18n built-in resource keys
|
|
34
34
|
* @experimental
|
|
35
35
|
*/
|
|
36
36
|
type CommandBuiltinResourceKeys = (typeof __constants)["COMMAND_I18N_RESOURCE_KEYS"][number];
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Command i18n built-in keys
|
|
39
39
|
* @description The command i18n built-in keys are used to {@link CommandContext.translation | translate} function
|
|
40
40
|
* @experimental
|
|
41
41
|
*/
|
|
42
42
|
type CommandBuiltinKeys = CommandBuiltinOptionsKeys | CommandBuiltinResourceKeys | "description" | "examples";
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Command environment
|
|
45
45
|
*/
|
|
46
46
|
interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Current working directory
|
|
49
49
|
* @see {@link CommandOptions.cwd}
|
|
50
50
|
*/
|
|
51
51
|
cwd: string | undefined;
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Command name
|
|
54
54
|
* @see {@link CommandOptions.name}
|
|
55
55
|
*/
|
|
56
56
|
name: string | undefined;
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Command description
|
|
59
59
|
* @see {@link CommandOptions.description}
|
|
60
60
|
*
|
|
61
61
|
*/
|
|
62
62
|
description: string | undefined;
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* Command version
|
|
65
65
|
* @see {@link CommandOptions.version}
|
|
66
66
|
*/
|
|
67
67
|
version: string | undefined;
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Left margin of the command output
|
|
70
70
|
* @default 2
|
|
71
71
|
* @see {@link CommandOptions.leftMargin}
|
|
72
72
|
*/
|
|
73
73
|
leftMargin: number;
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Middle margin of the command output
|
|
76
76
|
* @default 10
|
|
77
77
|
* @see {@link CommandOptions.middleMargin}
|
|
78
78
|
*/
|
|
@@ -84,7 +84,7 @@ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
|
|
|
84
84
|
*/
|
|
85
85
|
usageOptionType: boolean;
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* Sub commands
|
|
88
88
|
* @see {@link CommandOptions.subCommands}
|
|
89
89
|
*/
|
|
90
90
|
subCommands: Map<string, Command<Options> | LazyCommand<Options>> | undefined;
|
|
@@ -102,45 +102,40 @@ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
|
|
|
102
102
|
renderValidationErrors: ((ctx: CommandContext<Options>, error: AggregateError) => Promise<string>) | null | undefined;
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
105
|
+
* Command options
|
|
106
106
|
*/
|
|
107
107
|
interface CommandOptions<Options extends ArgOptions> {
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
110
|
-
* @description This is the current working directory path passed in the context of the run command. This is useful if you need your command about the current execution directory.
|
|
109
|
+
* Current working directory
|
|
111
110
|
*/
|
|
112
111
|
cwd?: string;
|
|
113
112
|
/**
|
|
114
|
-
*
|
|
115
|
-
* @description Please specify the name of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
|
|
113
|
+
* Command program name
|
|
116
114
|
*/
|
|
117
115
|
name?: string;
|
|
118
116
|
/**
|
|
119
|
-
*
|
|
120
|
-
* @description Please specify the description (summary) of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
|
|
117
|
+
* Command program description
|
|
121
118
|
*
|
|
122
119
|
*/
|
|
123
120
|
description?: string;
|
|
124
121
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @description Please specify the version of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
|
|
122
|
+
* Command program version
|
|
127
123
|
*/
|
|
128
124
|
version?: string;
|
|
129
125
|
/**
|
|
130
|
-
*
|
|
131
|
-
* @description The locale of the command that was executed. If you would specify it, gunshi command usage will be localized.
|
|
126
|
+
* Command program locale
|
|
132
127
|
*/
|
|
133
128
|
locale?: string | Intl.Locale;
|
|
134
129
|
/**
|
|
135
|
-
*
|
|
130
|
+
* Sub commands
|
|
136
131
|
*/
|
|
137
132
|
subCommands?: Map<string, Command<Options> | LazyCommand<Options>>;
|
|
138
133
|
/**
|
|
139
|
-
*
|
|
134
|
+
* Left margin of the command output
|
|
140
135
|
*/
|
|
141
136
|
leftMargin?: number;
|
|
142
137
|
/**
|
|
143
|
-
*
|
|
138
|
+
* Middle margin of the command output
|
|
144
139
|
*/
|
|
145
140
|
middleMargin?: number;
|
|
146
141
|
/**
|
|
@@ -161,44 +156,44 @@ interface CommandOptions<Options extends ArgOptions> {
|
|
|
161
156
|
renderValidationErrors?: ((ctx: Readonly<CommandContext<Options>>, error: AggregateError) => Promise<string>) | null;
|
|
162
157
|
}
|
|
163
158
|
/**
|
|
164
|
-
*
|
|
165
|
-
* @description
|
|
159
|
+
* Command context
|
|
160
|
+
* @description Command context is the context of the command execution
|
|
166
161
|
*/
|
|
167
162
|
interface CommandContext<
|
|
168
163
|
Options extends ArgOptions,
|
|
169
164
|
Values = ArgValues<Options>
|
|
170
165
|
> {
|
|
171
166
|
/**
|
|
172
|
-
*
|
|
167
|
+
* Command name, that is the command that is executed
|
|
173
168
|
* @description The command name is same {@link CommandEnvironment.name}
|
|
174
169
|
*/
|
|
175
170
|
name: string | undefined;
|
|
176
171
|
/**
|
|
177
|
-
*
|
|
172
|
+
* Command description, that is the description of the command that is executed
|
|
178
173
|
* @description The command description is same {@link CommandEnvironment.description}
|
|
179
174
|
*/
|
|
180
175
|
description: string | undefined;
|
|
181
176
|
/**
|
|
182
|
-
*
|
|
177
|
+
* Command locale, that is the locale of the command that is executed
|
|
183
178
|
*/
|
|
184
179
|
locale: Intl.Locale;
|
|
185
180
|
/**
|
|
186
|
-
*
|
|
181
|
+
* Command environment, that is the environment of the command that is executed
|
|
187
182
|
* @description The command environment is same {@link CommandEnvironment}
|
|
188
183
|
*/
|
|
189
184
|
env: CommandEnvironment<Options>;
|
|
190
185
|
/**
|
|
191
|
-
*
|
|
186
|
+
* Command options, that is the options of the command that is executed
|
|
192
187
|
* @description The command options is same {@link Command.options}
|
|
193
188
|
*/
|
|
194
189
|
options: Options | undefined;
|
|
195
190
|
/**
|
|
196
|
-
*
|
|
191
|
+
* Command values, that is the values of the command that is executed
|
|
197
192
|
* @description Resolve values with `resolveArgs` from command arguments and {@link Command.options}
|
|
198
193
|
*/
|
|
199
194
|
values: Values;
|
|
200
195
|
/**
|
|
201
|
-
*
|
|
196
|
+
* Command positionals arguments, that is the positionals of the command that is executed
|
|
202
197
|
* @description Resolve positionals with `resolveArgs` from command arguments
|
|
203
198
|
*/
|
|
204
199
|
positionals: string[];
|
|
@@ -207,20 +202,20 @@ interface CommandContext<
|
|
|
207
202
|
*/
|
|
208
203
|
omitted: boolean;
|
|
209
204
|
/**
|
|
210
|
-
*
|
|
211
|
-
* @description
|
|
205
|
+
* Command usage
|
|
206
|
+
* @description Usage of the command is same {@link Command.usage}, and more has `--help` and `--version` options
|
|
212
207
|
*/
|
|
213
208
|
usage: CommandUsage<Options>;
|
|
214
209
|
/**
|
|
215
|
-
* Load
|
|
210
|
+
* Load sub-commands
|
|
216
211
|
* @description The loaded commands are cached and returned when called again
|
|
217
212
|
* @returns loaded commands
|
|
218
213
|
*/
|
|
219
214
|
loadCommands: () => Promise<Command<Options>[]>;
|
|
220
215
|
/**
|
|
221
|
-
*
|
|
222
|
-
* @param key
|
|
223
|
-
* @returns
|
|
216
|
+
* Translation function
|
|
217
|
+
* @param key the key to be translated
|
|
218
|
+
* @returns A translated string
|
|
224
219
|
* @experimental
|
|
225
220
|
*/
|
|
226
221
|
translation: <
|
|
@@ -229,37 +224,32 @@ interface CommandContext<
|
|
|
229
224
|
>(key: Key) => string;
|
|
230
225
|
}
|
|
231
226
|
/**
|
|
232
|
-
*
|
|
233
|
-
* @description if the render function is async, it should return a promise
|
|
234
|
-
*/
|
|
235
|
-
type CommandUsageRender<Options extends ArgOptions> = ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | string;
|
|
236
|
-
/**
|
|
237
|
-
* The command usage
|
|
227
|
+
* Command usage
|
|
238
228
|
*/
|
|
239
229
|
interface CommandUsage<Options extends ArgOptions> {
|
|
240
230
|
/**
|
|
241
|
-
*
|
|
231
|
+
* Options usage
|
|
242
232
|
*/
|
|
243
233
|
options?: { [Option in keyof Options] : string };
|
|
244
234
|
/**
|
|
245
|
-
*
|
|
235
|
+
* Examples usage
|
|
246
236
|
*/
|
|
247
237
|
examples?: string;
|
|
248
238
|
}
|
|
249
239
|
/**
|
|
250
|
-
*
|
|
240
|
+
* Command interface
|
|
251
241
|
*/
|
|
252
242
|
interface Command<Options extends ArgOptions> {
|
|
253
243
|
/**
|
|
254
|
-
*
|
|
244
|
+
* Command name
|
|
255
245
|
* @description
|
|
256
|
-
*
|
|
246
|
+
* Command name is used to find command line arguments to execute from sub commands, so it's recommended to specify.
|
|
257
247
|
*/
|
|
258
248
|
name?: string;
|
|
259
249
|
/**
|
|
260
|
-
*
|
|
250
|
+
* Command description
|
|
261
251
|
* @description
|
|
262
|
-
*
|
|
252
|
+
* Command description is used to describe the command in usage, so it's recommended to specify.
|
|
263
253
|
*/
|
|
264
254
|
description?: string;
|
|
265
255
|
/**
|
|
@@ -268,57 +258,59 @@ interface Command<Options extends ArgOptions> {
|
|
|
268
258
|
*/
|
|
269
259
|
default?: boolean;
|
|
270
260
|
/**
|
|
271
|
-
*
|
|
261
|
+
* Command options
|
|
272
262
|
*/
|
|
273
263
|
options?: Options;
|
|
274
264
|
/**
|
|
275
|
-
*
|
|
265
|
+
* Command usage
|
|
276
266
|
* @description
|
|
277
|
-
*
|
|
267
|
+
* Command usage is used to describe the command in usage, so it's recommended to specify.
|
|
278
268
|
*/
|
|
279
269
|
usage?: CommandUsage<Options>;
|
|
280
270
|
/**
|
|
281
|
-
*
|
|
271
|
+
* Command runner, that's the command to be executed
|
|
282
272
|
*/
|
|
283
273
|
run: CommandRunner<Options>;
|
|
284
274
|
/**
|
|
285
|
-
*
|
|
275
|
+
* Command resource fetcher
|
|
286
276
|
* @experimental
|
|
287
277
|
*/
|
|
288
278
|
resource?: CommandResourceFetcher<Options>;
|
|
289
279
|
}
|
|
290
280
|
/**
|
|
291
|
-
*
|
|
281
|
+
* Command resource
|
|
292
282
|
* @experimental
|
|
293
283
|
*/
|
|
294
284
|
interface CommandResource<Options extends ArgOptions> {
|
|
295
285
|
/**
|
|
296
|
-
*
|
|
286
|
+
* Command description
|
|
297
287
|
*/
|
|
298
288
|
description: string;
|
|
299
289
|
/**
|
|
300
|
-
*
|
|
290
|
+
* Options usage
|
|
301
291
|
*/
|
|
302
292
|
options: { [Option in keyof Options] : string };
|
|
303
293
|
/**
|
|
304
|
-
*
|
|
294
|
+
* Examples usage
|
|
305
295
|
*/
|
|
306
296
|
examples: string;
|
|
307
297
|
}
|
|
308
298
|
/**
|
|
309
|
-
*
|
|
299
|
+
* Command resource fetcher
|
|
300
|
+
* @param ctx A {@link CommandContext | command context}
|
|
301
|
+
* @returns A fetched {@link CommandResource | command resource}
|
|
310
302
|
* @experimental
|
|
311
303
|
*/
|
|
312
304
|
type CommandResourceFetcher<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
|
|
313
305
|
/**
|
|
314
|
-
*
|
|
315
|
-
* @param ctx
|
|
306
|
+
* Command runner
|
|
307
|
+
* @param ctx A {@link CommandContext | command context}
|
|
316
308
|
*/
|
|
317
309
|
type CommandRunner<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
|
|
318
310
|
/**
|
|
319
|
-
*
|
|
320
|
-
* @description
|
|
311
|
+
* Lazy command interface
|
|
312
|
+
* @description lazy command that's not loaded until it is executed
|
|
321
313
|
*/
|
|
322
314
|
type LazyCommand<Options extends ArgOptions> = () => Awaitable<Command<Options>>;
|
|
323
315
|
|
|
324
|
-
export type { Command as C, LazyCommand as L, CommandOptions as a, CommandContext as b, CommandRunner as c, CommandBuiltinOptionsKeys as d, CommandBuiltinResourceKeys as e, CommandBuiltinKeys as f, CommandEnvironment as g,
|
|
316
|
+
export type { Command as C, LazyCommand as L, CommandOptions as a, CommandContext as b, CommandRunner as c, CommandBuiltinOptionsKeys as d, CommandBuiltinResourceKeys as e, CommandBuiltinKeys as f, CommandEnvironment as g, CommandResource as h, CommandResourceFetcher as i };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gunshi",
|
|
3
3
|
"description": "Modern javascript command-line library",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kazuya kawaguchi",
|
|
7
7
|
"email": "kawakazu80@gmail.com"
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"gh-changelogen": "^0.2.8",
|
|
87
87
|
"knip": "^5.45.0",
|
|
88
88
|
"lint-staged": "^15.4.3",
|
|
89
|
-
"pkg-pr-new": "^0.0.
|
|
89
|
+
"pkg-pr-new": "^0.0.41",
|
|
90
90
|
"prettier": "^3.5.3",
|
|
91
91
|
"tsdown": "^0.6.4",
|
|
92
|
-
"typescript": "^5.
|
|
92
|
+
"typescript": "^5.4.2",
|
|
93
93
|
"typescript-eslint": "^8.26.0",
|
|
94
94
|
"vitest": "^3.0.7"
|
|
95
95
|
},
|
|
@@ -113,11 +113,11 @@
|
|
|
113
113
|
"clean": "git clean -df",
|
|
114
114
|
"dev": "pnpx @eslint/config-inspector --config eslint.config.ts",
|
|
115
115
|
"dev:eslint": "pnpx @eslint/config-inspector --config eslint.config.ts",
|
|
116
|
-
"fix": "pnpm run --
|
|
116
|
+
"fix": "pnpm run --stream --color \"/^fix:/\"",
|
|
117
117
|
"fix:eslint": "eslint . --fix",
|
|
118
118
|
"fix:knip": "knip --fix --no-exit-code",
|
|
119
119
|
"fix:prettier": "prettier . --write",
|
|
120
|
-
"lint": "pnpm run --
|
|
120
|
+
"lint": "pnpm run --stream --color \"/^lint:/\"",
|
|
121
121
|
"lint:eslint": "eslint .",
|
|
122
122
|
"lint:knip": "knip",
|
|
123
123
|
"lint:prettier": "prettier . --check",
|
package/lib/renderer-Bo0DibAK.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { create } from "./utils-NHs5DuHk.js";
|
|
2
|
-
|
|
3
|
-
//#region src/renderer.ts
|
|
4
|
-
function renderHeader(ctx) {
|
|
5
|
-
const title = ctx.env.description || ctx.env.name || "";
|
|
6
|
-
return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
|
|
7
|
-
}
|
|
8
|
-
async function renderUsage(ctx) {
|
|
9
|
-
const messages = [];
|
|
10
|
-
if (!ctx.omitted && hasDescription(ctx)) messages.push(ctx.description, "");
|
|
11
|
-
messages.push(...await renderUsageSection(ctx), "");
|
|
12
|
-
if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
|
|
13
|
-
if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
|
|
14
|
-
if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
|
|
15
|
-
return messages.join("\n");
|
|
16
|
-
}
|
|
17
|
-
function renderValidationErrors(_ctx, error) {
|
|
18
|
-
const messages = [];
|
|
19
|
-
for (const err of error.errors) messages.push(err.message);
|
|
20
|
-
return Promise.resolve(messages.join("\n"));
|
|
21
|
-
}
|
|
22
|
-
async function renderOptionsSection(ctx) {
|
|
23
|
-
const messages = [];
|
|
24
|
-
messages.push(`${ctx.translation("OPTIONS")}:`);
|
|
25
|
-
const optionsPairs = getOptionsPairs(ctx);
|
|
26
|
-
messages.push(await generateOptionsUsage(ctx, optionsPairs));
|
|
27
|
-
return messages;
|
|
28
|
-
}
|
|
29
|
-
function renderExamplesSection(ctx) {
|
|
30
|
-
const messages = [];
|
|
31
|
-
const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
|
|
32
|
-
messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
|
|
33
|
-
return messages;
|
|
34
|
-
}
|
|
35
|
-
async function renderUsageSection(ctx) {
|
|
36
|
-
const messages = [`${ctx.translation("USAGE")}:`];
|
|
37
|
-
if (ctx.omitted) {
|
|
38
|
-
const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
|
|
39
|
-
messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
|
|
40
|
-
if (await hasCommands(ctx)) {
|
|
41
|
-
const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
|
|
42
|
-
messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${generateOptionsSymbols(ctx)}`;
|
|
46
|
-
messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
|
|
47
|
-
}
|
|
48
|
-
return messages;
|
|
49
|
-
}
|
|
50
|
-
async function renderCommandsSection(ctx) {
|
|
51
|
-
const messages = [`${ctx.translation("COMMANDS")}:`];
|
|
52
|
-
const loadedCommands = await ctx.loadCommands();
|
|
53
|
-
const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
|
|
54
|
-
const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
|
|
55
|
-
const key = cmd.name || "";
|
|
56
|
-
const desc = cmd.description || "";
|
|
57
|
-
const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
|
|
58
|
-
return `${command.padStart(ctx.env.leftMargin + command.length)} `;
|
|
59
|
-
}));
|
|
60
|
-
messages.push(...commandsStr, "", ctx.translation("FORMORE"));
|
|
61
|
-
messages.push(...loadedCommands.map((cmd) => {
|
|
62
|
-
const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
|
|
63
|
-
return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
|
|
64
|
-
}));
|
|
65
|
-
return messages;
|
|
66
|
-
}
|
|
67
|
-
function resolveEntry(ctx) {
|
|
68
|
-
return ctx.env.name || ctx.translation("COMMAND");
|
|
69
|
-
}
|
|
70
|
-
function resolveSubCommand(ctx) {
|
|
71
|
-
return ctx.name || ctx.translation("SUBCOMMAND");
|
|
72
|
-
}
|
|
73
|
-
function hasDescription(ctx) {
|
|
74
|
-
return !!ctx.description;
|
|
75
|
-
}
|
|
76
|
-
async function hasCommands(ctx) {
|
|
77
|
-
const loadedCommands = await ctx.loadCommands();
|
|
78
|
-
return loadedCommands.length > 1;
|
|
79
|
-
}
|
|
80
|
-
function hasOptions(ctx) {
|
|
81
|
-
return !!(ctx.options && Object.keys(ctx.options).length > 0);
|
|
82
|
-
}
|
|
83
|
-
function hasExamples(ctx) {
|
|
84
|
-
return !!ctx.usage.examples;
|
|
85
|
-
}
|
|
86
|
-
function hasAllDefaultOptions(ctx) {
|
|
87
|
-
return !!(ctx.options && Object.values(ctx.options).every((opt) => opt.default));
|
|
88
|
-
}
|
|
89
|
-
function generateOptionsSymbols(ctx) {
|
|
90
|
-
return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
|
|
91
|
-
}
|
|
92
|
-
function getOptionsPairs(ctx) {
|
|
93
|
-
return Object.entries(ctx.options).reduce((acc, [name, value]) => {
|
|
94
|
-
let key = `--${name}`;
|
|
95
|
-
if (value.short) key = `-${value.short}, ${key}`;
|
|
96
|
-
if (value.type !== "boolean") key = value.default ? `${key} [${name}]` : `${key} <${name}>`;
|
|
97
|
-
acc[name] = key;
|
|
98
|
-
return acc;
|
|
99
|
-
}, create());
|
|
100
|
-
}
|
|
101
|
-
async function generateOptionsUsage(ctx, optionsPairs) {
|
|
102
|
-
const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
|
|
103
|
-
const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
|
|
104
|
-
const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
|
|
105
|
-
const rawDesc = ctx.translation(key);
|
|
106
|
-
const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
|
|
107
|
-
const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
|
|
108
|
-
const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
|
|
109
|
-
return `${option.padStart(ctx.env.leftMargin + option.length)}`;
|
|
110
|
-
}));
|
|
111
|
-
return usages.join("\n");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
//#endregion
|
|
115
|
-
export { renderHeader, renderUsage, renderValidationErrors };
|
package/lib/renderer.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { ArgOptions } from 'args-tokens';
|
|
2
|
-
import { b as CommandContext } from './types.d-B3YGxDV6.js';
|
|
3
|
-
|
|
4
|
-
declare function renderHeader<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
|
|
5
|
-
declare function renderUsage<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
|
|
6
|
-
declare function renderValidationErrors<Options extends ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
|
|
7
|
-
|
|
8
|
-
export { renderHeader, renderUsage, renderValidationErrors };
|
package/lib/renderer.js
DELETED
package/lib/utils-NHs5DuHk.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
//#region src/utils.ts
|
|
3
|
-
async function resolveLazyCommand(cmd, name, entry = false) {
|
|
4
|
-
const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
|
|
5
|
-
if (resolved.name == null && name) resolved.name = name;
|
|
6
|
-
return deepFreeze(resolved);
|
|
7
|
-
}
|
|
8
|
-
function create(obj = null) {
|
|
9
|
-
return Object.create(obj);
|
|
10
|
-
}
|
|
11
|
-
function log(...args) {
|
|
12
|
-
console.log(...args);
|
|
13
|
-
}
|
|
14
|
-
function deepFreeze(obj) {
|
|
15
|
-
if (obj === null || typeof obj !== "object") return obj;
|
|
16
|
-
for (const key of Object.keys(obj)) {
|
|
17
|
-
const value = obj[key];
|
|
18
|
-
if (typeof value === "object" && value !== null) deepFreeze(value);
|
|
19
|
-
}
|
|
20
|
-
return Object.freeze(obj);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
export { create, deepFreeze, log, resolveLazyCommand };
|