gunshi 0.0.0 → 0.2.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 CHANGED
@@ -1,13 +1,67 @@
1
1
  <p align="center">
2
- <img width="110" src="./assets/logo.webp">
2
+ <img width="196" src="./assets/logo.webp">
3
3
  </p>
4
- <h1 align="center">🏯 gunshi</h1>
4
+ <h1 align="center">🏯 Gunshi</h1>
5
5
 
6
- Modern command-line interfaces composer
6
+ [![Version][npm-version-src]][npm-version-href]
7
+ [![CI][ci-src]][ci-href]
7
8
 
8
- > [!WARNING]
9
- > WIP
9
+ <!--
10
+ [![JSR][jsr-src]][jsr-href]
11
+ [![InstallSize][install-size-src]][install-size-src]
12
+ -->
13
+
14
+ Gunshi is a modern javascript command-line library
15
+
16
+ > [!TIP]
17
+ > gunshi (軍師) is a position in ancient Japanese samurai battle in which a amurai devised strategies and gave orders. That name is inspired by the word "command.”
18
+
19
+ ## ✨ Features
20
+
21
+ Gunshi is designed to simplify the creation of modern command-line interfaces:
22
+
23
+ - 📏 **Minimal**: Run the commands with a minimum API.
24
+ - 🛡️ **Type Safe**: Arguments parsing and options value resolution type-safely by [args-tokens](https://github.com/kazupon/args-tokens)
25
+ - ⚙️ **Declarative configuration**: Configure the command modules declaratively.
26
+ - 🧩 **Composable**: Sub-commands that can be composed with modularized commands.
27
+ - ⏳ **Lazy & Async**: Command modules lazy loading and asynchronously executing.
28
+ - 📜 **Auto usage generation**: Automatic usage message generation with modularized commands.
29
+ - 🎨 **Custom usage generation**: Usage message generation customizable.
30
+ - 🌍 **Internationalization**: I18n out of the box and locale resource lazy loading.
31
+
32
+ ## 💿 Installation
33
+
34
+ ```sh
35
+ # npm
36
+ npm install --save gunshi
37
+
38
+ ## pnpm
39
+ pnpm add gunshi
40
+
41
+ ## yarn
42
+ yarn add gunshi
43
+
44
+ ```
45
+
46
+ ## 🙌 Contributing guidelines
47
+
48
+ If you are interested in contributing to `gunshi`, I highly recommend checking out [the contributing guidelines](/CONTRIBUTING.md) here. You'll find all the relevant information such as [how to make a PR](/CONTRIBUTING.md#pull-request-guidelines), [how to setup development](/CONTRIBUTING.md#development-setup)) etc., there.
10
49
 
11
50
  ## ©️ License
12
51
 
13
52
  [MIT](http://opensource.org/licenses/MIT)
53
+
54
+ <!-- Badges -->
55
+
56
+ [npm-version-src]: https://img.shields.io/npm/v/gunshi?style=flat
57
+ [npm-version-href]: https://npmjs.com/package/gunshi
58
+ [jsr-src]: https://jsr.io/badges/@kazupon/gunishi
59
+ [jsr-href]: https://jsr.io/@kazupon/gunshi
60
+
61
+ <!--
62
+ [install-size-src]: https://pkg-size.dev/badge/install/35082
63
+ [install-size-href]: https://pkg-size.dev/gunishi
64
+ -->
65
+
66
+ [ci-src]: https://github.com/kazupon/gunshi/actions/workflows/ci.yml/badge.svg
67
+ [ci-href]: https://github.com/kazupon/gunshi/actions/workflows/ci.yml
package/lib/cli.d.ts ADDED
@@ -0,0 +1,9 @@
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>;
@@ -0,0 +1,14 @@
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"];
@@ -0,0 +1,14 @@
1
+ import type { ArgOptions, ArgValues } from "args-tokens";
2
+ import type { Command, CommandContext, CommandOptions } from "./types.js";
3
+ export declare const DEFAULT_LOCALE = "en-US";
4
+ export declare function createCommandContext<
5
+ Options extends ArgOptions,
6
+ Values = ArgValues<Options>
7
+ >({ options, values, positionals, command, commandOptions, omitted }: {
8
+ options: Options | undefined
9
+ values: Values
10
+ positionals: string[]
11
+ omitted: boolean
12
+ command: Command<Options>
13
+ commandOptions: CommandOptions<Options>
14
+ }): Promise<Readonly<CommandContext<Options, Values>>>;
package/lib/index.d.ts CHANGED
@@ -0,0 +1,2 @@
1
+ export * from "./cli.js";
2
+ export type * from "./types.js";
package/lib/index.js CHANGED
@@ -1,2 +1,723 @@
1
- "use strict";
2
- console.log('Hello, world!');
1
+
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
+ //#region src/cli.ts
642
+ async function cli(args, entry, opts = {}) {
643
+ const tokens = parseArgs(args);
644
+ const subCommand = getSubCommand(tokens);
645
+ const resolvedCommandOptions = resolveCommandOptions(opts);
646
+ const [name, command] = await resolveCommand(subCommand, entry, resolvedCommandOptions);
647
+ if (!command) throw new Error(`Command not found: ${name || ""}`);
648
+ if (command.name && !resolvedCommandOptions.subCommands.has(command.name)) resolvedCommandOptions.subCommands.set(command.name, command);
649
+ const options = resolveArgOptions(command.options);
650
+ const { values, positionals, error } = resolveArgs(options, tokens);
651
+ const omitted = !subCommand;
652
+ const ctx = await createCommandContext({
653
+ options,
654
+ values,
655
+ positionals,
656
+ omitted,
657
+ command,
658
+ commandOptions: opts
659
+ });
660
+ if (values.version) {
661
+ showVersion(ctx);
662
+ return;
663
+ }
664
+ await showHeader(ctx);
665
+ if (values.help) {
666
+ await showUsage(ctx);
667
+ return;
668
+ }
669
+ if (error) {
670
+ await showValidationErrors(ctx, error);
671
+ throw error;
672
+ }
673
+ await command.run(ctx);
674
+ }
675
+ function resolveArgOptions(options) {
676
+ return Object.assign(create(), options, COMMON_OPTIONS);
677
+ }
678
+ function resolveCommandOptions(options) {
679
+ const subCommands = new Map(options.subCommands);
680
+ return Object.assign(create(), COMMAND_OPTIONS_DEFAULT, { subCommands }, options);
681
+ }
682
+ function getSubCommand(tokens) {
683
+ const firstToken = tokens[0];
684
+ return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
685
+ }
686
+ async function showUsage(ctx) {
687
+ if (ctx.env.renderUsage === null) return;
688
+ const render = ctx.env.renderUsage || renderUsage;
689
+ log(await render(ctx));
690
+ }
691
+ function showVersion(ctx) {
692
+ log(ctx.env.version);
693
+ }
694
+ async function showHeader(ctx) {
695
+ if (ctx.env.renderHeader === null) return;
696
+ const header = await (ctx.env.renderHeader || renderHeader)(ctx);
697
+ if (header) {
698
+ log(header);
699
+ log();
700
+ }
701
+ }
702
+ async function showValidationErrors(ctx, error) {
703
+ if (ctx.env.renderValidationErrors === null) return;
704
+ const render = ctx.env.renderValidationErrors || renderValidationErrors;
705
+ log(await render(ctx, error));
706
+ }
707
+ async function resolveCommand(sub, entry, options) {
708
+ const omitted = !sub;
709
+ if (typeof entry === "function") return [undefined, {
710
+ run: entry,
711
+ default: true
712
+ }];
713
+ else if (omitted) return typeof entry === "object" ? [entry.name, await resolveLazyCommand(entry, undefined, true)] : [undefined, undefined];
714
+ else {
715
+ if (options.subCommands == null) return [sub, undefined];
716
+ const cmd = options.subCommands?.get(sub);
717
+ if (cmd == null) return [sub, undefined];
718
+ return [sub, await resolveLazyCommand(cmd, sub)];
719
+ }
720
+ }
721
+
722
+ //#endregion
723
+ export { cli };
@@ -0,0 +1,5 @@
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>;
package/lib/types.d.ts ADDED
@@ -0,0 +1,302 @@
1
+ import type { ArgOptions, ArgValues } from "args-tokens";
2
+ /**
3
+ * Define a promise type that can be await from T
4
+ */
5
+ type Awaitable<T> = T | Promise<T>;
6
+ /**
7
+ * The command i18n built-in options keys
8
+ * @experimental
9
+ */
10
+ export type CommandBuiltinOptionsKeys = keyof (typeof import("./constants"))["COMMON_OPTIONS"];
11
+ /**
12
+ * The command i18n built-in resource keys
13
+ * @experimental
14
+ */
15
+ export type CommandBuiltinResourceKeys = (typeof import("./constants"))["COMMAND_I18N_RESOURCE_KEYS"][number];
16
+ /**
17
+ * The command i18n built-in keys
18
+ * @description The command i18n built-in keys are used to {@link CommandContext.translation | translate} function
19
+ * @experimental
20
+ */
21
+ export type CommandBuiltinKeys = CommandBuiltinOptionsKeys | CommandBuiltinResourceKeys | "description" | "examples";
22
+ /**
23
+ * The command environment
24
+ */
25
+ export interface CommandEnvironment<Options extends ArgOptions = ArgOptions> {
26
+ /**
27
+ * The current working directory
28
+ * @see {@link CommandOptions.cwd}
29
+ */
30
+ cwd: string | undefined;
31
+ /**
32
+ * The command name
33
+ * @see {@link CommandOptions.name}
34
+ */
35
+ name: string | undefined;
36
+ /**
37
+ * The command description
38
+ * @see {@link CommandOptions.description}
39
+ *
40
+ */
41
+ description: string | undefined;
42
+ /**
43
+ * The command version
44
+ * @see {@link CommandOptions.version}
45
+ */
46
+ version: string | undefined;
47
+ /**
48
+ * The left margin of the command output
49
+ * @default 2
50
+ * @see {@link CommandOptions.leftMargin}
51
+ */
52
+ leftMargin: number;
53
+ /**
54
+ * The middle margin of the command output
55
+ * @default 10
56
+ * @see {@link CommandOptions.middleMargin}
57
+ */
58
+ middleMargin: number;
59
+ /**
60
+ * Whether to display the usage option type
61
+ * @default false
62
+ * @see {@link CommandOptions.usageOptionType}
63
+ */
64
+ usageOptionType: boolean;
65
+ /**
66
+ * The sub commands
67
+ * @see {@link CommandOptions.subCommands}
68
+ */
69
+ subCommands: Map<string, Command<Options> | LazyCommand<Options>> | undefined;
70
+ /**
71
+ * Render function the command usage
72
+ */
73
+ renderUsage: ((ctx: CommandContext<Options>) => Promise<string>) | null | undefined;
74
+ /**
75
+ * Render function the header section in the command usage
76
+ */
77
+ renderHeader: ((ctx: CommandContext<Options>) => Promise<string>) | null | undefined;
78
+ /**
79
+ * Render function the validation errors
80
+ */
81
+ renderValidationErrors: ((ctx: CommandContext<Options>, error: AggregateError) => Promise<string>) | null | undefined;
82
+ }
83
+ /**
84
+ * The command options
85
+ */
86
+ export interface CommandOptions<Options extends ArgOptions> {
87
+ /**
88
+ * The current working directory
89
+ * @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.
90
+ */
91
+ cwd?: string;
92
+ /**
93
+ * The command name
94
+ * @description Please specify the name of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
95
+ */
96
+ name?: string;
97
+ /**
98
+ * The command description
99
+ * @description Please specify the description (summary) of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
100
+ *
101
+ */
102
+ description?: string;
103
+ /**
104
+ * The command version
105
+ * @description Please specify the version of the command that was executed. If you would specify it, gunshi will be displayed in the usage.
106
+ */
107
+ version?: string;
108
+ /**
109
+ * The locale of the command
110
+ * @description The locale of the command that was executed. If you would specify it, gunshi command usage will be localized.
111
+ */
112
+ locale?: string | Intl.Locale;
113
+ /**
114
+ * The sub commands
115
+ */
116
+ subCommands?: Map<string, Command<Options> | LazyCommand<Options>>;
117
+ /**
118
+ * The left margin of the command output
119
+ */
120
+ leftMargin?: number;
121
+ /**
122
+ * The middle margin of the command output
123
+ */
124
+ middleMargin?: number;
125
+ /**
126
+ * Whether to display the usage option type
127
+ */
128
+ usageOptionType?: boolean;
129
+ /**
130
+ * Render function the command usage
131
+ */
132
+ renderUsage?: ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | null;
133
+ /**
134
+ * Render function the header section in the command usage
135
+ */
136
+ renderHeader?: ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | null;
137
+ /**
138
+ * Render function the validation errors
139
+ */
140
+ renderValidationErrors?: ((ctx: Readonly<CommandContext<Options>>, error: AggregateError) => Promise<string>) | null;
141
+ }
142
+ /**
143
+ * The command context
144
+ * @description The command context is the context of the command execution
145
+ */
146
+ export interface CommandContext<
147
+ Options extends ArgOptions,
148
+ Values = ArgValues<Options>
149
+ > {
150
+ /**
151
+ * The command name, that is the command that is executed
152
+ * @description The command name is same {@link CommandEnvironment.name}
153
+ */
154
+ name: string | undefined;
155
+ /**
156
+ * The command description, that is the description of the command that is executed
157
+ * @description The command description is same {@link CommandEnvironment.description}
158
+ */
159
+ description: string | undefined;
160
+ /**
161
+ * The command locale, that is the locale of the command that is executed
162
+ */
163
+ locale: Intl.Locale;
164
+ /**
165
+ * The command environment, that is the environment of the command that is executed
166
+ * @description The command environment is same {@link CommandEnvironment}
167
+ */
168
+ env: CommandEnvironment<Options>;
169
+ /**
170
+ * The command options, that is the options of the command that is executed
171
+ * @description The command options is same {@link Command.options}
172
+ */
173
+ options: Options | undefined;
174
+ /**
175
+ * The command values, that is the values of the command that is executed
176
+ * @description Resolve values with `resolveArgs` from command arguments and {@link Command.options}
177
+ */
178
+ values: Values;
179
+ /**
180
+ * The command positionals, that is the positionals of the command that is executed
181
+ * @description Resolve positionals with `resolveArgs` from command arguments
182
+ */
183
+ positionals: string[];
184
+ /**
185
+ * Whether the currently executing command has been executed with the sub-command name omitted
186
+ */
187
+ omitted: boolean;
188
+ /**
189
+ * The usage of the command
190
+ * @description The usage of the command is same {@link Command.usage}, and more has `--help` and `--version` options
191
+ */
192
+ usage: CommandUsage<Options>;
193
+ /**
194
+ * Load the sub-commands
195
+ * @description The loaded commands are cached and returned when called again
196
+ * @returns loaded commands
197
+ */
198
+ loadCommands: () => Promise<Command<Options>[]>;
199
+ /**
200
+ * The translation function
201
+ * @param key {CommandBuiltinKeys | T} - The key to be translated
202
+ * @returns The translated string, if the key is not found, the key itself is returned
203
+ * @experimental
204
+ */
205
+ translation: <
206
+ T = CommandBuiltinKeys,
207
+ Key = CommandBuiltinKeys | T
208
+ >(key: Key) => string;
209
+ }
210
+ /**
211
+ * The command usage render
212
+ * @description if the render function is async, it should return a promise
213
+ */
214
+ export type CommandUsageRender<Options extends ArgOptions> = ((ctx: Readonly<CommandContext<Options>>) => Promise<string>) | string;
215
+ /**
216
+ * The command usage
217
+ */
218
+ interface CommandUsage<Options extends ArgOptions> {
219
+ /**
220
+ * The options usage
221
+ */
222
+ options?: { [Option in keyof Options] : string };
223
+ /**
224
+ * The examples usage
225
+ */
226
+ examples?: string;
227
+ }
228
+ /**
229
+ * The command interface
230
+ */
231
+ export interface Command<Options extends ArgOptions> {
232
+ /**
233
+ * The command name
234
+ * @description
235
+ * The command name is used to find command line arguments to execute from sub commands, so it's recommended to specify.
236
+ */
237
+ name?: string;
238
+ /**
239
+ * The command description
240
+ * @description
241
+ * The command description is used to describe the command in usage, so it's recommended to specify.
242
+ */
243
+ description?: string;
244
+ /**
245
+ * whether the command is default or not
246
+ * @description if the command is default, it is executed when no sub-command is specified
247
+ */
248
+ default?: boolean;
249
+ /**
250
+ * The command options
251
+ */
252
+ options?: Options;
253
+ /**
254
+ * The command usage
255
+ * @description
256
+ * The command usage is used to describe the command in usage, so it's recommended to specify.
257
+ */
258
+ usage?: CommandUsage<Options>;
259
+ /**
260
+ * The command runner, that's the command to be executed
261
+ */
262
+ run: CommandRunner<Options>;
263
+ /**
264
+ * The command resource fetcher
265
+ * @experimental
266
+ */
267
+ resource?: CommandResourceFetcher<Options>;
268
+ }
269
+ /**
270
+ * The command resource
271
+ * @experimental
272
+ */
273
+ export interface CommandResource<Options extends ArgOptions> {
274
+ /**
275
+ * The command description resource
276
+ */
277
+ description: string;
278
+ /**
279
+ * The options usage resources
280
+ */
281
+ options: { [Option in keyof Options] : string };
282
+ /**
283
+ * The examples usage resources
284
+ */
285
+ examples: string;
286
+ }
287
+ /**
288
+ * The command resource fetcher
289
+ * @experimental
290
+ */
291
+ export type CommandResourceFetcher<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Promise<CommandResource<Options>>;
292
+ /**
293
+ * The command runner interface
294
+ * @param ctx - The {@link CommandContext | command context}
295
+ */
296
+ export type CommandRunner<Options extends ArgOptions> = (ctx: Readonly<CommandContext<Options>>) => Awaitable<void>;
297
+ /**
298
+ * The lazy command interface
299
+ * @description The lazy command that's not loaded until it is executed
300
+ */
301
+ export type LazyCommand<Options extends ArgOptions> = () => Awaitable<Command<Options>>;
302
+ export {};
package/lib/utils.d.ts ADDED
@@ -0,0 +1,6 @@
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>;
@@ -0,0 +1,11 @@
1
+ {
2
+ "COMMAND": "COMMAND",
3
+ "COMMANDS": "COMMANDS",
4
+ "SUBCOMMAND": "SUBCOMMAND",
5
+ "USAGE": "USAGE",
6
+ "OPTIONS": "OPTIONS",
7
+ "EXAMPLES": "EXAMPLES",
8
+ "FORMORE": "For more info, run any command with the `--help` flag:",
9
+ "help": "Display this help message",
10
+ "version": "Display this version"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "COMMAND": "コマンド",
3
+ "COMMANDS": "コマンド",
4
+ "SUBCOMMAND": "サブコマンド",
5
+ "USAGE": "使い方",
6
+ "OPTIONS": "オプション",
7
+ "EXAMPLES": "例",
8
+ "FORMORE": "詳細は、コマンドと`--help`フラグを実行してください:",
9
+ "help": "このヘルプメッセージを表示",
10
+ "version": "このバージョンを表示"
11
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gunshi",
3
- "description": "Modern command-line interfaces composer",
4
- "version": "0.0.0",
3
+ "description": "Modern javascript command-line library",
4
+ "version": "0.2.0",
5
5
  "author": {
6
6
  "name": "kazuya kawaguchi",
7
7
  "email": "kawakazu80@gmail.com"
@@ -31,7 +31,8 @@
31
31
  },
32
32
  "type": "module",
33
33
  "files": [
34
- "lib"
34
+ "lib",
35
+ "locales"
35
36
  ],
36
37
  "module": "lib/index.js",
37
38
  "exports": {
@@ -41,6 +42,18 @@
41
42
  "require": "./lib/index.js",
42
43
  "default": "./lib/index.js"
43
44
  },
45
+ "./context": {
46
+ "types": "./lib/context.d.ts",
47
+ "import": "./lib/context.js",
48
+ "require": "./lib/context.js",
49
+ "default": "./lib/context.js"
50
+ },
51
+ "./renderer": {
52
+ "types": "./lib/renderer.d.ts",
53
+ "import": "./lib/renderer.js",
54
+ "require": "./lib/renderer.js",
55
+ "default": "./lib/renderer.js"
56
+ },
44
57
  "./package.json": "./package.json",
45
58
  "./*": "./*"
46
59
  },
@@ -53,28 +66,33 @@
53
66
  ]
54
67
  }
