@reliverse/rempts 1.7.11 → 1.7.13

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
@@ -6,19 +6,20 @@
6
6
 
7
7
  ## Features
8
8
 
9
+ - 😘 drop-in alternative to `citty` + built-in prompts
10
+ - 📂 file-based commands (app-router style by default)
9
11
  - 🫂 rempts keeps you from fighting with your CLI tool
12
+ - 🏎️ prompt engine that *feels* modern — and actually is
10
13
  - ✨ rempts is your end-to-end CLI UI + command framework
11
14
  - 🌿 multi-level file-based subcommands (sibling + nested)
12
15
  - 💪 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
16
  - 🎭 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
17
  - 🎨 customizable themes and styled output
20
18
  - 📦 built-in output formatter and logger
21
19
  - 🚨 crash-safe (Ctrl+C, SIGINT, errors)
20
+ - ⚡ blazing-fast, zero runtime baggage
21
+ - 🧩 router + argument parser built-in
22
+ - 🧠 type-safe from args to prompts
22
23
  - 📐 smart layout for small terminals
23
24
  - 🎛️ override styles via prompt options
24
25
  - 🪄 minimal API surface, maximum expressiveness
@@ -1,8 +1,8 @@
1
+ import path from "@reliverse/pathkit";
1
2
  import { re } from "@reliverse/relico";
2
3
  import { relinka } from "@reliverse/relinka";
3
4
  import { loadConfig } from "c12";
4
5
  import fs from "fs-extra";
5
- import path from "pathe";
6
6
  import termkit from "terminal-kit";
7
7
  const { terminal: term } = termkit;
