@reliverse/rempts 1.7.1 β 1.7.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.
- package/README.md +137 -43
- package/bin/components/launcher/deprecated/launcher-mod.ts.txt +1 -1
- package/bin/components/launcher/deprecated/usage.ts.txt +1 -1
- package/bin/components/launcher/launcher-mod.d.ts +39 -15
- package/bin/components/launcher/launcher-mod.js +101 -17
- package/bin/components/msg-fmt/messages.js +6 -3
- package/bin/components/select/multiselect-prompt.js +1 -1
- package/bin/components/select/select-prompt.js +1 -1
- package/bin/components/select/toggle-prompt.js +1 -1
- package/bin/components/st-end/start.js +2 -2
- package/bin/components/visual/ascii-art/ascii-art.js +1 -1
- package/bin/mod.d.ts +1 -1
- package/bin/types.d.ts +1 -1
- package/bin/utils/prompt-end.js +8 -8
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> @reliverse/rempts is a modern, type-safe toolkit for building delightful cli experiences. it's fast, flexible, and made for developer happiness. file-based commands keep things simpleβno clutter, just clean and easy workflows. this is how cli should feel.
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
[sponsor](https://github.com/sponsors/blefnk) β [discord](https://discord.gg/Pb8uKbwpsJ) β [repo](https://github.com/reliverse/rempts) β [npm](https://npmjs.com/@reliverse/rempts)
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
bun add @reliverse/rempts
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
**Coming soon**:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bun i -g @reliverse/dler
|
|
34
|
+
dler rempts init --cmd my-cmd-1
|
|
35
|
+
dler rempts init --cmds
|
|
36
|
+
```
|
|
37
|
+
|
|
30
38
|
## Usage Examples
|
|
31
39
|
|
|
32
40
|
- [Prompts](#prompts)
|
|
@@ -130,15 +138,13 @@ await main();
|
|
|
130
138
|
### Terminology
|
|
131
139
|
|
|
132
140
|
- **Launcher/Router**: The main entry point for your CLI. Visit [CLI Launcher (Router)](#cli-launcher-router) section to learn more.
|
|
133
|
-
- **Command
|
|
141
|
+
- **Command**: A command is a function that defines the inner script launched by the main script where runMain() is used or by some other command.
|
|
134
142
|
- **Argument**: An argument is a value that is passed to a command.
|
|
135
143
|
- **Flag**: A flag is a boolean argument that is used to enable or disable a feature.
|
|
136
144
|
- **Option**: An option is a named argument that is used to configure a command.
|
|
137
145
|
|
|
138
146
|
#### Launcher Usage Example
|
|
139
147
|
|
|
140
|
-
βΌοΈ Go to [Usage Examples](#usage-examples) section for a more detailed example.
|
|
141
|
-
|
|
142
148
|
```ts
|
|
143
149
|
import { relinka } from "@reliverse/relinka";
|
|
144
150
|
|
|
@@ -156,7 +162,7 @@ const main = defineCommand({
|
|
|
156
162
|
onCmdEnd() {
|
|
157
163
|
relinka("success", "Cleanup");
|
|
158
164
|
},
|
|
159
|
-
|
|
165
|
+
commands: {
|
|
160
166
|
build: () => import("./app/build/cmd.js").then((r) => r.default),
|
|
161
167
|
deploy: () => import("./app/deploy/cmd.js").then((r) => r.default),
|
|
162
168
|
debug: () => import("./app/debug/cmd.js").then((r) => r.default),
|
|
@@ -177,9 +183,9 @@ await runMain(myCommand, {
|
|
|
177
183
|
});
|
|
178
184
|
```
|
|
179
185
|
|
|
180
|
-
This flexibility allows you to easily build a rich, multi-command CLI with minimal boilerplate. The launcher even supports nested
|
|
186
|
+
This flexibility allows you to easily build a rich, multi-command CLI with minimal boilerplate. The launcher even supports nested commands, making it simple to construct complex CLI applications.
|
|
181
187
|
|
|
182
|
-
#### File-Based
|
|
188
|
+
#### File-Based Commands
|
|
183
189
|
|
|
184
190
|
Drop a `./src/cli/app/add/index.ts` and it's live.
|
|
185
191
|
|
|
@@ -199,7 +205,7 @@ export default defineCommand({
|
|
|
199
205
|
}),
|
|
200
206
|
},
|
|
201
207
|
async run({ args }) {
|
|
202
|
-
relinka("
|
|
208
|
+
relinka("log", "Adding:", args.name);
|
|
203
209
|
},
|
|
204
210
|
});
|
|
205
211
|
```
|
|
@@ -227,7 +233,7 @@ defineCommand({
|
|
|
227
233
|
animals: { type: "array", options: ["cat","dog"] },
|
|
228
234
|
},
|
|
229
235
|
async run({ args, raw }) { // or `async run(ctx)`
|
|
230
|
-
relinka("
|
|
236
|
+
relinka("log", args.name, args.verbose, args.animals); // or `relinka("log", ctx.args.name, ...);`
|
|
231
237
|
},
|
|
232
238
|
});
|
|
233
239
|
```
|
|
@@ -362,7 +368,7 @@ const main = defineCommand({
|
|
|
362
368
|
],
|
|
363
369
|
});
|
|
364
370
|
|
|
365
|
-
relinka("
|
|
371
|
+
relinka("log", "You have selected:", { name, framework });
|
|
366
372
|
},
|
|
367
373
|
});
|
|
368
374
|
|
|
@@ -388,7 +394,7 @@ import {
|
|
|
388
394
|
* This command demonstrates the full range of launcher features along with all supported argument types:
|
|
389
395
|
*
|
|
390
396
|
* - Global Usage Handling: Automatically processes `--help` and `--version`.
|
|
391
|
-
* - File-Based
|
|
397
|
+
* - File-Based Commands: Scans "app" for commands (e.g., `init`).
|
|
392
398
|
* - Comprehensive Argument Parsing: Supports positional, boolean, string, number, and array arguments.
|
|
393
399
|
* - Interactive Prompts: Uses built-in prompt functions for an engaging CLI experience.
|
|
394
400
|
*/
|
|
@@ -397,7 +403,7 @@ const mainCommand = defineCommand({
|
|
|
397
403
|
name: "rempts",
|
|
398
404
|
version: "1.6.0",
|
|
399
405
|
description:
|
|
400
|
-
"An example CLI that supports file-based
|
|
406
|
+
"An example CLI that supports file-based commands and all argument types.",
|
|
401
407
|
},
|
|
402
408
|
args: {
|
|
403
409
|
// Positional arguments
|
|
@@ -440,10 +446,10 @@ const mainCommand = defineCommand({
|
|
|
440
446
|
},
|
|
441
447
|
async run({ args, raw }) {
|
|
442
448
|
// Display invocation details and parsed arguments.
|
|
443
|
-
relinka("
|
|
444
|
-
relinka("
|
|
445
|
-
relinka("
|
|
446
|
-
relinka("
|
|
449
|
+
relinka("log", "Main command was invoked!");
|
|
450
|
+
relinka("log", "Parsed main-command args:", args);
|
|
451
|
+
relinka("log", "Raw argv:", raw);
|
|
452
|
+
relinka("log", "\nHelp: `rempts --help`, `rempts cmdName --help`");
|
|
447
453
|
|
|
448
454
|
// Begin interactive session with a prompt.
|
|
449
455
|
await startPrompt({
|
|
@@ -467,7 +473,7 @@ const mainCommand = defineCommand({
|
|
|
467
473
|
});
|
|
468
474
|
|
|
469
475
|
// Log all gathered input details.
|
|
470
|
-
relinka("
|
|
476
|
+
relinka("log", "You have selected:", {
|
|
471
477
|
projectName,
|
|
472
478
|
framework,
|
|
473
479
|
inputFile: args.inputFile,
|
|
@@ -483,7 +489,7 @@ const mainCommand = defineCommand({
|
|
|
483
489
|
/**
|
|
484
490
|
* The `runMain()` function sets up the launcher with several advanced features:
|
|
485
491
|
*
|
|
486
|
-
* - File-Based
|
|
492
|
+
* - File-Based Commands: Enables scanning for commands within the "app" directory.
|
|
487
493
|
* - Alias Mapping: Shorthand flags (e.g., `-v`) are mapped to their full names (e.g., `--verbose`).
|
|
488
494
|
* - Strict Mode & Unknown Flag Warnings: Unknown flags are either warned about or handled via a callback.
|
|
489
495
|
* - Negated Boolean Support: Allows flags to be negated (e.g., `--no-verbose`).
|
|
@@ -491,8 +497,8 @@ const mainCommand = defineCommand({
|
|
|
491
497
|
*/
|
|
492
498
|
await runMain(mainCommand, {
|
|
493
499
|
fileBasedCmds: {
|
|
494
|
-
enable: true, // Enables file-based
|
|
495
|
-
cmdsRootPath: "app", // Directory to scan for
|
|
500
|
+
enable: true, // Enables file-based command detection.
|
|
501
|
+
cmdsRootPath: "app", // Directory to scan for commands.
|
|
496
502
|
},
|
|
497
503
|
alias: {
|
|
498
504
|
v: "verbose", // Maps shorthand flag -v to --verbose.
|
|
@@ -509,10 +515,10 @@ await runMain(mainCommand, {
|
|
|
509
515
|
|
|
510
516
|
### CLI Launcher (Router)
|
|
511
517
|
|
|
512
|
-
Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'s so called "launcher" is a uniquely powerful and ergonomic CLI toolkitβone that helps you build delightful developer experiences with less code and more confidence. The launcher supports both programmatically defined
|
|
518
|
+
Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'s so called "launcher" is a uniquely powerful and ergonomic CLI toolkitβone that helps you build delightful developer experiences with less code and more confidence. The launcher supports both programmatically defined commands and file-based routing, so you can structure your CLI however you like. It automatically detects and loads commands from your filesystem and provides robust usage and error handling out-of-the-box. The launcher is more than just a command runnerβit's a robust, developer-friendly engine with several advanced features and thoughtful design choices:
|
|
513
519
|
|
|
514
|
-
- **File-Based & Defined
|
|
515
|
-
Use `
|
|
520
|
+
- **File-Based & Defined Commands:**
|
|
521
|
+
Use `commands` in your command definition or let the launcher automatically load commands from a specified directory.
|
|
516
522
|
|
|
517
523
|
- **Automatic Command Detection:**
|
|
518
524
|
The launcher scans your specified `cmdsRootPath` for command files matching common patterns such as:
|
|
@@ -539,20 +545,20 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
539
545
|
- **Lifecycle Hooks:**
|
|
540
546
|
You can define optional lifecycle hooks in your main command:
|
|
541
547
|
- `onLauncherStart` and `onLauncherEnd` (global, called once per CLI process)
|
|
542
|
-
- `onCmdStart` and `onCmdEnd` (per-
|
|
548
|
+
- `onCmdStart` and `onCmdEnd` (per-command, called before/after each command, but NOT for the main `run()` handler)
|
|
543
549
|
|
|
544
550
|
**Global Hooks:**
|
|
545
|
-
- `onLauncherStart`: Called once, before any command/
|
|
546
|
-
- `onLauncherEnd`: Called once, after all command/
|
|
551
|
+
- `onLauncherStart`: Called once, before any command/run() is executed.
|
|
552
|
+
- `onLauncherEnd`: Called once, after all command/run() logic is finished (even if an error occurs).
|
|
547
553
|
|
|
548
|
-
**Per-
|
|
549
|
-
- `onCmdStart`: Called before each
|
|
550
|
-
- `onCmdEnd`: Called after each
|
|
554
|
+
**Per-Command Hooks:**
|
|
555
|
+
- `onCmdStart`: Called before each command (not for main `run()`).
|
|
556
|
+
- `onCmdEnd`: Called after each command (not for main `run()`).
|
|
551
557
|
|
|
552
558
|
This means:
|
|
553
|
-
- If your CLI has multiple
|
|
554
|
-
- If your main command has a `run()` handler (and no
|
|
555
|
-
- This allows you to perform setup/teardown logic specific to each
|
|
559
|
+
- If your CLI has multiple commands, `onCmdStart` and `onCmdEnd` will be called for each command invocation, not just once for the whole CLI process.
|
|
560
|
+
- If your main command has a `run()` handler (and no command is invoked), these hooks are **not** called; use the `run()` handler itself or the global hooks for such logic.
|
|
561
|
+
- This allows you to perform setup/teardown logic specific to each command execution.
|
|
556
562
|
- If you want logic to run only once for the entire CLI process, use `onLauncherStart` and `onLauncherEnd`.
|
|
557
563
|
|
|
558
564
|
**Example:**
|
|
@@ -561,27 +567,29 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
561
567
|
const main = defineCommand({
|
|
562
568
|
onLauncherStart() { relinka('info', 'Global setup (once per process)'); },
|
|
563
569
|
onLauncherEnd() { relinka('info', 'Global cleanup (once per process)'); },
|
|
564
|
-
onCmdStart() { relinka('info', 'Setup for each
|
|
565
|
-
onCmdEnd() { relinka('info', 'Cleanup for each
|
|
566
|
-
|
|
567
|
-
run() { relinka('info', 'Main run handler (no
|
|
570
|
+
onCmdStart() { relinka('info', 'Setup for each command'); },
|
|
571
|
+
onCmdEnd() { relinka('info', 'Cleanup for each command'); },
|
|
572
|
+
commands: { ... },
|
|
573
|
+
run() { relinka('info', 'Main run handler (no command)'); },
|
|
568
574
|
});
|
|
569
575
|
// onLauncherStart/onLauncherEnd are called once per process
|
|
570
|
-
// onCmdStart/onCmdEnd are called for every
|
|
576
|
+
// onCmdStart/onCmdEnd are called for every command (not for main run())
|
|
571
577
|
// If you want per-run() logic, use the run() handler or global hooks
|
|
572
578
|
```
|
|
573
579
|
|
|
574
|
-
|
|
580
|
+
- **Deprecation Notice**
|
|
581
|
+
- The legacy `setup` and `cleanup` names are still supported as aliases for per-command hooks, but will be removed in a future major version. Prefer `onCmdStart` and `onCmdEnd` going forward.
|
|
582
|
+
- The `subCommands` property is deprecated as well. Please use `commands` instead. `subCommands` will be removed in a future major version.
|
|
575
583
|
|
|
576
584
|
- **Dynamic Usage Examples:**
|
|
577
|
-
- The launcher inspects your available
|
|
585
|
+
- The launcher inspects your available commands and their argument definitions, then prints a plausible example CLI invocation for a random command directly in the help output. This helps users understand real-world usage at a glance.
|
|
578
586
|
|
|
579
|
-
- **File-Based & Programmatic
|
|
580
|
-
- Both file-based and object
|
|
581
|
-
- File-based
|
|
587
|
+
- **File-Based & Programmatic Commands:**
|
|
588
|
+
- Both file-based and object commands are fully supported. The launcher can introspect their argument definitions and metadata for help, usage, and validation.
|
|
589
|
+
- File-based commands are auto-discovered from your filesystem, while programmatic commands can be defined inline in your main command.
|
|
582
590
|
|
|
583
591
|
- **Context-Aware Help Output:**
|
|
584
|
-
- The help/usage output adapts to your CLI's structure, showing available
|
|
592
|
+
- The help/usage output adapts to your CLI's structure, showing available commands, their aliases, argument details, and even dynamic usage examples. It also displays global options and context-specific error messages.
|
|
585
593
|
|
|
586
594
|
- **Error Handling:**
|
|
587
595
|
- The launcher provides clear, actionable error messages for missing required arguments, invalid types, unknown commands, and import errors. It always shows relevant usage information to help users recover quickly.
|
|
@@ -598,6 +606,92 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
598
606
|
- **Prompt-First, Modern UX:**
|
|
599
607
|
- The launcher integrates tightly with the prompt engine, so you can build interactive, delightful CLIs with minimal effort.
|
|
600
608
|
|
|
609
|
+
### Launcher Programmatic Execution
|
|
610
|
+
|
|
611
|
+
For larger CLIs or when you want to programmatically run commands (e.g.: [prompt demo](./example/prompts/mod.ts), tests, etc), you can organize your commands in a `cmds.ts` file and use the `runCmd` utility.
|
|
612
|
+
|
|
613
|
+
**Pro Tips & Best Practices**:
|
|
614
|
+
|
|
615
|
+
- Install `dler` globally and run `dler rempts init --cmds` to generate a `src/app/cmds.ts` (custom path is supported) file in your project.
|
|
616
|
+
- You can use any name for the `cmds.ts` file and store it anywhere, but `src/app/cmds.ts` is a good convention you can follow.
|
|
617
|
+
- Use the async function pattern for lazy loading if you have many commands or care about startup performance.
|
|
618
|
+
- Use eager loading (const) for small CLIs or demos where simplicity is preferred.
|
|
619
|
+
|
|
620
|
+
**Lazy Loading (Recommended for Most CLIs)**:
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
// example/launcher/app/cmds.ts
|
|
624
|
+
|
|
625
|
+
export async function getCmdHooks() {
|
|
626
|
+
return (await import("./hooks/cmd.js")).default;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export async function getCmdFoo() {
|
|
630
|
+
return (await import("./foo/cmd.js")).default;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ...more commands
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Usage:
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
// example/prompts/mod.ts
|
|
640
|
+
|
|
641
|
+
import { getCmdHooks } from "@/launcher/app/cmds.js";
|
|
642
|
+
import { runCmd } from "@reliverse/rempts";
|
|
643
|
+
|
|
644
|
+
await runCmd(await getCmdHooks(), ["--flag"]);
|
|
645
|
+
// OR:
|
|
646
|
+
// const hooksCmd = await getCmdHooks();
|
|
647
|
+
// await runCmd(hooksCmd, ["--flag"]);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Alternative: Eager Loading (All Commands Loaded at Startup)**:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
// example/launcher/app/cmds.ts
|
|
654
|
+
export const hooksCmd = (await import("./hooks/cmd.js")).default;
|
|
655
|
+
export const fooCmd = (await import("./foo/cmd.js")).default;
|
|
656
|
+
// ...more commands
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
Usage:
|
|
660
|
+
|
|
661
|
+
```ts
|
|
662
|
+
import { hooksCmd } from "./cmds.js";
|
|
663
|
+
import { runCmd } from "@reliverse/rempts";
|
|
664
|
+
|
|
665
|
+
await runCmd(hooksCmd, ["--flag"]);
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**Programmatic Command Execution with `runCmd`**:
|
|
669
|
+
|
|
670
|
+
The `runCmd` utility lets you run a command's `run()` handler with parsed arguments, outside of the full launcher context. This is useful for demos, tests, or custom flows:
|
|
671
|
+
|
|
672
|
+
```ts
|
|
673
|
+
import { runCmd } from "@reliverse/rempts";
|
|
674
|
+
import { hooksCmd } from "./cmds.js";
|
|
675
|
+
|
|
676
|
+
await runCmd(hooksCmd, ["--flag"]); // argv as array of strings
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Or with lazy loading:
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
const hooksCmd = await getCmdHooks();
|
|
683
|
+
await runCmd(hooksCmd, ["--flag"]);
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Note:** `runCmd` only runs the command's `run()` handler and does not handle subcommands, file-based commands, or global hooks. For full CLI behavior, use `runMain`.
|
|
687
|
+
|
|
688
|
+
**Performance Note:**
|
|
689
|
+
|
|
690
|
+
- Eager loading (`const`) loads all commands at startup, which may impact performance for large CLIs.
|
|
691
|
+
- Lazy loading (`async function`) loads each command only when needed, improving startup time and memory usage.
|
|
692
|
+
|
|
693
|
+
Choose the pattern that best fits your CLI's size and usage!
|
|
694
|
+
|
|
601
695
|
## Contributing
|
|
602
696
|
|
|
603
697
|
Bug report? Prompt idea? Want to build the best DX possible?
|
|
@@ -27,7 +27,7 @@ export async function runMain<T extends ArgsDef = ArgsDef>(
|
|
|
27
27
|
if (!meta?.version) {
|
|
28
28
|
throw new CLIError("No version specified", "E_NO_VERSION");
|
|
29
29
|
}
|
|
30
|
-
relinka("
|
|
30
|
+
relinka("log", meta.version);
|
|
31
31
|
} else {
|
|
32
32
|
await runCommand(cmd, { rawArgs });
|
|
33
33
|
}
|
|
@@ -12,7 +12,7 @@ export async function showUsage<T extends ArgsDef = ArgsDef>(
|
|
|
12
12
|
) {
|
|
13
13
|
try {
|
|
14
14
|
// biome-ignore lint/style/useTemplate: <explanation>
|
|
15
|
-
relinka("
|
|
15
|
+
relinka("log", (await renderUsage(cmd, parent)) + "\n");
|
|
16
16
|
} catch (error) {
|
|
17
17
|
relinka("error", String(error));
|
|
18
18
|
}
|
|
@@ -44,10 +44,10 @@ type CommandMeta = {
|
|
|
44
44
|
* 2) A lazy import function returning a Promise that resolves to
|
|
45
45
|
* { default: Command<any> } or directly to a Command instance.
|
|
46
46
|
*/
|
|
47
|
-
type
|
|
47
|
+
type CommandSpec = string | (() => Promise<{
|
|
48
48
|
default: Command<any>;
|
|
49
49
|
} | Command<any>>);
|
|
50
|
-
export type
|
|
50
|
+
export type CommandsMap = Record<string, CommandSpec>;
|
|
51
51
|
type CommandContext<ARGS> = {
|
|
52
52
|
args: ARGS;
|
|
53
53
|
raw: string[];
|
|
@@ -57,13 +57,20 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
57
57
|
meta?: CommandMeta;
|
|
58
58
|
args?: A;
|
|
59
59
|
run?: CommandRun<InferArgTypes<A>>;
|
|
60
|
-
subCommands?: SubCommandsMap;
|
|
61
60
|
/**
|
|
62
|
-
*
|
|
61
|
+
* Object subcommands for this command.
|
|
62
|
+
*/
|
|
63
|
+
commands?: CommandsMap;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use `commands` instead. Will be removed in a future major version.
|
|
66
|
+
*/
|
|
67
|
+
subCommands?: CommandsMap;
|
|
68
|
+
/**
|
|
69
|
+
* Called before the command runs
|
|
63
70
|
*/
|
|
64
71
|
onCmdStart?: () => void | Promise<void>;
|
|
65
72
|
/**
|
|
66
|
-
* Called after the command
|
|
73
|
+
* Called after the command finishes
|
|
67
74
|
*/
|
|
68
75
|
onCmdEnd?: () => void | Promise<void>;
|
|
69
76
|
/**
|
|
@@ -75,11 +82,11 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
75
82
|
*/
|
|
76
83
|
cleanup?: () => void | Promise<void>;
|
|
77
84
|
/**
|
|
78
|
-
* Called once per CLI process, before any command/
|
|
85
|
+
* Called once per CLI process, before any command/run() is executed
|
|
79
86
|
*/
|
|
80
87
|
onLauncherStart?: () => void | Promise<void>;
|
|
81
88
|
/**
|
|
82
|
-
* Called once per CLI process, after all command/
|
|
89
|
+
* Called once per CLI process, after all command/run() logic is finished
|
|
83
90
|
*/
|
|
84
91
|
onLauncherEnd?: () => void | Promise<void>;
|
|
85
92
|
};
|
|
@@ -87,13 +94,20 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
87
94
|
meta?: CommandMeta;
|
|
88
95
|
args: A;
|
|
89
96
|
run?: CommandRun<InferArgTypes<A>>;
|
|
90
|
-
subCommands?: SubCommandsMap;
|
|
91
97
|
/**
|
|
92
|
-
*
|
|
98
|
+
* Object subcommands for this command.
|
|
99
|
+
*/
|
|
100
|
+
commands?: CommandsMap;
|
|
101
|
+
/**
|
|
102
|
+
* @deprecated Use `commands` instead. Will be removed in a future major version.
|
|
103
|
+
*/
|
|
104
|
+
subCommands?: CommandsMap;
|
|
105
|
+
/**
|
|
106
|
+
* Called before the command runs
|
|
93
107
|
*/
|
|
94
108
|
onCmdStart?: () => void | Promise<void>;
|
|
95
109
|
/**
|
|
96
|
-
* Called after the command
|
|
110
|
+
* Called after the command finishes
|
|
97
111
|
*/
|
|
98
112
|
onCmdEnd?: () => void | Promise<void>;
|
|
99
113
|
/**
|
|
@@ -105,11 +119,11 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
105
119
|
*/
|
|
106
120
|
cleanup?: () => void | Promise<void>;
|
|
107
121
|
/**
|
|
108
|
-
* Called once per CLI process, before any command/
|
|
122
|
+
* Called once per CLI process, before any command/run() is executed
|
|
109
123
|
*/
|
|
110
124
|
onLauncherStart?: () => void | Promise<void>;
|
|
111
125
|
/**
|
|
112
|
-
* Called once per CLI process, after all command/
|
|
126
|
+
* Called once per CLI process, after all command/run() logic is finished
|
|
113
127
|
*/
|
|
114
128
|
onLauncherEnd?: () => void | Promise<void>;
|
|
115
129
|
};
|
|
@@ -152,11 +166,11 @@ export declare function showUsage<A extends ArgDefinitions>(command: Command<A>,
|
|
|
152
166
|
/**
|
|
153
167
|
* Primary entry point to run a command. This function supports:
|
|
154
168
|
*
|
|
155
|
-
* - File-based
|
|
156
|
-
* -
|
|
169
|
+
* - File-based Commands: scanning for commands within a given commands root.
|
|
170
|
+
* - Commands defined within the command object.
|
|
157
171
|
* - Standard flags like --help, --version, and --debug.
|
|
158
172
|
*
|
|
159
|
-
* This function passes along remaining arguments to
|
|
173
|
+
* This function passes along remaining arguments to command runners to ensure
|
|
160
174
|
* consistent parsing.
|
|
161
175
|
*/
|
|
162
176
|
export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, parserOptions?: ReliArgParserOptions & {
|
|
@@ -174,4 +188,14 @@ export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: C
|
|
|
174
188
|
* precise default value validation (e.g., `options: ["a", "b"] as const`).
|
|
175
189
|
*/
|
|
176
190
|
export declare function defineArgs<A extends ArgDefinitions>(args: A & ValidateArrayDefaults<A>): A;
|
|
191
|
+
/**
|
|
192
|
+
* Programmatically run a command's run() handler with parsed arguments.
|
|
193
|
+
* Does not handle subcommands, file-based commands, or global hooks.
|
|
194
|
+
* Suitable for use in demos, tests, or programmatic invocation.
|
|
195
|
+
*
|
|
196
|
+
* @param command The command definition (from defineCommand)
|
|
197
|
+
* @param argv The argv array to parse (default: [])
|
|
198
|
+
* @param parserOptions Optional reliArgParser options
|
|
199
|
+
*/
|
|
200
|
+
export declare function runCmd<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, argv?: string[], parserOptions?: ReliArgParserOptions): Promise<void>;
|
|
177
201
|
export {};
|
|
@@ -50,7 +50,7 @@ function buildExampleArgs(args) {
|
|
|
50
50
|
const isDebugMode = process.argv.includes("--debug");
|
|
51
51
|
function debugLog(...args) {
|
|
52
52
|
if (isDebugMode) {
|
|
53
|
-
relinka("
|
|
53
|
+
relinka("log", "[DEBUG]", ...args);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
function isFlag(str) {
|
|
@@ -61,11 +61,15 @@ export function defineCommand(options) {
|
|
|
61
61
|
const onCmdEnd = options.onCmdEnd || options.cleanup;
|
|
62
62
|
const onLauncherStart = options.onLauncherStart;
|
|
63
63
|
const onLauncherEnd = options.onLauncherEnd;
|
|
64
|
-
|
|
64
|
+
let commands = options.commands;
|
|
65
|
+
if (!commands) {
|
|
66
|
+
commands = options.subCommands;
|
|
67
|
+
}
|
|
68
|
+
const cmdObj = {
|
|
65
69
|
meta: options.meta,
|
|
66
70
|
args: options.args || {},
|
|
67
71
|
run: options.run,
|
|
68
|
-
|
|
72
|
+
commands,
|
|
69
73
|
onCmdStart,
|
|
70
74
|
onCmdEnd,
|
|
71
75
|
onLauncherStart,
|
|
@@ -74,6 +78,14 @@ export function defineCommand(options) {
|
|
|
74
78
|
setup: onCmdStart,
|
|
75
79
|
cleanup: onCmdEnd
|
|
76
80
|
};
|
|
81
|
+
Object.defineProperty(cmdObj, "subCommands", {
|
|
82
|
+
get() {
|
|
83
|
+
return this.commands;
|
|
84
|
+
},
|
|
85
|
+
enumerable: false,
|
|
86
|
+
configurable: true
|
|
87
|
+
});
|
|
88
|
+
return cmdObj;
|
|
77
89
|
}
|
|
78
90
|
let _cachedDefaultCliName;
|
|
79
91
|
let _cachedDefaultCliVersion;
|
|
@@ -97,7 +109,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
97
109
|
const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
|
|
98
110
|
const cliName = command.meta?.name || fallbackName;
|
|
99
111
|
const cliVersion = command.meta?.version || fallbackVersion;
|
|
100
|
-
relinka("
|
|
112
|
+
relinka("log", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
|
|
101
113
|
if (parserOptions.metaSettings?.showDescriptionOnMain) {
|
|
102
114
|
let description = command.meta?.description;
|
|
103
115
|
if (!description) {
|
|
@@ -155,7 +167,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
155
167
|
);
|
|
156
168
|
}
|
|
157
169
|
if (subCommandNames.length > 0) {
|
|
158
|
-
relinka("
|
|
170
|
+
relinka("log", "Available commands (run with `help` to see more):");
|
|
159
171
|
subCommandDefs.forEach(({ name, def }) => {
|
|
160
172
|
const desc = def?.meta?.description ?? "";
|
|
161
173
|
relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
|
|
@@ -173,8 +185,9 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
173
185
|
} else {
|
|
174
186
|
const subCommandNames = [];
|
|
175
187
|
const subCommandDefs = [];
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
const objectCommands = command.commands;
|
|
189
|
+
if (objectCommands) {
|
|
190
|
+
for (const [name, spec] of Object.entries(objectCommands)) {
|
|
178
191
|
try {
|
|
179
192
|
const cmd = await loadSubCommand(spec);
|
|
180
193
|
if (!cmd?.meta?.hidden) {
|
|
@@ -183,7 +196,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
183
196
|
subCommandDefs.push({ name, def: cmd });
|
|
184
197
|
}
|
|
185
198
|
} catch (err) {
|
|
186
|
-
debugLog(`Error loading
|
|
199
|
+
debugLog(`Error loading command ${name}:`, err);
|
|
187
200
|
}
|
|
188
201
|
}
|
|
189
202
|
}
|
|
@@ -204,14 +217,14 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
204
217
|
);
|
|
205
218
|
}
|
|
206
219
|
if (subCommandNames.length > 0) {
|
|
207
|
-
relinka("
|
|
220
|
+
relinka("log", "Available commands (run with `help` to see more):");
|
|
208
221
|
subCommandDefs.forEach(({ name, def }) => {
|
|
209
222
|
const desc = def?.meta?.description ?? "";
|
|
210
223
|
relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
|
|
211
224
|
});
|
|
212
225
|
}
|
|
213
226
|
}
|
|
214
|
-
relinka("
|
|
227
|
+
relinka("log", "Available options:");
|
|
215
228
|
relinka("log", "\u2022 -h, --help | Show help");
|
|
216
229
|
relinka("log", "\u2022 -v, --version | Show version");
|
|
217
230
|
relinka("log", "\u2022 --debug | Enable debug mode");
|
|
@@ -240,7 +253,7 @@ export async function runMain(command, parserOptions = {}) {
|
|
|
240
253
|
if (typeof command.onLauncherStart === "function")
|
|
241
254
|
await command.onLauncherStart();
|
|
242
255
|
try {
|
|
243
|
-
if (!parserOptions.fileBasedCmds && !command.
|
|
256
|
+
if (!parserOptions.fileBasedCmds && !command.commands) {
|
|
244
257
|
let callerDir = process.cwd();
|
|
245
258
|
let callerFile;
|
|
246
259
|
try {
|
|
@@ -289,7 +302,7 @@ This can cause recursion or unexpected behavior.`
|
|
|
289
302
|
}
|
|
290
303
|
const rawArgv = process.argv.slice(2);
|
|
291
304
|
const autoExit = parserOptions.autoExit !== false;
|
|
292
|
-
if (!(parserOptions.fileBasedCmds?.enable || command.
|
|
305
|
+
if (!(parserOptions.fileBasedCmds?.enable || command.commands && Object.keys(command.commands).length > 0 || command.run)) {
|
|
293
306
|
relinka(
|
|
294
307
|
"error",
|
|
295
308
|
"Invalid CLI configuration: No file-based commands, subCommands, or run() handler are defined. This CLI will not do anything.\n\u2502 To fix: add file-based commands (./app), or provide at least one subCommand or a run() handler."
|
|
@@ -310,7 +323,7 @@ This can cause recursion or unexpected behavior.`
|
|
|
310
323
|
if (checkVersion(rawArgv)) {
|
|
311
324
|
if (command.meta?.name) {
|
|
312
325
|
relinka(
|
|
313
|
-
"
|
|
326
|
+
"log",
|
|
314
327
|
`${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
|
|
315
328
|
);
|
|
316
329
|
}
|
|
@@ -338,10 +351,10 @@ This can cause recursion or unexpected behavior.`
|
|
|
338
351
|
throw err;
|
|
339
352
|
}
|
|
340
353
|
}
|
|
341
|
-
if (!fileBasedEnabled && command.
|
|
354
|
+
if (!fileBasedEnabled && command.commands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
|
|
342
355
|
const [maybeSub, ...subCmdArgv] = rawArgv;
|
|
343
356
|
let subSpec;
|
|
344
|
-
for (const [key, spec] of Object.entries(command.
|
|
357
|
+
for (const [key, spec] of Object.entries(command.commands)) {
|
|
345
358
|
if (key === maybeSub) {
|
|
346
359
|
subSpec = spec;
|
|
347
360
|
break;
|
|
@@ -353,7 +366,7 @@ This can cause recursion or unexpected behavior.`
|
|
|
353
366
|
break;
|
|
354
367
|
}
|
|
355
368
|
} catch (err) {
|
|
356
|
-
debugLog(`Error checking alias for
|
|
369
|
+
debugLog(`Error checking alias for command ${key}:`, err);
|
|
357
370
|
}
|
|
358
371
|
}
|
|
359
372
|
if (subSpec) {
|
|
@@ -556,7 +569,7 @@ async function runCommandWithArgs(command, argv, parserOptions) {
|
|
|
556
569
|
if (command.run) {
|
|
557
570
|
await command.run(ctx);
|
|
558
571
|
} else {
|
|
559
|
-
const isDispatcher = parserOptions.fileBasedCmds?.enable || command.
|
|
572
|
+
const isDispatcher = parserOptions.fileBasedCmds?.enable || command.commands && Object.keys(command.commands).length > 0;
|
|
560
573
|
const noSubcommandArgInCurrentCall = !argv.some((arg) => !isFlag(arg));
|
|
561
574
|
if (isDispatcher && noSubcommandArgInCurrentCall) {
|
|
562
575
|
relinka("warn", "Please specify a command");
|
|
@@ -624,3 +637,74 @@ function renderPositional(args) {
|
|
|
624
637
|
export function defineArgs(args) {
|
|
625
638
|
return args;
|
|
626
639
|
}
|
|
640
|
+
export async function runCmd(command, argv = [], parserOptions = {}) {
|
|
641
|
+
const booleanKeys = Object.keys(command.args || {}).filter(
|
|
642
|
+
(k) => command.args[k].type === "boolean"
|
|
643
|
+
);
|
|
644
|
+
const defaultMap = {};
|
|
645
|
+
for (const [argKey, def] of Object.entries(command.args || {})) {
|
|
646
|
+
if (def.default !== void 0) {
|
|
647
|
+
if (def.type === "array" && typeof def.default === "string") {
|
|
648
|
+
defaultMap[argKey] = [def.default];
|
|
649
|
+
} else {
|
|
650
|
+
defaultMap[argKey] = def.default;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const mergedParserOptions = {
|
|
655
|
+
...parserOptions,
|
|
656
|
+
boolean: [...parserOptions.boolean || [], ...booleanKeys],
|
|
657
|
+
defaults: { ...defaultMap, ...parserOptions.defaults || {} }
|
|
658
|
+
};
|
|
659
|
+
const parsed = reliArgParser(argv, mergedParserOptions);
|
|
660
|
+
debugLog("Parsed arguments (runCmd):", parsed);
|
|
661
|
+
const finalArgs = {};
|
|
662
|
+
const positionalKeys = Object.keys(command.args || {}).filter(
|
|
663
|
+
(k) => command.args[k].type === "positional"
|
|
664
|
+
);
|
|
665
|
+
const leftoverPositionals = [...parsed._ || []];
|
|
666
|
+
for (let i = 0; i < positionalKeys.length; i++) {
|
|
667
|
+
const key = positionalKeys[i];
|
|
668
|
+
const def = command.args?.[key];
|
|
669
|
+
const val = leftoverPositionals[i];
|
|
670
|
+
if (val == null && def.required) {
|
|
671
|
+
throw new Error(`Missing required positional argument: <${key}>`);
|
|
672
|
+
}
|
|
673
|
+
finalArgs[key] = val != null ? castArgValue(def, val, key) : def.default;
|
|
674
|
+
}
|
|
675
|
+
const otherKeys = Object.keys(command.args || {}).filter(
|
|
676
|
+
(k) => command.args[k].type !== "positional"
|
|
677
|
+
);
|
|
678
|
+
for (const key of otherKeys) {
|
|
679
|
+
const def = command.args?.[key];
|
|
680
|
+
let rawVal = parsed[key];
|
|
681
|
+
if (def.type === "array" && rawVal !== void 0 && !Array.isArray(rawVal)) {
|
|
682
|
+
rawVal = [rawVal];
|
|
683
|
+
}
|
|
684
|
+
const valueOrDefault = rawVal ?? defaultMap[key];
|
|
685
|
+
if (valueOrDefault == null && def.required) {
|
|
686
|
+
throw new Error(`Missing required argument: --${key}`);
|
|
687
|
+
}
|
|
688
|
+
finalArgs[key] = castArgValue(def, rawVal, key);
|
|
689
|
+
if (def.type === "array" && def.options && finalArgs[key]) {
|
|
690
|
+
const values = finalArgs[key];
|
|
691
|
+
const invalidOptions = values.filter(
|
|
692
|
+
(v) => def.options && !def.options.includes(v)
|
|
693
|
+
);
|
|
694
|
+
if (invalidOptions.length > 0) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`Invalid choice(s) for --${key}: ${invalidOptions.join(", ")}. Allowed options: ${def.options.join(", ")}`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const ctx = {
|
|
702
|
+
args: finalArgs,
|
|
703
|
+
raw: argv
|
|
704
|
+
};
|
|
705
|
+
if (typeof command.run === "function") {
|
|
706
|
+
await command.run(ctx);
|
|
707
|
+
} else {
|
|
708
|
+
throw new Error("Command has no run() handler.");
|
|
709
|
+
}
|
|
710
|
+
}
|
|
@@ -21,7 +21,10 @@ export const symbols = {
|
|
|
21
21
|
step_active: u("\u25C6", "\u2666"),
|
|
22
22
|
step_error: u("\u{1F5F4}", "x"),
|
|
23
23
|
info: u("\u2139", "i"),
|
|
24
|
-
|
|
24
|
+
log: u("\u2502", "|"),
|
|
25
|
+
success: u("\u2705", "\u2713"),
|
|
26
|
+
warn: u("\u26A0", "!"),
|
|
27
|
+
error: u("\u274C", "x")
|
|
25
28
|
};
|
|
26
29
|
function wrapThenStyle(input, wrap, typographyName, colorName, variantName, borderColor) {
|
|
27
30
|
if (!input) return "";
|
|
@@ -309,8 +312,8 @@ export function msgUndoAll() {
|
|
|
309
312
|
}
|
|
310
313
|
export function printLineBar(text, indent = 2) {
|
|
311
314
|
if (text === "") {
|
|
312
|
-
relinka("
|
|
315
|
+
relinka("log", re.dim("\u2502"));
|
|
313
316
|
} else {
|
|
314
|
-
relinka("
|
|
317
|
+
relinka("log", `${re.dim("\u2502")}${" ".repeat(indent)}${text}`);
|
|
315
318
|
}
|
|
316
319
|
}
|
package/bin/mod.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { startEditor } from "./components/editor/editor-mod.js";
|
|
|
4
4
|
export { mainSymbols, fallbackSymbols, } from "./components/figures/figures-mod.js";
|
|
5
5
|
export { confirmPrompt } from "./components/input/confirm-prompt.js";
|
|
6
6
|
export { inputPrompt } from "./components/input/input-prompt.js";
|
|
7
|
-
export type { ArgDefinition, ArgDefinitions,
|
|
7
|
+
export type { ArgDefinition, ArgDefinitions, CommandsMap, Command, InferArgTypes, FileBasedCmdsOptions, } from "./components/launcher/launcher-mod.js";
|
|
8
8
|
export { defineCommand, defineArgs, showUsage, runMain, } from "./components/launcher/launcher-mod.js";
|
|
9
9
|
export { toBaseColor, toSolidColor } from "./components/msg-fmt/colors.js";
|
|
10
10
|
export { relinkaByRemptsDeprecated, relinkaAsyncByRemptsDeprecated, throwError, } from "./components/msg-fmt/logger.js";
|
package/bin/types.d.ts
CHANGED
|
@@ -241,7 +241,7 @@ export type RenderParams = {
|
|
|
241
241
|
/**
|
|
242
242
|
* Known symbol names that will have IntelliSense support
|
|
243
243
|
*/
|
|
244
|
-
export type SymbolName = "pointer" | "start" | "middle" | "end" | "line" | "corner_top_right" | "step_active" | "step_error" | "
|
|
244
|
+
export type SymbolName = "pointer" | "start" | "middle" | "end" | "line" | "corner_top_right" | "step_active" | "step_error" | "log" | "success" | "info" | "warn" | "error";
|
|
245
245
|
export type Symbols = Record<SymbolName, string>;
|
|
246
246
|
export type FmtMsgOptions = {
|
|
247
247
|
type: MsgType;
|
package/bin/utils/prompt-end.js
CHANGED
|
@@ -21,14 +21,14 @@ export async function completePrompt(prompt, isCtrlC, _endTitle = "", _endTitleC
|
|
|
21
21
|
return value ?? false;
|
|
22
22
|
}
|
|
23
23
|
export function renderEndLine() {
|
|
24
|
-
const lineLength = getExactTerminalWidth() -
|
|
25
|
-
relinka("
|
|
26
|
-
relinka("
|
|
27
|
-
relinka("
|
|
24
|
+
const lineLength = getExactTerminalWidth() - 6;
|
|
25
|
+
relinka("null", re.dim(symbols.middle));
|
|
26
|
+
relinka("null", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
|
|
27
|
+
relinka("null", "");
|
|
28
28
|
}
|
|
29
29
|
export function renderEndLineInput() {
|
|
30
|
-
const lineLength = getExactTerminalWidth() -
|
|
31
|
-
relinka("
|
|
32
|
-
relinka("
|
|
33
|
-
relinka("
|
|
30
|
+
const lineLength = getExactTerminalWidth() - 6;
|
|
31
|
+
relinka("null", "");
|
|
32
|
+
relinka("null", re.dim(`${symbols.end}${symbols.line.repeat(lineLength)}\u22B1`));
|
|
33
|
+
relinka("null", "");
|
|
34
34
|
}
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@figliolia/chalk-animation": "^1.0.4",
|
|
4
4
|
"@reliverse/reliarg": "^1.0.3",
|
|
5
|
-
"@reliverse/relico": "^1.1.
|
|
6
|
-
"@reliverse/relinka": "^1.4.
|
|
5
|
+
"@reliverse/relico": "^1.1.2",
|
|
6
|
+
"@reliverse/relinka": "^1.4.5",
|
|
7
7
|
"@reliverse/runtime": "^1.0.3",
|
|
8
8
|
"ansi-escapes": "^7.0.0",
|
|
9
9
|
"c12": "^3.0.3",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"name": "@reliverse/rempts",
|
|
30
30
|
"type": "module",
|
|
31
|
-
"version": "1.7.
|
|
31
|
+
"version": "1.7.2",
|
|
32
32
|
"author": "reliverse",
|
|
33
33
|
"bugs": {
|
|
34
34
|
"email": "blefnk@gmail.com",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
|
59
59
|
"eslint-plugin-perfectionist": "^4.13.0",
|
|
60
60
|
"jiti": "^2.4.2",
|
|
61
|
-
"knip": "^5.
|
|
61
|
+
"knip": "^5.56.0",
|
|
62
62
|
"typescript": "^5.8.3",
|
|
63
63
|
"typescript-eslint": "^8.32.1",
|
|
64
64
|
"vitest": "^3.1.3"
|