55
68
  },
69
+ "dependencies": {
70
+ "args-tokens": "^0.10.2"
71
+ },
56
72
  "devDependencies": {
57
73
  "@eslint/markdown": "^6.2.2",
58
74
  "@kazupon/eslint-config": "^0.22.0",
59
75
  "@kazupon/prettier-config": "^0.1.1",
60
- "@types/node": "^22.13.5",
61
- "@vitest/eslint-plugin": "^1.1.31",
76
+ "@types/node": "^22.13.9",
77
+ "@vitest/eslint-plugin": "^1.1.36",
62
78
  "bumpp": "^10.0.3",
63
79
  "eslint": "^9.21.0",
64
- "eslint-config-prettier": "^10.0.1",
80
+ "eslint-config-prettier": "^10.0.2",
65
81
  "eslint-plugin-jsonc": "^2.19.1",
66
82
  "eslint-plugin-promise": "^7.2.1",
67
83
  "eslint-plugin-regexp": "^2.7.0",
68
84
  "eslint-plugin-unicorn": "^57.0.0",
69
85
  "eslint-plugin-yml": "^1.17.0",
70
86
  "gh-changelogen": "^0.2.8",
71
- "knip": "^5.44.4",
87
+ "knip": "^5.45.0",
72
88
  "lint-staged": "^15.4.3",
73
- "pkg-pr-new": "^0.0.39",
74
- "prettier": "^3.5.2",
75
- "typescript": "^5.7.3",
76
- "typescript-eslint": "^8.24.1",
77
- "vitest": "^3.0.6"
89
+ "pkg-pr-new": "^0.0.40",
90
+ "prettier": "^3.5.3",
91
+ "rolldown": "1.0.0-beta.3",
92
+ "typescript": "^5.8.2",
93
+ "typescript-eslint": "^8.26.0",
94
+ "unplugin-isolated-decl": "^0.13.1",
95
+ "vitest": "^3.0.7"
78
96
  },
79
97
  "prettier": "@kazupon/prettier-config",
80
98
  "lint-staged": {
@@ -91,8 +109,9 @@
91
109
  ]
92
110
  },
93
111
  "scripts": {
94
- "build": "tsc -p ./tsconfig.build.json",
112
+ "build": "rolldown -c rolldown.config.ts",
95
113
  "changelog": "gh-changelogen --repo=kazupon/gunshi",
114
+ "clean": "git clean -df",
96
115
  "dev": "pnpx @eslint/config-inspector --config eslint.config.ts",
97
116
  "dev:eslint": "pnpx @eslint/config-inspector --config eslint.config.ts",
98
117
  "fix": "pnpm run --parallel --color \"/^fix:/\"",