8
8
  let state = {
@@ -1,140 +1,5 @@
1
1
  import type { ReliArgParserOptions } from "@reliverse/reliarg";
2
- type EmptyArgs = Record<string, never>;
3
- type BaseArgProps = {
4
- description?: string;
5
- required?: boolean;
6
- allowed?: string[];
7
- };
8
- type PositionalArgDefinition = {
9
- type: "positional";
10
- default?: string;
11
- } & BaseArgProps;
12
- type BooleanArgDefinition = {
13
- type: "boolean";
14
- default?: boolean;
15
- allowed?: boolean[];
16
- } & BaseArgProps;
17
- type StringArgDefinition = {
18
- type: "string";
19
- default?: string;
20
- } & BaseArgProps;
21
- type NumberArgDefinition = {
22
- type: "number";
23
- default?: number;
24
- allowed?: number[];
25
- } & BaseArgProps;
26
- type ArrayArgDefinition = {
27
- type: "array";
28
- default?: string | readonly string[];
29
- } & BaseArgProps;
30
- export type ArgDefinition = PositionalArgDefinition | BooleanArgDefinition | StringArgDefinition | NumberArgDefinition | ArrayArgDefinition;
31
- export type ArgDefinitions = Record<string, ArgDefinition>;
32
- type CommandMeta = {
33
- name: string;
34
- version?: string;
35
- description?: string;
36
- hidden?: boolean;
37
- aliases?: string[];
38
- };
39
- /**
40
- * A subcommand can be either:
41
- * 1) A string path to a module with a default export of type Command.
42
- * 2) A lazy import function returning a Promise that resolves to
43
- * { default: Command<any> } or directly to a Command instance.
44
- */
45
- type CommandSpec = string | (() => Promise<{
46
- default: Command<any>;
47
- } | Command<any>>);
48
- export type CommandsMap = Record<string, CommandSpec>;
49
- type CommandContext<ARGS> = {
50
- args: ARGS;
51
- raw: string[];
52
- };
53
- type CommandRun<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
54
- type CommandHook<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
55
- type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
56
- meta?: CommandMeta;
57
- args?: A;
58
- run?: CommandRun<InferArgTypes<A>>;
59
- /**
60
- * Object subcommands for this command.
61
- */
62
- commands?: CommandsMap;
63
- /**
64
- * @deprecated Use `commands` instead. Will be removed in a future major version.
65
- */
66
- subCommands?: CommandsMap;
67
- /**
68
- * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
69
- */
70
- onCmdInit?: CommandHook<InferArgTypes<A>>;
71
- /**
72
- * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
73
- */
74
- onCmdExit?: CommandHook<InferArgTypes<A>>;
75
- /**
76
- * @deprecated Use onCmdInit instead
77
- */
78
- setup?: CommandHook<InferArgTypes<A>>;
79
- /**
80
- * @deprecated Use onCmdExit instead
81
- */
82
- cleanup?: CommandHook<InferArgTypes<A>>;
83
- /**
84
- * Called once per CLI process, before any command/run() is executed
85
- */
86
- onLauncherInit?: () => void | Promise<void>;
87
- /**
88
- * Called once per CLI process, after all command/run() logic is finished
89
- */
90
- onLauncherExit?: () => void | Promise<void>;
91
- };
92
- export type Command<A extends ArgDefinitions = EmptyArgs> = {
93
- meta?: CommandMeta;
94
- args: A;
95
- run?: CommandRun<InferArgTypes<A>>;
96
- /**
97
- * Object subcommands for this command.
98
- */
99
- commands?: CommandsMap;
100
- /**
101
- * @deprecated Use `commands` instead. Will be removed in a future major version.
102
- */
103
- subCommands?: CommandsMap;
104
- /**
105
- * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
106
- */
107
- onCmdInit?: CommandHook<InferArgTypes<A>>;
108
- /**
109
- * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
110
- */
111
- onCmdExit?: CommandHook<InferArgTypes<A>>;
112
- /**
113
- * @deprecated Use onCmdInit instead
114
- */
115
- setup?: CommandHook<InferArgTypes<A>>;
116
- /**
117
- * @deprecated Use onCmdExit instead
118
- */
119
- cleanup?: CommandHook<InferArgTypes<A>>;
120
- /**
121
- * Called once per CLI process, before any command/run() is executed
122
- */
123
- onLauncherInit?: () => void | Promise<void>;
124
- /**
125
- * Called once per CLI process, after all command/run() logic is finished
126
- */
127
- onLauncherExit?: () => void | Promise<void>;
128
- };
129
- export type InferArgTypes<A extends ArgDefinitions> = {
130
- [K in keyof A]: A[K] extends PositionalArgDefinition ? string : A[K] extends BooleanArgDefinition ? boolean : A[K] extends StringArgDefinition ? string : A[K] extends NumberArgDefinition ? number : A[K] extends {
131
- type: "array";
132
- } ? string[] : never;
133
- };
134
- export type FileBasedCmdsOptions = {
135
- enable: boolean;
136
- cmdsRootPath: string;
137
- };
2
+ import type { ArgDefinitions, Command, DefineCommandOptions, EmptyArgs, FileBasedCmdsOptions } from "./launcher-types.js";
138
3
  /**
139
4
  * Defines a command with metadata, argument definitions,
140
5
  * an execution function, and (optional) subCommands.
@@ -185,4 +50,3 @@ export declare function defineArgs<A extends ArgDefinitions>(args: A): A;
185
50
  * @param parserOptions Optional reliArgParser options
186
51
  */
187
52
  export declare function runCmd<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, argv?: string[], parserOptions?: ReliArgParserOptions): Promise<void>;
188
- export {};
@@ -1,8 +1,9 @@
1
+ import path from "@reliverse/pathkit";
1
2
  import { reliArgParser } from "@reliverse/reliarg";
3
+ import { re } from "@reliverse/relico";
2
4
  import { relinka, relinkaConfig, relinkaShutdown } from "@reliverse/relinka";
3
5
  import fs from "fs-extra";
4
6
  import process from "node:process";
5
- import path from "pathe";
6
7
  import { readPackageJSON } from "pkg-types";
7
8
  function buildExampleArgs(args) {
8
9
  const parts = [];
@@ -101,29 +102,49 @@ async function getDefaultCliNameAndVersion() {
101
102
  return { name: "cli", version: void 0 };
102
103
  }
103
104
  }
