@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
- - πŸ«‚ Rempts prevents you from fighting with your CLI tool
10
- - ✨ Rempts is your end-to-end CLI UI + command framework
11
- - πŸ’ͺ Made for DX precision and high-context terminal UX
12
- - πŸ“‚ File-based commands (app router style by default)
13
- - 🏎️ Prompt engine that *feels* modern, actually is
14
- - 🧠 Type-safe from args to prompts
15
- - ⚑ Blazing-fast, no runtime baggage
16
- - 🧩 Router + argument parser built-in
17
- - 🎨 Customizable themes, styled output
18
- - 🚨 Crash-safe (Ctrl+C, SIGINT, errors)
19
- - πŸͺ„ Minimal API surface, max expressiveness
20
- - πŸ§ͺ Scriptable for testing, stable for production
21
- - πŸ†• Automatic commands creation (via `dler rempts init --cmd my-cool-cmd`)
22
- - 🏞️ No more hacking together `inquirer`, `citty`, `commander`, `chalk`
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 i -g @reliverse/dler
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 Minimal API
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
- ### Theming + Customization
256
+ **By the way! Multi-level subcommands!**
251
257
 
252
- - Built-in output formatter and logger
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
- ### Playground
260
+ ```bash
261
+ app/
262
+ foo/
263
+ bar/
264
+ baz/
265
+ cmd.ts
266
+ ```
267
+
268
+ Invoke with:
258
269
 
259
270
  ```bash
260
- bun i -g @reliverse/rempts-cli
261
- rempts examples # supported options: name
271
+ my-cli foo bar baz --some-flag
262
272
  ```
263
273
 
264
- OR:
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
- - Both `rempts examples` from @reliverse/rempts and `bun dev` (which is the same thing) are themselves examples of `launcher` functionality.
274
- - This launcher will show you a `multiselectPrompt()` where you can choose which CLI prompts you want to play with.
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 # or: bun i -g @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
- }, displayNotFoundMessage?: boolean): Promise<void>;
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
- export async function showUsage(command, parserOptions = {}, displayNotFoundMessage) {
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("log", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
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 usageLine = `Usage: ${cliName} <command> [command's options]`;
129
- relinka("log", usageLine);
130
- try {
131
- const commandsDir = path.resolve(fileCmds.cmdsRootPath);
132
- const items = await fs.readdir(commandsDir, {
133
- withFileTypes: true
134
- });
135
- const subCommandNames = [];
136
- const subCommandDefs = [];
137
- for (const dirent of items) {
138
- if (dirent.isDirectory()) {
139
- const name = dirent.name;
140
- const cmdTs = path.join(commandsDir, name, "cmd.ts");
141
- const cmdJs = path.join(commandsDir, name, "cmd.js");
142
- let imported;
143
- try {
144
- if (await fs.pathExists(cmdTs)) {
145
- imported = await import(path.resolve(cmdTs));
146
- } else if (await fs.pathExists(cmdJs)) {
147
- imported = await import(path.resolve(cmdJs));
148
- } else {
149
- continue;
150
- }
151
- if (imported.default && !imported.default.meta?.hidden) {
152
- subCommandNames.push(name);
153
- subCommandDefs.push({ name, def: imported.default });
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
- `Example: ${cliName} ${exampleCmd}${exampleArgs ? ` ${exampleArgs}` : ""}`
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 = cliName;
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[0] === "help") {
313
- await showUsage(command, parserOptions);
314
- if (autoExit) process.exit(0);
315
- return;
316
- }
317
- await relinkaConfig;
318
- if (checkHelp(rawArgv)) {
319
- await showUsage(command, parserOptions);
320
- if (autoExit) process.exit(0);
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
- const subPathDir = path.join(fileCmdOpts.cmdsRootPath, subName);
432
- let importPath;
433
- const possibleFiles = [
434
- // Only allow cmd.{ts,js} inside the subcommand directory
435
- path.join(subPathDir, "cmd.js"),
436
- path.join(subPathDir, "cmd.ts")
437
- ];
438
- const dirExists = await fs.pathExists(subPathDir);
439
- let isDirCommand = false;
440
- if (dirExists) {
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: ${attempted}
473
+ `Unknown command or arguments: ${args.join(" ")}
459
474
 
460
- Info for this CLI's developer: No valid command directory found, expected: ${expectedPath}`
475
+ Info for this CLI's developer: No valid command file found in ${baseDir}`
461
476
  );
462
477
  }
463
- } else {
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 expectedPath2 = path.relative(
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: ${expectedPath2}`
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 "${subName}" has no default export or is invalid.`
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
- argv,
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, true);
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.4",
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.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",