@reliverse/rempts 1.7.0 β 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 +346 -105
- 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 +75 -7
- package/bin/components/launcher/launcher-mod.js +226 -126
- 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 +5 -5
package/README.md
CHANGED
|
@@ -2,98 +2,148 @@
|
|
|
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
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- π File-based commands (optional)
|
|
9
|
+
- π« Rempts prevents you from fighting with your CLI tool
|
|
10
|
+
- β¨ Rempts is your end-to-end CLI UI + command framework
|
|
11
|
+
- πͺ Made for DX precision and high-context terminal UX
|
|
12
|
+
- π File-based commands (app router style by default)
|
|
13
|
+
- ποΈ Prompt engine that *feels* modern, actually is
|
|
16
14
|
- π§ Type-safe from args to prompts
|
|
17
|
-
- π¨ Customizable themes, styled output
|
|
18
|
-
- π§© Router + argument parser built-in
|
|
19
15
|
- β‘ Blazing-fast, no runtime baggage
|
|
16
|
+
- π§© Router + argument parser built-in
|
|
17
|
+
- π¨ Customizable themes, styled output
|
|
18
|
+
- π¨ Crash-safe (Ctrl+C, SIGINT, errors)
|
|
20
19
|
- πͺ Minimal API surface, max expressiveness
|
|
21
|
-
- ποΈ Prompt engine that *feels* modern, actually is
|
|
22
20
|
- π§ͺ Scriptable for testing, stable for production
|
|
23
|
-
-
|
|
24
|
-
-
|
|
21
|
+
- π Automatic commands creation (via `dler init --cmd my-cool-cmd`)
|
|
22
|
+
- ποΈ No more hacking together `inquirer`, `citty`, `commander`, `chalk`
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun add @reliverse/rempts
|
|
28
|
+
```
|
|
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
|
+
|
|
38
|
+
## Usage Examples
|
|
39
|
+
|
|
40
|
+
- [Prompts](#prompts)
|
|
41
|
+
- [Launcher](#launcher)
|
|
25
42
|
|
|
26
43
|
## Screenshot
|
|
27
44
|
|
|
28
|
-

|
|
45
|
+

|
|
29
46
|
|
|
30
|
-
##
|
|
47
|
+
## API Overview
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
- **Argument**: An argument is a value that is passed to a command.
|
|
34
|
-
- **Flag**: A flag is a boolean argument that is used to enable or disable a feature.
|
|
35
|
-
- **Option**: An option is a named argument that is used to configure a command.
|
|
49
|
+
All main prompts APIs are available from the package root:
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
```ts
|
|
52
|
+
import {
|
|
53
|
+
// ...prompts
|
|
54
|
+
defineCommand, runMain, defineArgs,
|
|
55
|
+
inputPrompt, selectPrompt, multiselectPrompt, numberPrompt,
|
|
56
|
+
confirmPrompt, togglePrompt, spinnerTaskPrompt, progressTaskPrompt,
|
|
57
|
+
startPrompt, endPrompt, resultPrompt, nextStepsPrompt,
|
|
58
|
+
// ...hooks
|
|
59
|
+
useSpinner,
|
|
60
|
+
// ...launcher
|
|
61
|
+
runMain, defineCommand, defineArgs,
|
|
62
|
+
// ...types
|
|
63
|
+
// ...more
|
|
64
|
+
} from "@reliverse/rempts";
|
|
65
|
+
```
|
|
38
66
|
|
|
39
|
-
|
|
67
|
+
> See [`src/mod.ts`](./src/mod.ts) for the full list of exports.
|
|
40
68
|
|
|
41
|
-
|
|
42
|
-
Use `subCommands` in your command definition or let the launcher automatically load commands from a specified directory.
|
|
69
|
+
## Prompts
|
|
43
70
|
|
|
44
|
-
-
|
|
45
|
-
The launcher scans your specified `cmdsRootPath` for command files matching common patterns such as:
|
|
46
|
-
- `arg-cmdName.{ts,js}`
|
|
47
|
-
- `cmdName/index.{ts,js}`
|
|
48
|
-
- `cmdName/cmdName-mod.{ts,js}`
|
|
49
|
-
- And more β with automatic usage output if a command file is not found.
|
|
71
|
+
### Built-in Prompts
|
|
50
72
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
| Prompt | Description |
|
|
74
|
+
|---------------------------|-----------------------------------------------------------|
|
|
75
|
+
| `inputPrompt` | Single-line input (with mask support, e.g. for passwords) |
|
|
76
|
+
| `selectPrompt` | Single-choice radio menu |
|
|
77
|
+
| `multiselectPrompt` | Multi-choice checkbox menu |
|
|
78
|
+
| `numberPrompt` | Type-safe number input |
|
|
79
|
+
| `confirmPrompt` | Yes/No toggle |
|
|
80
|
+
| `togglePrompt` | Custom on/off toggles |
|
|
81
|
+
| `progressTaskPrompt` | Progress bar for async tasks |
|
|
82
|
+
| `resultPrompt` | Show results in a styled box |
|
|
83
|
+
| `nextStepsPrompt` | Show next steps in a styled list |
|
|
84
|
+
| `startPrompt`/`endPrompt` | Makes CLI start/end flows look nice |
|
|
85
|
+
| `spinnerTaskPrompt` | Async loader with spinner (possibly will be deprecated) |
|
|
86
|
+
| `datePrompt` | Date input with format validation |
|
|
87
|
+
| `anykeyPrompt` | Wait for any keypress |
|
|
56
88
|
|
|
57
|
-
|
|
58
|
-
Seamlessly combines positional and named arguments with zero configuration, auto-parsing booleans, strings, numbers, arrays, and even supporting negated flags like `--no-flag`.
|
|
89
|
+
### Hooks
|
|
59
90
|
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
| Hook | Description |
|
|
92
|
+
|--------------|--------------------|
|
|
93
|
+
| `useSpinner` | Start/stop spinner |
|
|
62
94
|
|
|
63
|
-
|
|
64
|
-
The launcher provides clear error messages for missing required arguments, invalid types, or command import issues, and it automatically displays usage information for your CLI.
|
|
95
|
+
### Notices
|
|
65
96
|
|
|
66
|
-
-
|
|
67
|
-
- You can define optional `setup` and `cleanup` functions in your main command. These hooks are called automatically before and after any command or subcommand runs (including file-based and programmatic subcommands). This is perfect for initializing resources or cleaning up after execution.
|
|
97
|
+
- `setup`/`cleanup` are now `onCmdStart`/`onCmdEnd` (old names still work for now).
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
- The launcher inspects your available subcommands and their argument definitions, then prints a plausible example CLI invocation for a random subcommand directly in the help output. This helps users understand real-world usage at a glance.
|
|
99
|
+
### Prompts Usage Example
|
|
71
100
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- File-based subcommands are auto-discovered from your filesystem, while programmatic subcommands can be defined inline in your main command.
|
|
101
|
+
```ts
|
|
102
|
+
import { relinka } from "@reliverse/relinka";
|
|
75
103
|
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
import {
|
|
105
|
+
startPrompt,
|
|
106
|
+
inputPrompt,
|
|
107
|
+
selectPrompt,
|
|
108
|
+
defineCommand,
|
|
109
|
+
runMain
|
|
110
|
+
} from "@reliverse/rempts";
|
|
78
111
|
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
async function main() {
|
|
113
|
+
await startPrompt({ title: "Project Setup" });
|
|
81
114
|
|
|
82
|
-
|
|
83
|
-
|
|
115
|
+
const name = await inputPrompt({
|
|
116
|
+
title: "What's your project name?",
|
|
117
|
+
defaultValue: "my-cool-project",
|
|
118
|
+
});
|
|
84
119
|
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
const framework = await selectPrompt({
|
|
121
|
+
title: "Pick your framework",
|
|
122
|
+
options: [
|
|
123
|
+
{ value: "next", label: "Next.js" },
|
|
124
|
+
{ value: "svelte", label: "SvelteKit" },
|
|
125
|
+
{ value: "start", label: "TanStack Start" },
|
|
126
|
+
],
|
|
127
|
+
defaultValue: "next",
|
|
128
|
+
});
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
console.log("Your result:", { name, framework });
|
|
131
|
+
};
|
|
90
132
|
|
|
91
|
-
|
|
92
|
-
|
|
133
|
+
await main();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Launcher
|
|
93
137
|
|
|
94
|
-
###
|
|
138
|
+
### Terminology
|
|
95
139
|
|
|
96
|
-
|
|
140
|
+
- **Launcher/Router**: The main entry point for your CLI. Visit [CLI Launcher (Router)](#cli-launcher-router) section to learn more.
|
|
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.
|
|
142
|
+
- **Argument**: An argument is a value that is passed to a command.
|
|
143
|
+
- **Flag**: A flag is a boolean argument that is used to enable or disable a feature.
|
|
144
|
+
- **Option**: An option is a named argument that is used to configure a command.
|
|
145
|
+
|
|
146
|
+
#### Launcher Usage Example
|
|
97
147
|
|
|
98
148
|
```ts
|
|
99
149
|
import { relinka } from "@reliverse/relinka";
|
|
@@ -106,13 +156,13 @@ const main = defineCommand({
|
|
|
106
156
|
version: "1.0.0",
|
|
107
157
|
description: "Rempts Launcher Playground CLI",
|
|
108
158
|
},
|
|
109
|
-
|
|
159
|
+
onCmdStart() {
|
|
110
160
|
relinka("success", "Setup");
|
|
111
161
|
},
|
|
112
|
-
|
|
162
|
+
onCmdEnd() {
|
|
113
163
|
relinka("success", "Cleanup");
|
|
114
164
|
},
|
|
115
|
-
|
|
165
|
+
commands: {
|
|
116
166
|
build: () => import("./app/build/cmd.js").then((r) => r.default),
|
|
117
167
|
deploy: () => import("./app/deploy/cmd.js").then((r) => r.default),
|
|
118
168
|
debug: () => import("./app/debug/cmd.js").then((r) => r.default),
|
|
@@ -126,16 +176,16 @@ await runMain(main);
|
|
|
126
176
|
await runMain(myCommand, {
|
|
127
177
|
fileBasedCmds: {
|
|
128
178
|
enable: true,
|
|
129
|
-
cmdsRootPath: "
|
|
179
|
+
cmdsRootPath: "my-cmds", // default is `./app`
|
|
130
180
|
},
|
|
131
181
|
// Optionally disable auto-exit to handle errors manually:
|
|
132
182
|
autoExit: false,
|
|
133
183
|
});
|
|
134
184
|
```
|
|
135
185
|
|
|
136
|
-
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.
|
|
137
187
|
|
|
138
|
-
|
|
188
|
+
#### File-Based Commands
|
|
139
189
|
|
|
140
190
|
Drop a `./src/cli/app/add/index.ts` and it's live.
|
|
141
191
|
|
|
@@ -155,7 +205,7 @@ export default defineCommand({
|
|
|
155
205
|
}),
|
|
156
206
|
},
|
|
157
207
|
async run({ args }) {
|
|
158
|
-
relinka("
|
|
208
|
+
relinka("log", "Adding:", args.name);
|
|
159
209
|
},
|
|
160
210
|
});
|
|
161
211
|
```
|
|
@@ -169,22 +219,10 @@ export default defineCommand({
|
|
|
169
219
|
|
|
170
220
|
**Hint**:
|
|
171
221
|
|
|
172
|
-
- Install `bun
|
|
173
|
-
- Use `
|
|
174
|
-
|
|
175
|
-
## π¦ Built-In Prompts
|
|
176
|
-
|
|
177
|
-
- π§ `inputPrompt` β Single-line, password, masked
|
|
178
|
-
- β
`selectPrompt` β Radio menu
|
|
179
|
-
- π§° `multiselectPrompt` β Checkbox menu
|
|
180
|
-
- π’ `numberPrompt` β Type-safe number input
|
|
181
|
-
- π `confirmPrompt` β Yes/No toggle
|
|
182
|
-
- π₯ `togglePrompt` β Custom on/off toggles
|
|
183
|
-
- β³ `spinnerPrompt` β Async loaders with status
|
|
184
|
-
- π `logPrompt` β Styled logs / steps
|
|
185
|
-
- π§Ό `clearPrompt` β Clears console with style
|
|
222
|
+
- Install `bun add -D @reliverse/dler`
|
|
223
|
+
- Use `dler init --cmd cmd1 cmd2` to init commands for rempts launcher's automatically
|
|
186
224
|
|
|
187
|
-
|
|
225
|
+
### Advanced Minimal API
|
|
188
226
|
|
|
189
227
|
```ts
|
|
190
228
|
defineCommand({
|
|
@@ -195,7 +233,7 @@ defineCommand({
|
|
|
195
233
|
animals: { type: "array", options: ["cat","dog"] },
|
|
196
234
|
},
|
|
197
235
|
async run({ args, raw }) { // or `async run(ctx)`
|
|
198
|
-
relinka("
|
|
236
|
+
relinka("log", args.name, args.verbose, args.animals); // or `relinka("log", ctx.args.name, ...);`
|
|
199
237
|
},
|
|
200
238
|
});
|
|
201
239
|
```
|
|
@@ -207,14 +245,14 @@ defineCommand({
|
|
|
207
245
|
- Default values, validations, descriptions
|
|
208
246
|
- Full help rendering from metadata
|
|
209
247
|
|
|
210
|
-
|
|
248
|
+
### Theming + Customization
|
|
211
249
|
|
|
212
250
|
- Built-in output formatter and logger
|
|
213
251
|
- Override styles via prompt options
|
|
214
252
|
- Smart layout for small terminals
|
|
215
253
|
- Looks great in plain scripts or full CLI apps
|
|
216
254
|
|
|
217
|
-
|
|
255
|
+
### Playground
|
|
218
256
|
|
|
219
257
|
```bash
|
|
220
258
|
bun i -g @reliverse/rempts-cli
|
|
@@ -233,9 +271,9 @@ bun dev # supported options: name
|
|
|
233
271
|
- Both `rempts examples` from @reliverse/rempts and `bun dev` (which is the same thing) are themselves examples of `launcher` functionality.
|
|
234
272
|
- This launcher will show you a `multiselectPrompt()` where you can choose which CLI prompts you want to play with.
|
|
235
273
|
|
|
236
|
-
|
|
274
|
+
### Launcher Usage Examples
|
|
237
275
|
|
|
238
|
-
|
|
276
|
+
#### Minimal Usage Example
|
|
239
277
|
|
|
240
278
|
**1 Create a `src/mod.ts` file:**
|
|
241
279
|
|
|
@@ -262,7 +300,13 @@ export default defineCommand({
|
|
|
262
300
|
});
|
|
263
301
|
```
|
|
264
302
|
|
|
265
|
-
|
|
303
|
+
**4. Test it:**
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
bun src/mod.ts
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Medium Usage Example
|
|
266
310
|
|
|
267
311
|
```ts
|
|
268
312
|
import { defineCommand, runMain } from "@reliverse/rempts";
|
|
@@ -279,7 +323,7 @@ const main = defineCommand({
|
|
|
279
323
|
await runMain(main);
|
|
280
324
|
```
|
|
281
325
|
|
|
282
|
-
|
|
326
|
+
#### Classic Usage Example
|
|
283
327
|
|
|
284
328
|
```ts
|
|
285
329
|
import { relinka } from "@reliverse/relinka";
|
|
@@ -307,7 +351,7 @@ const main = defineCommand({
|
|
|
307
351
|
},
|
|
308
352
|
async run({ args }) {
|
|
309
353
|
await startPrompt({
|
|
310
|
-
title: "
|
|
354
|
+
title: "Project Setup",
|
|
311
355
|
});
|
|
312
356
|
|
|
313
357
|
const name = await inputPrompt({
|
|
@@ -324,14 +368,14 @@ const main = defineCommand({
|
|
|
324
368
|
],
|
|
325
369
|
});
|
|
326
370
|
|
|
327
|
-
relinka("
|
|
371
|
+
relinka("log", "You have selected:", { name, framework });
|
|
328
372
|
},
|
|
329
373
|
});
|
|
330
374
|
|
|
331
375
|
await runMain(main);
|
|
332
376
|
```
|
|
333
377
|
|
|
334
|
-
|
|
378
|
+
#### Advanced Usage Example
|
|
335
379
|
|
|
336
380
|
```ts
|
|
337
381
|
import { relinka } from "@reliverse/relinka";
|
|
@@ -350,7 +394,7 @@ import {
|
|
|
350
394
|
* This command demonstrates the full range of launcher features along with all supported argument types:
|
|
351
395
|
*
|
|
352
396
|
* - Global Usage Handling: Automatically processes `--help` and `--version`.
|
|
353
|
-
* - File-Based
|
|
397
|
+
* - File-Based Commands: Scans "app" for commands (e.g., `init`).
|
|
354
398
|
* - Comprehensive Argument Parsing: Supports positional, boolean, string, number, and array arguments.
|
|
355
399
|
* - Interactive Prompts: Uses built-in prompt functions for an engaging CLI experience.
|
|
356
400
|
*/
|
|
@@ -359,7 +403,7 @@ const mainCommand = defineCommand({
|
|
|
359
403
|
name: "rempts",
|
|
360
404
|
version: "1.6.0",
|
|
361
405
|
description:
|
|
362
|
-
"An example CLI that supports file-based
|
|
406
|
+
"An example CLI that supports file-based commands and all argument types.",
|
|
363
407
|
},
|
|
364
408
|
args: {
|
|
365
409
|
// Positional arguments
|
|
@@ -402,14 +446,14 @@ const mainCommand = defineCommand({
|
|
|
402
446
|
},
|
|
403
447
|
async run({ args, raw }) {
|
|
404
448
|
// Display invocation details and parsed arguments.
|
|
405
|
-
relinka("
|
|
406
|
-
relinka("
|
|
407
|
-
relinka("
|
|
408
|
-
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`");
|
|
409
453
|
|
|
410
454
|
// Begin interactive session with a prompt.
|
|
411
455
|
await startPrompt({
|
|
412
|
-
title: "
|
|
456
|
+
title: "Project Setup",
|
|
413
457
|
});
|
|
414
458
|
|
|
415
459
|
// Ask for the project name, falling back to provided argument or a default.
|
|
@@ -429,7 +473,7 @@ const mainCommand = defineCommand({
|
|
|
429
473
|
});
|
|
430
474
|
|
|
431
475
|
// Log all gathered input details.
|
|
432
|
-
relinka("
|
|
476
|
+
relinka("log", "You have selected:", {
|
|
433
477
|
projectName,
|
|
434
478
|
framework,
|
|
435
479
|
inputFile: args.inputFile,
|
|
@@ -445,7 +489,7 @@ const mainCommand = defineCommand({
|
|
|
445
489
|
/**
|
|
446
490
|
* The `runMain()` function sets up the launcher with several advanced features:
|
|
447
491
|
*
|
|
448
|
-
* - File-Based
|
|
492
|
+
* - File-Based Commands: Enables scanning for commands within the "app" directory.
|
|
449
493
|
* - Alias Mapping: Shorthand flags (e.g., `-v`) are mapped to their full names (e.g., `--verbose`).
|
|
450
494
|
* - Strict Mode & Unknown Flag Warnings: Unknown flags are either warned about or handled via a callback.
|
|
451
495
|
* - Negated Boolean Support: Allows flags to be negated (e.g., `--no-verbose`).
|
|
@@ -453,8 +497,8 @@ const mainCommand = defineCommand({
|
|
|
453
497
|
*/
|
|
454
498
|
await runMain(mainCommand, {
|
|
455
499
|
fileBasedCmds: {
|
|
456
|
-
enable: true, // Enables file-based
|
|
457
|
-
cmdsRootPath: "app", // Directory to scan for
|
|
500
|
+
enable: true, // Enables file-based command detection.
|
|
501
|
+
cmdsRootPath: "app", // Directory to scan for commands.
|
|
458
502
|
},
|
|
459
503
|
alias: {
|
|
460
504
|
v: "verbose", // Maps shorthand flag -v to --verbose.
|
|
@@ -469,6 +513,185 @@ await runMain(mainCommand, {
|
|
|
469
513
|
});
|
|
470
514
|
```
|
|
471
515
|
|
|
516
|
+
### CLI Launcher (Router)
|
|
517
|
+
|
|
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:
|
|
519
|
+
|
|
520
|
+
- **File-Based & Defined Commands:**
|
|
521
|
+
Use `commands` in your command definition or let the launcher automatically load commands from a specified directory.
|
|
522
|
+
|
|
523
|
+
- **Automatic Command Detection:**
|
|
524
|
+
The launcher scans your specified `cmdsRootPath` for command files matching common patterns such as:
|
|
525
|
+
- `arg-cmdName.{ts,js}`
|
|
526
|
+
- `cmdName/index.{ts,js}`
|
|
527
|
+
- `cmdName/cmdName-mod.{ts,js}`
|
|
528
|
+
- And more β with automatic usage output if a command file is not found.
|
|
529
|
+
|
|
530
|
+
- **Built-In Flag Handling:**
|
|
531
|
+
Automatically processes global flags such as:
|
|
532
|
+
- `--help` and `-h` to show usage details.
|
|
533
|
+
- `--version` and `-v` to display version information.
|
|
534
|
+
- `--debug` for verbose logging during development.
|
|
535
|
+
|
|
536
|
+
- **Unified Argument Parsing:**
|
|
537
|
+
Seamlessly combines positional and named arguments with zero configuration, auto-parsing booleans, strings, numbers, arrays, and even supporting negated flags like `--no-flag`.
|
|
538
|
+
|
|
539
|
+
- **Customizable Behavior:**
|
|
540
|
+
Options such as `fileBasedCmds.enable`, `cmdsRootPath`, and `autoExit` allow you to tailor the launcher's behavior. For example, you can choose whether the process should exit automatically on error or allow manual error handling.
|
|
541
|
+
|
|
542
|
+
- **Error Management & Usage Output:**
|
|
543
|
+
The launcher provides clear error messages for missing required arguments, invalid types, or command import issues, and it automatically displays usage information for your CLI.
|
|
544
|
+
|
|
545
|
+
- **Lifecycle Hooks:**
|
|
546
|
+
You can define optional lifecycle hooks in your main command:
|
|
547
|
+
- `onLauncherStart` and `onLauncherEnd` (global, called once per CLI process)
|
|
548
|
+
- `onCmdStart` and `onCmdEnd` (per-command, called before/after each command, but NOT for the main `run()` handler)
|
|
549
|
+
|
|
550
|
+
**Global Hooks:**
|
|
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).
|
|
553
|
+
|
|
554
|
+
**Per-Command Hooks:**
|
|
555
|
+
- `onCmdStart`: Called before each command (not for main `run()`).
|
|
556
|
+
- `onCmdEnd`: Called after each command (not for main `run()`).
|
|
557
|
+
|
|
558
|
+
This means:
|
|
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.
|
|
562
|
+
- If you want logic to run only once for the entire CLI process, use `onLauncherStart` and `onLauncherEnd`.
|
|
563
|
+
|
|
564
|
+
**Example:**
|
|
565
|
+
|
|
566
|
+
```ts
|
|
567
|
+
const main = defineCommand({
|
|
568
|
+
onLauncherStart() { relinka('info', 'Global setup (once per process)'); },
|
|
569
|
+
onLauncherEnd() { relinka('info', 'Global cleanup (once per process)'); },
|
|
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)'); },
|
|
574
|
+
});
|
|
575
|
+
// onLauncherStart/onLauncherEnd are called once per process
|
|
576
|
+
// onCmdStart/onCmdEnd are called for every command (not for main run())
|
|
577
|
+
// If you want per-run() logic, use the run() handler or global hooks
|
|
578
|
+
```
|
|
579
|
+
|
|
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.
|
|
583
|
+
|
|
584
|
+
- **Dynamic Usage Examples:**
|
|
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.
|
|
586
|
+
|
|
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.
|
|
590
|
+
|
|
591
|
+
- **Context-Aware Help Output:**
|
|
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.
|
|
593
|
+
|
|
594
|
+
- **Error Handling:**
|
|
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.
|
|
596
|
+
|
|
597
|
+
- **Unified Argument Parsing:**
|
|
598
|
+
- All arguments (positional, named, boolean, string, number, array) are parsed and validated automatically. Negated flags (like `--no-flag`) are supported out of the box.
|
|
599
|
+
|
|
600
|
+
- **Extensible & Flexible:**
|
|
601
|
+
- The launcher is highly extensible. You can use it with both Bun and Node.js, and it works seamlessly with both file-based and programmatic command definitions. You can also customize its behavior with options like `autoExit`, `cmdsRootPath`, and more.
|
|
602
|
+
|
|
603
|
+
- **Bun & Node.js Support:**
|
|
604
|
+
- The launcher is designed to work in both Bun and Node.js environments, so you can use it in any modern JavaScript/TypeScript project.
|
|
605
|
+
|
|
606
|
+
- **Prompt-First, Modern UX:**
|
|
607
|
+
- The launcher integrates tightly with the prompt engine, so you can build interactive, delightful CLIs with minimal effort.
|
|
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
|
+
|
|
472
695
|
## Contributing
|
|
473
696
|
|
|
474
697
|
Bug report? Prompt idea? Want to build the best DX possible?
|
|
@@ -481,6 +704,24 @@ You're in the right place:
|
|
|
481
704
|
|
|
482
705
|
> *No classes. No magic. Just clean, composable tools for CLI devs.*
|
|
483
706
|
|
|
707
|
+
### Notices For Contributors
|
|
708
|
+
|
|
709
|
+
**TypeScript Support**:
|
|
710
|
+
|
|
711
|
+
All APIs are fully typed. See [`src/types.ts`](./src/types.ts) for advanced customization and type inference.
|
|
712
|
+
|
|
713
|
+
**Examples**:
|
|
714
|
+
|
|
715
|
+
- **Classic CLI:** [`example/launcher/classic.ts`](./example/launcher/classic.ts)
|
|
716
|
+
- **Modern Minimal CLI:** [`example/launcher/modern.ts`](./example/launcher/modern.ts)
|
|
717
|
+
- **Full Prompt Demo:** [`example/prompts/mod.ts`](./example/prompts/mod.ts)
|
|
718
|
+
|
|
719
|
+
**Components and Utilities**:
|
|
720
|
+
|
|
721
|
+
- **components/**: All prompt UIs, CLI output, launcher logic, etc.
|
|
722
|
+
- **utils/**: Color, error, validation, streaming, and system helpers.
|
|
723
|
+
- **hooks/**: Useful hooks for prompt state and effects.
|
|
724
|
+
|
|
484
725
|
### Helpful Links
|
|
485
726
|
|
|
486
727
|
- [CLI application with the Node.js Readline module](https://dev.to/camptocamp-geo/cli-application-with-the-nodejs-readline-module-48ic)
|
|
@@ -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
|
}
|