104
- async function findDirectFileBasedSubcommands(currentDir) {
105
+ async function findRecursiveFileBasedCommands(baseDir, currentPath = []) {
105
106
  const results = [];
106
- const items = await fs.readdir(currentDir, { withFileTypes: true });
107
+ const items = await fs.readdir(path.join(baseDir, ...currentPath), {
108
+ withFileTypes: true
109
+ });
107
110
  for (const dirent of items) {
108
111
  if (dirent.isDirectory()) {
112
+ const newPath = [...currentPath, dirent.name];
109
113
  for (const fname of ["cmd.ts", "cmd.js"]) {
110
- const fpath = path.join(currentDir, dirent.name, fname);
114
+ const fpath = path.join(baseDir, ...newPath, fname);
111
115
  if (await fs.pathExists(fpath)) {
112
116
  try {
113
117
  const imported = await import(path.resolve(fpath));
114
118
  if (imported.default && !imported.default.meta?.hidden) {
115
- results.push({ name: dirent.name, def: imported.default });
119
+ results.push({
120
+ name: dirent.name,
121
+ def: imported.default,
122
+ path: newPath
123
+ });
116
124
  }
117
125
  } catch (err) {
118
- debugLog(`Skipping file-based subcommand in ${fpath}:`, err);
126
+ debugLog(`Skipping file-based command in ${fpath}:`, err);
119
127
  }
120
128
  break;
121
129
  }
122
130
  }
131
+ const subResults = await findRecursiveFileBasedCommands(baseDir, newPath);
132
+ results.push(...subResults);
123
133
  }
124
134
  }
125
135
  return results;
126
136
  }
137
+ function calculatePadding(items, minPad = 2) {
138
+ const maxLength = items.reduce(
139
+ (max, item) => Math.max(max, item.text.length),
140
+ 0
141
+ );
142
+ return maxLength + minPad;
143
+ }
144
+ function formatTableRow(text, desc, padding) {
145
+ const spaces = " ".repeat(Math.max(0, padding - text.length));
146
+ return `${text}${spaces}| ${desc || ""}`;
147
+ }
127
148
  export async function showUsage(command, parserOptions = {}) {
128
149
  const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
129
150
  const cliName = command.meta?.name || fallbackName;
@@ -146,36 +167,64 @@ export async function showUsage(command, parserOptions = {}) {
146
167
  const fileCmds = parserOptions.fileBasedCmds;
147
168
  if (fileCmds?.enable) {
148
169
  const commandsDir = path.resolve(fileCmds.cmdsRootPath);
149
- const currentDir = parserOptions._fileBasedCurrentDir || commandsDir;
150
170
  const pathSegments = parserOptions._fileBasedPathSegments || [];
151
171
  let usageLine = [pkgName, ...pathSegments].join(" ");
152
- const directSubs = await findDirectFileBasedSubcommands(currentDir);
153
- if (directSubs.length > 0) {
172
+ const allCommands = await findRecursiveFileBasedCommands(
173
+ commandsDir,
174
+ pathSegments
175
+ );
176
+ const directCommands = allCommands.filter(
177
+ (cmd) => cmd.path.length === pathSegments.length + 1
178
+ );
179
+ if (directCommands.length > 0) {
154
180
  usageLine += " <command> [command's options]";
155
181
  } else {
156
182
  usageLine += " [command's options]";
157
183
  const pos = renderPositional(command.args);
158
184
  if (pos) usageLine += ` ${pos}`;
159
185
  }
160
- relinka("log", `Usage: ${usageLine}`);
161
- if (directSubs.length > 0) {
162
- const randomIdx = Math.floor(Math.random() * directSubs.length);
163
- const { name: exampleCmd, def: exampleDef } = directSubs[randomIdx];
186
+ relinka("log", re.cyan(`Usage: ${usageLine}`));
187
+ if (directCommands.length > 0) {
188
+ const randomIdx = Math.floor(Math.random() * allCommands.length);
189
+ const { path: path2, def: exampleDef } = allCommands[randomIdx];
164
190
  const exampleArgs = buildExampleArgs(exampleDef.args || {});
165
191
  relinka(
166
192
  "log",
167
- `Example: ${pkgName} ${[...pathSegments, exampleCmd].join(" ")}${exampleArgs ? ` ${exampleArgs}` : ""}`
193
+ re.cyan(
194
+ `Example: ${pkgName} ${path2.join(" ")}${exampleArgs ? ` ${exampleArgs}` : ""}`
195
+ )
168
196
  );
169
197
  }
170
- if (directSubs.length > 0) {
198
+ if (allCommands.length > 0) {
171
199
  relinka("info", "Available commands (run with `help` to see more):");
172
- directSubs.forEach(({ name, def }) => {
173
- const desc = def?.meta?.description ?? "";
174
- relinka(
175
- "log",
176
- `\u2022 ${[...pathSegments, name].join(" ")}${desc ? ` | ${desc}` : ""}`
177
- );
178
- });
200
+ const commandsByPath = /* @__PURE__ */ new Map();
201
+ for (const cmd of allCommands) {
202
+ const parentPath = cmd.path.slice(0, -1).join("/") || "/";
203
+ if (!commandsByPath.has(parentPath)) {
204
+ commandsByPath.set(parentPath, []);
205
+ }
206
+ commandsByPath.get(parentPath).push(cmd);
207
+ }
208
+ const groupPaddings = /* @__PURE__ */ new Map();
209
+ for (const [parentPath, cmds] of commandsByPath) {
210
+ const items = cmds.map(({ path: path2, def }) => ({
211
+ text: `${parentPath === "/" ? "" : " "}\u2022 ${path2.join(" ")}`,
212
+ desc: def?.meta?.description
213
+ }));
214
+ groupPaddings.set(parentPath, calculatePadding(items));
215
+ }
216
+ for (const [parentPath, cmds] of commandsByPath) {
217
+ if (parentPath !== "/") {
218
+ relinka("log", re.cyanPastel(`Sub-commands in ${parentPath}:`));
219
+ }
220
+ const padding = groupPaddings.get(parentPath);
221
+ for (const { def, path: path2 } of cmds) {
222
+ const desc = def?.meta?.description ?? "";
223
+ const indent = parentPath === "/" ? "" : " ";
224
+ const text = `${indent}\u2022 ${path2.join(" ")}`;
225
+ relinka("log", formatTableRow(text, desc, padding));
226
+ }
227
+ }
179
228
  }
180
229
  } else {
181
230
  const subCommandNames = [];
@@ -204,46 +253,57 @@ export async function showUsage(command, parserOptions = {}) {
204
253
  } else {
205
254
  usageLine += ` [command's options] ${renderPositional(command.args)}`;
206
255
  }
207
- relinka("log", `Usage: ${usageLine}`);
256
+ relinka("log", re.cyan(`Usage: ${usageLine}`));
208
257
  if (subCommandDefs.length > 0) {
209
258
  const randomIdx = Math.floor(Math.random() * subCommandDefs.length);
210
259
  const { name: exampleCmd, def: exampleDef } = subCommandDefs[randomIdx];
211
260
  const exampleArgs = buildExampleArgs(exampleDef.args || {});
212
261
  relinka(
213
262
  "log",
214
- `Example: ${pkgName}${parserOptions._isSubcommand ? ` ${cliName}` : ""} ${exampleCmd}${exampleArgs ? ` ${exampleArgs}` : ""}`
263
+ re.cyan(
264
+ `Example: ${pkgName}${parserOptions._isSubcommand ? ` ${cliName}` : ""} ${exampleCmd}${exampleArgs ? ` ${exampleArgs}` : ""}`
265
+ )
215
266
  );
216
267
  }
217
268
  if (subCommandNames.length > 0) {
218
269
  relinka("info", "Available commands (run with `help` to see more):");
219
- subCommandDefs.forEach(({ name, def }) => {
220
- const desc = def?.meta?.description ?? "";
221
- relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
222
- });
270
+ const commandItems = subCommandDefs.map(({ name, def }) => ({
271
+ text: `\u2022 ${name}`,
272
+ desc: def?.meta?.description
273
+ }));
274
+ const padding = calculatePadding(commandItems);
275
+ for (const { text, desc } of commandItems) {
276
+ relinka("log", formatTableRow(text, desc, padding));
277
+ }
223
278
  }
224
279
  }
225
280
  relinka("info", "Available options:");
226
- relinka("log", "\u2022 -h, --help | Show help");
227
- relinka("log", "\u2022 -v, --version | Show version");
228
- relinka("log", "\u2022 --debug | Enable debug mode");
281
+ const optionItems = [
282
+ { text: "\u2022 -h, --help", desc: "Show help" },
283
+ { text: "\u2022 -v, --version", desc: "Show version" },
284
+ { text: "\u2022 --debug", desc: "Enable debug mode" }
285
+ ];
229
286
  for (const [key, def] of Object.entries(command.args || {})) {
230
287
  if (def.type === "positional") {
231
- relinka(
232
- "log",
233
- `\u2022 <${key}> | ${def.description ?? ""}${def.required ? " | required" : ""}`
234
- );
288
+ optionItems.push({
289
+ text: `\u2022 <${key}>`,
290
+ desc: `${def.description ?? ""}${def.required ? " | required" : ""}`
291
+ });
235
292
  } else {
293
+ const text = `\u2022 --${key}${"alias" in def && def.alias ? `, -${def.alias}` : ""}`;
236
294
  const parts = [
237
- `\u2022 --${key}${"alias" in def && def.alias ? `, -${def.alias}` : ""}`,
238
295
  def.description ?? "",
239
- `type=${def.type}`
240
- ];
241
- if (def.default !== void 0)
242
- parts.push(`default=${JSON.stringify(def.default)}`);
243
- if (def.required) parts.push("required");
244
- relinka("log", parts.filter(Boolean).join(" | "));
296
+ `type=${def.type}`,
297
+ def.default !== void 0 ? `default=${JSON.stringify(def.default)}` : null,
298
+ def.required ? "required" : null
299
+ ].filter(Boolean);
300
+ optionItems.push({ text, desc: parts.join(" | ") });
245
301
  }
246
302
  }
303
+ const optionsPadding = calculatePadding(optionItems);
304
+ for (const { text, desc } of optionItems) {
305
+ relinka("log", formatTableRow(text, desc, optionsPadding));
306
+ }
247
307
  }
248
308
  export async function runMain(command, parserOptions = {}) {
249
309
  if (typeof command.onLauncherInit === "function")
@@ -464,8 +524,8 @@ async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions, par
464
524
  }
465
525
  }
466
526
  throw new Error(
467
- `Unknown command or arguments: ${args.join(" ")}
468
-
527
+ `
528
+ Unknown command or arguments: ${args.join(" ")}
469
529
  Info for this CLI's developer: No valid command file found in ${baseDir}`
470
530
  );
471
531
  }
@@ -483,8 +543,8 @@ Info for this CLI's developer: No valid command file found in ${baseDir}`
483
543
  }
484
544
  }
