@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 +6 -5
- package/bin/components/editor/editor-mod.js +1 -1
- package/bin/components/launcher/launcher-mod.d.ts +1 -137
- package/bin/components/launcher/launcher-mod.js +150 -48
- package/bin/components/launcher/launcher-types.d.ts +136 -0
- package/bin/components/launcher/launcher-types.js +0 -0
- package/bin/components/launcher/run-command.d.ts +2 -0
- package/bin/components/launcher/run-command.js +18 -0
- package/bin/mod.d.ts +2 -1
- package/bin/mod.js +2 -0
- package/package.json +7 -27
- package/bin/components/launcher/deprecated/_parser.ts.txt +0 -167
- package/bin/components/launcher/deprecated/_utils.ts.txt +0 -41
- package/bin/components/launcher/deprecated/args.ts.txt +0 -108
- package/bin/components/launcher/deprecated/command.ts.txt +0 -95
- package/bin/components/launcher/deprecated/launcher-mod.ts.txt +0 -50
- package/bin/components/launcher/deprecated/usage.ts.txt +0 -157
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
|
|
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
|
|
105
|
+
async function findRecursiveFileBasedCommands(baseDir, currentPath = []) {
|
|
105
106
|
const results = [];
|
|
106
|
-
const items = await fs.readdir(
|
|
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(
|
|
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({
|
|
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
|
|
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
|
|
153
|
-
|
|
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 (
|
|
162
|
-
const randomIdx = Math.floor(Math.random() *
|
|
163
|
-
const {
|
|
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
|
-
|
|
193
|
+
re.cyan(
|
|
194
|
+
`Example: ${pkgName} ${path2.join(" ")}${exampleArgs ? ` ${exampleArgs}` : ""}`
|
|
195
|
+
)
|
|
168
196
|
);
|
|
169
197
|
}
|
|
170
|
-
if (
|
|
198
|
+
if (allCommands.length > 0) {
|
|
171
199
|
relinka("info", "Available commands (run with `help` to see more):");
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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.
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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,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
|
|
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,
|