gunshi 0.2.0 → 0.2.1
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/lib/context-BQKZW5bg.js +177 -0
- package/lib/context.d.ts +7 -4
- package/lib/context.js +4 -0
- package/lib/index.d.ts +14 -2
- package/lib/index.js +4 -639
- package/lib/renderer-Bo0DibAK.js +115 -0
- package/lib/renderer.d.ts +8 -5
- package/lib/renderer.js +4 -0
- package/lib/{types.d.ts → types.d-B3YGxDV6.d.ts} +36 -14
- package/lib/utils-NHs5DuHk.js +24 -0
- package/package.json +3 -4
- package/lib/cli.d.ts +0 -9
- package/lib/constants.d.ts +0 -14
- package/lib/utils.d.ts +0 -6
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { create, deepFreeze, resolveLazyCommand } from "./utils-NHs5DuHk.js";
|
|
2
|
+
|
|
3
|
+
//#region locales/en-US.json
|
|
4
|
+
var COMMAND = "COMMAND";
|
|
5
|
+
var COMMANDS = "COMMANDS";
|
|
6
|
+
var SUBCOMMAND = "SUBCOMMAND";
|
|
7
|
+
var USAGE = "USAGE";
|
|
8
|
+
var OPTIONS = "OPTIONS";
|
|
9
|
+
var EXAMPLES = "EXAMPLES";
|
|
10
|
+
var FORMORE = "For more info, run any command with the `--help` flag:";
|
|
11
|
+
var help = "Display this help message";
|
|
12
|
+
var version = "Display this version";
|
|
13
|
+
var en_US_default = {
|
|
14
|
+
COMMAND,
|
|
15
|
+
COMMANDS,
|
|
16
|
+
SUBCOMMAND,
|
|
17
|
+
USAGE,
|
|
18
|
+
OPTIONS,
|
|
19
|
+
EXAMPLES,
|
|
20
|
+
FORMORE,
|
|
21
|
+
help,
|
|
22
|
+
version
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/constants.ts
|
|
27
|
+
const COMMON_OPTIONS = {
|
|
28
|
+
help: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
short: "h"
|
|
31
|
+
},
|
|
32
|
+
version: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
short: "v"
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const COMMAND_OPTIONS_DEFAULT = {
|
|
38
|
+
name: undefined,
|
|
39
|
+
description: undefined,
|
|
40
|
+
version: undefined,
|
|
41
|
+
cwd: undefined,
|
|
42
|
+
subCommands: undefined,
|
|
43
|
+
leftMargin: 2,
|
|
44
|
+
middleMargin: 10,
|
|
45
|
+
usageOptionType: false,
|
|
46
|
+
renderHeader: undefined,
|
|
47
|
+
renderUsage: undefined,
|
|
48
|
+
renderValidationErrors: undefined
|
|
49
|
+
};
|
|
50
|
+
const COMMAND_I18N_RESOURCE_KEYS = [
|
|
51
|
+
"USAGE",
|
|
52
|
+
"COMMAND",
|
|
53
|
+
"SUBCOMMAND",
|
|
54
|
+
"COMMANDS",
|
|
55
|
+
"OPTIONS",
|
|
56
|
+
"EXAMPLES",
|
|
57
|
+
"FORMORE"
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/context.ts
|
|
62
|
+
const DEFAULT_LOCALE = "en-US";
|
|
63
|
+
async function createCommandContext({ options, values, positionals, command, commandOptions, omitted = false }) {
|
|
64
|
+
/**
|
|
65
|
+
* tweak the options and values
|
|
66
|
+
*/
|
|
67
|
+
const _options = options == null ? undefined : Object.entries(options).reduce((acc, [key, value]) => {
|
|
68
|
+
acc[key] = Object.assign(create(), value);
|
|
69
|
+
return acc;
|
|
70
|
+
}, create());
|
|
71
|
+
const _values = Object.assign(create(), values);
|
|
72
|
+
/**
|
|
73
|
+
* normalize the usage
|
|
74
|
+
*/
|
|
75
|
+
const usage = Object.assign(create(), command.usage);
|
|
76
|
+
const { help: help$1, version: version$1 } = en_US_default;
|
|
77
|
+
usage.options = Object.assign(create(), usage.options, {
|
|
78
|
+
help: help$1,
|
|
79
|
+
version: version$1
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* setup the environment
|
|
83
|
+
*/
|
|
84
|
+
const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, commandOptions);
|
|
85
|
+
const locale = resolveLocale(commandOptions.locale);
|
|
86
|
+
const localeResources = new Map();
|
|
87
|
+
const commandResources = new Map();
|
|
88
|
+
let builtInLoadedResources;
|
|
89
|
+
/**
|
|
90
|
+
* load the built-in locale resources
|
|
91
|
+
*/
|
|
92
|
+
localeResources.set(DEFAULT_LOCALE, en_US_default);
|
|
93
|
+
if (DEFAULT_LOCALE !== locale.toString()) try {
|
|
94
|
+
builtInLoadedResources = await import(`../locales/${locale.toString()}.json`, { with: { type: "json" } });
|
|
95
|
+
localeResources.set(locale.toString(), builtInLoadedResources);
|
|
96
|
+
} catch {}
|
|
97
|
+
/**
|
|
98
|
+
* define the translation function
|
|
99
|
+
*/
|
|
100
|
+
function translation(key) {
|
|
101
|
+
if (COMMAND_I18N_RESOURCE_KEYS.includes(key)) {
|
|
102
|
+
const resource = localeResources.get(locale.toString()) || localeResources.get(DEFAULT_LOCALE);
|
|
103
|
+
return resource[key] || key;
|
|
104
|
+
} else {
|
|
105
|
+
const resource = commandResources.get(locale.toString()) || commandResources.get(DEFAULT_LOCALE);
|
|
106
|
+
return resource[key] || "";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* load the sub commands
|
|
111
|
+
*/
|
|
112
|
+
let cachedCommands;
|
|
113
|
+
async function loadCommands() {
|
|
114
|
+
if (cachedCommands) return cachedCommands;
|
|
115
|
+
const subCommands = [...env.subCommands || []];
|
|
116
|
+
return cachedCommands = await Promise.all(subCommands.map(async ([name, cmd]) => await resolveLazyCommand(cmd, name)));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* create the context
|
|
120
|
+
*/
|
|
121
|
+
const ctx = deepFreeze(Object.assign(create(), {
|
|
122
|
+
name: command.name,
|
|
123
|
+
description: command.description,
|
|
124
|
+
omitted,
|
|
125
|
+
locale,
|
|
126
|
+
env,
|
|
127
|
+
options: _options,
|
|
128
|
+
values: _values,
|
|
129
|
+
positionals,
|
|
130
|
+
usage,
|
|
131
|
+
loadCommands,
|
|
132
|
+
translation
|
|
133
|
+
}));
|
|
134
|
+
/**
|
|
135
|
+
* load the command resources
|
|
136
|
+
*/
|
|
137
|
+
const loadedOptionsResources = Object.entries(usage.options || create()).map(([key, _]) => {
|
|
138
|
+
const option = usage.options[key];
|
|
139
|
+
return [key, option];
|
|
140
|
+
});
|
|
141
|
+
const defaultCommandResource = loadedOptionsResources.reduce((res, [key, value]) => {
|
|
142
|
+
res[key] = value;
|
|
143
|
+
return res;
|
|
144
|
+
}, create());
|
|
145
|
+
defaultCommandResource.description = command.description || "";
|
|
146
|
+
defaultCommandResource.examples = usage.examples || "";
|
|
147
|
+
commandResources.set(DEFAULT_LOCALE, defaultCommandResource);
|
|
148
|
+
const originalResource = await loadCommandResource(ctx, command);
|
|
149
|
+
if (originalResource) {
|
|
150
|
+
const resource = Object.entries(originalResource.options).reduce((res, [key, value]) => {
|
|
151
|
+
res[key] = value;
|
|
152
|
+
return res;
|
|
153
|
+
}, Object.assign(create(), {
|
|
154
|
+
description: originalResource.description,
|
|
155
|
+
examples: originalResource.examples
|
|
156
|
+
}));
|
|
157
|
+
if (builtInLoadedResources) {
|
|
158
|
+
resource.help = builtInLoadedResources.help;
|
|
159
|
+
resource.version = builtInLoadedResources.version;
|
|
160
|
+
}
|
|
161
|
+
commandResources.set(locale.toString(), resource);
|
|
162
|
+
}
|
|
163
|
+
return ctx;
|
|
164
|
+
}
|
|
165
|
+
function resolveLocale(locale) {
|
|
166
|
+
return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE);
|
|
167
|
+
}
|
|
168
|
+
async function loadCommandResource(ctx, command) {
|
|
169
|
+
let resource;
|
|
170
|
+
try {
|
|
171
|
+
resource = await command.resource?.(ctx);
|
|
172
|
+
} catch {}
|
|
173
|
+
return resource;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
export { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, DEFAULT_LOCALE, createCommandContext };
|
package/lib/context.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { ArgOptions, ArgValues } from 'args-tokens';
|
|
2
|
+
import { C as Command, a as CommandOptions, b as CommandContext } from './types.d-B3YGxDV6.js';
|
|
3
|
+
|
|
4
|
+
declare const DEFAULT_LOCALE = "en-US";
|
|
5
|
+
declare function createCommandContext<
|
|
5
6
|
Options extends ArgOptions,
|
|
6
7
|
Values = ArgValues<Options>
|
|
7
8
|
>({ options, values, positionals, command, commandOptions, omitted }: {
|
|
@@ -12,3 +13,5 @@ export declare function createCommandContext<
|
|
|
12
13
|
command: Command<Options>
|
|
13
14
|
commandOptions: CommandOptions<Options>
|
|
14
15
|
}): Promise<Readonly<CommandContext<Options, Values>>>;
|
|
16
|
+
|
|
17
|
+
export { DEFAULT_LOCALE, createCommandContext };
|
package/lib/context.js
ADDED
package/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { ArgOptions } from 'args-tokens';
|
|
2
|
+
export { ArgOptionSchema, ArgOptions, ArgValues } from 'args-tokens';
|
|
3
|
+
import { C as Command, c as CommandRunner, a as CommandOptions } from './types.d-B3YGxDV6.js';
|
|
4
|
+
export { f as CommandBuiltinKeys, d as CommandBuiltinOptionsKeys, e as CommandBuiltinResourceKeys, b as CommandContext, g as CommandEnvironment, i as CommandResource, j as CommandResourceFetcher, h as CommandUsageRender, L as LazyCommand } from './types.d-B3YGxDV6.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Run the command
|
|
8
|
+
* @param args - command line arguments
|
|
9
|
+
* @param entry - a {@link Command | entry command} or an {@link CommandRunner | inline command runner}
|
|
10
|
+
* @param opts - a {@link CommandOptions | command options}
|
|
11
|
+
*/
|
|
12
|
+
declare function cli<Options extends ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<void>;
|
|
13
|
+
|
|
14
|
+
export { Command, CommandOptions, CommandRunner, cli };
|
package/lib/index.js
CHANGED
|
@@ -1,643 +1,8 @@
|
|
|
1
|
+
import { COMMAND_OPTIONS_DEFAULT, COMMON_OPTIONS, createCommandContext } from "./context-BQKZW5bg.js";
|
|
2
|
+
import { create, log, resolveLazyCommand } from "./utils-NHs5DuHk.js";
|
|
3
|
+
import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-Bo0DibAK.js";
|
|
4
|
+
import { parseArgs, resolveArgs } from "args-tokens";
|
|
1
5
|
|
|
2
|
-
//#region node_modules/.pnpm/args-tokens@0.10.2/node_modules/args-tokens/lib/parser.js
|
|
3
|
-
const HYPHEN_CHAR = "-";
|
|
4
|
-
const HYPHEN_CODE = HYPHEN_CHAR.codePointAt(0);
|
|
5
|
-
const EQUAL_CHAR = "=";
|
|
6
|
-
const EQUAL_CODE = EQUAL_CHAR.codePointAt(0);
|
|
7
|
-
const TERMINATOR = "--";
|
|
8
|
-
const SHORT_OPTION_PREFIX = HYPHEN_CHAR;
|
|
9
|
-
const LONG_OPTION_PREFIX = "--";
|
|
10
|
-
function parseArgs(args, options = {}) {
|
|
11
|
-
const { allowCompatible = false } = options;
|
|
12
|
-
const tokens = [];
|
|
13
|
-
const remainings = [...args];
|
|
14
|
-
let index = -1;
|
|
15
|
-
let groupCount = 0;
|
|
16
|
-
let hasShortValueSeparator = false;
|
|
17
|
-
while (remainings.length > 0) {
|
|
18
|
-
const arg = remainings.shift();
|
|
19
|
-
if (arg == undefined) break;
|
|
20
|
-
const nextArg = remainings[0];
|
|
21
|
-
if (groupCount > 0) groupCount--;
|
|
22
|
-
else index++;
|
|
23
|
-
if (arg === TERMINATOR) {
|
|
24
|
-
tokens.push({
|
|
25
|
-
kind: "option-terminator",
|
|
26
|
-
index
|
|
27
|
-
});
|
|
28
|
-
const mapped = remainings.map((arg$1) => {
|
|
29
|
-
return {
|
|
30
|
-
kind: "positional",
|
|
31
|
-
index: ++index,
|
|
32
|
-
value: arg$1
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
tokens.push(...mapped);
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
if (isShortOption(arg)) {
|
|
39
|
-
const shortOption = arg.charAt(1);
|
|
40
|
-
let value;
|
|
41
|
-
let inlineValue;
|
|
42
|
-
if (groupCount) {
|
|
43
|
-
tokens.push({
|
|
44
|
-
kind: "option",
|
|
45
|
-
name: shortOption,
|
|
46
|
-
rawName: arg,
|
|
47
|
-
index,
|
|
48
|
-
value,
|
|
49
|
-
inlineValue
|
|
50
|
-
});
|
|
51
|
-
if (groupCount === 1 && hasOptionValue(nextArg)) {
|
|
52
|
-
value = remainings.shift();
|
|
53
|
-
if (hasShortValueSeparator) {
|
|
54
|
-
inlineValue = true;
|
|
55
|
-
hasShortValueSeparator = false;
|
|
56
|
-
}
|
|
57
|
-
tokens.push({
|
|
58
|
-
kind: "option",
|
|
59
|
-
index,
|
|
60
|
-
value,
|
|
61
|
-
inlineValue
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
} else tokens.push({
|
|
65
|
-
kind: "option",
|
|
66
|
-
name: shortOption,
|
|
67
|
-
rawName: arg,
|
|
68
|
-
index,
|
|
69
|
-
value,
|
|
70
|
-
inlineValue
|
|
71
|
-
});
|
|
72
|
-
if (value != null) ++index;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
if (isShortOptionGroup(arg)) {
|
|
76
|
-
const expanded = [];
|
|
77
|
-
let shortValue = "";
|
|
78
|
-
for (let i = 1; i < arg.length; i++) {
|
|
79
|
-
const shortableOption = arg.charAt(i);
|
|
80
|
-
if (hasShortValueSeparator) shortValue += shortableOption;
|
|
81
|
-
else if (!allowCompatible && shortableOption.codePointAt(0) === EQUAL_CODE) hasShortValueSeparator = true;
|
|
82
|
-
else expanded.push(`${SHORT_OPTION_PREFIX}${shortableOption}`);
|
|
83
|
-
}
|
|
84
|
-
if (shortValue) expanded.push(shortValue);
|
|
85
|
-
remainings.unshift(...expanded);
|
|
86
|
-
groupCount = expanded.length;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (isLongOption(arg)) {
|
|
90
|
-
const longOption = arg.slice(2);
|
|
91
|
-
tokens.push({
|
|
92
|
-
kind: "option",
|
|
93
|
-
name: longOption,
|
|
94
|
-
rawName: arg,
|
|
95
|
-
index,
|
|
96
|
-
value: undefined,
|
|
97
|
-
inlineValue: undefined
|
|
98
|
-
});
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (isLongOptionAndValue(arg)) {
|
|
102
|
-
const equalIndex = arg.indexOf(EQUAL_CHAR);
|
|
103
|
-
const longOption = arg.slice(2, equalIndex);
|
|
104
|
-
const value = arg.slice(equalIndex + 1);
|
|
105
|
-
tokens.push({
|
|
106
|
-
kind: "option",
|
|
107
|
-
name: longOption,
|
|
108
|
-
rawName: `${LONG_OPTION_PREFIX}${longOption}`,
|
|
109
|
-
index,
|
|
110
|
-
value,
|
|
111
|
-
inlineValue: true
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
tokens.push({
|
|
116
|
-
kind: "positional",
|
|
117
|
-
index,
|
|
118
|
-
value: arg
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
return tokens;
|
|
122
|
-
}
|
|
123
|
-
function isShortOption(arg) {
|
|
124
|
-
return arg.length === 2 && arg.codePointAt(0) === HYPHEN_CODE && arg.codePointAt(1) !== HYPHEN_CODE;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Check if `arg` is a short option group (e.g. `-abc`)
|
|
128
|
-
* @param arg the argument to check
|
|
129
|
-
* @returns whether `arg` is a short option group
|
|
130
|
-
*/
|
|
131
|
-
function isShortOptionGroup(arg) {
|
|
132
|
-
if (arg.length <= 2) return false;
|
|
133
|
-
if (arg.codePointAt(0) !== HYPHEN_CODE) return false;
|
|
134
|
-
if (arg.codePointAt(1) === HYPHEN_CODE) return false;
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Check if `arg` is a long option (e.g. `--foo`)
|
|
139
|
-
* @param arg the argument to check
|
|
140
|
-
* @returns whether `arg` is a long option
|
|
141
|
-
*/
|
|
142
|
-
function isLongOption(arg) {
|
|
143
|
-
return hasLongOptionPrefix(arg) && !arg.includes(EQUAL_CHAR, 3);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Check if `arg` is a long option with value (e.g. `--foo=bar`)
|
|
147
|
-
* @param arg the argument to check
|
|
148
|
-
* @returns whether `arg` is a long option
|
|
149
|
-
*/
|
|
150
|
-
function isLongOptionAndValue(arg) {
|
|
151
|
-
return hasLongOptionPrefix(arg) && arg.includes(EQUAL_CHAR, 3);
|
|
152
|
-
}
|
|
153
|
-
function hasLongOptionPrefix(arg) {
|
|
154
|
-
return arg.length > 2 && ~arg.indexOf(LONG_OPTION_PREFIX);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Check if a `value` is an option value
|
|
158
|
-
* @param value a value to check
|
|
159
|
-
* @returns whether a `value` is an option value
|
|
160
|
-
*/
|
|
161
|
-
function hasOptionValue(value) {
|
|
162
|
-
return !(value == null) && value.codePointAt(0) !== HYPHEN_CODE;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
//#region node_modules/.pnpm/args-tokens@0.10.2/node_modules/args-tokens/lib/resolver.js
|
|
167
|
-
function resolveArgs(options, tokens) {
|
|
168
|
-
const positionals = [];
|
|
169
|
-
const longOptionTokens = [];
|
|
170
|
-
const shortOptionTokens = [];
|
|
171
|
-
let currentLongOption;
|
|
172
|
-
let currentShortOption;
|
|
173
|
-
const expandableShortOptions = [];
|
|
174
|
-
function toShortValue() {
|
|
175
|
-
if (expandableShortOptions.length === 0) return undefined;
|
|
176
|
-
else {
|
|
177
|
-
const value = expandableShortOptions.map((token) => token.name).join("");
|
|
178
|
-
expandableShortOptions.length = 0;
|
|
179
|
-
return value;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
function applyLongOptionValue(value = undefined) {
|
|
183
|
-
if (currentLongOption) {
|
|
184
|
-
currentLongOption.value = value;
|
|
185
|
-
longOptionTokens.push({ ...currentLongOption });
|
|
186
|
-
currentLongOption = undefined;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function applyShortOptionValue(value = undefined) {
|
|
190
|
-
if (currentShortOption) {
|
|
191
|
-
currentShortOption.value = value || toShortValue();
|
|
192
|
-
shortOptionTokens.push({ ...currentShortOption });
|
|
193
|
-
currentShortOption = undefined;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* analyze phase to resolve value
|
|
198
|
-
* separate tokens into positionals, long and short options, after that resolve values
|
|
199
|
-
*/
|
|
200
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
201
|
-
const token = tokens[i];
|
|
202
|
-
if (token.kind === "positional") {
|
|
203
|
-
positionals.push(token.value);
|
|
204
|
-
applyLongOptionValue(token.value);
|
|
205
|
-
applyShortOptionValue(token.value);
|
|
206
|
-
} else if (token.kind === "option") if (token.rawName) {
|
|
207
|
-
if (hasLongOptionPrefix(token.rawName)) {
|
|
208
|
-
if (token.inlineValue) longOptionTokens.push({ ...token });
|
|
209
|
-
else currentLongOption = { ...token };
|
|
210
|
-
applyShortOptionValue();
|
|
211
|
-
} else if (isShortOption(token.rawName)) if (currentShortOption) {
|
|
212
|
-
if (currentShortOption.index === token.index) expandableShortOptions.push({ ...token });
|
|
213
|
-
else {
|
|
214
|
-
currentShortOption.value = toShortValue();
|
|
215
|
-
shortOptionTokens.push({ ...currentShortOption });
|
|
216
|
-
currentShortOption = { ...token };
|
|
217
|
-
}
|
|
218
|
-
applyLongOptionValue();
|
|
219
|
-
} else {
|
|
220
|
-
currentShortOption = { ...token };
|
|
221
|
-
applyLongOptionValue();
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
if (currentShortOption && currentShortOption.index == token.index && token.inlineValue) {
|
|
225
|
-
currentShortOption.value = token.value;
|
|
226
|
-
shortOptionTokens.push({ ...currentShortOption });
|
|
227
|
-
currentShortOption = undefined;
|
|
228
|
-
}
|
|
229
|
-
applyLongOptionValue();
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
applyLongOptionValue();
|
|
233
|
-
applyShortOptionValue();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* check if the last long or short option is not resolved
|
|
238
|
-
*/
|
|
239
|
-
applyLongOptionValue();
|
|
240
|
-
applyShortOptionValue();
|
|
241
|
-
/**
|
|
242
|
-
* resolve values
|
|
243
|
-
*/
|
|
244
|
-
const values = Object.create(null);
|
|
245
|
-
const errors = [];
|
|
246
|
-
for (const [option, schema] of Object.entries(options)) {
|
|
247
|
-
if (schema.required) {
|
|
248
|
-
const found = longOptionTokens.find((token) => token.name === option) || schema.short && shortOptionTokens.find((token) => token.name === schema.short);
|
|
249
|
-
if (!found) {
|
|
250
|
-
errors.push(createRequireError(option, schema));
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
for (let i = 0; i < longOptionTokens.length; i++) {
|
|
255
|
-
const token = longOptionTokens[i];
|
|
256
|
-
if (option === token.name && token.rawName != null && hasLongOptionPrefix(token.rawName)) {
|
|
257
|
-
const invalid = validateRequire(token, option, schema);
|
|
258
|
-
if (invalid) {
|
|
259
|
-
errors.push(invalid);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (schema.type === "boolean") token.value = undefined;
|
|
263
|
-
else {
|
|
264
|
-
const invalid$1 = validateValue(token, option, schema);
|
|
265
|
-
if (invalid$1) {
|
|
266
|
-
errors.push(invalid$1);
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
values[option] = resolveOptionValue(token, schema);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
for (let i = 0; i < shortOptionTokens.length; i++) {
|
|
275
|
-
const token = shortOptionTokens[i];
|
|
276
|
-
if (schema.short === token.name && token.rawName != null && isShortOption(token.rawName)) {
|
|
277
|
-
const invalid = validateRequire(token, option, schema);
|
|
278
|
-
if (invalid) {
|
|
279
|
-
errors.push(invalid);
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (schema.type === "boolean") token.value = undefined;
|
|
283
|
-
else {
|
|
284
|
-
const invalid$1 = validateValue(token, option, schema);
|
|
285
|
-
if (invalid$1) {
|
|
286
|
-
errors.push(invalid$1);
|
|
287
|
-
continue;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
values[option] = resolveOptionValue(token, schema);
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (values[option] == null && schema.default != null) values[option] = schema.default;
|
|
295
|
-
}
|
|
296
|
-
return {
|
|
297
|
-
values,
|
|
298
|
-
positionals,
|
|
299
|
-
error: errors.length > 0 ? new AggregateError(errors) : undefined
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
function createRequireError(option, schema) {
|
|
303
|
-
return new Error(`Option '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}is required`);
|
|
304
|
-
}
|
|
305
|
-
function validateRequire(token, option, schema) {
|
|
306
|
-
if (schema.required && schema.type !== "boolean" && !token.value) return createRequireError(option, schema);
|
|
307
|
-
}
|
|
308
|
-
function validateValue(token, option, schema) {
|
|
309
|
-
switch (schema.type) {
|
|
310
|
-
case "number": {
|
|
311
|
-
if (!isNumeric(token.value)) return createTypeError(option, schema);
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
case "string": {
|
|
315
|
-
if (typeof token.value !== "string") return createTypeError(option, schema);
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
function isNumeric(str) {
|
|
321
|
-
return str.trim() !== "" && !isNaN(str);
|
|
322
|
-
}
|
|
323
|
-
function createTypeError(option, schema) {
|
|
324
|
-
return new TypeError(`Option '--${option}' ${schema.short ? `or '-${schema.short}' ` : ""}should be '${schema.type}'`);
|
|
325
|
-
}
|
|
326
|
-
function resolveOptionValue(token, schema) {
|
|
327
|
-
if (token.value) return schema.type === "number" ? +token.value : token.value;
|
|
328
|
-
if (schema.type === "boolean") return true;
|
|
329
|
-
return schema.type === "number" ? +(schema.default || "") : schema.default;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
//#endregion
|
|
333
|
-
//#region src/constants.ts
|
|
334
|
-
const COMMON_OPTIONS = {
|
|
335
|
-
help: {
|
|
336
|
-
type: "boolean",
|
|
337
|
-
short: "h"
|
|
338
|
-
},
|
|
339
|
-
version: {
|
|
340
|
-
type: "boolean",
|
|
341
|
-
short: "v"
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
const COMMAND_OPTIONS_DEFAULT = {
|
|
345
|
-
name: undefined,
|
|
346
|
-
description: undefined,
|
|
347
|
-
version: undefined,
|
|
348
|
-
cwd: undefined,
|
|
349
|
-
subCommands: undefined,
|
|
350
|
-
leftMargin: 2,
|
|
351
|
-
middleMargin: 10,
|
|
352
|
-
usageOptionType: false,
|
|
353
|
-
renderHeader: undefined,
|
|
354
|
-
renderUsage: undefined,
|
|
355
|
-
renderValidationErrors: undefined
|
|
356
|
-
};
|
|
357
|
-
const COMMAND_I18N_RESOURCE_KEYS = [
|
|
358
|
-
"USAGE",
|
|
359
|
-
"COMMAND",
|
|
360
|
-
"SUBCOMMAND",
|
|
361
|
-
"COMMANDS",
|
|
362
|
-
"OPTIONS",
|
|
363
|
-
"EXAMPLES",
|
|
364
|
-
"FORMORE"
|
|
365
|
-
];
|
|
366
|
-
|
|
367
|
-
//#endregion
|
|
368
|
-
//#region locales/en-US.json
|
|
369
|
-
var COMMAND = "COMMAND";
|
|
370
|
-
var COMMANDS = "COMMANDS";
|
|
371
|
-
var SUBCOMMAND = "SUBCOMMAND";
|
|
372
|
-
var USAGE = "USAGE";
|
|
373
|
-
var OPTIONS = "OPTIONS";
|
|
374
|
-
var EXAMPLES = "EXAMPLES";
|
|
375
|
-
var FORMORE = "For more info, run any command with the `--help` flag:";
|
|
376
|
-
var help = "Display this help message";
|
|
377
|
-
var version = "Display this version";
|
|
378
|
-
var en_US_default = {
|
|
379
|
-
COMMAND,
|
|
380
|
-
COMMANDS,
|
|
381
|
-
SUBCOMMAND,
|
|
382
|
-
USAGE,
|
|
383
|
-
OPTIONS,
|
|
384
|
-
EXAMPLES,
|
|
385
|
-
FORMORE,
|
|
386
|
-
help,
|
|
387
|
-
version
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
//#endregion
|
|
391
|
-
//#region src/utils.ts
|
|
392
|
-
async function resolveLazyCommand(cmd, name, entry = false) {
|
|
393
|
-
const resolved = Object.assign(create(), typeof cmd == "function" ? await cmd() : cmd, { default: entry });
|
|
394
|
-
if (resolved.name == null && name) resolved.name = name;
|
|
395
|
-
return deepFreeze(resolved);
|
|
396
|
-
}
|
|
397
|
-
function create(obj = null) {
|
|
398
|
-
return Object.create(obj);
|
|
399
|
-
}
|
|
400
|
-
function log(...args) {
|
|
401
|
-
console.log(...args);
|
|
402
|
-
}
|
|
403
|
-
function deepFreeze(obj) {
|
|
404
|
-
if (obj === null || typeof obj !== "object") return obj;
|
|
405
|
-
for (const key of Object.keys(obj)) {
|
|
406
|
-
const value = obj[key];
|
|
407
|
-
if (typeof value === "object" && value !== null) deepFreeze(value);
|
|
408
|
-
}
|
|
409
|
-
return Object.freeze(obj);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
//#endregion
|
|
413
|
-
//#region src/context.ts
|
|
414
|
-
const DEFAULT_LOCALE = "en-US";
|
|
415
|
-
async function createCommandContext({ options, values, positionals, command, commandOptions, omitted = false }) {
|
|
416
|
-
/**
|
|
417
|
-
* tweak the options and values
|
|
418
|
-
*/
|
|
419
|
-
const _options = options == null ? undefined : Object.entries(options).reduce((acc, [key, value]) => {
|
|
420
|
-
acc[key] = Object.assign(create(), value);
|
|
421
|
-
return acc;
|
|
422
|
-
}, create());
|
|
423
|
-
const _values = Object.assign(create(), values);
|
|
424
|
-
/**
|
|
425
|
-
* normalize the usage
|
|
426
|
-
*/
|
|
427
|
-
const usage = Object.assign(create(), command.usage);
|
|
428
|
-
const { help: help$1, version: version$1 } = en_US_default;
|
|
429
|
-
usage.options = Object.assign(create(), usage.options, {
|
|
430
|
-
help: help$1,
|
|
431
|
-
version: version$1
|
|
432
|
-
});
|
|
433
|
-
/**
|
|
434
|
-
* setup the environment
|
|
435
|
-
*/
|
|
436
|
-
const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, commandOptions);
|
|
437
|
-
const locale = resolveLocale(commandOptions.locale);
|
|
438
|
-
const localeResources = new Map();
|
|
439
|
-
const commandResources = new Map();
|
|
440
|
-
let builtInLoadedResources;
|
|
441
|
-
/**
|
|
442
|
-
* load the built-in locale resources
|
|
443
|
-
*/
|
|
444
|
-
localeResources.set(DEFAULT_LOCALE, en_US_default);
|
|
445
|
-
if (DEFAULT_LOCALE !== locale.toString()) try {
|
|
446
|
-
builtInLoadedResources = await import(`../locales/${locale.toString()}.json`, { with: { type: "json" } });
|
|
447
|
-
localeResources.set(locale.toString(), builtInLoadedResources);
|
|
448
|
-
} catch {}
|
|
449
|
-
/**
|
|
450
|
-
* define the translation function
|
|
451
|
-
*/
|
|
452
|
-
function translation(key) {
|
|
453
|
-
if (COMMAND_I18N_RESOURCE_KEYS.includes(key)) {
|
|
454
|
-
const resource = localeResources.get(locale.toString()) || localeResources.get(DEFAULT_LOCALE);
|
|
455
|
-
return resource[key] || key;
|
|
456
|
-
} else {
|
|
457
|
-
const resource = commandResources.get(locale.toString()) || commandResources.get(DEFAULT_LOCALE);
|
|
458
|
-
return resource[key] || "";
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* load the sub commands
|
|
463
|
-
*/
|
|
464
|
-
let cachedCommands;
|
|
465
|
-
async function loadCommands() {
|
|
466
|
-
if (cachedCommands) return cachedCommands;
|
|
467
|
-
const subCommands = [...env.subCommands || []];
|
|
468
|
-
return cachedCommands = await Promise.all(subCommands.map(async ([name, cmd]) => await resolveLazyCommand(cmd, name)));
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* create the context
|
|
472
|
-
*/
|
|
473
|
-
const ctx = deepFreeze(Object.assign(create(), {
|
|
474
|
-
name: command.name,
|
|
475
|
-
description: command.description,
|
|
476
|
-
omitted,
|
|
477
|
-
locale,
|
|
478
|
-
env,
|
|
479
|
-
options: _options,
|
|
480
|
-
values: _values,
|
|
481
|
-
positionals,
|
|
482
|
-
usage,
|
|
483
|
-
loadCommands,
|
|
484
|
-
translation
|
|
485
|
-
}));
|
|
486
|
-
/**
|
|
487
|
-
* load the command resources
|
|
488
|
-
*/
|
|
489
|
-
const loadedOptionsResources = Object.entries(usage.options || create()).map(([key, _]) => {
|
|
490
|
-
const option = usage.options[key];
|
|
491
|
-
return [key, option];
|
|
492
|
-
});
|
|
493
|
-
const defaultCommandResource = loadedOptionsResources.reduce((res, [key, value]) => {
|
|
494
|
-
res[key] = value;
|
|
495
|
-
return res;
|
|
496
|
-
}, create());
|
|
497
|
-
defaultCommandResource.description = command.description || "";
|
|
498
|
-
defaultCommandResource.examples = usage.examples || "";
|
|
499
|
-
commandResources.set(DEFAULT_LOCALE, defaultCommandResource);
|
|
500
|
-
const originalResource = await loadCommandResource(ctx, command);
|
|
501
|
-
if (originalResource) {
|
|
502
|
-
const resource = Object.entries(originalResource.options).reduce((res, [key, value]) => {
|
|
503
|
-
res[key] = value;
|
|
504
|
-
return res;
|
|
505
|
-
}, Object.assign(create(), {
|
|
506
|
-
description: originalResource.description,
|
|
507
|
-
examples: originalResource.examples
|
|
508
|
-
}));
|
|
509
|
-
if (builtInLoadedResources) {
|
|
510
|
-
resource.help = builtInLoadedResources.help;
|
|
511
|
-
resource.version = builtInLoadedResources.version;
|
|
512
|
-
}
|
|
513
|
-
commandResources.set(locale.toString(), resource);
|
|
514
|
-
}
|
|
515
|
-
return ctx;
|
|
516
|
-
}
|
|
517
|
-
function resolveLocale(locale) {
|
|
518
|
-
return locale instanceof Intl.Locale ? locale : typeof locale === "string" ? new Intl.Locale(locale) : new Intl.Locale(DEFAULT_LOCALE);
|
|
519
|
-
}
|
|
520
|
-
async function loadCommandResource(ctx, command) {
|
|
521
|
-
let resource;
|
|
522
|
-
try {
|
|
523
|
-
resource = await command.resource?.(ctx);
|
|
524
|
-
} catch {}
|
|
525
|
-
return resource;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
//#endregion
|
|
529
|
-
//#region src/renderer.ts
|
|
530
|
-
function renderHeader(ctx) {
|
|
531
|
-
const title = ctx.env.description || ctx.env.name || "";
|
|
532
|
-
return Promise.resolve(title ? `${title} (${ctx.env.name || ""}${ctx.env.version ? ` v${ctx.env.version}` : ""})` : title);
|
|
533
|
-
}
|
|
534
|
-
async function renderUsage(ctx) {
|
|
535
|
-
const messages = [];
|
|
536
|
-
if (!ctx.omitted && hasDescription(ctx)) messages.push(ctx.description, "");
|
|
537
|
-
messages.push(...await renderUsageSection(ctx), "");
|
|
538
|
-
if (ctx.omitted && await hasCommands(ctx)) messages.push(...await renderCommandsSection(ctx), "");
|
|
539
|
-
if (hasOptions(ctx)) messages.push(...await renderOptionsSection(ctx), "");
|
|
540
|
-
if (hasExamples(ctx)) messages.push(...renderExamplesSection(ctx), "");
|
|
541
|
-
return messages.join("\n");
|
|
542
|
-
}
|
|
543
|
-
function renderValidationErrors(_ctx, error) {
|
|
544
|
-
const messages = [];
|
|
545
|
-
for (const err of error.errors) messages.push(err.message);
|
|
546
|
-
return Promise.resolve(messages.join("\n"));
|
|
547
|
-
}
|
|
548
|
-
async function renderOptionsSection(ctx) {
|
|
549
|
-
const messages = [];
|
|
550
|
-
messages.push(`${ctx.translation("OPTIONS")}:`);
|
|
551
|
-
const optionsPairs = getOptionsPairs(ctx);
|
|
552
|
-
messages.push(await generateOptionsUsage(ctx, optionsPairs));
|
|
553
|
-
return messages;
|
|
554
|
-
}
|
|
555
|
-
function renderExamplesSection(ctx) {
|
|
556
|
-
const messages = [];
|
|
557
|
-
const examples = ctx.usage.examples.split("\n").map((example) => example.padStart(ctx.env.leftMargin + example.length));
|
|
558
|
-
messages.push(`${ctx.translation("EXAMPLES")}:`, ...examples);
|
|
559
|
-
return messages;
|
|
560
|
-
}
|
|
561
|
-
async function renderUsageSection(ctx) {
|
|
562
|
-
const messages = [`${ctx.translation("USAGE")}:`];
|
|
563
|
-
if (ctx.omitted) {
|
|
564
|
-
const defaultCommand = `${resolveEntry(ctx)}${await hasCommands(ctx) ? ` [${resolveSubCommand(ctx)}]` : ""} ${hasOptions(ctx) ? `<${ctx.translation("OPTIONS")}>` : ""} `;
|
|
565
|
-
messages.push(defaultCommand.padStart(ctx.env.leftMargin + defaultCommand.length));
|
|
566
|
-
if (await hasCommands(ctx)) {
|
|
567
|
-
const commandsUsage = `${resolveEntry(ctx)} <${ctx.translation("COMMANDS")}>`;
|
|
568
|
-
messages.push(commandsUsage.padStart(ctx.env.leftMargin + commandsUsage.length));
|
|
569
|
-
}
|
|
570
|
-
} else {
|
|
571
|
-
const usageStr = `${resolveEntry(ctx)} ${resolveSubCommand(ctx)} ${generateOptionsSymbols(ctx)}`;
|
|
572
|
-
messages.push(usageStr.padStart(ctx.env.leftMargin + usageStr.length));
|
|
573
|
-
}
|
|
574
|
-
return messages;
|
|
575
|
-
}
|
|
576
|
-
async function renderCommandsSection(ctx) {
|
|
577
|
-
const messages = [`${ctx.translation("COMMANDS")}:`];
|
|
578
|
-
const loadedCommands = await ctx.loadCommands();
|
|
579
|
-
const commandMaxLength = Math.max(...loadedCommands.map((cmd) => (cmd.name || "").length));
|
|
580
|
-
const commandsStr = await Promise.all(loadedCommands.map((cmd) => {
|
|
581
|
-
const key = cmd.name || "";
|
|
582
|
-
const desc = cmd.description || "";
|
|
583
|
-
const command = `${key.padEnd(commandMaxLength + ctx.env.middleMargin)}${desc} `;
|
|
584
|
-
return `${command.padStart(ctx.env.leftMargin + command.length)} `;
|
|
585
|
-
}));
|
|
586
|
-
messages.push(...commandsStr, "", ctx.translation("FORMORE"));
|
|
587
|
-
messages.push(...loadedCommands.map((cmd) => {
|
|
588
|
-
const commandHelp = `${ctx.env.name} ${cmd.name} --help`;
|
|
589
|
-
return `${commandHelp.padStart(ctx.env.leftMargin + commandHelp.length)}`;
|
|
590
|
-
}));
|
|
591
|
-
return messages;
|
|
592
|
-
}
|
|
593
|
-
function resolveEntry(ctx) {
|
|
594
|
-
return ctx.env.name || ctx.translation("COMMAND");
|
|
595
|
-
}
|
|
596
|
-
function resolveSubCommand(ctx) {
|
|
597
|
-
return ctx.name || ctx.translation("SUBCOMMAND");
|
|
598
|
-
}
|
|
599
|
-
function hasDescription(ctx) {
|
|
600
|
-
return !!ctx.description;
|
|
601
|
-
}
|
|
602
|
-
async function hasCommands(ctx) {
|
|
603
|
-
const loadedCommands = await ctx.loadCommands();
|
|
604
|
-
return loadedCommands.length > 1;
|
|
605
|
-
}
|
|
606
|
-
function hasOptions(ctx) {
|
|
607
|
-
return !!(ctx.options && Object.keys(ctx.options).length > 0);
|
|
608
|
-
}
|
|
609
|
-
function hasExamples(ctx) {
|
|
610
|
-
return !!ctx.usage.examples;
|
|
611
|
-
}
|
|
612
|
-
function hasAllDefaultOptions(ctx) {
|
|
613
|
-
return !!(ctx.options && Object.values(ctx.options).every((opt) => opt.default));
|
|
614
|
-
}
|
|
615
|
-
function generateOptionsSymbols(ctx) {
|
|
616
|
-
return hasOptions(ctx) ? hasAllDefaultOptions(ctx) ? `[${ctx.translation("OPTIONS")}]` : `<${ctx.translation("OPTIONS")}>` : "";
|
|
617
|
-
}
|
|
618
|
-
function getOptionsPairs(ctx) {
|
|
619
|
-
return Object.entries(ctx.options).reduce((acc, [name, value]) => {
|
|
620
|
-
let key = `--${name}`;
|
|
621
|
-
if (value.short) key = `-${value.short}, ${key}`;
|
|
622
|
-
if (value.type !== "boolean") key = value.default ? `${key} [${name}]` : `${key} <${name}>`;
|
|
623
|
-
acc[name] = key;
|
|
624
|
-
return acc;
|
|
625
|
-
}, create());
|
|
626
|
-
}
|
|
627
|
-
async function generateOptionsUsage(ctx, optionsPairs) {
|
|
628
|
-
const optionsMaxLength = Math.max(...Object.entries(optionsPairs).map(([_, value]) => value.length));
|
|
629
|
-
const optionSchemaMaxLength = ctx.env.usageOptionType ? Math.max(...Object.entries(optionsPairs).map(([key, _]) => ctx.options[key].type.length)) : 0;
|
|
630
|
-
const usages = await Promise.all(Object.entries(optionsPairs).map(([key, value]) => {
|
|
631
|
-
const rawDesc = ctx.translation(key);
|
|
632
|
-
const optionsSchema = ctx.env.usageOptionType ? `[${ctx.options[key].type}] ` : "";
|
|
633
|
-
const desc = `${optionsSchema ? optionsSchema.padEnd(optionSchemaMaxLength + 3) : ""}${rawDesc}`;
|
|
634
|
-
const option = `${value.padEnd(optionsMaxLength + ctx.env.middleMargin)}${desc}`;
|
|
635
|
-
return `${option.padStart(ctx.env.leftMargin + option.length)}`;
|
|
636
|
-
}));
|
|
637
|
-
return usages.join("\n");
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
//#endregion
|
|
641
6
|
//#region src/cli.ts
|
|
642
7
|
async function cli(args, entry, opts = {}) {
|
|
643
8
|
const tokens = parseArgs(args);
|
|
@@ -0,0 +1,115 @@
|
|
|
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
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
ADDED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ArgOptions, ArgValues } from 'args-tokens';
|
|
2
|
+
|
|
3
|
+
declare const COMMON_OPTIONS: {
|
|
4
|
+
readonly help: {
|
|
5
|
+
readonly type: "boolean"
|
|
6
|
+
readonly short: "h"
|
|
7
|
+
}
|
|
8
|
+
readonly version: {
|
|
9
|
+
readonly type: "boolean"
|
|
10
|
+
readonly short: "v"
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
declare const COMMAND_OPTIONS_DEFAULT: CommandOptions<ArgOptions>;
|
|
14
|
+
declare const COMMAND_I18N_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "OPTIONS", "EXAMPLES", "FORMORE"];
|
|
15
|
+
|
|
16
|
+
declare const __constants_COMMAND_I18N_RESOURCE_KEYS: typeof COMMAND_I18N_RESOURCE_KEYS;
|
|
17
|
+
declare const __constants_COMMAND_OPTIONS_DEFAULT: typeof COMMAND_OPTIONS_DEFAULT;
|
|
18
|
+
declare const __constants_COMMON_OPTIONS: typeof COMMON_OPTIONS;
|
|
19
|
+
declare namespace __constants {
|
|
20
|
+
export { __constants_COMMAND_I18N_RESOURCE_KEYS as COMMAND_I18N_RESOURCE_KEYS, __constants_COMMAND_OPTIONS_DEFAULT as COMMAND_OPTIONS_DEFAULT, __constants_COMMON_OPTIONS as COMMON_OPTIONS };
|
|
21
|
+
}
|
|
22
|
+
|
|
2
23
|
/**
|
|
3
24
|
* Define a promise type that can be await from T
|
|
4
25
|
*/
|
|
@@ -7,22 +28,22 @@ type Awaitable<T> = T | Promise<T>;
|
|
|
7
28
|
* The command i18n built-in options keys
|
|
8
29
|
* @experimental
|
|
9
30
|
*/
|
|
10
|
-
|
|
31
|
+
type CommandBuiltinOptionsKeys = keyof (typeof __constants)["COMMON_OPTIONS"];
|
|
11
32
|
/**
|
|
12
33
|
* The command i18n built-in resource keys
|
|
13
34
|
* @experimental
|
|
14
35
|
*/
|
|
15
|
-
|
|
36
|
+
type CommandBuiltinResourceKeys = (typeof __constants)["COMMAND_I18N_RESOURCE_KEYS"][number];
|
|
16
37
|
/**
|
|
17
38
|
* The command i18n built-in keys
|
|
18
39
|
* @description The command i18n built-in keys are used to {@link CommandContext.translation | translate} function
|
|
19
40
|
* @experimental
|
|
20
41
|
*/
|
|
21
|
-
|
|
42
|
+
type CommandBuiltinKeys = CommandBuiltinOptionsKeys | CommandBuiltinResourceKeys | "description" | "examples";
|
|
22
43
|
/**
|
|
23
44
|
* The command environment
|
|
24
45
|
*/
|
|
25
|
-
|
|
46
|
+
interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
|
|
26
47
|
/**
|
|
27
48
|
* The current working directory
|
|
28
49
|
* @see {@link CommandOptions.cwd}
|
|
@@ -83,7 +104,7 @@ export interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
|
|
|
83
104
|
/**
|
|
84
105
|
* The command options
|
|
85
106
|
*/
|
|
86
|
-
|
|
107
|
+
interface CommandOptions<Options extends ArgOptions> {
|
|
87
108
|
/**
|
|
88
109
|
* The current working directory
|
|
89
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.
|
|
@@ -143,7 +164,7 @@ export interface CommandOptions<Options extends ArgOptions> {
|
|
|
143
164
|
* The command context
|
|
144
165
|
* @description The command context is the context of the command execution
|
|
145
166
|
*/
|
|
146
|
-
|
|
167
|
+
interface CommandContext<
|
|
147
168
|
Options extends ArgOptions,
|
|
148
169
|
Values = ArgValues<Options>
|
|
149
170
|
> {
|
|
@@ -211,7 +232,7 @@ export interface CommandContext<
|
|
|
211
232
|
* The command usage render
|
|
212
233
|
* @description if the render function is async, it should return a promise
|
|
213
234
|
*/
|
|
214
|
-
|
|
235
|
+
type CommandUsageRender<Options extends ArgOptions> = ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | string;
|
|
215
236
|
/**
|
|
216
237
|
* The command usage
|
|
217
238
|
*/
|
|
@@ -228,7 +249,7 @@ interface CommandUsage<Options extends ArgOptions> {
|
|
|
228
249
|
/**
|
|
229
250
|
* The command interface
|
|
230
251
|
*/
|
|
231
|
-
|
|
252
|
+
interface Command<Options extends ArgOptions> {
|
|
232
253
|
/**
|
|
233
254
|
* The command name
|
|
234
255
|
* @description
|
|
@@ -270,7 +291,7 @@ export interface Command<Options extends ArgOptions> {
|
|
|
270
291
|
* The command resource
|
|
271
292
|
* @experimental
|
|
272
293
|
*/
|
|
273
|
-
|
|
294
|
+
interface CommandResource<Options extends ArgOptions> {
|
|
274
295
|
/**
|
|
275
296
|
* The command description resource
|
|
276
297
|
*/
|
|
@@ -288,15 +309,16 @@ export interface CommandResource<Options extends ArgOptions> {
|
|
|
288
309
|
* The command resource fetcher
|
|
289
310
|
* @experimental
|
|
290
311
|
*/
|
|
291
|
-
|
|
312
|
+
type CommandResourceFetcher<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
|
|
292
313
|
/**
|
|
293
314
|
* The command runner interface
|
|
294
315
|
* @param ctx - The {@link CommandContext | command context}
|
|
295
316
|
*/
|
|
296
|
-
|
|
317
|
+
type CommandRunner<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
|
|
297
318
|
/**
|
|
298
319
|
* The lazy command interface
|
|
299
320
|
* @description The lazy command that's not loaded until it is executed
|
|
300
321
|
*/
|
|
301
|
-
|
|
302
|
-
|
|
322
|
+
type LazyCommand<Options extends ArgOptions> = () => Awaitable<Command<Options>>;
|
|
323
|
+
|
|
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, CommandUsageRender as h, CommandResource as i, CommandResourceFetcher as j };
|
|
@@ -0,0 +1,24 @@
|
|
|
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 };
|
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.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kazuya kawaguchi",
|
|
7
7
|
"email": "kawakazu80@gmail.com"
|
|
@@ -88,10 +88,9 @@
|
|
|
88
88
|
"lint-staged": "^15.4.3",
|
|
89
89
|
"pkg-pr-new": "^0.0.40",
|
|
90
90
|
"prettier": "^3.5.3",
|
|
91
|
-
"
|
|
91
|
+
"tsdown": "^0.6.4",
|
|
92
92
|
"typescript": "^5.8.2",
|
|
93
93
|
"typescript-eslint": "^8.26.0",
|
|
94
|
-
"unplugin-isolated-decl": "^0.13.1",
|
|
95
94
|
"vitest": "^3.0.7"
|
|
96
95
|
},
|
|
97
96
|
"prettier": "@kazupon/prettier-config",
|
|
@@ -109,7 +108,7 @@
|
|
|
109
108
|
]
|
|
110
109
|
},
|
|
111
110
|
"scripts": {
|
|
112
|
-
"build": "
|
|
111
|
+
"build": "tsdown",
|
|
113
112
|
"changelog": "gh-changelogen --repo=kazupon/gunshi",
|
|
114
113
|
"clean": "git clean -df",
|
|
115
114
|
"dev": "pnpx @eslint/config-inspector --config eslint.config.ts",
|
package/lib/cli.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ArgOptions } from "args-tokens";
|
|
2
|
-
import type { Command, CommandOptions, CommandRunner } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Run the command
|
|
5
|
-
* @param args - command line arguments
|
|
6
|
-
* @param entry - a {@link Command | entry command} or an {@link CommandRunner | inline command runner}
|
|
7
|
-
* @param opts - a {@link CommandOptions | command options}
|
|
8
|
-
*/
|
|
9
|
-
export declare function cli<Options extends ArgOptions>(args: string[], entry: Command<Options> | CommandRunner<Options>, opts?: CommandOptions<Options>): Promise<void>;
|
package/lib/constants.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ArgOptions } from "args-tokens";
|
|
2
|
-
import type { CommandOptions } from "./types.js";
|
|
3
|
-
export declare const COMMON_OPTIONS: {
|
|
4
|
-
readonly help: {
|
|
5
|
-
readonly type: "boolean"
|
|
6
|
-
readonly short: "h"
|
|
7
|
-
}
|
|
8
|
-
readonly version: {
|
|
9
|
-
readonly type: "boolean"
|
|
10
|
-
readonly short: "v"
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
export declare const COMMAND_OPTIONS_DEFAULT: CommandOptions<ArgOptions>;
|
|
14
|
-
export declare const COMMAND_I18N_RESOURCE_KEYS: readonly ["USAGE", "COMMAND", "SUBCOMMAND", "COMMANDS", "OPTIONS", "EXAMPLES", "FORMORE"];
|
package/lib/utils.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { ArgOptions } from "args-tokens";
|
|
2
|
-
import type { Command, LazyCommand } from "./types.js";
|
|
3
|
-
export declare function resolveLazyCommand<Options extends ArgOptions>(cmd: Command<Options> | LazyCommand<Options>, name: string | undefined, entry?: boolean): Promise<Command<Options>>;
|
|
4
|
-
export declare function create<T>(obj?: object | null): T;
|
|
5
|
-
export declare function log(...args: unknown[]): void;
|
|
6
|
-
export declare function deepFreeze<T extends Record<string, any>>(obj: T): Readonly<T>;
|