485
545
  throw new Error(
486
- `Unknown command or arguments: ${args.join(" ")}
487
-
546
+ `
547
+ Unknown command or arguments: ${args.join(" ")}
488
548
  Info for this CLI's developer: No valid command file found in ${baseDir}`
489
549
  );
490
550
  }
@@ -495,9 +555,23 @@ Info for this CLI's developer: No valid command file found in ${baseDir}`
495
555
  process.cwd(),
496
556
  path.join(fileCmdOpts.cmdsRootPath, subName, "cmd.{ts,js}")
497
557
  );
558
+ const allCommands = await findRecursiveFileBasedCommands(
559
+ fileCmdOpts.cmdsRootPath
560
+ );
561
+ const commandNames = allCommands.map((cmd) => cmd.path.join(" "));
562
+ let closestMatch = "";
563
+ let minDistance = Number.POSITIVE_INFINITY;
564
+ for (const cmd of commandNames) {
565
+ const distance = levenshteinDistance(subName, cmd.split(" ")[0]);
566
+ if (distance < minDistance) {
567
+ minDistance = distance;
568
+ closestMatch = cmd;
569
+ }
570
+ }
571
+ const suggestion = minDistance <= 3 ? ` (Did you mean: \`${closestMatch}\`?)` : "";
498
572
  throw new Error(
499
- `Unknown command or arguments: ${attempted}
500
-
573
+ `
574
+ Unknown command or arguments: ${attempted}${suggestion}
501
575
  Info for this CLI's developer: No valid command directory found, expected: ${expectedPath}`
502
576
  );
