gunshi 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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,24 @@
1
- import type { ArgOptions } from "args-tokens";
2
- import type { CommandContext } from "./types.js";
3
- export declare function renderHeader<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
4
- export declare function renderUsage<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
5
- export declare function renderValidationErrors<Options extends ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
1
+ import { ArgOptions } from 'args-tokens';
2
+ import { b as CommandContext } from './types.d-00BVt8hZ.js';
3
+
4
+ /**
5
+ * Render the header
6
+ * @param ctx A {@link CommandContext | command context}
7
+ * @returns A rendered header
8
+ */
9
+ declare function renderHeader<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
10
+ /**
11
+ * Render the usage
12
+ * @param ctx A {@link CommandContext | command context}
13
+ * @returns A rendered usage
14
+ */
15
+ declare function renderUsage<Options extends ArgOptions>(ctx: Readonly<CommandContext<Options>>): Promise<string>;
16
+ /**
17
+ * Render the validation errors
18
+ * @param ctx A {@link CommandContext | command context}
19
+ * @param error An {@link AggregateError} of option in `args-token` validation
20
+ * @returns A rendered validation error
21
+ */
22
+ declare function renderValidationErrors<Options extends ArgOptions>(_ctx: CommandContext<Options>, error: AggregateError): Promise<string>;
23
+
24
+ export { renderHeader, renderUsage, renderValidationErrors };
@@ -0,0 +1,4 @@
1
+ import "./utils-NHs5DuHk.js";
2
+ import { renderHeader, renderUsage, renderValidationErrors } from "./renderer-Bo0DibAK.js";
3
+
4
+ export { renderHeader, renderUsage, renderValidationErrors };
@@ -0,0 +1,316 @@
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
+
23
+ /**
24
+ * Define a promise type that can be await from T
25
+ */
26
+ type Awaitable<T> = T | Promise<T>;
27
+ /**
28
+ * Command i18n built-in options keys
29
+ * @experimental
30
+ */
31
+ type CommandBuiltinOptionsKeys = keyof (typeof __constants)["COMMON_OPTIONS"];
32
+ /**
33
+ * Command i18n built-in resource keys
34
+ * @experimental
35
+ */
36
+ type CommandBuiltinResourceKeys = (typeof __constants)["COMMAND_I18N_RESOURCE_KEYS"][number];
37
+ /**
38
+ * Command i18n built-in keys
39
+ * @description The command i18n built-in keys are used to {@link CommandContext.translation | translate} function
40
+ * @experimental
41
+ */
42
+ type CommandBuiltinKeys = CommandBuiltinOptionsKeys | CommandBuiltinResourceKeys | "description" | "examples";
43
+ /**
44
+ * Command environment
45
+ */
46
+ interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
47
+ /**
48
+ * Current working directory
49
+ * @see {@link CommandOptions.cwd}
50
+ */
51
+ cwd: string | undefined;
52
+ /**
53
+ * Command name
54
+ * @see {@link CommandOptions.name}
55
+ */
56
+ name: string | undefined;
57
+ /**
58
+ * Command description
59
+ * @see {@link CommandOptions.description}
60
+ *
61
+ */
62
+ description: string | undefined;
63
+ /**
64
+ * Command version
65
+ * @see {@link CommandOptions.version}
66
+ */
67
+ version: string | undefined;
68
+ /**
69
+ * Left margin of the command output
70
+ * @default 2
71
+ * @see {@link CommandOptions.leftMargin}
72
+ */
73
+ leftMargin: number;
74
+ /**
75
+ * Middle margin of the command output
76
+ * @default 10
77
+ * @see {@link CommandOptions.middleMargin}
78
+ */
79
+ middleMargin: number;
80
+ /**
81
+ * Whether to display the usage option type
82
+ * @default false
83
+ * @see {@link CommandOptions.usageOptionType}
84
+ */
85
+ usageOptionType: boolean;
86
+ /**
87
+ * Sub commands
88
+ * @see {@link CommandOptions.subCommands}
89
+ */
90
+ subCommands: Map<string, Command<Options> | LazyCommand<Options>> | undefined;
91
+ /**
92
+ * Render function the command usage
93
+ */
94
+ renderUsage: ((ctx: CommandContext<Options>) => Promise<string>) | null | undefined;
95
+ /**
96
+ * Render function the header section in the command usage
97
+ */
98
+ renderHeader: ((ctx: CommandContext<Options>) => Promise<string>) | null | undefined;
99
+ /**
100
+ * Render function the validation errors
101
+ */
102
+ renderValidationErrors: ((ctx: CommandContext<Options>, error: AggregateError) => Promise<string>) | null | undefined;
103
+ }
104
+ /**
105
+ * Command options
106
+ */
107
+ interface CommandOptions<Options extends ArgOptions> {
108
+ /**
109
+ * Current working directory
110
+ */
111
+ cwd?: string;
112
+ /**
113
+ * Command program name
114
+ */
115
+ name?: string;
116
+ /**
117
+ * Command program description
118
+ *
119
+ */
120
+ description?: string;
121
+ /**
122
+ * Command program version
123
+ */
124
+ version?: string;
125
+ /**
126
+ * Command program locale
127
+ */
128
+ locale?: string | Intl.Locale;
129
+ /**
130
+ * Sub commands
131
+ */
132
+ subCommands?: Map<string, Command<Options> | LazyCommand<Options>>;
133
+ /**
134
+ * Left margin of the command output
135
+ */
136
+ leftMargin?: number;
137
+ /**
138
+ * Middle margin of the command output
139
+ */
140
+ middleMargin?: number;
141
+ /**
142
+ * Whether to display the usage option type
143
+ */
144
+ usageOptionType?: boolean;
145
+ /**
146
+ * Render function the command usage
147
+ */
148
+ renderUsage?: ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | null;
149
+ /**
150
+ * Render function the header section in the command usage
151
+ */
152
+ renderHeader?: ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | null;
153
+ /**
154
+ * Render function the validation errors
155
+ */
156
+ renderValidationErrors?: ((ctx: Readonly<CommandContext<Options>>, error: AggregateError) => Promise<string>) | null;
157
+ }
158
+ /**
159
+ * Command context
160
+ * @description Command context is the context of the command execution
161
+ */
162
+ interface CommandContext<
163
+ Options extends ArgOptions,
164
+ Values = ArgValues<Options>
165
+ > {
166
+ /**
167
+ * Command name, that is the command that is executed
168
+ * @description The command name is same {@link CommandEnvironment.name}
169
+ */
170
+ name: string | undefined;
171
+ /**
172
+ * Command description, that is the description of the command that is executed
173
+ * @description The command description is same {@link CommandEnvironment.description}
174
+ */
175
+ description: string | undefined;
176
+ /**
177
+ * Command locale, that is the locale of the command that is executed
178
+ */
179
+ locale: Intl.Locale;
180
+ /**
181
+ * Command environment, that is the environment of the command that is executed
182
+ * @description The command environment is same {@link CommandEnvironment}
183
+ */
184
+ env: CommandEnvironment<Options>;
185
+ /**
186
+ * Command options, that is the options of the command that is executed
187
+ * @description The command options is same {@link Command.options}
188
+ */
189
+ options: Options | undefined;
190
+ /**
191
+ * Command values, that is the values of the command that is executed
192
+ * @description Resolve values with `resolveArgs` from command arguments and {@link Command.options}
193
+ */
194
+ values: Values;
195
+ /**
196
+ * Command positionals arguments, that is the positionals of the command that is executed
197
+ * @description Resolve positionals with `resolveArgs` from command arguments
198
+ */
199
+ positionals: string[];
200
+ /**
201
+ * Whether the currently executing command has been executed with the sub-command name omitted
202
+ */
203
+ omitted: boolean;
204
+ /**
205
+ * Command usage
206
+ * @description Usage of the command is same {@link Command.usage}, and more has `--help` and `--version` options
207
+ */
208
+ usage: CommandUsage<Options>;
209
+ /**
210
+ * Load sub-commands
211
+ * @description The loaded commands are cached and returned when called again
212
+ * @returns loaded commands
213
+ */
214
+ loadCommands: () => Promise<Command<Options>[]>;
215
+ /**
216
+ * Translation function
217
+ * @param key the key to be translated
218
+ * @returns A translated string
219
+ * @experimental
220
+ */
221
+ translation: <
222
+ T = CommandBuiltinKeys,
223
+ Key = CommandBuiltinKeys | T
224
+ >(key: Key) => string;
225
+ }
226
+ /**
227
+ * Command usage
228
+ */
229
+ interface CommandUsage<Options extends ArgOptions> {
230
+ /**
231
+ * Options usage
232
+ */
233
+ options?: { [Option in keyof Options] : string };
234
+ /**
235
+ * Examples usage
236
+ */
237
+ examples?: string;
238
+ }
239
+ /**
240
+ * Command interface
241
+ */
242
+ interface Command<Options extends ArgOptions> {
243
+ /**
244
+ * Command name
245
+ * @description
246
+ * Command name is used to find command line arguments to execute from sub commands, so it's recommended to specify.
247
+ */
248
+ name?: string;
249
+ /**
250
+ * Command description
251
+ * @description
252
+ * Command description is used to describe the command in usage, so it's recommended to specify.
253
+ */
254
+ description?: string;
255
+ /**
256
+ * whether the command is default or not
257
+ * @description if the command is default, it is executed when no sub-command is specified
258
+ */
259
+ default?: boolean;
260
+ /**
261
+ * Command options
262
+ */
263
+ options?: Options;
264
+ /**
265
+ * Command usage
266
+ * @description
267
+ * Command usage is used to describe the command in usage, so it's recommended to specify.
268
+ */
269
+ usage?: CommandUsage<Options>;
270
+ /**
271
+ * Command runner, that's the command to be executed
272
+ */
273
+ run: CommandRunner<Options>;
274
+ /**
275
+ * Command resource fetcher
276
+ * @experimental
277
+ */
278
+ resource?: CommandResourceFetcher<Options>;
279
+ }
280
+ /**
281
+ * Command resource
282
+ * @experimental
283
+ */
284
+ interface CommandResource<Options extends ArgOptions> {
285
+ /**
286
+ * Command description
287
+ */
288
+ description: string;
289
+ /**
290
+ * Options usage
291
+ */
292
+ options: { [Option in keyof Options] : string };
293
+ /**
294
+ * Examples usage
295
+ */
296
+ examples: string;
297
+ }
298
+ /**
299
+ * Command resource fetcher
300
+ * @param ctx A {@link CommandContext | command context}
301
+ * @returns A fetched {@link CommandResource | command resource}
302
+ * @experimental
303
+ */
304
+ type CommandResourceFetcher<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
305
+ /**
306
+ * Command runner
307
+ * @param ctx A {@link CommandContext | command context}
308
+ */
309
+ type CommandRunner<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
310
+ /**
311
+ * Lazy command interface
312
+ * @description lazy command that's not loaded until it is executed
313
+ */
314
+ type LazyCommand<Options extends ArgOptions> = () => Awaitable<Command<Options>>;
315
+
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 };
@@ -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.0",
4
+ "version": "0.2.2",
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
- "rolldown": "1.0.0-beta.3",
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": "rolldown -c rolldown.config.ts",
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>;
@@ -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"];