@reliverse/rempts 2.2.9 → 2.3.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 +213 -1431
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +51 -0
- package/dist/create-project.d.ts +14 -0
- package/dist/create-project.js +84 -0
- package/dist/create.d.ts +11 -0
- package/dist/create.js +62 -0
- package/dist/mod.d.ts +4 -6
- package/dist/mod.js +7 -6
- package/dist/template-engine.d.ts +27 -0
- package/dist/template-engine.js +145 -0
- package/dist/types.d.ts +45 -0
- package/package.json +41 -39
- package/src/cli.ts +64 -0
- package/templates/advanced/README.md +118 -0
- package/templates/advanced/dler.config.ts +41 -0
- package/templates/advanced/package.json +42 -0
- package/templates/advanced/src/commands/config.ts +157 -0
- package/templates/advanced/src/commands/init.ts +149 -0
- package/templates/advanced/src/commands/serve.ts +172 -0
- package/templates/advanced/src/commands/validate.ts +130 -0
- package/templates/advanced/src/mod.ts +44 -0
- package/templates/advanced/src/utils/config.ts +83 -0
- package/templates/advanced/src/utils/constants.ts +12 -0
- package/templates/advanced/src/utils/glob.ts +49 -0
- package/templates/advanced/src/utils/validator.ts +128 -0
- package/templates/advanced/template.json +40 -0
- package/templates/advanced/tsconfig.json +23 -0
- package/templates/basic/README.md +41 -0
- package/templates/basic/dler.config.ts +40 -0
- package/templates/basic/package.json +31 -0
- package/templates/basic/src/commands/hello.ts +26 -0
- package/templates/basic/src/mod.ts +13 -0
- package/templates/basic/template.json +27 -0
- package/templates/basic/tsconfig.json +19 -0
- package/templates/monorepo/README.md +74 -0
- package/templates/monorepo/dler.config.ts +45 -0
- package/templates/monorepo/package.json +30 -0
- package/templates/monorepo/packages/cli/package.json +40 -0
- package/templates/monorepo/packages/cli/src/mod.ts +22 -0
- package/templates/monorepo/packages/cli/tsconfig.json +13 -0
- package/templates/monorepo/packages/core/package.json +33 -0
- package/templates/monorepo/packages/core/scripts/build.ts +18 -0
- package/templates/monorepo/packages/core/src/commands/analyze.ts +87 -0
- package/templates/monorepo/packages/core/src/commands/process.ts +57 -0
- package/templates/monorepo/packages/core/src/mod.ts +3 -0
- package/templates/monorepo/packages/core/src/types.ts +21 -0
- package/templates/monorepo/packages/core/tsconfig.json +14 -0
- package/templates/monorepo/packages/utils/package.json +27 -0
- package/templates/monorepo/packages/utils/scripts/build.ts +17 -0
- package/templates/monorepo/packages/utils/src/format.ts +29 -0
- package/templates/monorepo/packages/utils/src/json.ts +11 -0
- package/templates/monorepo/packages/utils/src/logger.ts +19 -0
- package/templates/monorepo/packages/utils/src/mod.ts +3 -0
- package/templates/monorepo/packages/utils/tsconfig.json +13 -0
- package/templates/monorepo/template.json +27 -0
- package/templates/monorepo/tsconfig.json +14 -0
- package/templates/monorepo/turbo.json +28 -0
- package/LICENSE +0 -21
- package/cleanup.mjs +0 -33
- package/dist/cancel.d.ts +0 -31
- package/dist/cancel.js +0 -28
- package/dist/ffi.d.ts +0 -1
- package/dist/ffi.js +0 -165
- package/dist/group.d.ts +0 -16
- package/dist/group.js +0 -22
- package/dist/launcher/command.d.ts +0 -8
- package/dist/launcher/command.js +0 -10
- package/dist/launcher/discovery.d.ts +0 -3
- package/dist/launcher/discovery.js +0 -207
- package/dist/launcher/errors.d.ts +0 -15
- package/dist/launcher/errors.js +0 -31
- package/dist/launcher/help.d.ts +0 -3
- package/dist/launcher/help.js +0 -145
- package/dist/launcher/mod.d.ts +0 -12
- package/dist/launcher/mod.js +0 -222
- package/dist/launcher/parser.d.ts +0 -14
- package/dist/launcher/parser.js +0 -255
- package/dist/launcher/registry.d.ts +0 -10
- package/dist/launcher/registry.js +0 -42
- package/dist/launcher/types.d.ts +0 -78
- package/dist/launcher/validator.d.ts +0 -3
- package/dist/launcher/validator.js +0 -39
- package/dist/prompt.d.ts +0 -13
- package/dist/prompt.js +0 -53
- package/dist/selection.d.ts +0 -92
- package/dist/selection.js +0 -191
- package/dist/spinner.d.ts +0 -26
- package/dist/spinner.js +0 -141
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -11
- /package/dist/{launcher/types.js → types.js} +0 -0
package/README.md
CHANGED
|
@@ -1,1534 +1,316 @@
|
|
|
1
|
-
#
|
|
1
|
+
# rempts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffold new Rempts CLI projects with ease.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- 😘 drop-in to libraries like `unjs/citty` and `@clack/prompts`
|
|
10
|
-
- 📝 includes comprehensive set of built-in cli prompts
|
|
11
|
-
- 📂 file-based commands (app-router style by default)
|
|
12
|
-
- 🫂 rempts keeps you from fighting with your CLI tool
|
|
13
|
-
- 🏎️ prompt engine that *feels* modern — and actually is
|
|
14
|
-
- ✨ rempts is your end-to-end CLI UI + command framework
|
|
15
|
-
- 🌿 multi-level file-based subcommands (sibling + nested)
|
|
16
|
-
- 💪 built for DX precision and high-context terminal UX
|
|
17
|
-
- 🎭 looks great in plain scripts or full CLI apps
|
|
18
|
-
- 🎨 customizable themes and styled output
|
|
19
|
-
- 📦 built-in output formatter and logger
|
|
20
|
-
- 🚨 crash-safe (Ctrl+C, SIGINT, errors)
|
|
21
|
-
- ⚡ blazing-fast, zero runtime baggage
|
|
22
|
-
- 🧩 router + argument parser built-in
|
|
23
|
-
- 🧠 type-safe from args to prompts
|
|
24
|
-
- 📐 smart layout for small terminals
|
|
25
|
-
- 🎛️ override styles via prompt options
|
|
26
|
-
- 🪄 minimal API surface, maximum expressiveness
|
|
27
|
-
- 🧪 scriptable for testing, stable for production
|
|
28
|
-
- 🏞️ no more hacking together `inquirer`/`citty`/`commander`/`chalk`
|
|
29
|
-
- 🆕 automatic command creation (`bun dler rempts --init cmd1 cmd2`)
|
|
30
|
-
- 🐦🔥 automatic creation of `src/app/cmds.ts` file (`bun dler rempts`)
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
5
|
+
## Quick Start
|
|
33
6
|
|
|
34
7
|
```bash
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Usage Examples
|
|
39
|
-
|
|
40
|
-
- [Prompts](#prompts)
|
|
41
|
-
- [Launcher](#launcher)
|
|
42
|
-
|
|
43
|
-
## Screenshot
|
|
44
|
-
|
|
45
|
-

|
|
8
|
+
# Using bunx (recommended)
|
|
9
|
+
bunx @reliverse/rempts my-cli
|
|
46
10
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
import {
|
|
53
|
-
// ...prompts
|
|
54
|
-
inputPrompt, selectPrompt, multiselectPrompt, numberPrompt,
|
|
55
|
-
confirmPrompt, togglePrompt,
|
|
56
|
-
startPrompt, endPrompt, resultPrompt, nextStepsPrompt,
|
|
57
|
-
// ...hooks
|
|
58
|
-
createSpinner,
|
|
59
|
-
// ...launcher
|
|
60
|
-
createCli, defineCommand, defineArgs,
|
|
61
|
-
// ...types
|
|
62
|
-
// ...more
|
|
63
|
-
} from "@reliverse/rempts";
|
|
11
|
+
# Or install globally and use directly
|
|
12
|
+
bun add -g rempts
|
|
13
|
+
rempts my-cli
|
|
64
14
|
```
|
|
65
15
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
## Prompts
|
|
69
|
-
|
|
70
|
-
### Built-in Prompts
|
|
71
|
-
|
|
72
|
-
| Prompt | Description |
|
|
73
|
-
|---------------------------|-----------------------------------------------------------|
|
|
74
|
-
| `createSpinner` | Start/stop spinner |
|
|
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
|
-
| `resultPrompt` | Show results in a styled box |
|
|
82
|
-
| `nextStepsPrompt` | Show next steps in a styled list |
|
|
83
|
-
| `startPrompt`/`endPrompt` | Makes CLI start/end flows look nice |
|
|
84
|
-
| `datePrompt` | Date input with format validation |
|
|
85
|
-
| `anykeyPrompt` | Wait for any keypress |
|
|
86
|
-
|
|
87
|
-
### Aliases
|
|
88
|
-
|
|
89
|
-
To help you migrate from the different CLI frameworks, `@reliverse/rempts` has some aliases for the most popular prompts.
|
|
90
|
-
|
|
91
|
-
| Prompt | Aliases |
|
|
92
|
-
|-----------------------|------------------|
|
|
93
|
-
| `createCli` | `runMain` |
|
|
94
|
-
| `onCmdInit` | `setup` |
|
|
95
|
-
| `onCmdExit` | `cleanup` |
|
|
96
|
-
| `createSpinner` | `spinner` |
|
|
97
|
-
| `selectPrompt` | `select` |
|
|
98
|
-
| `multiselectPrompt` | `multiselect` |
|
|
99
|
-
| `inputPrompt` | `text`, `input` |
|
|
100
|
-
| `confirmPrompt` | `confirm` |
|
|
101
|
-
| `introPrompt` | `intro`, `start` |
|
|
102
|
-
| `outroPrompt` | `outro`, `end` |
|
|
103
|
-
| `log` | `relinka` |
|
|
104
|
-
|
|
105
|
-
### Prompts Usage Example
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
import { relinka } from "@reliverse/relinka";
|
|
109
|
-
|
|
110
|
-
import {
|
|
111
|
-
startPrompt,
|
|
112
|
-
inputPrompt,
|
|
113
|
-
selectPrompt,
|
|
114
|
-
defineCommand,
|
|
115
|
-
runMain
|
|
116
|
-
} from "@reliverse/rempts";
|
|
117
|
-
|
|
118
|
-
async function main() {
|
|
119
|
-
await startPrompt({ title: "Project Setup" });
|
|
120
|
-
|
|
121
|
-
const name = await inputPrompt({
|
|
122
|
-
message: "What's your project name?",
|
|
123
|
-
initialValue: "my-cool-project", // Pre-fills the input field
|
|
124
|
-
defaultValue: "untitled-project", // Used if user submits empty input
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
const spinner = createSpinner({
|
|
128
|
-
text: "Loading...",
|
|
129
|
-
indicator: "timer", // or "dots"
|
|
130
|
-
frames: ["◒", "◐", "◓", "◑"], // custom frames
|
|
131
|
-
delay: 80, // custom delay
|
|
132
|
-
onCancel: () => {
|
|
133
|
-
console.log("Operation cancelled");
|
|
134
|
-
},
|
|
135
|
-
cancelMessage: "Operation cancelled by user",
|
|
136
|
-
errorMessage: "Operation failed",
|
|
137
|
-
signal: abortController.signal,
|
|
138
|
-
}).start();
|
|
139
|
-
|
|
140
|
-
// The spinner will show:
|
|
141
|
-
// ◒ Loading... [5s]
|
|
142
|
-
// With animated frames and timer
|
|
143
|
-
|
|
144
|
-
const framework = await selectPrompt({
|
|
145
|
-
message: "Pick your framework",
|
|
146
|
-
options: [
|
|
147
|
-
{ value: "next", label: "Next.js" },
|
|
148
|
-
{ value: "svelte", label: "SvelteKit" },
|
|
149
|
-
{ value: "start", label: "TanStack Start" },
|
|
150
|
-
],
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
console.log("Your result:", { name, framework });
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
await main();
|
|
157
|
-
```
|
|
16
|
+
## Features
|
|
158
17
|
|
|
159
|
-
**
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| `errorMessage` | The message to display when the spinner fails |
|
|
167
|
-
| `failText` | The text to display when the spinner fails |
|
|
168
|
-
| `frames` | The frames to use for the spinner |
|
|
169
|
-
| `hideCursor` | Whether to hide the cursor |
|
|
170
|
-
| `indicator` | The indicator to use for the spinner |
|
|
171
|
-
| `onCancel` | The function to call when the spinner is cancelled |
|
|
172
|
-
| `prefixText` | The text to display before the spinner |
|
|
173
|
-
| `signal` | The signal to use for the spinner |
|
|
174
|
-
| `silent` | Whether to hide the spinner |
|
|
175
|
-
| `spinner` | The spinner to use for the spinner |
|
|
176
|
-
| `successText` | The text to display when the spinner succeeds |
|
|
177
|
-
| `text` | The text to display next to the spinner |
|
|
178
|
-
|
|
179
|
-
**Available indicator options:**
|
|
180
|
-
|
|
181
|
-
| Option | Description |
|
|
182
|
-
|--------|-------------|
|
|
183
|
-
| `timer` | The timer indicator |
|
|
184
|
-
| `dots` | The dots indicator |
|
|
185
|
-
|
|
186
|
-
**Available signal options:**
|
|
187
|
-
|
|
188
|
-
| Option | Description |
|
|
189
|
-
|--------|-------------|
|
|
190
|
-
| `abortController.signal` | The signal to use for the spinner |
|
|
191
|
-
|
|
192
|
-
**Available frames options:**
|
|
193
|
-
|
|
194
|
-
| Option | Description |
|
|
195
|
-
|--------|-------------|
|
|
196
|
-
| `["◒", "◐", "◓", "◑"]` | The frames to use for the spinner |
|
|
197
|
-
|
|
198
|
-
**Available delay options:**
|
|
199
|
-
|
|
200
|
-
| Option | Description |
|
|
201
|
-
|--------|-------------|
|
|
202
|
-
| `80` | The delay between frames |
|
|
203
|
-
|
|
204
|
-
**Available onCancel options:**
|
|
205
|
-
|
|
206
|
-
| Option | Description |
|
|
207
|
-
|--------|-------------|
|
|
208
|
-
| `() => { console.log("Operation cancelled"); }` | The function to call when the spinner is cancelled |
|
|
209
|
-
|
|
210
|
-
**Available inputPrompt options:**
|
|
211
|
-
|
|
212
|
-
| Option | Type | Description |
|
|
213
|
-
|--------|------|-------------|
|
|
214
|
-
| `message` | `string` | The prompt message (required) |
|
|
215
|
-
| `title` | `string` | Optional title. When both `title` and `message` are provided, title is shown first, then message (dimmed). When only `message` is provided, it acts as the title. |
|
|
216
|
-
| `charLimit` | `number` | Maximum character limit for input |
|
|
217
|
-
| `required` | `boolean` | Whether input is required (default: `true`). When `false`, cancelling the prompt returns an empty string instead of throwing `PromptCancelledError`. |
|
|
218
|
-
| `echoMode` | `"normal" \| "password" \| "none"` | Input echo mode (default: `"normal"`) |
|
|
219
|
-
| `validateOkPrefix` | `string` | Prefix to show when validation passes |
|
|
220
|
-
| `validateErrPrefix` | `string` | Prefix to show when validation fails |
|
|
221
|
-
| `defaultValue` | `string` | The value that is used if the user just presses Enter without typing anything. For input prompts, this is returned when the input field is empty. |
|
|
222
|
-
| `initialValue` | `string` | The value that is pre-filled in the input field when the prompt appears (user can edit it). This is different from `defaultValue` - `initialValue` is what the user sees and can modify, while `defaultValue` is what gets returned if they submit an empty input. |
|
|
223
|
-
| `validate` | `(value: string) => boolean \| string \| null \| undefined` | Custom validation function. Returns `true`, `null`, or `undefined` for valid input. Returns `false` or a string (error message) for invalid input. If validation fails, the prompt will re-prompt with the error message. |
|
|
224
|
-
|
|
225
|
-
> `inputPrompt` returns `Promise<string>`. When `required` is `true` (default), cancellation throws `PromptCancelledError`. When `required` is `false`, cancellation returns an empty string.
|
|
226
|
-
|
|
227
|
-
**Available selectPrompt options:**
|
|
228
|
-
|
|
229
|
-
| Option | Type | Description |
|
|
230
|
-
|--------|------|-------------|
|
|
231
|
-
| `message` | `string` | The prompt message (required) |
|
|
232
|
-
| `title` | `string` | Optional title. When both `title` and `message` are provided, title is shown first, then message (dimmed). When only `message` is provided, it acts as the title. |
|
|
233
|
-
| `options` | `readonly SelectionItem[]` | List of items with `value`, `label`, optional `hint`, and optional `disabled` |
|
|
234
|
-
| `perPage` | `number` | How many options to show per page (default: `5`) |
|
|
235
|
-
| `headerText` | `string` | Optional header text (defaults to formatted title/message) |
|
|
236
|
-
| `footerText` | `string` | Optional footer hint |
|
|
237
|
-
| `required` | `boolean` | When `false`, cancelling the prompt resolves to `null` instead of throwing |
|
|
238
|
-
| `autocomplete` | `boolean` | When `true` (default), users can type to jump between matching options |
|
|
239
|
-
| `defaultValue` | `string` | The value that is selected by default (if user presses Enter without changing selection). |
|
|
240
|
-
| `initialValue` | `string` | The value that the cursor starts on (user can navigate away). |
|
|
241
|
-
|
|
242
|
-
> `selectPrompt` has typed overloads: when `required` is omitted or `true`, it resolves to the selected value; when `required` is `false`, it resolves to either the selected value or `null`.
|
|
243
|
-
|
|
244
|
-
**Available multiselectPrompt options:**
|
|
245
|
-
|
|
246
|
-
| Option | Type | Description |
|
|
247
|
-
|--------|------|-------------|
|
|
248
|
-
| `message` | `string` | The prompt message (required) |
|
|
249
|
-
| `title` | `string` | Optional title. When both `title` and `message` are provided, title is shown first, then message (dimmed). When only `message` is provided, it acts as the title. |
|
|
250
|
-
| `options` | `readonly SelectionItem[]` | List of items with `value`, `label`, optional `hint`, and optional `disabled` |
|
|
251
|
-
| `perPage` | `number` | How many options to show per page (default: `5`) |
|
|
252
|
-
| `headerText` | `string` | Optional header text (defaults to formatted title/message) |
|
|
253
|
-
| `footerText` | `string` | Optional footer hint (defaults to usage instructions) |
|
|
254
|
-
| `required` | `boolean` | When `false`, cancelling the prompt resolves to `null`; otherwise a cancellation throws `PromptCancelledError` |
|
|
255
|
-
| `autocomplete` | `boolean` | When `true` (default), typing filters/highlights matching options |
|
|
256
|
-
| `defaultValue` | `string[]` | Array of values to pre-select (pre-checked items that user can unselect). If both `defaultValue` and `initialValue` are specified, `initialValue` is preferred. |
|
|
257
|
-
| `initialValue` | `string[]` | Array of values to pre-select (pre-checked items that user can unselect). Preferred over `defaultValue` if both are specified. The cursor starts on the first preselected value. |
|
|
258
|
-
|
|
259
|
-
> The resolved value is always an array of the selected option values. When `required` is `false`, the promise can resolve to `null` if the user cancels.
|
|
260
|
-
|
|
261
|
-
**Available confirmPrompt options:**
|
|
262
|
-
|
|
263
|
-
| Option | Type | Description |
|
|
264
|
-
|--------|------|-------------|
|
|
265
|
-
| `message` | `string` | The prompt message (required) |
|
|
266
|
-
| `title` | `string` | Optional title. When both `title` and `message` are provided, title is shown first, then message (dimmed). When only `message` is provided, it acts as the title. |
|
|
267
|
-
| `headerText` | `string` | Optional header text (defaults to formatted title/message) |
|
|
268
|
-
| `footerText` | `string` | Optional footer hint (defaults to navigation instructions) |
|
|
269
|
-
| `required` | `boolean` | When `false`, cancelling returns `defaultValue` or `false` instead of throwing; when `true` (default), cancelling throws `PromptCancelledError` |
|
|
270
|
-
| `defaultValue` | `boolean` | The value that is selected by default (if user presses Enter without changing selection). |
|
|
271
|
-
| `initialValue` | `boolean` | The value that the cursor starts on (user can navigate away). |
|
|
272
|
-
|
|
273
|
-
> `confirmPrompt` returns `Promise<boolean>`. When `required` is `true` (default), cancellation throws `PromptCancelledError`. When `required` is `false`, cancellation returns `defaultValue` or `false`.
|
|
274
|
-
|
|
275
|
-
**inputPrompt validation examples:**
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
// Simple boolean validation
|
|
279
|
-
const email = await inputPrompt({
|
|
280
|
-
message: "Enter your email:",
|
|
281
|
-
validate: (value) => {
|
|
282
|
-
if (!value.includes("@")) {
|
|
283
|
-
return "Please enter a valid email address";
|
|
284
|
-
}
|
|
285
|
-
return true; // or return null/undefined
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// Regex validation
|
|
290
|
-
const username = await inputPrompt({
|
|
291
|
-
message: "Enter username:",
|
|
292
|
-
validate: (value) => {
|
|
293
|
-
if (!/^[a-z0-9_]+$/.test(value)) {
|
|
294
|
-
return "Username must contain only lowercase letters, numbers, and underscores";
|
|
295
|
-
}
|
|
296
|
-
return true;
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
// Multiple validation rules
|
|
301
|
-
const password = await inputPrompt({
|
|
302
|
-
message: "Enter password:",
|
|
303
|
-
echoMode: "password",
|
|
304
|
-
validate: (value) => {
|
|
305
|
-
if (value.length < 8) {
|
|
306
|
-
return "Password must be at least 8 characters";
|
|
307
|
-
}
|
|
308
|
-
if (!/[A-Z]/.test(value)) {
|
|
309
|
-
return "Password must contain at least one uppercase letter";
|
|
310
|
-
}
|
|
311
|
-
if (!/[0-9]/.test(value)) {
|
|
312
|
-
return "Password must contain at least one number";
|
|
313
|
-
}
|
|
314
|
-
return true;
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
```
|
|
18
|
+
- 🚀 **Fast scaffolding** - Get started in seconds
|
|
19
|
+
- 📦 **Multiple templates** - Choose from basic, advanced, or monorepo setups
|
|
20
|
+
- 🔧 **TypeScript ready** - Full TypeScript support out of the box
|
|
21
|
+
- 🧪 **Testing included** - Comes with rempts-test for CLI testing
|
|
22
|
+
- 🎨 **Best practices** - Follows Rempts conventions and patterns
|
|
23
|
+
- 🌐 **Flexible sources** - Use bundled templates or any GitHub repository
|
|
24
|
+
- ⚡ **Type generation** - All templates include codegen for enhanced developer experience
|
|
318
25
|
|
|
319
|
-
##
|
|
26
|
+
## Usage
|
|
320
27
|
|
|
321
|
-
|
|
28
|
+
### Basic Usage
|
|
322
29
|
|
|
323
|
-
|
|
30
|
+
Create a new project with the default template:
|
|
324
31
|
|
|
325
32
|
```bash
|
|
326
|
-
|
|
327
|
-
bun dler rempts --init cmd1 cmd2 # creates `src/app/cmd1/cmd.ts` and `src/app/cmd2/cmd.ts` files
|
|
328
|
-
bun dler rempts # creates `src/app/cmds.ts` file
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Terminology
|
|
332
|
-
|
|
333
|
-
- **Launcher/Router**: The main entry point for your CLI. Visit [CLI Launcher (Router)](#cli-launcher-router) section to learn more.
|
|
334
|
-
- **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.
|
|
335
|
-
- **Argument**: An argument is a value that is passed to a command.
|
|
336
|
-
- **Flag**: A flag is a boolean argument that is used to enable or disable a feature.
|
|
337
|
-
- **Option**: An option is a named argument that is used to configure a command.
|
|
338
|
-
|
|
339
|
-
#### Launcher Usage Example
|
|
340
|
-
|
|
341
|
-
**Important**: Ensure your commands don't have `await main();`, `await createCli();`, or something like that — to prevent any unexpected behavior. Only main command should have it.
|
|
342
|
-
|
|
343
|
-
```ts
|
|
344
|
-
import { relinka } from "@reliverse/relinka";
|
|
345
|
-
|
|
346
|
-
import { defineCommand, createCli } from "@reliverse/rempts";
|
|
347
|
-
|
|
348
|
-
const main = defineCommand({
|
|
349
|
-
meta: {
|
|
350
|
-
name: "rempts",
|
|
351
|
-
version: "1.0.0",
|
|
352
|
-
description: "rempts Launcher Playground CLI",
|
|
353
|
-
},
|
|
354
|
-
onCmdInit() {
|
|
355
|
-
relinka("success", "Setup");
|
|
356
|
-
},
|
|
357
|
-
onCmdExit() {
|
|
358
|
-
relinka("success", "Cleanup");
|
|
359
|
-
},
|
|
360
|
-
commands: {
|
|
361
|
-
build: () => import("./app/build/cmd.js").then((r) => r.default),
|
|
362
|
-
deploy: () => import("./app/deploy/cmd.js").then((r) => r.default),
|
|
363
|
-
debug: () => import("./app/debug/cmd.js").then((r) => r.default),
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// New object format (recommended)
|
|
368
|
-
await createCli({
|
|
369
|
-
mainCommand: main,
|
|
370
|
-
fileBased: {
|
|
371
|
-
enable: true,
|
|
372
|
-
cmdsRootPath: "my-cmds", // default is `./app`
|
|
373
|
-
},
|
|
374
|
-
// Optionally disable auto-exit to handle errors manually:
|
|
375
|
-
autoExit: false,
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
// Legacy format (still supported)
|
|
379
|
-
await createCli(main, {
|
|
380
|
-
fileBased: {
|
|
381
|
-
enable: true,
|
|
382
|
-
cmdsRootPath: "my-cmds", // default is `./app`
|
|
383
|
-
},
|
|
384
|
-
// Optionally disable auto-exit to handle errors manually:
|
|
385
|
-
autoExit: false,
|
|
386
|
-
});
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
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.
|
|
390
|
-
|
|
391
|
-
#### File-Based Commands
|
|
392
|
-
|
|
393
|
-
Drop a `./src/cli/app/add/index.ts` and it's live.
|
|
394
|
-
|
|
395
|
-
```ts
|
|
396
|
-
import { defineArgs, defineCommand } from "@reliverse/rempts";
|
|
397
|
-
export default defineCommand({
|
|
398
|
-
meta: {
|
|
399
|
-
name: "add",
|
|
400
|
-
version: "1.0.0",
|
|
401
|
-
description: "Add stuff to your project",
|
|
402
|
-
},
|
|
403
|
-
args: {
|
|
404
|
-
name: defineArgs({ // 💡 PRO TIP: use defineArgs() to get fully correct intellisense
|
|
405
|
-
type: "string",
|
|
406
|
-
required: true,
|
|
407
|
-
description: "Name of what to add",
|
|
408
|
-
}),
|
|
409
|
-
},
|
|
410
|
-
async run({ args }) {
|
|
411
|
-
relinka("log", "Adding:", args.name);
|
|
412
|
-
},
|
|
413
|
-
});
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
**Supports**:
|
|
417
|
-
|
|
418
|
-
- `arg-cmdName.{ts,js}`,
|
|
419
|
-
- `cmdName/index.{ts,js}`,
|
|
420
|
-
- `cmdName/cmdName-mod.{ts,js}`,
|
|
421
|
-
- **Multi-level subcommands:** `foo/bar/baz/cmd.ts` → `my-cli foo bar baz`
|
|
422
|
-
- And more — with automatic usage output.
|
|
423
|
-
|
|
424
|
-
**Hint**:
|
|
425
|
-
|
|
426
|
-
- Install `bun add -D @reliverse/dler`
|
|
427
|
-
- Use `bun dler rempts --init cmd1 cmd2` to init commands for rempts launcher's automatically
|
|
428
|
-
|
|
429
|
-
### Advanced Launcher Usage
|
|
430
|
-
|
|
431
|
-
```ts
|
|
432
|
-
defineCommand({
|
|
433
|
-
meta: { name: "cli", version: "1.0.0" },
|
|
434
|
-
args: {
|
|
435
|
-
name: { type: "string", required: true },
|
|
436
|
-
verbose: { type: "boolean", default: false },
|
|
437
|
-
animals: { type: "array", default: ["cat","dog"] },
|
|
438
|
-
},
|
|
439
|
-
async run({ args, raw }) { // or `async run(ctx)`
|
|
440
|
-
relinka("log", args.name, args.verbose, args.animals); // or `relinka("log", ctx.args.name, ...);`
|
|
441
|
-
},
|
|
442
|
-
});
|
|
33
|
+
bunx @reliverse/rempts my-cli
|
|
443
34
|
```
|
|
444
35
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
- `positional` args
|
|
448
|
-
- `array` types (`--tag foo --tag bar`)
|
|
449
|
-
- Default values, validations, descriptions
|
|
450
|
-
- Full help rendering from metadata
|
|
36
|
+
### Using Templates
|
|
451
37
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
You can also nest subcommands arbitrarily deep:
|
|
38
|
+
Choose from bundled templates:
|
|
455
39
|
|
|
456
40
|
```bash
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
bar/
|
|
460
|
-
baz/
|
|
461
|
-
cmd.ts
|
|
462
|
-
```
|
|
41
|
+
# Basic single-command CLI
|
|
42
|
+
bunx @reliverse/rempts my-cli --template basic
|
|
463
43
|
|
|
464
|
-
|
|
44
|
+
# Advanced multi-command CLI with subcommands
|
|
45
|
+
bunx @reliverse/rempts my-cli --template advanced
|
|
465
46
|
|
|
466
|
-
|
|
467
|
-
my-cli
|
|
47
|
+
# Monorepo setup with Turborepo
|
|
48
|
+
bunx @reliverse/rempts my-cli --template monorepo
|
|
468
49
|
```
|
|
469
50
|
|
|
470
|
-
|
|
51
|
+
### Using External Templates
|
|
471
52
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
When playing with the example, you can run e.g. `bun dev:modern nested foo bar baz` to see the result in action.
|
|
475
|
-
|
|
476
|
-
### Playground
|
|
53
|
+
Use any GitHub repository as a template:
|
|
477
54
|
|
|
478
55
|
```bash
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
bun i
|
|
482
|
-
bun dev
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
- `bun dev:prompts`: This example will show you a `multiselectPrompt()` where you can choose which CLI prompts you want to play with.
|
|
486
|
-
- `bun dev:modern`: This example will show you a modern CLI launcher usage with file-based commands.
|
|
487
|
-
- `bun dev:classic`: This example will show you a classic CLI launcher usage with programmatic commands.
|
|
488
|
-
|
|
489
|
-
### Launcher Usage Examples
|
|
490
|
-
|
|
491
|
-
#### Minimal Usage Example
|
|
56
|
+
# GitHub repository
|
|
57
|
+
bunx @reliverse/rempts my-cli --template username/repo
|
|
492
58
|
|
|
493
|
-
|
|
59
|
+
# With full GitHub URL
|
|
60
|
+
bunx @reliverse/rempts my-cli --template github:username/repo
|
|
494
61
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
// New object format (recommended)
|
|
499
|
-
await createCli({
|
|
500
|
-
mainCommand: defineCommand({}),
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
// Legacy format (still supported)
|
|
504
|
-
await createCli(defineCommand({}));
|
|
62
|
+
# Specific branch or tag
|
|
63
|
+
bunx @reliverse/rempts my-cli --template username/repo#branch
|
|
505
64
|
```
|
|
506
65
|
|
|
507
|
-
|
|
66
|
+
### Options
|
|
508
67
|
|
|
509
68
|
```bash
|
|
510
|
-
|
|
511
|
-
bun dler rempts --init my-cmd-1 # or: dler rempts --init my-cmd-1 my-cmd-2 --main src/mod.ts
|
|
512
|
-
# * `--main` is optional, default is `./src/mod.ts`
|
|
513
|
-
# * you can specify multiple commands at once
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
**3 Visit `src/app/my-cmd-1/mod.ts` and edit it:**
|
|
69
|
+
bunx @reliverse/rempts [name] [options]
|
|
517
70
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
71
|
+
Options:
|
|
72
|
+
-t, --template <template> Project template (default: "basic")
|
|
73
|
+
-d, --dir <dir> Directory to create project in
|
|
74
|
+
-g, --git Initialize git repository (default: true)
|
|
75
|
+
-i, --install Install dependencies (default: true)
|
|
76
|
+
--offline Use cached templates when available
|
|
77
|
+
-h, --help Display help
|
|
78
|
+
-v, --version Display version
|
|
522
79
|
```
|
|
523
80
|
|
|
524
|
-
|
|
81
|
+
### Examples
|
|
525
82
|
|
|
526
83
|
```bash
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
#### Medium Usage Example
|
|
531
|
-
|
|
532
|
-
```ts
|
|
533
|
-
import { defineCommand, createCli } from "@reliverse/rempts";
|
|
84
|
+
# Create in current directory
|
|
85
|
+
bunx @reliverse/rempts .
|
|
534
86
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
name: "mycli",
|
|
538
|
-
},
|
|
539
|
-
run() {
|
|
540
|
-
console.log("Happy, Reliversing!");
|
|
541
|
-
},
|
|
542
|
-
});
|
|
87
|
+
# Create without installing dependencies
|
|
88
|
+
bunx @reliverse/rempts my-cli --no-install
|
|
543
89
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
mainCommand: main,
|
|
547
|
-
});
|
|
90
|
+
# Create in custom directory
|
|
91
|
+
bunx @reliverse/rempts my-cli --dir ~/projects/my-cli
|
|
548
92
|
|
|
549
|
-
|
|
550
|
-
|
|
93
|
+
# Use external template
|
|
94
|
+
bunx @reliverse/rempts my-cli --template pvtnbr/rempts-starter
|
|
551
95
|
```
|
|
552
96
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
```ts
|
|
556
|
-
import { relinka } from "@reliverse/relinka";
|
|
557
|
-
|
|
558
|
-
import {
|
|
559
|
-
startPrompt,
|
|
560
|
-
inputPrompt,
|
|
561
|
-
selectPrompt,
|
|
562
|
-
defineCommand,
|
|
563
|
-
createCli
|
|
564
|
-
} from "@reliverse/rempts";
|
|
565
|
-
|
|
566
|
-
const main = defineCommand({
|
|
567
|
-
meta: {
|
|
568
|
-
name: "mycli",
|
|
569
|
-
version: "1.0.0",
|
|
570
|
-
description: "CLI powered by rempts",
|
|
571
|
-
},
|
|
572
|
-
args: {
|
|
573
|
-
name: {
|
|
574
|
-
type: "string",
|
|
575
|
-
required: true,
|
|
576
|
-
description: "The name of the project",
|
|
577
|
-
},
|
|
578
|
-
},
|
|
579
|
-
async run({ args }) {
|
|
580
|
-
await startPrompt({
|
|
581
|
-
title: "Project Setup",
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const name = await inputPrompt({
|
|
585
|
-
message: "What's your project name?",
|
|
586
|
-
defaultValue: args.name,
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const framework = await selectPrompt({
|
|
590
|
-
message: "Pick your framework",
|
|
591
|
-
options: [
|
|
592
|
-
{ value: "next", label: "Next.js" },
|
|
593
|
-
{ value: "svelte", label: "SvelteKit" },
|
|
594
|
-
{ value: "start", label: "TanStack Start" },
|
|
595
|
-
],
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
relinka("log", "You have selected:", { name, framework });
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// New object format (recommended)
|
|
603
|
-
await createCli({
|
|
604
|
-
mainCommand: main,
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
// Legacy format (still supported)
|
|
608
|
-
await createCli(main);
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
#### Advanced Usage Example
|
|
612
|
-
|
|
613
|
-
```ts
|
|
614
|
-
import { relinka } from "@reliverse/relinka";
|
|
615
|
-
|
|
616
|
-
import {
|
|
617
|
-
startPrompt,
|
|
618
|
-
inputPrompt,
|
|
619
|
-
selectPrompt,
|
|
620
|
-
defineCommand,
|
|
621
|
-
runMain,
|
|
622
|
-
} from "@reliverse/rempts";
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Main command defined using `defineCommand()`.
|
|
626
|
-
*
|
|
627
|
-
* This command demonstrates the full range of launcher features along with all supported argument types:
|
|
628
|
-
*
|
|
629
|
-
* - Global Usage Handling: Automatically processes `--help` and `--version`.
|
|
630
|
-
* - File-Based Commands: Scans "app" for commands (e.g., `init`).
|
|
631
|
-
* - Comprehensive Argument Parsing: Supports positional, boolean, string, number, and array arguments.
|
|
632
|
-
* - Interactive Prompts: Uses built-in prompt functions for an engaging CLI experience.
|
|
633
|
-
*/
|
|
634
|
-
const mainCommand = defineCommand({
|
|
635
|
-
meta: {
|
|
636
|
-
name: "rempts",
|
|
637
|
-
version: "1.6.0",
|
|
638
|
-
description:
|
|
639
|
-
"An example CLI that supports file-based commands and all argument types.",
|
|
640
|
-
},
|
|
641
|
-
args: {
|
|
642
|
-
// Positional arguments
|
|
643
|
-
inputFile: {
|
|
644
|
-
type: "positional",
|
|
645
|
-
description: "Path to the input file (only for the main command).",
|
|
646
|
-
},
|
|
647
|
-
config: {
|
|
648
|
-
type: "positional",
|
|
649
|
-
description: "Path to the configuration file.",
|
|
650
|
-
},
|
|
651
|
-
// Boolean arguments
|
|
652
|
-
verbose: {
|
|
653
|
-
type: "boolean",
|
|
654
|
-
default: false,
|
|
655
|
-
description: "Whether to print verbose logs in the main command.",
|
|
656
|
-
},
|
|
657
|
-
debug: {
|
|
658
|
-
type: "boolean",
|
|
659
|
-
default: false,
|
|
660
|
-
description: "Enable debug mode for additional logging.",
|
|
661
|
-
},
|
|
662
|
-
// String argument
|
|
663
|
-
name: {
|
|
664
|
-
type: "string",
|
|
665
|
-
description: "The name of the project.",
|
|
666
|
-
},
|
|
667
|
-
// Number argument
|
|
668
|
-
timeout: {
|
|
669
|
-
type: "number",
|
|
670
|
-
default: 30,
|
|
671
|
-
description: "Timeout in seconds for the CLI operation.",
|
|
672
|
-
},
|
|
673
|
-
// Array argument
|
|
674
|
-
tags: {
|
|
675
|
-
type: "array",
|
|
676
|
-
default: ["cli", "rempts"],
|
|
677
|
-
description: "List of tags associated with the project.",
|
|
678
|
-
},
|
|
679
|
-
},
|
|
680
|
-
async run({ args, raw }) {
|
|
681
|
-
// Display invocation details and parsed arguments.
|
|
682
|
-
relinka("log", "Main command was invoked!");
|
|
683
|
-
relinka("log", "Parsed main-command args:", args);
|
|
684
|
-
relinka("log", "Raw argv:", raw);
|
|
685
|
-
relinka("log", "\nHelp: `rempts --help`, `rempts cmdName --help`");
|
|
686
|
-
|
|
687
|
-
// Begin interactive session with a prompt.
|
|
688
|
-
await startPrompt({
|
|
689
|
-
title: "Project Setup",
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
// Ask for the project name, falling back to provided argument or a default.
|
|
693
|
-
const projectName = await inputPrompt({
|
|
694
|
-
message: "What's your project name?",
|
|
695
|
-
defaultValue: args.name ?? "my-cool-cli",
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
// Let the user pick a framework from a select prompt.
|
|
699
|
-
const framework = await selectPrompt({
|
|
700
|
-
message: "Pick your framework",
|
|
701
|
-
options: [
|
|
702
|
-
{ value: "next", label: "Next.js" },
|
|
703
|
-
{ value: "svelte", label: "SvelteKit" },
|
|
704
|
-
{ value: "start", label: "TanStack Start" },
|
|
705
|
-
],
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
// Log all gathered input details.
|
|
709
|
-
relinka("log", "You have selected:", {
|
|
710
|
-
projectName,
|
|
711
|
-
framework,
|
|
712
|
-
inputFile: args.inputFile,
|
|
713
|
-
config: args.config,
|
|
714
|
-
verbose: args.verbose,
|
|
715
|
-
debug: args.debug,
|
|
716
|
-
timeout: args.timeout,
|
|
717
|
-
tags: args.tags,
|
|
718
|
-
});
|
|
719
|
-
},
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* The `createCli()` function sets up the launcher with several advanced features:
|
|
724
|
-
*
|
|
725
|
-
* - File-Based Commands: Enables scanning for commands within the "app" directory.
|
|
726
|
-
* - Alias Mapping: Shorthand flags (e.g., `-v`) are mapped to their full names (e.g., `--verbose`).
|
|
727
|
-
* - Strict Mode & Unknown Flag Warnings: Unknown flags are either warned about or handled via a callback.
|
|
728
|
-
* - Negated Boolean Support: Allows flags to be negated (e.g., `--no-verbose`).
|
|
729
|
-
* - Custom Unknown Flag Handler: Provides custom handling for unrecognized flags.
|
|
730
|
-
*/
|
|
731
|
-
// New object format (recommended)
|
|
732
|
-
await createCli({
|
|
733
|
-
mainCommand: mainCommand,
|
|
734
|
-
fileBased: {
|
|
735
|
-
enable: true, // Enables file-based command detection.
|
|
736
|
-
cmdsRootPath: "app", // Directory to scan for commands.
|
|
737
|
-
},
|
|
738
|
-
alias: {
|
|
739
|
-
v: "verbose", // Maps shorthand flag -v to --verbose.
|
|
740
|
-
},
|
|
741
|
-
strict: false, // Do not throw errors for unknown flags.
|
|
742
|
-
warnOnUnknown: false, // Warn when encountering unknown flags.
|
|
743
|
-
negatedBoolean: true, // Support for negated booleans (e.g., --no-verbose).
|
|
744
|
-
// unknown: (flagName) => {
|
|
745
|
-
// relinka("warn", "Unknown flag encountered:", flagName);
|
|
746
|
-
// return false;
|
|
747
|
-
// },
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
// Legacy format (still supported)
|
|
751
|
-
await createCli(mainCommand, {
|
|
752
|
-
fileBased: {
|
|
753
|
-
enable: true, // Enables file-based command detection.
|
|
754
|
-
cmdsRootPath: "app", // Directory to scan for commands.
|
|
755
|
-
},
|
|
756
|
-
alias: {
|
|
757
|
-
v: "verbose", // Maps shorthand flag -v to --verbose.
|
|
758
|
-
},
|
|
759
|
-
strict: false, // Do not throw errors for unknown flags.
|
|
760
|
-
warnOnUnknown: false, // Warn when encountering unknown flags.
|
|
761
|
-
negatedBoolean: true, // Support for negated booleans (e.g., --no-verbose).
|
|
762
|
-
// unknown: (flagName) => {
|
|
763
|
-
// relinka("warn", "Unknown flag encountered:", flagName);
|
|
764
|
-
// return false;
|
|
765
|
-
// },
|
|
766
|
-
});
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
### CLI Launcher (Router)
|
|
770
|
-
|
|
771
|
-
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:
|
|
772
|
-
|
|
773
|
-
- **File-Based & Defined Commands:**
|
|
774
|
-
Use `commands` in your command definition or let the launcher automatically load commands from a specified directory.
|
|
97
|
+
## Templates
|
|
775
98
|
|
|
776
|
-
|
|
777
|
-
The launcher scans your specified `cmdsRootPath` for command files matching common patterns such as:
|
|
778
|
-
- `arg-cmdName.{ts,js}`
|
|
779
|
-
- `cmdName/index.{ts,js}`
|
|
780
|
-
- `cmdName/cmdName-mod.{ts,js}`
|
|
781
|
-
- And more — with automatic usage output if a command file is not found.
|
|
99
|
+
### Basic Template
|
|
782
100
|
|
|
783
|
-
|
|
784
|
-
Automatically processes global flags such as:
|
|
785
|
-
- `--help` and `-h` to show usage details.
|
|
786
|
-
- `--version` and `-v` to display version information.
|
|
787
|
-
- `--debug` for verbose logging during development.
|
|
101
|
+
Perfect for simple CLI tools with a single command.
|
|
788
102
|
|
|
789
|
-
|
|
790
|
-
Seamlessly combines positional and named arguments with zero configuration, auto-parsing booleans, strings, numbers, arrays, and even supporting negated flags like `--no-flag`.
|
|
103
|
+
**Features:**
|
|
791
104
|
|
|
792
|
-
-
|
|
793
|
-
|
|
105
|
+
- Single command setup
|
|
106
|
+
- TypeScript configuration
|
|
107
|
+
- Test setup with rempts-test
|
|
108
|
+
- Build script using rempts
|
|
794
109
|
|
|
795
|
-
|
|
796
|
-
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.
|
|
797
|
-
|
|
798
|
-
- **Lifecycle Hooks:**
|
|
799
|
-
You can define optional lifecycle hooks in your main command:
|
|
800
|
-
- `onLauncherInit` and `onLauncherExit` (global, called once per CLI process)
|
|
801
|
-
- `onCmdInit` and `onCmdExit` (per-command, called before/after each command, but NOT for the main `run()` handler)
|
|
802
|
-
|
|
803
|
-
**Global Hooks:**
|
|
804
|
-
- `onLauncherInit`: Called once, before any command/run() is executed.
|
|
805
|
-
- `onLauncherExit`: Called once, after all command/run() logic is finished (even if an error occurs).
|
|
806
|
-
|
|
807
|
-
**Per-Command Hooks:**
|
|
808
|
-
- `onCmdInit`: Called before each command (not for main `run()`).
|
|
809
|
-
- `onCmdExit`: Called after each command (not for main `run()`).
|
|
810
|
-
|
|
811
|
-
This means:
|
|
812
|
-
- If your CLI has multiple commands, `onCmdInit` and `onCmdExit` will be called for each command invocation, not just once for the whole CLI process.
|
|
813
|
-
- 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.
|
|
814
|
-
- This allows you to perform setup/teardown logic specific to each command execution.
|
|
815
|
-
- If you want logic to run only once for the entire CLI process, use `onLauncherInit` and `onLauncherExit`.
|
|
816
|
-
|
|
817
|
-
**Example:**
|
|
818
|
-
|
|
819
|
-
```ts
|
|
820
|
-
const main = defineCommand({
|
|
821
|
-
onLauncherInit() { relinka('info', 'Global setup (once per process)'); },
|
|
822
|
-
onLauncherExit() { relinka('info', 'Global cleanup (once per process)'); },
|
|
823
|
-
onCmdInit() { relinka('info', 'Setup for each command'); },
|
|
824
|
-
onCmdExit() { relinka('info', 'Cleanup for each command'); },
|
|
825
|
-
commands: { ... },
|
|
826
|
-
run() { relinka('info', 'Main run handler (no command)'); },
|
|
827
|
-
});
|
|
828
|
-
// onLauncherInit/onLauncherExit are called once per process
|
|
829
|
-
// onCmdInit/onCmdExit are called for every command (not for main run())
|
|
830
|
-
// If you want per-run() logic, use the run() handler or global hooks
|
|
831
|
-
```
|
|
832
|
-
|
|
833
|
-
- **Dynamic Usage Examples:**
|
|
834
|
-
- 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.
|
|
835
|
-
|
|
836
|
-
- **File-Based & Programmatic Commands:**
|
|
837
|
-
- Both file-based and object commands are fully supported. The launcher can introspect their argument definitions and metadata for help, usage, and validation.
|
|
838
|
-
- File-based commands are auto-discovered from your filesystem, while programmatic commands can be defined inline in your main command.
|
|
839
|
-
|
|
840
|
-
- **Context-Aware Help Output:**
|
|
841
|
-
- 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.
|
|
842
|
-
|
|
843
|
-
- **Error Handling:**
|
|
844
|
-
- 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.
|
|
845
|
-
|
|
846
|
-
- **Unified Argument Parsing:**
|
|
847
|
-
- All arguments (positional, named, boolean, string, number, array) are parsed and validated automatically. Negated flags (like `--no-flag`) are supported out of the box.
|
|
848
|
-
|
|
849
|
-
- **Extensible & Flexible:**
|
|
850
|
-
- 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.
|
|
851
|
-
|
|
852
|
-
- **Bun & Node.js Support:**
|
|
853
|
-
- The launcher is designed to work in both Bun and Node.js environments, so you can use it in any modern JavaScript/TypeScript project.
|
|
854
|
-
|
|
855
|
-
- **Prompt-First, Modern UX:**
|
|
856
|
-
- The launcher integrates tightly with the prompt engine, so you can build interactive, delightful CLIs with minimal effort.
|
|
857
|
-
|
|
858
|
-
### Launcher Programmatic Execution
|
|
859
|
-
|
|
860
|
-
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. Example:
|
|
861
|
-
|
|
862
|
-
```ts
|
|
863
|
-
// example/launcher/app/runcmd/cmd.ts
|
|
864
|
-
|
|
865
|
-
import { relinka } from "@reliverse/relinka";
|
|
866
|
-
import { defineArgs, defineCommand, runCmd } from "@reliverse/rempts";
|
|
867
|
-
import { cmdMinimal } from "../cmds";
|
|
868
|
-
|
|
869
|
-
export default defineCommand({
|
|
870
|
-
meta: {
|
|
871
|
-
name: "runcmd",
|
|
872
|
-
description:
|
|
873
|
-
"Demonstrate how to use runCmd() to invoke another command programmatically.",
|
|
874
|
-
},
|
|
875
|
-
args: defineArgs({
|
|
876
|
-
name: {
|
|
877
|
-
type: "string",
|
|
878
|
-
description: "your name",
|
|
879
|
-
},
|
|
880
|
-
}),
|
|
881
|
-
async run({ args }) {
|
|
882
|
-
// const username = args.name ?? "Alice";
|
|
883
|
-
const username = args.name; // intentionally missing fallback
|
|
884
|
-
relinka(
|
|
885
|
-
"info",
|
|
886
|
-
`Running the 'minimal' command using runCmd() with name='${username}'`,
|
|
887
|
-
);
|
|
888
|
-
await runCmd(await cmdMinimal(), ["--name", username]);
|
|
889
|
-
relinka("log", "Done running 'minimal' via runCmd().");
|
|
890
|
-
},
|
|
891
|
-
});
|
|
892
|
-
```
|
|
893
|
-
|
|
894
|
-
### Using `runCmd` with Flexible Argument Handling
|
|
895
|
-
|
|
896
|
-
The `runCmd` function supports flexible argument passing, automatically normalizing template literals and space-separated strings:
|
|
897
|
-
|
|
898
|
-
```ts
|
|
899
|
-
import { runCmd } from "@reliverse/rempts";
|
|
900
|
-
|
|
901
|
-
// Traditional way - each argument as separate array element
|
|
902
|
-
await runCmd(cmd, ["--dev", "true", "--name", "John"]);
|
|
903
|
-
|
|
904
|
-
// Template literals work automatically
|
|
905
|
-
await runCmd(cmd, [`--dev ${isDev}`]); // Automatically converted to ["--dev", "true"]
|
|
906
|
-
await runCmd(cmd, [`--dev ${isDev} --build mod.ts`]); // ["--dev", "true", "--build", "mod.ts"]
|
|
907
|
-
|
|
908
|
-
// Mixed arrays with template literals and regular strings
|
|
909
|
-
await runCmd(cmd, [
|
|
910
|
-
`--dev ${isDev} --build mod.ts`,
|
|
911
|
-
"--pub true",
|
|
912
|
-
"--someBoolean",
|
|
913
|
-
]);
|
|
914
|
-
|
|
915
|
-
// Multiple template literals
|
|
916
|
-
await runCmd(cmd, [`--dev ${isDev}`, `--name ${userName}`, `--count ${count}`]);
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
**Remember**:
|
|
920
|
-
|
|
921
|
-
- If you need to pass a value with spaces (e.g. a name like "John Doe"), you should quote it in your template literal: `await runCmd(cmd, ['--name "John Doe"']);`
|
|
922
|
-
- Otherwise, it will be split into two arguments: `"John"` and `"Doe"`.
|
|
923
|
-
- We do not handle this intentionally, because some library users might rely on this Node.js behavior and handle it themselves in their own way (e.g. space can serve as a separator for values).
|
|
924
|
-
|
|
925
|
-
### Loading Commands with `loadCommand`
|
|
926
|
-
|
|
927
|
-
The `loadCommand` utility helps you load command files from your filesystem. It automatically handles:
|
|
928
|
-
|
|
929
|
-
- Relative paths (both `./build` and `build` work the same)
|
|
930
|
-
- Automatic detection of `cmd.{ts,js}` files
|
|
931
|
-
- Clear error messages when files are not found
|
|
932
|
-
|
|
933
|
-
```ts
|
|
934
|
-
import { loadCommand } from "@reliverse/rempts";
|
|
935
|
-
|
|
936
|
-
// These are equivalent:
|
|
937
|
-
const cmd1 = await loadCommand("./build"); // Looks for build/cmd.ts or build/cmd.js
|
|
938
|
-
const cmd2 = await loadCommand("build"); // Same as above
|
|
939
|
-
const cmd3 = await loadCommand("./build/cmd"); // Explicit path to cmd file
|
|
940
|
-
|
|
941
|
-
// You can then use the loaded command with runCmd:
|
|
942
|
-
await runCmd(cmd1, ["--some-flag"]);
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
```ts
|
|
946
|
-
// src/app/cmds.ts
|
|
947
|
-
export const getBuildCmd = async (): Promise<Command> => loadCommand("./build");
|
|
948
|
-
|
|
949
|
-
// src/cli.ts
|
|
950
|
-
import { runCmd } from "@reliverse/rempts";
|
|
951
|
-
import { getBuildCmd } from "./app/cmds";
|
|
952
|
-
await runCmd(await getBuildCmd(), ["--prod"]);
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
**Error Handling:**
|
|
956
|
-
If the command file is not found, you'll get a clear error message:
|
|
110
|
+
**Structure:**
|
|
957
111
|
|
|
958
112
|
```bash
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
113
|
+
my-cli/
|
|
114
|
+
├── src/
|
|
115
|
+
│ ├── mod.ts # CLI entry point
|
|
116
|
+
│ └── commands/
|
|
117
|
+
│ └── hello.ts # Example command
|
|
118
|
+
├── test/
|
|
119
|
+
│ └── hello.test.ts # Example test
|
|
120
|
+
├── package.json
|
|
121
|
+
├── tsconfig.json
|
|
122
|
+
└── README.md
|
|
963
123
|
```
|
|
964
124
|
|
|
965
|
-
|
|
125
|
+
### Advanced Template
|
|
966
126
|
|
|
967
|
-
|
|
968
|
-
- Use `runCmd` to execute the loaded command with arguments
|
|
969
|
-
- Keep your command files in a consistent location (e.g., `src/app/yourCmdName/cmd.ts`)
|
|
970
|
-
- Export commands from a central file like `src/app/cmds.ts` for better organization
|
|
127
|
+
For complex CLIs with multiple commands and features.
|
|
971
128
|
|
|
972
|
-
|
|
973
|
-
// example/launcher/app/cmds.ts
|
|
974
|
-
import { loadCommand } from "@reliverse/rempts";
|
|
129
|
+
**Features:**
|
|
975
130
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
return loadCommand("./deploy");
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Usage:
|
|
985
|
-
import { getBuildCmd } from "./cmds";
|
|
986
|
-
const buildCmd = await getBuildCmd();
|
|
987
|
-
await runCmd(buildCmd, ["--prod"]);
|
|
988
|
-
```
|
|
989
|
-
|
|
990
|
-
```ts
|
|
991
|
-
// example/launcher/app/minimal/cmd.ts
|
|
992
|
-
|
|
993
|
-
import { relinka } from "@reliverse/relinka";
|
|
994
|
-
import { defineArgs, defineCommand } from "@reliverse/rempts";
|
|
995
|
-
|
|
996
|
-
export default defineCommand({
|
|
997
|
-
meta: {
|
|
998
|
-
name: "minimal",
|
|
999
|
-
description: "hello world",
|
|
1000
|
-
},
|
|
1001
|
-
args: defineArgs({
|
|
1002
|
-
name: {
|
|
1003
|
-
type: "string",
|
|
1004
|
-
description: "your name",
|
|
1005
|
-
required: true,
|
|
1006
|
-
},
|
|
1007
|
-
}),
|
|
1008
|
-
run({ args }) {
|
|
1009
|
-
relinka("success", `👋 Hello, ${args.name}!`);
|
|
1010
|
-
},
|
|
1011
|
-
});
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
### Using `runCmdWithSubcommands` for Subcommands and Nested Subcommands
|
|
1015
|
-
|
|
1016
|
-
If you need to programmatically run commands that support subcommands (including nested subcommands), use `runCmdWithSubcommands`:
|
|
1017
|
-
|
|
1018
|
-
```ts
|
|
1019
|
-
import { runCmdWithSubcommands } from "@reliverse/rempts";
|
|
131
|
+
- Multiple commands with subcommands
|
|
132
|
+
- Configuration management
|
|
133
|
+
- File validation system
|
|
134
|
+
- Built-in development server
|
|
135
|
+
- Advanced command examples
|
|
1020
136
|
|
|
1021
|
-
|
|
1022
|
-
await runCmdWithSubcommands(mainCmd, [`build --input src/mod.ts --someBoolean`]);
|
|
137
|
+
**Commands included:**
|
|
1023
138
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
await runCmdWithSubcommands(mainCmd, [`build someSubCmd src/mod.ts --no-cjs`]);
|
|
1029
|
-
await runCmdWithSubcommands(mainCmd, [`build sub1 sub2 sub3 file.ts --flag`]);
|
|
1030
|
-
|
|
1031
|
-
// Mixed array with subcommands
|
|
1032
|
-
await runCmdWithSubcommands(mainCmd, [
|
|
1033
|
-
`build someSubCmd src/mod.ts`,
|
|
1034
|
-
"--no-cjs",
|
|
1035
|
-
"--verbose"
|
|
1036
|
-
]);
|
|
1037
|
-
```
|
|
139
|
+
- `init` - Initialize configuration
|
|
140
|
+
- `validate` - Validate files with rules
|
|
141
|
+
- `serve` - Start development server
|
|
142
|
+
- `config` - Manage configuration
|
|
1038
143
|
|
|
1039
|
-
**
|
|
1040
|
-
|
|
1041
|
-
- `runCmdWithSubcommands` automatically normalizes template literals and space-separated strings, just like `runCmd`.
|
|
1042
|
-
- If you need to pass a value with spaces (e.g. a name like "John Doe"), you should quote it in your template literal: `await runCmdWithSubcommands(cmd, ['--name "John Doe"']);`
|
|
1043
|
-
- For subcommands, always use `runCmdWithSubcommands` for the most robust behavior.
|
|
1044
|
-
|
|
1045
|
-
## Argument Types: Usage Comparison
|
|
1046
|
-
|
|
1047
|
-
Below is a demonstration of how to define and use all supported argument types in rempts: positional, boolean, string, number, and array. This includes example CLI invocations and the resulting parsed output.
|
|
1048
|
-
|
|
1049
|
-
```ts
|
|
1050
|
-
import { defineCommand, createCli } from "@reliverse/rempts";
|
|
1051
|
-
|
|
1052
|
-
const main = defineCommand({
|
|
1053
|
-
meta: {
|
|
1054
|
-
name: "mycli",
|
|
1055
|
-
version: "1.0.0",
|
|
1056
|
-
description: "Demo of all argument types",
|
|
1057
|
-
},
|
|
1058
|
-
args: {
|
|
1059
|
-
// Positional argument (required)
|
|
1060
|
-
input: {
|
|
1061
|
-
type: "positional",
|
|
1062
|
-
required: true,
|
|
1063
|
-
description: "Input file path",
|
|
1064
|
-
},
|
|
1065
|
-
// Boolean flag (default: false)
|
|
1066
|
-
verbose: {
|
|
1067
|
-
type: "boolean",
|
|
1068
|
-
default: false,
|
|
1069
|
-
description: "Enable verbose output",
|
|
1070
|
-
},
|
|
1071
|
-
// String option (optional)
|
|
1072
|
-
name: {
|
|
1073
|
-
type: "string",
|
|
1074
|
-
description: "Your name",
|
|
1075
|
-
},
|
|
1076
|
-
// Number option (optional, with default)
|
|
1077
|
-
count: {
|
|
1078
|
-
type: "number",
|
|
1079
|
-
default: 1,
|
|
1080
|
-
description: "How many times to run",
|
|
1081
|
-
},
|
|
1082
|
-
// Array option (can be repeated, accepts any value)
|
|
1083
|
-
tags: {
|
|
1084
|
-
type: "array",
|
|
1085
|
-
default: ["demo"],
|
|
1086
|
-
description: "Tags for this run (repeatable)",
|
|
1087
|
-
},
|
|
1088
|
-
},
|
|
1089
|
-
run({ args }) {
|
|
1090
|
-
console.log("Parsed args:", args);
|
|
1091
|
-
},
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
// New object format (recommended)
|
|
1095
|
-
await createCli({
|
|
1096
|
-
mainCommand: main,
|
|
1097
|
-
});
|
|
1098
|
-
|
|
1099
|
-
// Legacy format (still supported)
|
|
1100
|
-
await createCli(main);
|
|
1101
|
-
```
|
|
1102
|
-
|
|
1103
|
-
### Example CLI Invocations
|
|
1104
|
-
|
|
1105
|
-
#### 1. Positional argument
|
|
1106
|
-
|
|
1107
|
-
```bash
|
|
1108
|
-
mycli input.txt
|
|
1109
|
-
# → args.input = "input.txt"
|
|
1110
|
-
```
|
|
1111
|
-
|
|
1112
|
-
#### 2. Boolean flag
|
|
1113
|
-
|
|
1114
|
-
```bash
|
|
1115
|
-
mycli input.txt --verbose
|
|
1116
|
-
# → args.verbose = true
|
|
1117
|
-
mycli input.txt --no-verbose
|
|
1118
|
-
# → args.verbose = false
|
|
1119
|
-
```
|
|
1120
|
-
|
|
1121
|
-
#### 3. String option
|
|
1122
|
-
|
|
1123
|
-
```bash
|
|
1124
|
-
mycli input.txt --name Alice
|
|
1125
|
-
# → args.name = "Alice"
|
|
1126
|
-
mycli input.txt
|
|
1127
|
-
# → args.name = undefined
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
#### 4. Number option
|
|
1131
|
-
|
|
1132
|
-
```bash
|
|
1133
|
-
mycli input.txt --count 5
|
|
1134
|
-
# → args.count = 5
|
|
1135
|
-
mycli input.txt
|
|
1136
|
-
# → args.count = 1 (default)
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1139
|
-
#### 5. Array option (repeatable, accepts any value)
|
|
1140
|
-
|
|
1141
|
-
You can provide array values using any of the following syntaxes (mix and match as needed):
|
|
1142
|
-
|
|
1143
|
-
- Repeated flags:
|
|
1144
|
-
|
|
1145
|
-
```bash
|
|
1146
|
-
mycli input.txt --tags foo --tags bar --tags baz
|
|
1147
|
-
# → args.tags = ["foo", "bar", "baz"]
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
- Comma-separated values (with or without spaces):
|
|
1151
|
-
|
|
1152
|
-
```bash
|
|
1153
|
-
mycli input.txt --tags foo,bar,baz
|
|
1154
|
-
mycli input.txt --tags foo, bar, baz
|
|
1155
|
-
# → args.tags = ["foo", "bar", "baz"]
|
|
1156
|
-
```
|
|
1157
|
-
|
|
1158
|
-
- Bracketed values (must be passed as a single argument!):
|
|
1159
|
-
|
|
1160
|
-
```bash
|
|
1161
|
-
mycli input.txt --tags "[foo,bar,baz]"
|
|
1162
|
-
# → args.tags = ["foo", "bar", "baz"]
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
- Mix and match:
|
|
1166
|
-
|
|
1167
|
-
```bash
|
|
1168
|
-
mycli input.txt --tags foo --tags "[bar,bar2,bar3]" --tags baz
|
|
1169
|
-
# → args.tags = ["foo", "bar", "bar2", "bar3", "baz"]
|
|
1170
|
-
```
|
|
1171
|
-
|
|
1172
|
-
> **Important:**
|
|
1173
|
-
>
|
|
1174
|
-
> - **Quoted values (single or double quotes around elements) are NOT supported and will throw an error.**
|
|
1175
|
-
> - Example: `--tags 'foo'` or `--tags "[\"bar\",'baz']"` will throw an error.
|
|
1176
|
-
> - **Bracketed or comma-separated lists must be passed as a single argument.**
|
|
1177
|
-
> - Example: `--tags "[foo,bar]"` (quotes around the whole value, not around elements)
|
|
1178
|
-
> - If you split a bracketed value across arguments, you will get a warning or incorrect parsing.
|
|
1179
|
-
> - **Shells remove quotes before passing arguments to the CLI.** If you want to pass a value with commas or brackets, always quote the whole value.
|
|
1180
|
-
> - **Troubleshooting:**
|
|
1181
|
-
> - If you see a warning about possible shell splitting, try quoting the whole value: `--tags "[a,b,c]"`
|
|
1182
|
-
> - If you see an error about quoted values, remove quotes around individual elements.
|
|
1183
|
-
|
|
1184
|
-
**Example error:**
|
|
144
|
+
**Structure:**
|
|
1185
145
|
|
|
1186
146
|
```bash
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
147
|
+
my-cli/
|
|
148
|
+
├── src/
|
|
149
|
+
│ ├── mod.ts # CLI entry point
|
|
150
|
+
│ ├── commands/ # Command implementations
|
|
151
|
+
│ │ ├── init.ts
|
|
152
|
+
│ │ ├── validate.ts
|
|
153
|
+
│ │ ├── serve.ts
|
|
154
|
+
│ │ └── config.ts
|
|
155
|
+
│ └── utils/ # Utility functions
|
|
156
|
+
│ ├── config.ts
|
|
157
|
+
│ ├── validator.ts
|
|
158
|
+
│ └── glob.ts
|
|
159
|
+
├── test/
|
|
160
|
+
├── package.json
|
|
161
|
+
├── tsconfig.json
|
|
162
|
+
└── README.md
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Monorepo Template
|
|
166
|
+
|
|
167
|
+
For large projects with multiple packages.
|
|
168
|
+
|
|
169
|
+
**Features:**
|
|
170
|
+
|
|
171
|
+
- Turborepo configuration
|
|
172
|
+
- Multiple packages setup
|
|
173
|
+
- Shared dependencies
|
|
174
|
+
- Changeset support
|
|
175
|
+
- Parallel builds
|
|
176
|
+
|
|
177
|
+
**Structure:**
|
|
1195
178
|
|
|
1196
179
|
```bash
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
#
|
|
1200
|
-
#
|
|
1201
|
-
#
|
|
1202
|
-
#
|
|
1203
|
-
#
|
|
1204
|
-
#
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
force: {
|
|
1223
|
-
type: "boolean",
|
|
1224
|
-
allowed: [true],
|
|
1225
|
-
description: "Force the operation"
|
|
1226
|
-
},
|
|
1227
|
-
|
|
1228
|
-
// Only allow specific numbers
|
|
1229
|
-
level: {
|
|
1230
|
-
type: "number",
|
|
1231
|
-
allowed: [1, 2, 3],
|
|
1232
|
-
description: "The level to use"
|
|
1233
|
-
},
|
|
1234
|
-
|
|
1235
|
-
// Only allow specific values in an array
|
|
1236
|
-
tags: {
|
|
1237
|
-
type: "array",
|
|
1238
|
-
allowed: ["web", "api", "mobile"],
|
|
1239
|
-
description: "Tags to apply"
|
|
180
|
+
my-cli/
|
|
181
|
+
├── packages/
|
|
182
|
+
│ ├── cli/ # Main CLI package
|
|
183
|
+
│ ├── core/ # Core functionality
|
|
184
|
+
│ └── utils/ # Shared utilities
|
|
185
|
+
├── turbo.json # Turborepo config
|
|
186
|
+
├── package.json # Root package.json
|
|
187
|
+
├── tsconfig.json # Root TypeScript config
|
|
188
|
+
└── README.md
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Creating Custom Templates
|
|
192
|
+
|
|
193
|
+
Templates can include a `template.json` manifest:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"name": "my-template",
|
|
198
|
+
"description": "My custom template",
|
|
199
|
+
"variables": [
|
|
200
|
+
{
|
|
201
|
+
"name": "projectName",
|
|
202
|
+
"message": "Project name",
|
|
203
|
+
"type": "string",
|
|
204
|
+
"default": "my-project"
|
|
1240
205
|
},
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
type: "
|
|
1245
|
-
|
|
1246
|
-
description: "The action to perform"
|
|
206
|
+
{
|
|
207
|
+
"name": "license",
|
|
208
|
+
"message": "License",
|
|
209
|
+
"type": "select",
|
|
210
|
+
"choices": ["MIT", "Apache-2.0", "GPL-3.0"]
|
|
1247
211
|
}
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
```
|
|
1251
|
-
|
|
1252
|
-
If someone tries to pass a value that's not in the `allowed` list, they'll get a helpful error message:
|
|
1253
|
-
|
|
1254
|
-
```bash
|
|
1255
|
-
mycli --mode staging
|
|
1256
|
-
# Error: Invalid value for --mode: staging. Allowed values are: development, production, test
|
|
1257
|
-
|
|
1258
|
-
mycli --level 4
|
|
1259
|
-
# Error: Invalid value for --level: 4. Allowed values are: 1, 2, 3
|
|
1260
|
-
|
|
1261
|
-
mycli --tags desktop
|
|
1262
|
-
# Error: Invalid value in array --tags: desktop. Allowed values are: web, api, mobile
|
|
1263
|
-
```
|
|
1264
|
-
|
|
1265
|
-
The validation happens after type casting, so for example with numbers, the input will first be converted to a number and then checked against the allowed list.
|
|
1266
|
-
|
|
1267
|
-
## Typed Commands System
|
|
1268
|
-
|
|
1269
|
-
The typed commands system provides TypeScript intellisense and type safety for rempts launcher usage while maintaining dynamic code execution.
|
|
1270
|
-
|
|
1271
|
-
- 🎯 **TypeScript Intellisense**: Full autocomplete for command names and arguments
|
|
1272
|
-
- 🔒 **Type Safety**: Compile-time checking for argument types and required fields
|
|
1273
|
-
- ⚡ **Dynamic Execution**: Commands are still loaded and executed dynamically
|
|
1274
|
-
- 📝 **Automatic Sync**: Utility script to keep types in sync with actual command definitions
|
|
1275
|
-
|
|
1276
|
-
### Usage
|
|
1277
|
-
|
|
1278
|
-
#### Basic Usage
|
|
1279
|
-
|
|
1280
|
-
```typescript
|
|
1281
|
-
import { callCmd } from "~/app/cmds";
|
|
1282
|
-
|
|
1283
|
-
// Simple command with typed arguments
|
|
1284
|
-
await callCmd("pub", { dev: true });
|
|
1285
|
-
|
|
1286
|
-
// Command with multiple arguments
|
|
1287
|
-
await callCmd("check", {
|
|
1288
|
-
directory: "src",
|
|
1289
|
-
checks: "missing-deps,file-extensions",
|
|
1290
|
-
strict: true,
|
|
1291
|
-
json: false
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
// Command with no arguments
|
|
1295
|
-
await callCmd("update");
|
|
1296
|
-
|
|
1297
|
-
// Generators with typed arguments
|
|
1298
|
-
await callCmd("rempts", {
|
|
1299
|
-
init: "new-cmd another-cmd",
|
|
1300
|
-
overwrite: true,
|
|
1301
|
-
outFile: "src/app/cmds.ts"
|
|
1302
|
-
});
|
|
212
|
+
]
|
|
213
|
+
}
|
|
1303
214
|
```
|
|
1304
215
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
```typescript
|
|
1308
|
-
import { getTypedCmd } from "~/app/cmds";
|
|
216
|
+
### Type Generation
|
|
1309
217
|
|
|
1310
|
-
|
|
1311
|
-
const { command, run } = await getTypedCmd("magic");
|
|
1312
|
-
|
|
1313
|
-
console.log(`Running: ${command.meta.name}`);
|
|
1314
|
-
console.log(`Description: ${command.meta.description}`);
|
|
1315
|
-
|
|
1316
|
-
await run({
|
|
1317
|
-
targets: ["dist-npm", "dist-jsr"],
|
|
1318
|
-
concurrency: 4,
|
|
1319
|
-
stopOnError: true
|
|
1320
|
-
});
|
|
1321
|
-
```
|
|
218
|
+
All templates include type generation configuration for enhanced developer experience:
|
|
1322
219
|
|
|
1323
|
-
|
|
220
|
+
This provides:
|
|
1324
221
|
|
|
1325
|
-
|
|
222
|
+
- **Autocomplete** for command names and options
|
|
223
|
+
- **Type safety** at compile time
|
|
224
|
+
- **IntelliSense** for command metadata
|
|
225
|
+
- **CLI wrappers** for programmatic execution
|
|
1326
226
|
|
|
1327
|
-
|
|
227
|
+
Learn more in the [Type Generation Guide](/docs/guides/type-generation).
|
|
1328
228
|
|
|
1329
|
-
|
|
229
|
+
### Template Variables
|
|
1330
230
|
|
|
1331
|
-
|
|
231
|
+
Use these variables in your template files:
|
|
1332
232
|
|
|
1333
|
-
-
|
|
1334
|
-
-
|
|
1335
|
-
-
|
|
233
|
+
- `{{projectName}}` - The project name
|
|
234
|
+
- `{{description}}` - Project description
|
|
235
|
+
- `{{author}}` - Author name
|
|
236
|
+
- `{{license}}` - License type
|
|
237
|
+
- `{{year}}` - Current year
|
|
1336
238
|
|
|
1337
|
-
|
|
239
|
+
Variables can be used in file contents and filenames:
|
|
1338
240
|
|
|
1339
|
-
|
|
1340
|
-
// ✅ Correct usage
|
|
1341
|
-
await callCmd("create", {
|
|
1342
|
-
mode: "files", // Only "template" | "files" allowed
|
|
1343
|
-
multiple: true // boolean
|
|
1344
|
-
});
|
|
1345
|
-
|
|
1346
|
-
// ❌ TypeScript errors
|
|
1347
|
-
await callCmd("create", {
|
|
1348
|
-
mode: "invalid", // Error: not assignable to type
|
|
1349
|
-
multiple: "yes" // Error: string not assignable to boolean
|
|
1350
|
-
});
|
|
1351
|
-
```
|
|
241
|
+
- `__projectName__.config.js` → `my-app.config.js`
|
|
1352
242
|
|
|
1353
|
-
|
|
243
|
+
## Programmatic Usage
|
|
1354
244
|
|
|
1355
245
|
```typescript
|
|
1356
|
-
|
|
1357
|
-
await callCmd("magic", {
|
|
1358
|
-
targets: ["dist-npm"] // Required field
|
|
1359
|
-
});
|
|
1360
|
-
|
|
1361
|
-
// ❌ TypeScript error: missing required field 'targets'
|
|
1362
|
-
await callCmd("magic", {
|
|
1363
|
-
concurrency: 4
|
|
1364
|
-
});
|
|
1365
|
-
```
|
|
1366
|
-
|
|
1367
|
-
### Maintaining the System
|
|
1368
|
-
|
|
1369
|
-
#### Adding New Commands
|
|
1370
|
-
|
|
1371
|
-
1. Create your command in `src/app/<command-name>/cmd.ts` using `defineCommand` and `defineArgs`
|
|
1372
|
-
2. Run the generator: `dler rempts --overwrite`
|
|
1373
|
-
3. The `CommandArgsMap` interface in `src/app/cmds.ts` will be automatically updated
|
|
1374
|
-
|
|
1375
|
-
#### Manual Updates
|
|
246
|
+
import { createProject } from 'rempts'
|
|
1376
247
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
mode: "development" | "production";
|
|
1384
|
-
|
|
1385
|
-
// Use template literal types for patterns
|
|
1386
|
-
version: `${number}.${number}.${number}`;
|
|
1387
|
-
|
|
1388
|
-
// Use branded types for validation
|
|
1389
|
-
port: number & { __brand: "Port" };
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
248
|
+
await createProject({
|
|
249
|
+
name: 'my-cli',
|
|
250
|
+
template: 'advanced',
|
|
251
|
+
install: true,
|
|
252
|
+
git: true
|
|
253
|
+
})
|
|
1392
254
|
```
|
|
1393
255
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
#### Before (Old System, still supported)
|
|
256
|
+
## Development
|
|
1397
257
|
|
|
1398
|
-
```
|
|
1399
|
-
|
|
1400
|
-
|
|
258
|
+
```bash
|
|
259
|
+
# Clone the repository
|
|
260
|
+
git clone https://github.com/reliverse/dler.git
|
|
261
|
+
cd rempts/packages/rempts
|
|
1401
262
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
```
|
|
263
|
+
# Install dependencies
|
|
264
|
+
bun install
|
|
1405
265
|
|
|
1406
|
-
|
|
266
|
+
# Run in development
|
|
267
|
+
bun dev
|
|
1407
268
|
|
|
1408
|
-
|
|
1409
|
-
|
|
269
|
+
# Run tests
|
|
270
|
+
bun test
|
|
1410
271
|
|
|
1411
|
-
|
|
1412
|
-
|
|
272
|
+
# Build
|
|
273
|
+
bun run build
|
|
1413
274
|
```
|
|
1414
275
|
|
|
1415
|
-
|
|
276
|
+
## Troubleshooting
|
|
1416
277
|
|
|
1417
|
-
|
|
278
|
+
### Template not found
|
|
1418
279
|
|
|
1419
|
-
|
|
1420
|
-
2. **Argument Conversion**: Typed arguments are converted to string array format that `runCmd` expects
|
|
1421
|
-
3. **Type Mapping**: `CommandArgsMap` interface maps command names to their argument types
|
|
1422
|
-
4. **Generic Types**: `callCmd<T extends keyof CommandArgsMap>` provides type safety
|
|
280
|
+
If you get a "template not found" error, ensure:
|
|
1423
281
|
|
|
1424
|
-
|
|
282
|
+
- The template name is correct
|
|
283
|
+
- For GitHub templates, the repository exists and is public
|
|
284
|
+
- You have internet connection (for external templates)
|
|
1425
285
|
|
|
1426
|
-
|
|
286
|
+
### Installation fails
|
|
1427
287
|
|
|
1428
|
-
|
|
288
|
+
If dependency installation fails:
|
|
1429
289
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
init: "auth login logout", // Commands to create
|
|
1434
|
-
overwrite: true, // Overwrite existing
|
|
1435
|
-
outFile: "src/app/cmds.ts" // Export file path
|
|
1436
|
-
});
|
|
1437
|
-
|
|
1438
|
-
// Create commands in custom location
|
|
1439
|
-
await callCmd("rempts", {
|
|
1440
|
-
init: "api-handler",
|
|
1441
|
-
customCmdsRoot: "src/modules/api",
|
|
1442
|
-
outFile: "src/modules/api/exports.ts",
|
|
1443
|
-
overwrite: true
|
|
1444
|
-
});
|
|
1445
|
-
```
|
|
290
|
+
- Check your internet connection
|
|
291
|
+
- Ensure Bun is installed correctly
|
|
292
|
+
- Try running with `--no-install` and install manually with `bun install`
|
|
1446
293
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
```typescript
|
|
1450
|
-
// Regenerate exports file only
|
|
1451
|
-
await callCmd("rempts", {
|
|
1452
|
-
overwrite: true,
|
|
1453
|
-
outFile: "src/app/cmds.ts"
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1456
|
-
// Generate exports for specific directories
|
|
1457
|
-
await callCmd("rempts", {
|
|
1458
|
-
cmdDirs: ["build", "pub", "magic"],
|
|
1459
|
-
outFile: "src/app/core-cmds.ts",
|
|
1460
|
-
overwrite: true
|
|
1461
|
-
});
|
|
1462
|
-
```
|
|
294
|
+
### Permission errors
|
|
1463
295
|
|
|
1464
|
-
|
|
296
|
+
If you get permission errors:
|
|
1465
297
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
for (const module of modules) {
|
|
1471
|
-
await callCmd("rempts", {
|
|
1472
|
-
init: `${module}-create ${module}-update ${module}-delete`,
|
|
1473
|
-
customCmdsRoot: `src/modules/${module}`,
|
|
1474
|
-
outFile: `src/modules/${module}/cmds.ts`,
|
|
1475
|
-
overwrite: true
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1478
|
-
```
|
|
298
|
+
- Ensure you have write access to the target directory
|
|
299
|
+
- Try running in a different directory
|
|
300
|
+
- Check disk space availability
|
|
1479
301
|
|
|
1480
302
|
## Contributing
|
|
1481
303
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
You're in the right place! Please help us make the best CLI toolkit possible.
|
|
1485
|
-
|
|
1486
|
-
### Notices For Contributors
|
|
1487
|
-
|
|
1488
|
-
**TypeScript Support**:
|
|
1489
|
-
|
|
1490
|
-
All APIs are fully typed. See [`src/types.ts`](./src/types.ts) for advanced customization and type inference.
|
|
1491
|
-
|
|
1492
|
-
**Examples**:
|
|
1493
|
-
|
|
1494
|
-
- **Classic CLI:** [`example/launcher/classic.ts`](./example/launcher/classic.ts)
|
|
1495
|
-
- **Modern Minimal CLI:** [`example/launcher/modern.ts`](./example/launcher/modern.ts)
|
|
1496
|
-
- **Full Prompt Demo:** [`example/prompts/mod.ts`](./example/prompts/mod.ts)
|
|
1497
|
-
|
|
1498
|
-
**Components and Utilities**:
|
|
1499
|
-
|
|
1500
|
-
- **components/**: All prompt UIs, CLI output, launcher logic, etc.
|
|
1501
|
-
- **utils/**: Color, error, validation, streaming, and system helpers.
|
|
1502
|
-
- **hooks/**: Useful hooks for prompt state and effects.
|
|
1503
|
-
|
|
1504
|
-
### Helpful Links
|
|
1505
|
-
|
|
1506
|
-
- [CLI application with the Node.js Readline module](https://dev.to/camptocamp-geo/cli-application-with-the-nodejs-readline-module-48ic)
|
|
1507
|
-
|
|
1508
|
-
## TODO
|
|
1509
|
-
|
|
1510
|
-
- [ ] migrate to `dler libs` in the future (all main components will be published as separate packages; `@reliverse/rempts` will be a wrapper for all of them)
|
|
1511
|
-
- [ ] migrate all tests to `bun:test`
|
|
1512
|
-
|
|
1513
|
-
## Related
|
|
1514
|
-
|
|
1515
|
-
- [`@reliverse/rse`](https://npmjs.com/package/@reliverse/rse) – CLI-first toolkit for fullstack workflows
|
|
1516
|
-
- [`@reliverse/relinka`](https://npmjs.com/package/@reliverse/relinka) – Styled CLI logs, steps, and symbols
|
|
1517
|
-
|
|
1518
|
-
## Shoutouts
|
|
1519
|
-
|
|
1520
|
-
- [citty](https://github.com/unjs/citty#readme) - launcher design inspiration
|
|
1521
|
-
|
|
1522
|
-
## Support
|
|
1523
|
-
|
|
1524
|
-
Bug report? Prompt idea? Want to build the best DX possible?
|
|
304
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
1525
305
|
|
|
1526
|
-
|
|
306
|
+
### Adding a New Template
|
|
1527
307
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
308
|
+
1. Create a new directory in `templates/`
|
|
309
|
+
2. Add all template files
|
|
310
|
+
3. Create a `template.json` manifest
|
|
311
|
+
4. Test the template thoroughly
|
|
312
|
+
5. Submit a pull request
|
|
1531
313
|
|
|
1532
314
|
## License
|
|
1533
315
|
|
|
1534
|
-
|
|
316
|
+
MIT
|