503
577
  }
@@ -904,3 +978,31 @@ async function resolveFileBasedCommandPath(cmdsRoot, argv) {
904
978
  }
905
979
  return null;
906
980
  }
981
+ function levenshteinDistance(a, b) {
982
+ if (a.length === 0) return b.length;
983
+ if (b.length === 0) return a.length;
984
+ const matrix = [];
985
+ for (let i = 0; i <= b.length; i++) {
986
+ matrix[i] = [i];
987
+ }
988
+ for (let j = 0; j <= a.length; j++) {
989
+ matrix[0][j] = j;
990
+ }
991
+ for (let i = 1; i <= b.length; i++) {
992
+ for (let j = 1; j <= a.length; j++) {
993
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
994
+ matrix[i][j] = matrix[i - 1][j - 1];
995
+ } else {
996
+ matrix[i][j] = Math.min(
997
+ matrix[i - 1][j - 1] + 1,
998
+ // substitution
999
+ matrix[i][j - 1] + 1,
1000
+ // insertion
1001
+ matrix[i - 1][j] + 1
1002
+ // deletion
1003
+ );
1004
+ }
1005
+ }
1006
+ }
1007
+ return matrix[b.length][a.length];
1008
+ }
@@ -0,0 +1,136 @@
1
+ export type EmptyArgs = Record<string, never>;
2
+ export type BaseArgProps = {
3
+ description?: string;
4
+ required?: boolean;
5
+ allowed?: string[];
6
+ };
7
+ export type PositionalArgDefinition = {
8
+ type: "positional";
9
+ default?: string;
10
+ } & BaseArgProps;
11
+ export type BooleanArgDefinition = {
12
+ type: "boolean";
13
+ default?: boolean;
14
+ allowed?: boolean[];
15
+ } & BaseArgProps;
16
+ export type StringArgDefinition = {
17
+ type: "string";
18
+ default?: string;
19
+ } & BaseArgProps;
20
+ export type NumberArgDefinition = {
21
+ type: "number";
22
+ default?: number;
23
+ allowed?: number[];
24
+ } & BaseArgProps;
25
+ export type ArrayArgDefinition = {
26
+ type: "array";
27
+ default?: string | readonly string[];
28
+ } & BaseArgProps;
29
+ export type ArgDefinition = PositionalArgDefinition | BooleanArgDefinition | StringArgDefinition | NumberArgDefinition | ArrayArgDefinition;
30
+ export type ArgDefinitions = Record<string, ArgDefinition>;
31
+ export type CommandMeta = {
32
+ name: string;
33
+ version?: string;
34
+ description?: string;
35
+ hidden?: boolean;
36
+ aliases?: string[];
37
+ };
38
+ /**
39
+ * A subcommand can be either:
40
+ * 1) A string path to a module with a default export of type Command.
41
+ * 2) A lazy import function returning a Promise that resolves to
42
+ * { default: Command<any> } or directly to a Command instance.
43
+ */
44
+ export type CommandSpec = string | (() => Promise<{
45
+ default: Command<any>;
46
+ } | Command<any>>);
47
+ export type CommandsMap = Record<string, CommandSpec>;
48
+ export type CommandContext<ARGS> = {
49
+ args: ARGS;
50
+ raw: string[];
51
+ };
52
+ export type CommandRun<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
53
+ export type CommandHook<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
54
+ export type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
55
+ meta?: CommandMeta;
56
+ args?: A;
57
+ run?: CommandRun<InferArgTypes<A>>;
58
+ /**
59
+ * Object subcommands for this command.
60
+ */
61
+ commands?: CommandsMap;
62
+ /**
63
+ * @deprecated Use `commands` instead. Will be removed in a future major version.
64
+ */
65
+ subCommands?: CommandsMap;
66
+ /**
67
+ * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
68
+ */
69
+ onCmdInit?: CommandHook<InferArgTypes<A>>;
70
+ /**
71
+ * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
72
+ */
73
+ onCmdExit?: CommandHook<InferArgTypes<A>>;
74
+ /**
75
+ * @deprecated Use onCmdInit instead
76
+ */
77
+ setup?: CommandHook<InferArgTypes<A>>;
78
+ /**
79
+ * @deprecated Use onCmdExit instead
80
+ */
81
+ cleanup?: CommandHook<InferArgTypes<A>>;
82
+ /**
83
+ * Called once per CLI process, before any command/run() is executed
84
+ */
85
+ onLauncherInit?: () => void | Promise<void>;
86
+ /**
87
+ * Called once per CLI process, after all command/run() logic is finished
88
+ */
89
+ onLauncherExit?: () => void | Promise<void>;
90
+ };
91
+ export type Command<A extends ArgDefinitions = EmptyArgs> = {
92
+ meta?: CommandMeta;
93
+ args: A;
94
+ run?: CommandRun<InferArgTypes<A>>;
95
+ /**
96
+ * Object subcommands for this command.
97
+ */
98
+ commands?: CommandsMap;
99
+ /**
100
+ * @deprecated Use `commands` instead. Will be removed in a future major version.
101
+ */
102
+ subCommands?: CommandsMap;
103
+ /**
104
+ * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
105
+ */
106
+ onCmdInit?: CommandHook<InferArgTypes<A>>;
107
+ /**
108
+ * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
109
+ */
110
+ onCmdExit?: CommandHook<InferArgTypes<A>>;
111
+ /**
112
+ * @deprecated Use onCmdInit instead
113
+ */
114
+ setup?: CommandHook<InferArgTypes<A>>;
115
+ /**
116
+ * @deprecated Use onCmdExit instead
117
+ */
118
+ cleanup?: CommandHook<InferArgTypes<A>>;
119
+ /**
120
+ * Called once per CLI process, before any command/run() is executed
121
+ */
122
+ onLauncherInit?: () => void | Promise<void>;
123
+ /**
124
+ * Called once per CLI process, after all command/run() logic is finished
125
+ */
126
+ onLauncherExit?: () => void | Promise<void>;
127
+ };
128
+ export type InferArgTypes<A extends ArgDefinitions> = {
129
+ [K in keyof A]: A[K] extends PositionalArgDefinition ? string : A[K] extends BooleanArgDefinition ? boolean : A[K] extends StringArgDefinition ? string : A[K] extends NumberArgDefinition ? number : A[K] extends {
130
+ type: "array";
131
+ } ? string[] : never;
132
+ };
133
+ export type FileBasedCmdsOptions = {
134
+ enable: boolean;
135
+ cmdsRootPath: string;
136
+ };
File without changes
@@ -0,0 +1,2 @@
1
+ import type { Command } from "./launcher-types.js";
2
+ export declare function loadCommand(path: string): Promise<Command>;
@@ -0,0 +1,18 @@
1
+ import { relinka } from "@reliverse/relinka";
2
+ import { createJiti } from "jiti";
3
+ const jiti = createJiti(import.meta.url, {
4
+ debug: process.env.NODE_ENV === "development",
5
+ fsCache: true,
6
+ sourceMaps: true
7
+ });
8
+ export async function loadCommand(path) {
9
+ try {
10
+ relinka("verbose", `Loading command from: ${path}`);
11
+ const cmd = await jiti.import(path, { default: true });
12
+ relinka("verbose", `Successfully loaded command from: ${path}`);
13
+ return cmd;
14
+ } catch (error) {
15
+ relinka("error", `Failed to load command from ${path}:`, error);
16
+ throw error;
17
+ }
18
+ }
package/bin/mod.d.ts CHANGED
@@ -4,8 +4,9 @@ export { startEditor } from "./components/editor/editor-mod.js";
4
4
  export { mainSymbols, fallbackSymbols, } from "./components/figures/figures-mod.js";
