gunshi 0.9.0 → 0.10.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 +55 -32
- package/lib/cli-BmK-tBjI.js +96 -0
- package/lib/{context-DeTM_Qpg.js → context-BeFe0i70.js} +15 -25
- package/lib/context.d.ts +2 -2
- package/lib/context.js +2 -2
- package/lib/generator.d.ts +13 -0
- package/lib/generator.js +17 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +4 -94
- package/lib/renderer/index.d.ts +1 -1
- package/lib/renderer/index.js +2 -2
- package/lib/{renderer-DIRwkoRd.js → renderer-CZGhbue4.js} +20 -13
- package/lib/{types.d-Dp0YJQNw.d.ts → types.d-DTe4N67v.d.ts} +12 -26
- package/lib/{utils-jm146hfy.js → utils-DhI1qcqR.js} +4 -2
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img width="196" src="./assets/logo.
|
|
2
|
+
<img width="196" src="./assets/logo.png">
|
|
3
3
|
</p>
|
|
4
4
|
<h1 align="center">🏯 Gunshi</h1>
|
|
5
5
|
|
|
@@ -10,21 +10,25 @@
|
|
|
10
10
|
|
|
11
11
|
Gunshi is a modern javascript command-line library
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
14
|
+
|
|
15
|
+
> [!TIP]
|
|
14
16
|
> gunshi (軍師) is a position in ancient Japanese samurai battle in which a samurai devised strategies and gave orders. That name is inspired by the word "command".
|
|
15
17
|
|
|
18
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
19
|
+
|
|
16
20
|
## ✨ Features
|
|
17
21
|
|
|
18
22
|
Gunshi is designed to simplify the creation of modern command-line interfaces:
|
|
19
23
|
|
|
20
|
-
- 📏 **Simple**: Run the commands with
|
|
21
|
-
- ⚙️ **Declarative configuration**: Configure
|
|
22
|
-
- 🛡️ **Type Safe**:
|
|
23
|
-
- 🧩 **Composable**:
|
|
24
|
-
- ⏳ **Lazy & Async**:
|
|
25
|
-
- 📜 **Auto usage generation**:
|
|
26
|
-
- 🎨 **Custom usage generation**:
|
|
27
|
-
- 🌍 **Internationalization**:
|
|
24
|
+
- 📏 **Simple & Universal**: Run the commands with simple API and support universal runtime.
|
|
25
|
+
- ⚙️ **Declarative configuration**: Configure command modules declaratively for better organization and maintainability.
|
|
26
|
+
- 🛡️ **Type Safe**: TypeScript support with type-safe argument parsing and option resolution by [args-tokens](https://github.com/kazupon/args-tokens)
|
|
27
|
+
- 🧩 **Composable**: Create modular sub-commands that can be composed together for complex CLIs.
|
|
28
|
+
- ⏳ **Lazy & Async**: Load command modules lazily and execute them asynchronously for better performance.
|
|
29
|
+
- 📜 **Auto usage generation**: Generate helpful usage messages automatically for your commands.
|
|
30
|
+
- 🎨 **Custom usage generation**: Customize how usage messages are generated to match your CLI's style.
|
|
31
|
+
- 🌍 **Internationalization**: Support multiple languages with built-in i18n, locale resource lazy loading and i18n library integration.
|
|
28
32
|
|
|
29
33
|
## 💿 Installation
|
|
30
34
|
|
|
@@ -82,15 +86,22 @@ const command = {
|
|
|
82
86
|
name: 'greet',
|
|
83
87
|
description: 'A greeting command',
|
|
84
88
|
options: {
|
|
85
|
-
name: {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
name: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
short: 'n',
|
|
92
|
+
description: 'Name to greet'
|
|
93
|
+
},
|
|
94
|
+
greeting: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
short: 'g',
|
|
97
|
+
default: 'Hello',
|
|
98
|
+
description: 'Greeting to use (default: "Hello")'
|
|
99
|
+
},
|
|
100
|
+
times: {
|
|
101
|
+
type: 'number',
|
|
102
|
+
short: 't',
|
|
103
|
+
default: 1,
|
|
104
|
+
description: 'Number of times to repeat the greeting (default: 1)'
|
|
94
105
|
}
|
|
95
106
|
},
|
|
96
107
|
run: ctx => {
|
|
@@ -262,19 +273,25 @@ const command = {
|
|
|
262
273
|
name: 'app',
|
|
263
274
|
description: 'My application',
|
|
264
275
|
options: {
|
|
265
|
-
path: {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
operation: 'Operation to perform (list, copy, move, delete)'
|
|
276
|
+
path: {
|
|
277
|
+
type: 'string',
|
|
278
|
+
short: 'p',
|
|
279
|
+
description: 'File or directory path'
|
|
280
|
+
},
|
|
281
|
+
recursive: {
|
|
282
|
+
type: 'boolean',
|
|
283
|
+
short: 'r',
|
|
284
|
+
description: 'Operate recursively on directories'
|
|
275
285
|
},
|
|
276
|
-
|
|
286
|
+
operation: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
short: 'o',
|
|
289
|
+
required: true,
|
|
290
|
+
description: 'Operation to perform (list, copy, move, delete)'
|
|
291
|
+
}
|
|
277
292
|
},
|
|
293
|
+
// define examples
|
|
294
|
+
examples: '# Example\n$ my-app --operation list --path ./src',
|
|
278
295
|
run: ctx => {
|
|
279
296
|
// command implementation
|
|
280
297
|
}
|
|
@@ -350,8 +367,14 @@ import enUS from './locales/en-US.json' with { type: 'json' }
|
|
|
350
367
|
const command = {
|
|
351
368
|
name: 'greeter',
|
|
352
369
|
options: {
|
|
353
|
-
name: {
|
|
354
|
-
|
|
370
|
+
name: {
|
|
371
|
+
type: 'string',
|
|
372
|
+
short: 'n'
|
|
373
|
+
},
|
|
374
|
+
formal: {
|
|
375
|
+
type: 'boolean',
|
|
376
|
+
short: 'f'
|
|
377
|
+
}
|
|
355
378
|
},
|
|
356
379
|
// resource fetcher for translations
|
|
357
380
|
resource: async ctx => {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createCommandContext } from "./context-BeFe0i70.js";
|
|
2
|
+
import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, create, resolveLazyCommand } from "./utils-DhI1qcqR.js";
|
|
3
|
+
import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-CZGhbue4.js";
|
|
4
|
+
import { parseArgs, resolveArgs } from "args-tokens";
|
|
5
|
+
|
|
6
|
+
//#region src/cli.ts
|
|
7
|
+
async function cli(args, entry, opts = {}) {
|
|
8
|
+
const tokens = parseArgs(args);
|
|
9
|
+
const subCommand = getSubCommand(tokens);
|
|
10
|
+
const resolvedCommandOptions = resolveCommandOptions(opts, entry);
|
|
11
|
+
const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions);
|
|
12
|
+
if (!command) throw new Error(`Command not found: ${name || ""}`);
|
|
13
|
+
const options = resolveArgOptions(command.options);
|
|
14
|
+
const { values, positionals, error } = resolveArgs(options, tokens);
|
|
15
|
+
const omitted = !subCommand;
|
|
16
|
+
const ctx = await createCommandContext({
|
|
17
|
+
options,
|
|
18
|
+
values,
|
|
19
|
+
positionals,
|
|
20
|
+
omitted,
|
|
21
|
+
command,
|
|
22
|
+
commandOptions: resolvedCommandOptions
|
|
23
|
+
});
|
|
24
|
+
if (values.version) {
|
|
25
|
+
showVersion(ctx);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const usageBuffer = [];
|
|
29
|
+
const header = await showHeader(ctx);
|
|
30
|
+
if (header) usageBuffer.push(header);
|
|
31
|
+
if (values.help) {
|
|
32
|
+
const usage = await showUsage(ctx);
|
|
33
|
+
if (usage) usageBuffer.push(usage);
|
|
34
|
+
return usageBuffer.join("\n");
|
|
35
|
+
}
|
|
36
|
+
if (error) {
|
|
37
|
+
await showValidationErrors(ctx, error);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await command.run(ctx);
|
|
41
|
+
}
|
|
42
|
+
function resolveArgOptions(options) {
|
|
43
|
+
return Object.assign(create(), options, COMMON_OPTIONS);
|
|
44
|
+
}
|
|
45
|
+
function resolveCommandOptions(options, entry) {
|
|
46
|
+
const subCommands = new Map(options.subCommands);
|
|
47
|
+
if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
|
|
48
|
+
const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
|
|
49
|
+
return resolvedOptions;
|
|
50
|
+
}
|
|
51
|
+
function getSubCommand(tokens) {
|
|
52
|
+
const firstToken = tokens[0];
|
|
53
|
+
return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
|
|
54
|
+
}
|
|
55
|
+
async function showUsage(ctx) {
|
|
56
|
+
if (ctx.env.renderUsage === null) return;
|
|
57
|
+
const usage = await (ctx.env.renderUsage || renderUsage)(ctx);
|
|
58
|
+
if (usage) {
|
|
59
|
+
ctx.log(usage);
|
|
60
|
+
return usage;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function showVersion(ctx) {
|
|
64
|
+
ctx.log(ctx.env.version);
|
|
65
|
+
}
|
|
66
|
+
async function showHeader(ctx) {
|
|
67
|
+
if (ctx.env.renderHeader === null) return;
|
|
68
|
+
const header = await (ctx.env.renderHeader || renderHeader)(ctx);
|
|
69
|
+
if (header) {
|
|
70
|
+
ctx.log(header);
|
|
71
|
+
ctx.log();
|
|
72
|
+
return header;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function showValidationErrors(ctx, error) {
|
|
76
|
+
if (ctx.env.renderValidationErrors === null) return;
|
|
77
|
+
const render = ctx.env.renderValidationErrors || renderValidationErrors;
|
|
78
|
+
ctx.log(await render(ctx, error));
|
|
79
|
+
}
|
|
80
|
+
async function resolveCommand(sub, entry, options) {
|
|
81
|
+
const omitted = !sub;
|
|
82
|
+
if (typeof entry === "function") return [void 0, {
|
|
83
|
+
run: entry,
|
|
84
|
+
default: true
|
|
85
|
+
}];
|
|
86
|
+
else if (omitted) return typeof entry === "object" ? [entry.name, await resolveLazyCommand(entry, void 0, true)] : [void 0, void 0];
|
|
87
|
+
else {
|
|
88
|
+
if (options.subCommands == null) return [sub, void 0];
|
|
89
|
+
const cmd = options.subCommands?.get(sub);
|
|
90
|
+
if (cmd == null) return [sub, void 0];
|
|
91
|
+
return [sub, await resolveLazyCommand(cmd, sub)];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
export { cli };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, DEFAULT_LOCALE, NOOP, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveLazyCommand } from "./utils-
|
|
1
|
+
import { BUILT_IN_PREFIX, COMMAND_OPTIONS_DEFAULT, DEFAULT_LOCALE, NOOP, create, deepFreeze, log, mapResourceWithBuiltinKey, resolveLazyCommand } from "./utils-DhI1qcqR.js";
|
|
2
2
|
|
|
3
3
|
//#region locales/en-US.json
|
|
4
4
|
var COMMAND = "COMMAND";
|
|
@@ -61,30 +61,21 @@ var DefaultTranslation = class {
|
|
|
61
61
|
const BUILT_IN_PREFIX_CODE = BUILT_IN_PREFIX.codePointAt(0);
|
|
62
62
|
async function createCommandContext({ options, values, positionals, command, commandOptions, omitted = false }) {
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* normailize the options schema and values, to avoid prototype pollution
|
|
65
65
|
*/
|
|
66
66
|
const _options = Object.entries(options).reduce((acc, [key, value]) => {
|
|
67
67
|
acc[key] = Object.assign(create(), value);
|
|
68
68
|
return acc;
|
|
69
69
|
}, create());
|
|
70
|
-
const _values = Object.assign(create(), values);
|
|
71
|
-
/**
|
|
72
|
-
* normalize the usage
|
|
73
|
-
*/
|
|
74
|
-
const usage = Object.assign(create(), command.usage);
|
|
75
|
-
const { help: help$1, version: version$1 } = en_US_default;
|
|
76
|
-
usage.options = Object.assign(create(), usage.options, {
|
|
77
|
-
help: help$1,
|
|
78
|
-
version: version$1
|
|
79
|
-
});
|
|
80
70
|
/**
|
|
81
71
|
* setup the environment
|
|
82
72
|
*/
|
|
83
73
|
const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, commandOptions);
|
|
84
74
|
const locale = resolveLocale(commandOptions.locale);
|
|
75
|
+
const localeStr = locale.toString();
|
|
85
76
|
const translationAdapterFactory = commandOptions.translationAdapterFactory || createTranslationAdapter;
|
|
86
77
|
const adapter = translationAdapterFactory({
|
|
87
|
-
locale:
|
|
78
|
+
locale: localeStr,
|
|
88
79
|
fallbackLocale: DEFAULT_LOCALE
|
|
89
80
|
});
|
|
90
81
|
const localeResources = new Map();
|
|
@@ -93,9 +84,9 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
93
84
|
* load the built-in locale resources
|
|
94
85
|
*/
|
|
95
86
|
localeResources.set(DEFAULT_LOCALE, mapResourceWithBuiltinKey(en_US_default));
|
|
96
|
-
if (DEFAULT_LOCALE !==
|
|
97
|
-
builtInLoadedResources = await import(`../locales/${
|
|
98
|
-
localeResources.set(
|
|
87
|
+
if (DEFAULT_LOCALE !== localeStr) try {
|
|
88
|
+
builtInLoadedResources = await import(`../locales/${localeStr}.json`, { with: { type: "json" } });
|
|
89
|
+
localeResources.set(localeStr, mapResourceWithBuiltinKey(builtInLoadedResources));
|
|
99
90
|
} catch {}
|
|
100
91
|
/**
|
|
101
92
|
* define the translation function, which is used to {@link CommandContext.translate}.
|
|
@@ -104,7 +95,7 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
104
95
|
function translate(key, values$1 = create()) {
|
|
105
96
|
const strKey = key;
|
|
106
97
|
if (strKey.codePointAt(0) === BUILT_IN_PREFIX_CODE) {
|
|
107
|
-
const resource = localeResources.get(
|
|
98
|
+
const resource = localeResources.get(localeStr) || localeResources.get(DEFAULT_LOCALE);
|
|
108
99
|
return resource[strKey] || strKey;
|
|
109
100
|
} else return adapter.translate(locale.toString(), strKey, values$1) || "";
|
|
110
101
|
}
|
|
@@ -114,7 +105,7 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
114
105
|
let cachedCommands;
|
|
115
106
|
async function loadCommands() {
|
|
116
107
|
if (cachedCommands) return cachedCommands;
|
|
117
|
-
const subCommands = [...
|
|
108
|
+
const subCommands = [...commandOptions.subCommands || []];
|
|
118
109
|
return cachedCommands = await Promise.all(subCommands.map(async ([name, cmd]) => await resolveLazyCommand(cmd, name)));
|
|
119
110
|
}
|
|
120
111
|
/**
|
|
@@ -127,9 +118,8 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
127
118
|
locale,
|
|
128
119
|
env,
|
|
129
120
|
options: _options,
|
|
130
|
-
values
|
|
121
|
+
values,
|
|
131
122
|
positionals,
|
|
132
|
-
usage,
|
|
133
123
|
log: commandOptions.usageSilent ? NOOP : log,
|
|
134
124
|
loadCommands,
|
|
135
125
|
translate
|
|
@@ -137,16 +127,16 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
137
127
|
/**
|
|
138
128
|
* load the command resources
|
|
139
129
|
*/
|
|
140
|
-
const loadedOptionsResources = Object.entries(
|
|
141
|
-
const
|
|
142
|
-
return [key,
|
|
130
|
+
const loadedOptionsResources = Object.entries(options).map(([key, option]) => {
|
|
131
|
+
const description = option.description || "";
|
|
132
|
+
return [key, description];
|
|
143
133
|
});
|
|
144
134
|
const defaultCommandResource = loadedOptionsResources.reduce((res, [key, value]) => {
|
|
145
135
|
res[key] = value;
|
|
146
136
|
return res;
|
|
147
137
|
}, create());
|
|
148
138
|
defaultCommandResource.description = command.description || "";
|
|
149
|
-
defaultCommandResource.examples =
|
|
139
|
+
defaultCommandResource.examples = command.examples || "";
|
|
150
140
|
adapter.setResource(DEFAULT_LOCALE, defaultCommandResource);
|
|
151
141
|
const originalResource = await loadCommandResource(ctx, command);
|
|
152
142
|
if (originalResource) {
|
|
@@ -158,7 +148,7 @@ async function createCommandContext({ options, values, positionals, command, com
|
|
|
158
148
|
resource.help = builtInLoadedResources.help;
|
|
159
149
|
resource.version = builtInLoadedResources.version;
|
|
160
150
|
}
|
|
161
|
-
adapter.setResource(
|
|
151
|
+
adapter.setResource(localeStr, resource);
|
|
162
152
|
}
|
|
163
153
|
return ctx;
|
|
164
154
|
}
|
package/lib/context.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-DTe4N67v.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Parameters of {@link createCommandContext}
|
|
@@ -40,7 +40,7 @@ interface CommandContextParams<
|
|
|
40
40
|
*/
|
|
41
41
|
declare function createCommandContext<
|
|
42
42
|
Options extends ArgOptions = ArgOptions,
|
|
43
|
-
Values = ArgValues<Options>
|
|
43
|
+
Values extends ArgValues<Options> = ArgValues<Options>
|
|
44
44
|
>({ options, values, positionals, command, commandOptions, omitted }: CommandContextParams<Options, Values>): Promise<Readonly<CommandContext<Options, Values>>>;
|
|
45
45
|
|
|
46
46
|
export { createCommandContext };
|
package/lib/context.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ArgOptions } from 'args-tokens';
|
|
2
|
+
import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-DTe4N67v.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate the command usage
|
|
6
|
+
* @param command - usage generate command, if you want to generate the usage of the default command where there are target commands and sub-commands, specify `null`.
|
|
7
|
+
* @param entry - A {@link Command | entry command} or an {@link CommandRunner | inline command runner}
|
|
8
|
+
* @param opts - A {@link CommandOptions | command options}
|
|
9
|
+
* @returns A rendered usage
|
|
10
|
+
*/
|
|
11
|
+
declare function generate<Options extends ArgOptions = ArgOptions>(command: string | null, entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string>;
|
|
12
|
+
|
|
13
|
+
export { generate };
|
package/lib/generator.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "./context-BeFe0i70.js";
|
|
2
|
+
import { create } from "./utils-DhI1qcqR.js";
|
|
3
|
+
import "./renderer-CZGhbue4.js";
|
|
4
|
+
import { cli } from "./cli-BmK-tBjI.js";
|
|
5
|
+
|
|
6
|
+
//#region src/generator.ts
|
|
7
|
+
async function generate(command, entry, opts = {}) {
|
|
8
|
+
const args = ["-h"];
|
|
9
|
+
if (command != null) args.unshift(command);
|
|
10
|
+
return await cli(args, entry, Object.assign(create(), opts, {
|
|
11
|
+
usageSilent: true,
|
|
12
|
+
__proto__: null
|
|
13
|
+
})) || "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
export { generate };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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, T as TranslationAdapter, d as TranslationAdapterFactoryOptions } from './types.d-
|
|
4
|
-
export { g as CommandBuiltinKeys, e as CommandBuiltinOptionsKeys, f as CommandBuiltinResourceKeys, b as CommandContext, h as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, l as Commandable, G as GenerateNamespacedKey, L as LazyCommand, k as TranslationAdapterFactory } from './types.d-
|
|
3
|
+
import { C as Command, c as CommandRunner, a as CommandOptions, T as TranslationAdapter, d as TranslationAdapterFactoryOptions } from './types.d-DTe4N67v.js';
|
|
4
|
+
export { g as CommandBuiltinKeys, e as CommandBuiltinOptionsKeys, f as CommandBuiltinResourceKeys, b as CommandContext, h as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, l as Commandable, G as GenerateNamespacedKey, L as LazyCommand, k as TranslationAdapterFactory } from './types.d-DTe4N67v.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Run the command
|
|
8
8
|
* @param args - command line arguments
|
|
9
9
|
* @param entry - A {@link Command | entry command} or an {@link CommandRunner | inline command runner}
|
|
10
10
|
* @param opts - A {@link CommandOptions | command options}
|
|
11
|
+
* @returns A rendered usage or undefined. if you will use {@link CommandOptions.usageSilent} option, it will return rendered usage string.
|
|
11
12
|
*/
|
|
12
13
|
declare function cli<Options extends ArgOptions = ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<string | undefined>;
|
|
13
14
|
|
package/lib/index.js
CHANGED
|
@@ -1,96 +1,6 @@
|
|
|
1
|
-
import { DefaultTranslation
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import { DefaultTranslation } from "./context-BeFe0i70.js";
|
|
2
|
+
import "./utils-DhI1qcqR.js";
|
|
3
|
+
import "./renderer-CZGhbue4.js";
|
|
4
|
+
import { cli } from "./cli-BmK-tBjI.js";
|
|
5
5
|
|
|
6
|
-
//#region src/cli.ts
|
|
7
|
-
async function cli(args, entry, opts = {}) {
|
|
8
|
-
const tokens = parseArgs(args);
|
|
9
|
-
const subCommand = getSubCommand(tokens);
|
|
10
|
-
const resolvedCommandOptions = resolveCommandOptions(opts, entry);
|
|
11
|
-
const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions);
|
|
12
|
-
if (!command) throw new Error(`Command not found: ${name || ""}`);
|
|
13
|
-
const options = resolveArgOptions(command.options);
|
|
14
|
-
const { values, positionals, error } = resolveArgs(options, tokens);
|
|
15
|
-
const omitted = !subCommand;
|
|
16
|
-
const ctx = await createCommandContext({
|
|
17
|
-
options,
|
|
18
|
-
values,
|
|
19
|
-
positionals,
|
|
20
|
-
omitted,
|
|
21
|
-
command,
|
|
22
|
-
commandOptions: resolvedCommandOptions
|
|
23
|
-
});
|
|
24
|
-
if (values.version) {
|
|
25
|
-
showVersion(ctx);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const usageBuffer = [];
|
|
29
|
-
const header = await showHeader(ctx);
|
|
30
|
-
if (header) usageBuffer.push(header);
|
|
31
|
-
if (values.help) {
|
|
32
|
-
const usage = await showUsage(ctx);
|
|
33
|
-
if (usage) usageBuffer.push(usage);
|
|
34
|
-
return usageBuffer.join("\n");
|
|
35
|
-
}
|
|
36
|
-
if (error) {
|
|
37
|
-
await showValidationErrors(ctx, error);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
await command.run(ctx);
|
|
41
|
-
}
|
|
42
|
-
function resolveArgOptions(options) {
|
|
43
|
-
return Object.assign(create(), options, COMMON_OPTIONS);
|
|
44
|
-
}
|
|
45
|
-
function resolveCommandOptions(options, entry) {
|
|
46
|
-
const subCommands = new Map(options.subCommands);
|
|
47
|
-
if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
|
|
48
|
-
const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
|
|
49
|
-
return resolvedOptions;
|
|
50
|
-
}
|
|
51
|
-
function getSubCommand(tokens) {
|
|
52
|
-
const firstToken = tokens[0];
|
|
53
|
-
return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
|
|
54
|
-
}
|
|
55
|
-
async function showUsage(ctx) {
|
|
56
|
-
if (ctx.env.renderUsage === null) return;
|
|
57
|
-
const usage = await (ctx.env.renderUsage || renderUsage)(ctx);
|
|
58
|
-
if (usage) {
|
|
59
|
-
ctx.log(usage);
|
|
60
|
-
return usage;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function showVersion(ctx) {
|
|
64
|
-
ctx.log(ctx.env.version);
|
|
65
|
-
}
|
|
66
|
-
async function showHeader(ctx) {
|
|
67
|
-
if (ctx.env.renderHeader === null) return;
|
|
68
|
-
const header = await (ctx.env.renderHeader || renderHeader)(ctx);
|
|
69
|
-
if (header) {
|
|
70
|
-
ctx.log(header);
|
|
71
|
-
ctx.log();
|
|
72
|
-
return header;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async function showValidationErrors(ctx, error) {
|
|
76
|
-
if (ctx.env.renderValidationErrors === null) return;
|
|
77
|
-
const render = ctx.env.renderValidationErrors || renderValidationErrors;
|
|
78
|
-
ctx.log(await render(ctx, error));
|
|
79
|
-
}
|
|
80
|
-
async function resolveCommand(sub, entry, options) {
|
|
81
|
-
const omitted = !sub;
|
|
82
|
-
if (typeof entry === "function") return [void 0, {
|
|
83
|
-
run: entry,
|
|
84
|
-
default: true
|
|
85
|
-
}];
|
|
86
|
-
else if (omitted) return typeof entry === "object" ? [entry.name, await resolveLazyCommand(entry, void 0, true)] : [void 0, void 0];
|
|
87
|
-
else {
|
|
88
|
-
if (options.subCommands == null) return [sub, void 0];
|
|
89
|
-
const cmd = options.subCommands?.get(sub);
|
|
90
|
-
if (cmd == null) return [sub, void 0];
|
|
91
|
-
return [sub, await resolveLazyCommand(cmd, sub)];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
//#endregion
|
|
96
6
|
export { DefaultTranslation, cli };
|
package/lib/renderer/index.d.ts
CHANGED
package/lib/renderer/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../utils-
|
|
2
|
-
import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-
|
|
1
|
+
import "../utils-DhI1qcqR.js";
|
|
2
|
+
import { renderHeader, renderUsage, renderValidationErrors } from "../renderer-CZGhbue4.js";
|
|
3
3
|
|
|
4
4
|
export { renderHeader, renderUsage, renderValidationErrors };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { create, resolveBuiltInKey } from "./utils-
|
|
1
|
+
import { create, resolveBuiltInKey } from "./utils-DhI1qcqR.js";
|
|
2
2
|
|
|
3
3
|
//#region src/renderer/header.ts
|
|
4
4
|
function renderHeader(ctx) {
|
|
@@ -17,7 +17,8 @@ async function renderUsage(ctx) {
|
|
|
17
17
|
messages.push(...await renderUsageSection(ctx), "");
|
|
18
18
|
if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
|
|
19
19
|
if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
|
|
20
|
-
|
|
20
|
+
const examples = renderExamplesSection(ctx);
|
|
21
|
+
if (examples.length > 0) messages.push(...examples, "");
|
|
21
22
|
return messages.join("\n");
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
@@ -38,8 +39,11 @@ async function renderOptionsSection(ctx) {
|
|
|
38
39
|
*/
|
|
39
40
|
function renderExamplesSection(ctx) {
|
|
40
41
|
const messages = [];
|
|
41
|
-
const
|
|
42
|
-
|
|
42
|
+
const resolvedExamples = resolveExamples(ctx);
|
|
43
|
+
if (resolvedExamples) {
|
|
44
|
+
const examples = resolvedExamples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
|
|
45
|
+
messages.push(`${ctx.translate(resolveBuiltInKey("EXAMPLES"))}:`, ...examples);
|
|
46
|
+
}
|
|
43
47
|
return messages;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
@@ -109,6 +113,17 @@ function resolveDescription(ctx) {
|
|
|
109
113
|
return ctx.translate("description") || ctx.description || "";
|
|
110
114
|
}
|
|
111
115
|
/**
|
|
116
|
+
* Resolve the command examples
|
|
117
|
+
* @param ctx A {@link CommandContext | command context}
|
|
118
|
+
* @returns resolved command examples, if not resolved, return empty string
|
|
119
|
+
*/
|
|
120
|
+
function resolveExamples(ctx) {
|
|
121
|
+
const ret = ctx.translate("examples");
|
|
122
|
+
if (ret) return ret;
|
|
123
|
+
const command = ctx.env.subCommands?.get(ctx.name || "");
|
|
124
|
+
return command?.examples ?? "";
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
112
127
|
* Check if the command has sub commands
|
|
113
128
|
* @param ctx A {@link CommandContext | command context}
|
|
114
129
|
* @returns True if the command has sub commands
|
|
@@ -126,14 +141,6 @@ function hasOptions(ctx) {
|
|
|
126
141
|
return !!(ctx.options && Object.keys(ctx.options).length > 0);
|
|
127
142
|
}
|
|
128
143
|
/**
|
|
129
|
-
* Check if the command has examples
|
|
130
|
-
* @param ctx A {@link CommandContext | command context}
|
|
131
|
-
* @returns True if the command has examples
|
|
132
|
-
*/
|
|
133
|
-
function hasExamples(ctx) {
|
|
134
|
-
return !!ctx.usage.examples;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
144
|
* Check if all options have default values
|
|
138
145
|
* @param ctx A {@link CommandContext | command context}
|
|
139
146
|
* @returns True if all options have default values
|
|
@@ -147,7 +154,7 @@ function hasAllDefaultOptions(ctx) {
|
|
|
147
154
|
* @returns Options symbols for usage
|
|
148
155
|
*/
|
|
149
156
|
function generateOptionsSymbols(ctx) {
|
|
150
|
-
return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate("
|
|
157
|
+
return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translate(resolveBuiltInKey("OPTIONS"))}]` : `<${ctx.translate(resolveBuiltInKey("OPTIONS"))}>` : "";
|
|
151
158
|
}
|
|
152
159
|
/**
|
|
153
160
|
* Get options pairs for usage
|
|
@@ -11,10 +11,12 @@ type CommonOptionType = {
|
|
|
11
11
|
readonly help: {
|
|
12
12
|
readonly type: "boolean"
|
|
13
13
|
readonly short: "h"
|
|
14
|
+
readonly description: string
|
|
14
15
|
}
|
|
15
16
|
readonly version: {
|
|
16
17
|
readonly type: "boolean"
|
|
17
18
|
readonly short: "v"
|
|
19
|
+
readonly description: string
|
|
18
20
|
}
|
|
19
21
|
};
|
|
20
22
|
declare const COMMON_OPTIONS: CommonOptionType;
|
|
@@ -212,7 +214,7 @@ interface CommandContext<
|
|
|
212
214
|
* Command environment, that is the environment of the command that is executed
|
|
213
215
|
* @description The command environment is same {@link CommandEnvironment}
|
|
214
216
|
*/
|
|
215
|
-
env: CommandEnvironment<Options
|
|
217
|
+
env: Readonly<CommandEnvironment<Options>>;
|
|
216
218
|
/**
|
|
217
219
|
* Command options, that is the options of the command that is executed
|
|
218
220
|
* @description The command options is same {@link Command.options}
|
|
@@ -233,11 +235,6 @@ interface CommandContext<
|
|
|
233
235
|
*/
|
|
234
236
|
omitted: boolean;
|
|
235
237
|
/**
|
|
236
|
-
* Command usage
|
|
237
|
-
* @description Usage of the command is same {@link Command.usage}, and more has `--help` and `--version` options
|
|
238
|
-
*/
|
|
239
|
-
usage: CommandUsage<Options>;
|
|
240
|
-
/**
|
|
241
238
|
* Output a message
|
|
242
239
|
* @description if {@link CommandEnvironment.usageSilent} is true, the message is not output
|
|
243
240
|
* @param message an output message, @see {@link console.log}
|
|
@@ -263,19 +260,6 @@ interface CommandContext<
|
|
|
263
260
|
>(key: Key, values?: Record<string, unknown>) => string;
|
|
264
261
|
}
|
|
265
262
|
/**
|
|
266
|
-
* Command usage
|
|
267
|
-
*/
|
|
268
|
-
interface CommandUsage<Options extends ArgOptions = ArgOptions> {
|
|
269
|
-
/**
|
|
270
|
-
* Options usage
|
|
271
|
-
*/
|
|
272
|
-
options?: { [Option in keyof Options] : string };
|
|
273
|
-
/**
|
|
274
|
-
* Examples usage
|
|
275
|
-
*/
|
|
276
|
-
examples?: string;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
263
|
* Command interface
|
|
280
264
|
*/
|
|
281
265
|
interface Command<Options extends ArgOptions = ArgOptions> {
|
|
@@ -287,8 +271,7 @@ interface Command<Options extends ArgOptions = ArgOptions> {
|
|
|
287
271
|
name?: string;
|
|
288
272
|
/**
|
|
289
273
|
* Command description
|
|
290
|
-
* @description
|
|
291
|
-
* Command description is used to describe the command in usage, so it's recommended to specify.
|
|
274
|
+
* @description command description is used to describe the command in usage, so it's recommended to specify.
|
|
292
275
|
*/
|
|
293
276
|
description?: string;
|
|
294
277
|
/**
|
|
@@ -298,14 +281,14 @@ interface Command<Options extends ArgOptions = ArgOptions> {
|
|
|
298
281
|
default?: boolean;
|
|
299
282
|
/**
|
|
300
283
|
* Command options
|
|
284
|
+
* @description each option can include a description property to describe the option in usage.
|
|
301
285
|
*/
|
|
302
286
|
options?: Options;
|
|
303
287
|
/**
|
|
304
|
-
* Command
|
|
305
|
-
* @description
|
|
306
|
-
* Command usage is used to describe the command in usage, so it's recommended to specify.
|
|
288
|
+
* Command examples
|
|
289
|
+
* @description examples of how to use the command.
|
|
307
290
|
*/
|
|
308
|
-
|
|
291
|
+
examples?: string;
|
|
309
292
|
/**
|
|
310
293
|
* Command runner, that's the command to be executed
|
|
311
294
|
*/
|
|
@@ -338,7 +321,10 @@ type CommandResource<Options extends ArgOptions = ArgOptions> = {
|
|
|
338
321
|
* @returns A fetched {@link CommandResource | command resource}
|
|
339
322
|
* @experimental
|
|
340
323
|
*/
|
|
341
|
-
type CommandResourceFetcher<
|
|
324
|
+
type CommandResourceFetcher<
|
|
325
|
+
Options extends ArgOptions = ArgOptions,
|
|
326
|
+
Values = ArgValues<Options>
|
|
327
|
+
> = (ctx: Readonly<CommandContext<Options, Values>>) => Promise<CommandResource<Options>>;
|
|
342
328
|
/**
|
|
343
329
|
* Translation adapter factory
|
|
344
330
|
*/
|
|
@@ -7,11 +7,13 @@ const NOOP = () => {};
|
|
|
7
7
|
const COMMON_OPTIONS = {
|
|
8
8
|
help: {
|
|
9
9
|
type: "boolean",
|
|
10
|
-
short: "h"
|
|
10
|
+
short: "h",
|
|
11
|
+
description: "Display this help message"
|
|
11
12
|
},
|
|
12
13
|
version: {
|
|
13
14
|
type: "boolean",
|
|
14
|
-
short: "v"
|
|
15
|
+
short: "v",
|
|
16
|
+
description: "Display this version"
|
|
15
17
|
}
|
|
16
18
|
};
|
|
17
19
|
const COMMAND_OPTIONS_DEFAULT = {
|
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.10.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kazuya kawaguchi",
|
|
7
7
|
"email": "kawakazu80@gmail.com"
|
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
"require": "./lib/renderer/index.js",
|
|
55
55
|
"default": "./lib/renderer/index.js"
|
|
56
56
|
},
|
|
57
|
+
"./generator": {
|
|
58
|
+
"types": "./lib/generator.d.ts",
|
|
59
|
+
"import": "./lib/generator.js",
|
|
60
|
+
"require": "./lib/generator.js",
|
|
61
|
+
"default": "./lib/generator.js"
|
|
62
|
+
},
|
|
57
63
|
"./package.json": "./package.json",
|
|
58
64
|
"./*": "./*"
|
|
59
65
|
},
|
|
@@ -67,7 +73,7 @@
|
|
|
67
73
|
}
|
|
68
74
|
},
|
|
69
75
|
"dependencies": {
|
|
70
|
-
"args-tokens": "^0.
|
|
76
|
+
"args-tokens": "^0.12.0"
|
|
71
77
|
},
|
|
72
78
|
"devDependencies": {
|
|
73
79
|
"@eslint/markdown": "^6.2.2",
|
|
@@ -85,7 +91,7 @@
|
|
|
85
91
|
"eslint-plugin-module-interop": "^0.3.0",
|
|
86
92
|
"eslint-plugin-promise": "^7.2.1",
|
|
87
93
|
"eslint-plugin-regexp": "^2.7.0",
|
|
88
|
-
"eslint-plugin-unicorn": "^
|
|
94
|
+
"eslint-plugin-unicorn": "^58.0.0",
|
|
89
95
|
"eslint-plugin-unused-imports": "^4.1.4",
|
|
90
96
|
"eslint-plugin-yml": "^1.17.0",
|
|
91
97
|
"gh-changelogen": "^0.2.8",
|
|
@@ -99,6 +105,7 @@
|
|
|
99
105
|
"typescript": "^5.4.2",
|
|
100
106
|
"typescript-eslint": "^8.26.0",
|
|
101
107
|
"vitepress": "^1.6.3",
|
|
108
|
+
"vitepress-plugin-group-icons": "^1.3.8",
|
|
102
109
|
"vitest": "^3.0.7"
|
|
103
110
|
},
|
|
104
111
|
"prettier": "@kazupon/prettier-config",
|