@reliverse/rempts 1.7.4 β 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# rempts β’ powerful js/ts cli builder
|
|
1
|
+
# π rempts β’ powerful js/ts cli builder
|
|
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
|
|
|
@@ -6,20 +6,25 @@
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- π«
|
|
10
|
-
- β¨
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- ποΈ
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
9
|
+
- π« rempts keeps you from fighting with your CLI tool
|
|
10
|
+
- β¨ rempts is your end-to-end CLI UI + command framework
|
|
11
|
+
- πΏ multi-level file-based subcommands (sibling + nested)
|
|
12
|
+
- πͺ built for DX precision and high-context terminal UX
|
|
13
|
+
- ποΈ prompt engine that *feels* modern β and actually is
|
|
14
|
+
- π file-based commands (app-router style by default)
|
|
15
|
+
- π looks great in plain scripts or full CLI apps
|
|
16
|
+
- π§ type-safe from args to prompts
|
|
17
|
+
- β‘ blazing-fast, zero runtime baggage
|
|
18
|
+
- π§© router + argument parser built-in
|
|
19
|
+
- π¨ customizable themes and styled output
|
|
20
|
+
- π¦ built-in output formatter and logger
|
|
21
|
+
- π¨ crash-safe (Ctrl+C, SIGINT, errors)
|
|
22
|
+
- π smart layout for small terminals
|
|
23
|
+
- ποΈ override styles via prompt options
|
|
24
|
+
- πͺ minimal API surface, maximum expressiveness
|
|
25
|
+
- π§ͺ scriptable for testing, stable for production
|
|
26
|
+
- ποΈ no more hacking together `inquirer`/`citty`/`commander`/`chalk`
|
|
27
|
+
- π automatic command creation (`bun dler rempts init --cmd my-cmd`)
|
|
23
28
|
|
|
24
29
|
## Installation
|
|
25
30
|
|
|
@@ -30,9 +35,9 @@ bun add @reliverse/rempts
|
|
|
30
35
|
**Coming soon**:
|
|
31
36
|
|
|
32
37
|
```bash
|
|
33
|
-
bun
|
|
34
|
-
dler rempts init --cmd my-cmd-1
|
|
35
|
-
dler rempts init --cmds
|
|
38
|
+
bun add -D @reliverse/dler
|
|
39
|
+
bun dler rempts init --cmd my-cmd-1
|
|
40
|
+
bun dler rempts init --cmds
|
|
36
41
|
```
|
|
37
42
|
|
|
38
43
|
## Usage Examples
|
|
@@ -217,14 +222,15 @@ export default defineCommand({
|
|
|
217
222
|
- `arg-cmdName.{ts,js}`,
|
|
218
223
|
- `cmdName/index.{ts,js}`,
|
|
219
224
|
- `cmdName/cmdName-mod.{ts,js}`,
|
|
225
|
+
- **Multi-level subcommands:** `foo/bar/baz/cmd.ts` β `my-cli foo bar baz`
|
|
220
226
|
- And more β with automatic usage output.
|
|
221
227
|
|
|
222
228
|
**Hint**:
|
|
223
229
|
|
|
224
230
|
- Install `bun add -D @reliverse/dler`
|
|
225
|
-
- Use `dler rempts init --cmd cmd1 cmd2` to init commands for rempts launcher's automatically
|
|
231
|
+
- Use `bun dler rempts init --cmd cmd1 cmd2` to init commands for rempts launcher's automatically
|
|
226
232
|
|
|
227
|
-
### Advanced
|
|
233
|
+
### Advanced Launcher Usage
|
|
228
234
|
|
|
229
235
|
```ts
|
|
230
236
|
defineCommand({
|
|
@@ -247,21 +253,31 @@ defineCommand({
|
|
|
247
253
|
- Default values, validations, descriptions
|
|
248
254
|
- Full help rendering from metadata
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
**By the way! Multi-level subcommands!**
|
|
251
257
|
|
|
252
|
-
|
|
253
|
-
- Override styles via prompt options
|
|
254
|
-
- Smart layout for small terminals
|
|
255
|
-
- Looks great in plain scripts or full CLI apps
|
|
258
|
+
You can also nest subcommands arbitrarily deep:
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
```bash
|
|
261
|
+
app/
|
|
262
|
+
foo/
|
|
263
|
+
bar/
|
|
264
|
+
baz/
|
|
265
|
+
cmd.ts
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Invoke with:
|
|
258
269
|
|
|
259
270
|
```bash
|
|
260
|
-
|
|
261
|
-
rempts examples # supported options: name
|
|
271
|
+
my-cli foo bar baz --some-flag
|
|
262
272
|
```
|
|
263
273
|
|
|
264
|
-
|
|
274
|
+
The launcher will recursively traverse subfolders for each non-flag argument, loading the deepest `cmd.ts`/`cmd.js` it finds, and passing the remaining arguments to it.
|
|
275
|
+
|
|
276
|
+
See [example/launcher/app/nested](./example/launcher/app/nested/) and [example/launcher/app/sibling](./example/launcher/app/sibling/) folders to learn more.
|
|
277
|
+
|
|
278
|
+
When playing with the example, you can run e.g. `bun dev:modern nested foo bar baz` to see the result in action.
|
|
279
|
+
|
|
280
|
+
### Playground
|
|
265
281
|
|
|
266
282
|
```bash
|
|
267
283
|
git clone https://github.com/reliverse/rempts
|
|
@@ -270,8 +286,9 @@ bun i
|
|
|
270
286
|
bun dev # supported options: name
|
|
271
287
|
```
|
|
272
288
|
|
|
273
|
-
-
|
|
274
|
-
- This
|
|
289
|
+
- `bun dev:prompts`: This example will show you a `multiselectPrompt()` where you can choose which CLI prompts you want to play with.
|
|
290
|
+
- `bun dev:modern`: This example will show you a modern CLI launcher usage with file-based commands.
|
|
291
|
+
- `bun dev:classic`: This example will show you a classic CLI launcher usage with programmatic commands.
|
|
275
292
|
|
|
276
293
|
### Launcher Usage Examples
|
|
277
294
|
|
|
@@ -288,7 +305,7 @@ await runMain(defineCommand({}));
|
|
|
288
305
|
**2 Run the following:**
|
|
289
306
|
|
|
290
307
|
```bash
|
|
291
|
-
bun add -D @reliverse/dler
|
|
308
|
+
bun add -D @reliverse/dler
|
|
292
309
|
bun dler rempts init --cmd my-cmd-1 # or: dler rempts init my-cmd-1 my-cmd-2 --main src/mod.ts
|
|
293
310
|
# * `--main` is optional, default is `./src/mod.ts`
|
|
294
311
|
# * you can specify multiple commands at once
|
|
@@ -163,7 +163,10 @@ export declare function showUsage<A extends ArgDefinitions>(command: Command<A>,
|
|
|
163
163
|
metaSettings?: {
|
|
164
164
|
showDescriptionOnMain?: boolean;
|
|
165
165
|
};
|
|
166
|
-
|
|
166
|
+
_fileBasedCurrentDir?: string;
|
|
167
|
+
_fileBasedPathSegments?: string[];
|
|
168
|
+
_isSubcommand?: boolean;
|
|
169
|
+
}): Promise<void>;
|
|
167
170
|
/**
|
|
168
171
|
* Primary entry point to run a command. This function supports:
|
|
169
172
|
*
|
|
@@ -105,11 +105,34 @@ async function getDefaultCliNameAndVersion() {
|
|
|
105
105
|
return { name: "cli", version: void 0 };
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
async function findDirectFileBasedSubcommands(currentDir) {
|
|
109
|
+
const results = [];
|
|
110
|
+
const items = await fs.readdir(currentDir, { withFileTypes: true });
|
|
111
|
+
for (const dirent of items) {
|
|
112
|
+
if (dirent.isDirectory()) {
|
|
113
|
+
for (const fname of ["cmd.ts", "cmd.js"]) {
|
|
114
|
+
const fpath = path.join(currentDir, dirent.name, fname);
|
|
115
|
+
if (await fs.pathExists(fpath)) {
|
|
116
|
+
try {
|
|
117
|
+
const imported = await import(path.resolve(fpath));
|
|
118
|
+
if (imported.default && !imported.default.meta?.hidden) {
|
|
119
|
+
results.push({ name: dirent.name, def: imported.default });
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
debugLog(`Skipping file-based subcommand in ${fpath}:`, err);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
export async function showUsage(command, parserOptions = {}) {
|
|
109
132
|
const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
|
|
110
133
|
const cliName = command.meta?.name || fallbackName;
|
|
111
134
|
const cliVersion = command.meta?.version || fallbackVersion;
|
|
112
|
-
relinka("
|
|
135
|
+
relinka("info", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
|
|
113
136
|
if (parserOptions.metaSettings?.showDescriptionOnMain) {
|
|
114
137
|
let description = command.meta?.description;
|
|
115
138
|
if (!description) {
|
|
@@ -123,64 +146,40 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
123
146
|
relinka("log", description);
|
|
124
147
|
}
|
|
125
148
|
}
|
|
149
|
+
const { name: pkgName } = await getDefaultCliNameAndVersion();
|
|
126
150
|
const fileCmds = parserOptions.fileBasedCmds;
|
|
127
151
|
if (fileCmds?.enable) {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
} catch (err) {
|
|
156
|
-
debugLog(`Skipping file-based subcommand in ${name}:`, err);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (subCommandDefs.length > 0) {
|
|
161
|
-
const randomIdx = Math.floor(Math.random() * subCommandDefs.length);
|
|
162
|
-
const { name: exampleCmd, def: exampleDef } = subCommandDefs[randomIdx];
|
|
163
|
-
const exampleArgs = buildExampleArgs(exampleDef.args || {});
|
|
152
|
+
const commandsDir = path.resolve(fileCmds.cmdsRootPath);
|
|
153
|
+
const currentDir = parserOptions._fileBasedCurrentDir || commandsDir;
|
|
154
|
+
const pathSegments = parserOptions._fileBasedPathSegments || [];
|
|
155
|
+
let usageLine = [pkgName, ...pathSegments].join(" ");
|
|
156
|
+
const directSubs = await findDirectFileBasedSubcommands(currentDir);
|
|
157
|
+
if (directSubs.length > 0) {
|
|
158
|
+
usageLine += " <command> [command's options]";
|
|
159
|
+
} else {
|
|
160
|
+
usageLine += " [command's options]";
|
|
161
|
+
const pos = renderPositional(command.args);
|
|
162
|
+
if (pos) usageLine += ` ${pos}`;
|
|
163
|
+
}
|
|
164
|
+
relinka("log", `Usage: ${usageLine}`);
|
|
165
|
+
if (directSubs.length > 0) {
|
|
166
|
+
const randomIdx = Math.floor(Math.random() * directSubs.length);
|
|
167
|
+
const { name: exampleCmd, def: exampleDef } = directSubs[randomIdx];
|
|
168
|
+
const exampleArgs = buildExampleArgs(exampleDef.args || {});
|
|
169
|
+
relinka(
|
|
170
|
+
"log",
|
|
171
|
+
`Example: ${pkgName} ${[...pathSegments, exampleCmd].join(" ")}${exampleArgs ? ` ${exampleArgs}` : ""}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (directSubs.length > 0) {
|
|
175
|
+
relinka("info", "Available commands (run with `help` to see more):");
|
|
176
|
+
directSubs.forEach(({ name, def }) => {
|
|
177
|
+
const desc = def?.meta?.description ?? "";
|
|
164
178
|
relinka(
|
|
165
179
|
"log",
|
|
166
|
-
|
|
180
|
+
`\u2022 ${[...pathSegments, name].join(" ")}${desc ? ` | ${desc}` : ""}`
|
|
167
181
|
);
|
|
168
|
-
}
|
|
169
|
-
if (subCommandNames.length > 0) {
|
|
170
|
-
relinka("info", "Available commands (run with `help` to see more):");
|
|
171
|
-
subCommandDefs.forEach(({ name, def }) => {
|
|
172
|
-
const desc = def?.meta?.description ?? "";
|
|
173
|
-
relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
} catch (err) {
|
|
177
|
-
if (displayNotFoundMessage) {
|
|
178
|
-
relinka(
|
|
179
|
-
"warn",
|
|
180
|
-
`No file-based subcommands found in ${fileCmds.cmdsRootPath}`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
debugLog("Error reading file-based commands:", err);
|
|
182
|
+
});
|
|
184
183
|
}
|
|
185
184
|
} else {
|
|
186
185
|
const subCommandNames = [];
|
|
@@ -200,7 +199,10 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
|
-
let usageLine =
|
|
202
|
+
let usageLine = pkgName;
|
|
203
|
+
if (parserOptions._isSubcommand) {
|
|
204
|
+
usageLine += ` ${cliName}`;
|
|
205
|
+
}
|
|
204
206
|
if (subCommandNames.length > 0) {
|
|
205
207
|
usageLine += " <command> [command's options]";
|
|
206
208
|
} else {
|
|
@@ -213,7 +215,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
|
|
|
213
215
|
const exampleArgs = buildExampleArgs(exampleDef.args || {});
|
|
214
216
|
relinka(
|
|
215
217
|
"log",
|
|
216
|
-
`Example: ${cliName} ${exampleCmd}${exampleArgs ? ` ${exampleArgs}` : ""}`
|
|
218
|
+
`Example: ${pkgName}${parserOptions._isSubcommand ? ` ${cliName}` : ""} ${exampleCmd}${exampleArgs ? ` ${exampleArgs}` : ""}`
|
|
217
219
|
);
|
|
218
220
|
}
|
|
219
221
|
if (subCommandNames.length > 0) {
|
|
@@ -309,26 +311,26 @@ This can cause recursion or unexpected behavior.`
|
|
|
309
311
|
);
|
|
310
312
|
process.exit(1);
|
|
311
313
|
}
|
|
312
|
-
if (rawArgv
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
if (checkVersion(rawArgv)) {
|
|
324
|
-
if (command.meta?.name) {
|
|
325
|
-
relinka(
|
|
326
|
-
"log",
|
|
327
|
-
`${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
|
|
314
|
+
if (parserOptions.fileBasedCmds?.enable && rawArgv.length > 0) {
|
|
315
|
+
const commandsDir = path.resolve(
|
|
316
|
+
parserOptions.fileBasedCmds.cmdsRootPath
|
|
317
|
+
);
|
|
318
|
+
const resolved = await resolveFileBasedCommandPath(commandsDir, rawArgv);
|
|
319
|
+
if (resolved) {
|
|
320
|
+
const { def: subCommand, leftoverArgv, path: pathSegments } = resolved;
|
|
321
|
+
const helpIdx = leftoverArgv.findIndex(
|
|
322
|
+
(arg) => arg === "help" || arg === "--help" || arg === "-h"
|
|
328
323
|
);
|
|
324
|
+
if (helpIdx !== -1) {
|
|
325
|
+
await showUsage(subCommand, {
|
|
326
|
+
...parserOptions,
|
|
327
|
+
_fileBasedCurrentDir: pathSegments.length ? path.join(commandsDir, ...pathSegments) : commandsDir,
|
|
328
|
+
_fileBasedPathSegments: pathSegments
|
|
329
|
+
});
|
|
330
|
+
if (autoExit) process.exit(0);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
329
333
|
}
|
|
330
|
-
if (autoExit) process.exit(0);
|
|
331
|
-
return;
|
|
332
334
|
}
|
|
333
335
|
const fileBasedEnabled = parserOptions.fileBasedCmds?.enable;
|
|
334
336
|
if (fileBasedEnabled && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
|
|
@@ -373,6 +375,18 @@ This can cause recursion or unexpected behavior.`
|
|
|
373
375
|
}
|
|
374
376
|
}
|
|
375
377
|
if (subSpec) {
|
|
378
|
+
const helpIdx = subCmdArgv.findIndex(
|
|
379
|
+
(arg) => arg === "help" || arg === "--help" || arg === "-h"
|
|
380
|
+
);
|
|
381
|
+
if (helpIdx !== -1) {
|
|
382
|
+
const subCommandDef = await loadSubCommand(subSpec);
|
|
383
|
+
await showUsage(subCommandDef, {
|
|
384
|
+
...parserOptions,
|
|
385
|
+
_isSubcommand: true
|
|
386
|
+
});
|
|
387
|
+
if (autoExit) process.exit(0);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
376
390
|
try {
|
|
377
391
|
const ctx = getParsedContext(command, rawArgv, parserOptions);
|
|
378
392
|
if (typeof command.onCmdInit === "function")
|
|
@@ -380,7 +394,7 @@ This can cause recursion or unexpected behavior.`
|
|
|
380
394
|
await runSubCommand(
|
|
381
395
|
subSpec,
|
|
382
396
|
subCmdArgv,
|
|
383
|
-
parserOptions,
|
|
397
|
+
{ ...parserOptions, _isSubcommand: true },
|
|
384
398
|
command.onCmdExit ? async (_subCtx) => {
|
|
385
399
|
await command.onCmdExit?.(ctx);
|
|
386
400
|
} : void 0
|
|
@@ -394,6 +408,22 @@ This can cause recursion or unexpected behavior.`
|
|
|
394
408
|
}
|
|
395
409
|
}
|
|
396
410
|
}
|
|
411
|
+
await relinkaConfig;
|
|
412
|
+
if (rawArgv[0] === "help" || checkHelp(rawArgv)) {
|
|
413
|
+
await showUsage(command, parserOptions);
|
|
414
|
+
if (autoExit) process.exit(0);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (checkVersion(rawArgv)) {
|
|
418
|
+
if (command.meta?.name) {
|
|
419
|
+
relinka(
|
|
420
|
+
"info",
|
|
421
|
+
`${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (autoExit) process.exit(0);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
397
427
|
try {
|
|
398
428
|
await runCommandWithArgs(command, rawArgv, parserOptions);
|
|
399
429
|
} finally {
|
|
@@ -428,61 +458,67 @@ async function loadSubCommand(spec) {
|
|
|
428
458
|
throw new Error("Subcommand import did not return a valid command");
|
|
429
459
|
}
|
|
430
460
|
async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions, parentFinish) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const stats = await fs.stat(subPathDir);
|
|
442
|
-
isDirCommand = stats.isDirectory();
|
|
443
|
-
}
|
|
444
|
-
if (isDirCommand) {
|
|
445
|
-
for (const pattern of possibleFiles) {
|
|
446
|
-
if (await fs.pathExists(pattern)) {
|
|
447
|
-
importPath = pattern;
|
|
448
|
-
break;
|
|
461
|
+
async function resolveCmdPath(baseDir, args) {
|
|
462
|
+
if (args.length === 0 || isFlag(args[0])) {
|
|
463
|
+
const possibleFiles2 = [
|
|
464
|
+
path.join(baseDir, "cmd.js"),
|
|
465
|
+
path.join(baseDir, "cmd.ts")
|
|
466
|
+
];
|
|
467
|
+
for (const file of possibleFiles2) {
|
|
468
|
+
if (await fs.pathExists(file)) {
|
|
469
|
+
return { importPath: file, leftoverArgv: args };
|
|
470
|
+
}
|
|
449
471
|
}
|
|
450
|
-
}
|
|
451
|
-
if (!importPath) {
|
|
452
|
-
const attempted = [subName, ...argv].join(" ");
|
|
453
|
-
const expectedPath = path.relative(
|
|
454
|
-
process.cwd(),
|
|
455
|
-
path.join(fileCmdOpts.cmdsRootPath, subName, "cmd.{ts,js}")
|
|
456
|
-
);
|
|
457
472
|
throw new Error(
|
|
458
|
-
`Unknown command or arguments: ${
|
|
473
|
+
`Unknown command or arguments: ${args.join(" ")}
|
|
459
474
|
|
|
460
|
-
Info for this CLI's developer: No valid command
|
|
475
|
+
Info for this CLI's developer: No valid command file found in ${baseDir}`
|
|
461
476
|
);
|
|
462
477
|
}
|
|
463
|
-
|
|
478
|
+
const nextDir = path.join(baseDir, args[0]);
|
|
479
|
+
if (await fs.pathExists(nextDir) && (await fs.stat(nextDir)).isDirectory()) {
|
|
480
|
+
return resolveCmdPath(nextDir, args.slice(1));
|
|
481
|
+
}
|
|
482
|
+
const possibleFiles = [
|
|
483
|
+
path.join(baseDir, "cmd.js"),
|
|
484
|
+
path.join(baseDir, "cmd.ts")
|
|
485
|
+
];
|
|
486
|
+
for (const file of possibleFiles) {
|
|
487
|
+
if (await fs.pathExists(file)) {
|
|
488
|
+
return { importPath: file, leftoverArgv: args };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
throw new Error(
|
|
492
|
+
`Unknown command or arguments: ${args.join(" ")}
|
|
493
|
+
|
|
494
|
+
Info for this CLI's developer: No valid command file found in ${baseDir}`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const startDir = path.join(fileCmdOpts.cmdsRootPath, subName);
|
|
498
|
+
if (!await fs.pathExists(startDir) || !(await fs.stat(startDir)).isDirectory()) {
|
|
464
499
|
const attempted = [subName, ...argv].join(" ");
|
|
465
|
-
const
|
|
500
|
+
const expectedPath = path.relative(
|
|
466
501
|
process.cwd(),
|
|
467
502
|
path.join(fileCmdOpts.cmdsRootPath, subName, "cmd.{ts,js}")
|
|
468
503
|
);
|
|
469
504
|
throw new Error(
|
|
470
505
|
`Unknown command or arguments: ${attempted}
|
|
471
506
|
|
|
472
|
-
Info for this CLI's developer: No valid command directory found, expected: ${
|
|
507
|
+
Info for this CLI's developer: No valid command directory found, expected: ${expectedPath}`
|
|
473
508
|
);
|
|
474
509
|
}
|
|
510
|
+
const { importPath, leftoverArgv } = await resolveCmdPath(startDir, argv);
|
|
475
511
|
const imported = await import(path.resolve(importPath));
|
|
476
512
|
const subCommand = imported.default;
|
|
477
513
|
if (!subCommand) {
|
|
478
514
|
throw new Error(
|
|
479
|
-
`File-based subcommand
|
|
515
|
+
`File-based subcommand has no default export or is invalid: ${importPath}`
|
|
480
516
|
);
|
|
481
517
|
}
|
|
482
518
|
try {
|
|
483
519
|
const subCtx = await runCommandWithArgs(
|
|
484
520
|
subCommand,
|
|
485
|
-
|
|
521
|
+
leftoverArgv,
|
|
486
522
|
parserOptions,
|
|
487
523
|
true
|
|
488
524
|
);
|
|
@@ -494,6 +530,14 @@ Info for this CLI's developer: No valid command directory found, expected: ${exp
|
|
|
494
530
|
async function runSubCommand(spec, argv, parserOptions, parentFinish) {
|
|
495
531
|
const subCommand = await loadSubCommand(spec);
|
|
496
532
|
try {
|
|
533
|
+
const helpIdx = argv.findIndex(
|
|
534
|
+
(arg) => arg === "help" || arg === "--help" || arg === "-h"
|
|
535
|
+
);
|
|
536
|
+
if (helpIdx !== -1) {
|
|
537
|
+
await showUsage(subCommand, { ...parserOptions, _isSubcommand: true });
|
|
538
|
+
if (parserOptions.autoExit !== false) process.exit(0);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
497
541
|
const subCtx = await runCommandWithArgs(
|
|
498
542
|
subCommand,
|
|
499
543
|
argv,
|
|
@@ -597,7 +641,7 @@ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
|
|
|
597
641
|
const noSubcommandArgInCurrentCall = !argv.some((arg) => !isFlag(arg));
|
|
598
642
|
if (isDispatcher && noSubcommandArgInCurrentCall) {
|
|
599
643
|
relinka("warn", "Please specify a command");
|
|
600
|
-
await showUsage(command, parserOptions
|
|
644
|
+
await showUsage(command, parserOptions);
|
|
601
645
|
if (autoExit) process.exit(0);
|
|
602
646
|
return;
|
|
603
647
|
}
|
|
@@ -786,3 +830,32 @@ function getParsedContext(command, argv, parserOptions) {
|
|
|
786
830
|
}
|
|
787
831
|
return { args: finalArgs, raw: argv };
|
|
788
832
|
}
|
|
833
|
+
async function resolveFileBasedCommandPath(cmdsRoot, argv) {
|
|
834
|
+
let currentDir = cmdsRoot;
|
|
835
|
+
const pathSegments = [];
|
|
836
|
+
let leftover = [...argv];
|
|
837
|
+
while (leftover.length > 0 && !isFlag(leftover[0])) {
|
|
838
|
+
const nextDir = path.join(currentDir, leftover[0]);
|
|
839
|
+
if (await fs.pathExists(nextDir) && (await fs.stat(nextDir)).isDirectory()) {
|
|
840
|
+
currentDir = nextDir;
|
|
841
|
+
pathSegments.push(leftover[0]);
|
|
842
|
+
leftover = leftover.slice(1);
|
|
843
|
+
} else {
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
for (const fname of ["cmd.ts", "cmd.js"]) {
|
|
848
|
+
const fpath = path.join(currentDir, fname);
|
|
849
|
+
if (await fs.pathExists(fpath)) {
|
|
850
|
+
const imported = await import(path.resolve(fpath));
|
|
851
|
+
if (imported.default) {
|
|
852
|
+
return {
|
|
853
|
+
def: imported.default,
|
|
854
|
+
path: pathSegments,
|
|
855
|
+
leftoverArgv: leftover
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return null;
|
|
861
|
+
}
|
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"name": "@reliverse/rempts",
|
|
30
30
|
"type": "module",
|
|
31
|
-
"version": "1.7.
|
|
31
|
+
"version": "1.7.6",
|
|
32
32
|
"author": "reliverse",
|
|
33
33
|
"bugs": {
|
|
34
34
|
"email": "blefnk@gmail.com",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "1.9.4",
|
|
46
46
|
"@eslint/js": "^9.26.0",
|
|
47
|
-
"@reliverse/dler": "^1.2.
|
|
47
|
+
"@reliverse/dler": "^1.2.3",
|
|
48
48
|
"@reliverse/relidler-cfg": "^1.1.3",
|
|
49
49
|
"@stylistic/eslint-plugin": "^4.2.0",
|
|
50
50
|
"@total-typescript/ts-reset": "^0.6.1",
|