5
5
  export { confirmPrompt } from "./components/input/confirm-prompt.js";
6
6
  export { inputPrompt } from "./components/input/input-prompt.js";
7
- export type { ArgDefinition, ArgDefinitions, CommandsMap, Command, InferArgTypes, FileBasedCmdsOptions, } from "./components/launcher/launcher-mod.js";
7
+ export * from "./components/launcher/launcher-types.js";
8
8
  export { defineCommand, defineArgs, showUsage, runMain, runCmd, } from "./components/launcher/launcher-mod.js";
9
+ export { loadCommand } from "./components/launcher/run-command.js";
9
10
  export { toBaseColor, toSolidColor } from "./components/msg-fmt/colors.js";
10
11
  export { relinkaByRemptsDeprecated, relinkaAsyncByRemptsDeprecated, throwError, } from "./components/msg-fmt/logger.js";
11
12
  export { colorMap, typographyMap } from "./components/msg-fmt/mapping.js";
package/bin/mod.js CHANGED
@@ -7,6 +7,7 @@ export {
7
7
  } from "./components/figures/figures-mod.js";
8
8
  export { confirmPrompt } from "./components/input/confirm-prompt.js";
9
9
  export { inputPrompt } from "./components/input/input-prompt.js";
10
+ export * from "./components/launcher/launcher-types.js";
10
11
  export {
11
12
  defineCommand,
12
13
  defineArgs,
@@ -14,6 +15,7 @@ export {
14
15
  runMain,
15
16
  runCmd
16
17
  } from "./components/launcher/launcher-mod.js";
18
+ export { loadCommand } from "./components/launcher/run-command.js";
17
19
  export { toBaseColor, toSolidColor } from "./components/msg-fmt/colors.js";
18
20
  export {
19
21
  relinkaByRemptsDeprecated,