@reliverse/rempts 1.7.52 → 1.7.53
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/bin/libs/animate/animate-mod.d.ts +14 -0
- package/bin/libs/animate/animate-mod.js +60 -0
- package/bin/libs/anykey/anykey-mod.d.ts +12 -0
- package/bin/libs/anykey/anykey-mod.js +125 -0
- package/bin/libs/cancel/cancel.d.ts +45 -0
- package/bin/libs/cancel/cancel.js +72 -0
- package/bin/libs/confirm/confirm-alias.d.ts +2 -0
- package/bin/libs/confirm/confirm-alias.js +2 -0
- package/bin/libs/confirm/confirm-mod.d.ts +5 -0
- package/bin/libs/confirm/confirm-mod.js +179 -0
- package/bin/libs/date/date.d.ts +2 -0
- package/bin/libs/date/date.js +214 -0
- package/bin/libs/editor/editor-mod.d.ts +25 -0
- package/bin/libs/editor/editor-mod.js +1133 -0
- package/bin/libs/figures/figures-mod.d.ts +461 -0
- package/bin/libs/figures/figures-mod.js +285 -0
- package/bin/libs/group/group-mod.d.ts +33 -0
- package/bin/libs/group/group-mod.js +89 -0
- package/bin/libs/input/input-alias.d.ts +5 -0
- package/bin/libs/input/input-alias.js +4 -0
- package/bin/libs/input/input-mod.d.ts +16 -0
- package/bin/libs/input/input-mod.js +372 -0
- package/bin/libs/intro/intro-alias.d.ts +3 -0
- package/bin/libs/intro/intro-alias.js +3 -0
- package/bin/libs/intro/intro-mod.d.ts +20 -0
- package/bin/libs/intro/intro-mod.js +77 -0
- package/bin/libs/launcher/command-runner.d.ts +31 -0
- package/bin/libs/launcher/command-runner.js +229 -0
- package/bin/libs/launcher/launcher-alias.d.ts +2 -0
- package/bin/libs/launcher/launcher-alias.js +2 -0
- package/bin/libs/launcher/launcher-mod.d.ts +66 -0
- package/bin/libs/launcher/launcher-mod.js +975 -0
- package/bin/libs/launcher/launcher-types.d.ts +176 -0
- package/bin/libs/launcher/launcher-types.js +0 -0
- package/bin/libs/log/log-alias.d.ts +1 -0
- package/bin/libs/log/log-alias.js +2 -0
- package/bin/libs/msg-fmt/colors.d.ts +30 -0
- package/bin/libs/msg-fmt/colors.js +42 -0
- package/bin/libs/msg-fmt/logger.d.ts +17 -0
- package/bin/libs/msg-fmt/logger.js +103 -0
- package/bin/libs/msg-fmt/mapping.d.ts +3 -0
- package/bin/libs/msg-fmt/mapping.js +41 -0
- package/bin/libs/msg-fmt/messages.d.ts +35 -0
- package/bin/libs/msg-fmt/messages.js +305 -0
- package/bin/libs/msg-fmt/terminal.d.ts +15 -0
- package/bin/libs/msg-fmt/terminal.js +60 -0
- package/bin/libs/msg-fmt/variants.d.ts +11 -0
- package/bin/libs/msg-fmt/variants.js +52 -0
- package/bin/libs/multiselect/multiselect-alias.d.ts +2 -0
- package/bin/libs/multiselect/multiselect-alias.js +2 -0
- package/bin/libs/multiselect/multiselect-prompt.d.ts +2 -0
- package/bin/libs/multiselect/multiselect-prompt.js +340 -0
- package/bin/libs/next-steps/next-steps.d.ts +13 -0
- package/bin/libs/next-steps/next-steps.js +24 -0
- package/bin/libs/number/number-mod.d.ts +28 -0
- package/bin/libs/number/number-mod.js +194 -0
- package/bin/libs/outro/outro-alias.d.ts +3 -0
- package/bin/libs/outro/outro-alias.js +3 -0
- package/bin/libs/outro/outro-mod.d.ts +8 -0
- package/bin/libs/outro/outro-mod.js +55 -0
- package/bin/libs/reliarg/reliarg-mod.d.ts +76 -0
- package/bin/libs/reliarg/reliarg-mod.js +276 -0
- package/bin/libs/results/results.d.ts +7 -0
- package/bin/libs/results/results.js +27 -0
- package/bin/libs/select/nummultiselect-prompt.d.ts +6 -0
- package/bin/libs/select/nummultiselect-prompt.js +101 -0
- package/bin/libs/select/numselect-prompt.d.ts +7 -0
- package/bin/libs/select/numselect-prompt.js +111 -0
- package/bin/libs/select/select-alias.d.ts +9 -0
- package/bin/libs/select/select-alias.js +9 -0
- package/bin/libs/select/select-prompt.d.ts +5 -0
- package/bin/libs/select/select-prompt.js +308 -0
- package/bin/libs/select/toggle-prompt.d.ts +5 -0
- package/bin/libs/select/toggle-prompt.js +207 -0
- package/bin/libs/spinner/spinner-alias.d.ts +2 -0
- package/bin/libs/spinner/spinner-alias.js +2 -0
- package/bin/libs/spinner/spinner-mod.d.ts +106 -0
- package/bin/libs/spinner/spinner-mod.js +255 -0
- package/bin/libs/task/progress.d.ts +2 -0
- package/bin/libs/task/progress.js +57 -0
- package/bin/libs/task/task-spin.d.ts +15 -0
- package/bin/libs/task/task-spin.js +106 -0
- package/bin/libs/utils/colorize.d.ts +2 -0
- package/bin/libs/utils/colorize.js +122 -0
- package/bin/libs/utils/errors.d.ts +1 -0
- package/bin/libs/utils/errors.js +17 -0
- package/bin/libs/utils/prevent.d.ts +8 -0
- package/bin/libs/utils/prevent.js +62 -0
- package/bin/libs/utils/prompt-end.d.ts +8 -0
- package/bin/libs/utils/prompt-end.js +34 -0
- package/bin/libs/utils/stream-text.d.ts +18 -0
- package/bin/libs/utils/stream-text.js +137 -0
- package/bin/libs/utils/system.d.ts +6 -0
- package/bin/libs/utils/system.js +7 -0
- package/bin/libs/utils/validate.d.ts +21 -0
- package/bin/libs/utils/validate.js +17 -0
- package/bin/libs/visual/visual-mod.d.ts +6 -0
- package/bin/libs/visual/visual-mod.js +13 -0
- package/bin/mod.d.ts +53 -0
- package/bin/mod.js +107 -0
- package/bin/types.d.ts +371 -0
- package/bin/types.js +0 -0
- package/package.json +34 -59
- package/dist-npm/bin/mod.d.mts +0 -1753
- package/dist-npm/bin/mod.mjs +0 -6779
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { re } from "@reliverse/relico";
|
|
4
|
+
import { relinka, relinkaConfig, relinkaShutdown } from "@reliverse/relinka";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { readPackageJSON } from "pkg-types";
|
|
7
|
+
import { reliArgParser } from "../reliarg/reliarg-mod.js";
|
|
8
|
+
async function pathExists(path2) {
|
|
9
|
+
try {
|
|
10
|
+
await stat(path2);
|
|
11
|
+
return true;
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function buildExampleArgs(args) {
|
|
17
|
+
const parts = [];
|
|
18
|
+
const positionalKeys = Object.keys(args || {}).filter((k) => args?.[k]?.type === "positional");
|
|
19
|
+
positionalKeys.forEach((key) => {
|
|
20
|
+
const def = args?.[key];
|
|
21
|
+
if (def && (def.required || Math.random() > 0.5)) {
|
|
22
|
+
parts.push(String(def.default ?? `<${key}>`));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const otherKeys = Object.keys(args || {}).filter((k) => args?.[k]?.type !== "positional");
|
|
26
|
+
for (const key of otherKeys) {
|
|
27
|
+
const def = args?.[key];
|
|
28
|
+
if (def && (def.required || Math.random() > 0.7)) {
|
|
29
|
+
switch (def.type) {
|
|
30
|
+
case "boolean":
|
|
31
|
+
if (def.default === true) {
|
|
32
|
+
if (Math.random() > 0.5) parts.push(`--no-${key}`);
|
|
33
|
+
} else {
|
|
34
|
+
parts.push(`--${key}`);
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
case "string":
|
|
38
|
+
parts.push(`--${key}=${String(def.default ?? key)}`);
|
|
39
|
+
break;
|
|
40
|
+
case "number":
|
|
41
|
+
parts.push(`--${key}=${String(def.default ?? 42)}`);
|
|
42
|
+
break;
|
|
43
|
+
case "array":
|
|
44
|
+
parts.push(`--${key}=${String(def.default ?? key)}`);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return parts.join(" ");
|
|
50
|
+
}
|
|
51
|
+
const isDebugMode = process.argv.includes("--debug");
|
|
52
|
+
function debugLog(...args) {
|
|
53
|
+
if (isDebugMode) {
|
|
54
|
+
relinka("log", "[DEBUG]", ...args);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isFlag(str) {
|
|
58
|
+
return str.startsWith("-");
|
|
59
|
+
}
|
|
60
|
+
export function defineCommand(options) {
|
|
61
|
+
const onCmdInit = options.onCmdInit || options.setup;
|
|
62
|
+
const onCmdExit = options.onCmdExit || options.cleanup;
|
|
63
|
+
const onLauncherInit = options.onLauncherInit;
|
|
64
|
+
const onLauncherExit = options.onLauncherExit;
|
|
65
|
+
let commands = options.commands;
|
|
66
|
+
if (!commands) {
|
|
67
|
+
commands = options.subCommands;
|
|
68
|
+
}
|
|
69
|
+
const cmdObj = {
|
|
70
|
+
meta: options.meta,
|
|
71
|
+
args: options.args || {},
|
|
72
|
+
run: options.run,
|
|
73
|
+
commands,
|
|
74
|
+
onCmdInit,
|
|
75
|
+
onCmdExit,
|
|
76
|
+
onLauncherInit,
|
|
77
|
+
onLauncherExit,
|
|
78
|
+
// Backward-compatible aliases
|
|
79
|
+
setup: onCmdInit,
|
|
80
|
+
cleanup: onCmdExit
|
|
81
|
+
};
|
|
82
|
+
Object.defineProperty(cmdObj, "subCommands", {
|
|
83
|
+
get() {
|
|
84
|
+
return this.commands;
|
|
85
|
+
},
|
|
86
|
+
enumerable: false,
|
|
87
|
+
configurable: true
|
|
88
|
+
});
|
|
89
|
+
return cmdObj;
|
|
90
|
+
}
|
|
91
|
+
let _cachedDefaultCliName;
|
|
92
|
+
let _cachedDefaultCliVersion;
|
|
93
|
+
async function getDefaultCliNameAndVersion() {
|
|
94
|
+
if (_cachedDefaultCliName)
|
|
95
|
+
return { name: _cachedDefaultCliName, version: _cachedDefaultCliVersion };
|
|
96
|
+
try {
|
|
97
|
+
const pkg = await readPackageJSON();
|
|
98
|
+
let name = pkg.name || "cli";
|
|
99
|
+
if (name.startsWith("@")) {
|
|
100
|
+
name = name.split("/").pop() || name;
|
|
101
|
+
}
|
|
102
|
+
_cachedDefaultCliName = name;
|
|
103
|
+
_cachedDefaultCliVersion = pkg.version;
|
|
104
|
+
return { name, version: pkg.version };
|
|
105
|
+
} catch (_e) {
|
|
106
|
+
return { name: "cli", version: void 0 };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function findRecursiveFileBasedCommands(baseDir, currentPath = []) {
|
|
110
|
+
const results = [];
|
|
111
|
+
const items = await readdir(path.join(baseDir, ...currentPath), {
|
|
112
|
+
withFileTypes: true
|
|
113
|
+
});
|
|
114
|
+
for (const dirent of items) {
|
|
115
|
+
if (dirent.isDirectory()) {
|
|
116
|
+
const newPath = [...currentPath, dirent.name];
|
|
117
|
+
for (const fname of ["cmd.ts", "cmd.js"]) {
|
|
118
|
+
const fpath = path.join(baseDir, ...newPath, fname);
|
|
119
|
+
if (await pathExists(fpath)) {
|
|
120
|
+
try {
|
|
121
|
+
const imported = await import(path.resolve(fpath));
|
|
122
|
+
if (imported.default && !imported.default.meta?.hidden) {
|
|
123
|
+
results.push({
|
|
124
|
+
name: dirent.name,
|
|
125
|
+
def: imported.default,
|
|
126
|
+
path: newPath
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
debugLog(`Skipping file-based command in ${fpath}:`, err);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const subResults = await findRecursiveFileBasedCommands(baseDir, newPath);
|
|
136
|
+
results.push(...subResults);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
function calculatePadding(items, minPad = 2) {
|
|
142
|
+
const maxLength = items.reduce((max, item) => Math.max(max, item.text.length), 0);
|
|
143
|
+
return maxLength + minPad;
|
|
144
|
+
}
|
|
145
|
+
function formatTableRow(text, desc, padding) {
|
|
146
|
+
const spaces = " ".repeat(Math.max(0, padding - text.length));
|
|
147
|
+
return `${text}${spaces}| ${desc || ""}`;
|
|
148
|
+
}
|
|
149
|
+
export async function showUsage(command, parserOptions = {}, globalCliMeta) {
|
|
150
|
+
const { name: fallbackName, version: fallbackVersion } = await getDefaultCliNameAndVersion();
|
|
151
|
+
const cliName = globalCliMeta?.name || command.meta?.name || fallbackName;
|
|
152
|
+
const cliVersion = globalCliMeta?.version || command.meta?.version || fallbackVersion;
|
|
153
|
+
relinka("info", `${cliName}${cliVersion ? ` v${cliVersion}` : ""}`);
|
|
154
|
+
if (parserOptions.metaSettings?.showDescriptionOnMain) {
|
|
155
|
+
let description = globalCliMeta?.description || command.meta?.description;
|
|
156
|
+
if (!description) {
|
|
157
|
+
try {
|
|
158
|
+
const pkg = await readPackageJSON();
|
|
159
|
+
if (pkg.description) description = pkg.description;
|
|
160
|
+
} catch (_e) {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (description) {
|
|
164
|
+
relinka("log", description);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const { name: pkgName } = await getDefaultCliNameAndVersion();
|
|
168
|
+
const fileCmds = parserOptions.fileBased;
|
|
169
|
+
if (fileCmds?.enable) {
|
|
170
|
+
const commandsDir = path.resolve(fileCmds.cmdsRootPath);
|
|
171
|
+
const pathSegments = parserOptions._fileBasedPathSegments || [];
|
|
172
|
+
let usageLine = [pkgName, ...pathSegments].join(" ");
|
|
173
|
+
const allCommands = await findRecursiveFileBasedCommands(commandsDir, pathSegments);
|
|
174
|
+
const directCommands = allCommands.filter((cmd) => cmd.path.length === pathSegments.length + 1);
|
|
175
|
+
if (directCommands.length > 0) {
|
|
176
|
+
usageLine += " <command> [command's options]";
|
|
177
|
+
} else {
|
|
178
|
+
usageLine += " [command's options]";
|
|
179
|
+
const pos = renderPositional(command.args);
|
|
180
|
+
if (pos) usageLine += ` ${pos}`;
|
|
181
|
+
}
|
|
182
|
+
relinka("log", re.cyan(`Usage: ${usageLine}`));
|
|
183
|
+
if (directCommands.length > 0 && allCommands.length > 0) {
|
|
184
|
+
const randomIdx = Math.floor(Math.random() * allCommands.length);
|
|
185
|
+
const exampleCmd = allCommands[randomIdx];
|
|
186
|
+
if (exampleCmd) {
|
|
187
|
+
const { path: path2, def: exampleDef } = exampleCmd;
|
|
188
|
+
const exampleArgs = buildExampleArgs(exampleDef.args || {});
|
|
189
|
+
relinka(
|
|
190
|
+
"log",
|
|
191
|
+
re.cyan(`Example: ${pkgName} ${path2.join(" ")}${exampleArgs ? ` ${exampleArgs}` : ""}`)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (allCommands.length > 0) {
|
|
196
|
+
relinka("info", "Available commands (run with `help` to see more):");
|
|
197
|
+
const commandsByPath = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const cmd of allCommands) {
|
|
199
|
+
const parentPath = cmd.path.slice(0, -1).join("/") || "/";
|
|
200
|
+
if (!commandsByPath.has(parentPath)) {
|
|
201
|
+
commandsByPath.set(parentPath, []);
|
|
202
|
+
}
|
|
203
|
+
const group = commandsByPath.get(parentPath);
|
|
204
|
+
if (group) {
|
|
205
|
+
group.push(cmd);
|
|
206
|
+
}
|
|
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.cyanBright(`Sub-commands in ${parentPath}:`));
|
|
219
|
+
}
|
|
220
|
+
const padding = groupPaddings.get(parentPath) || 0;
|
|
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
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const subCommandNames = [];
|
|
231
|
+
const subCommandDefs = [];
|
|
232
|
+
const objectCommands = command.commands;
|
|
233
|
+
if (objectCommands) {
|
|
234
|
+
for (const [name, spec] of Object.entries(objectCommands)) {
|
|
235
|
+
try {
|
|
236
|
+
const cmd = await loadSubCommand(spec);
|
|
237
|
+
if (!cmd?.meta?.hidden) {
|
|
238
|
+
const aliasDisplay = cmd.meta?.aliases ? ` (aliases: ${cmd.meta.aliases.join(", ")})` : "";
|
|
239
|
+
subCommandNames.push(`${name}${aliasDisplay}`);
|
|
240
|
+
subCommandDefs.push({ name, def: cmd });
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
debugLog(`Error loading command ${name}:`, err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
let usageLine = pkgName;
|
|
248
|
+
if (parserOptions._isSubcommand) {
|
|
249
|
+
usageLine += ` ${cliName}`;
|
|
250
|
+
}
|
|
251
|
+
if (subCommandNames.length > 0) {
|
|
252
|
+
usageLine += " <command> [command's options]";
|
|
253
|
+
} else {
|
|
254
|
+
usageLine += ` [command's options] ${renderPositional(command.args)}`;
|
|
255
|
+
}
|
|
256
|
+
relinka("log", re.cyan(`Usage: ${usageLine}`));
|
|
257
|
+
if (subCommandDefs.length > 0) {
|
|
258
|
+
const randomIdx = Math.floor(Math.random() * subCommandDefs.length);
|
|
259
|
+
const exampleCmd = subCommandDefs[randomIdx];
|
|
260
|
+
if (exampleCmd) {
|
|
261
|
+
const { name: exampleCmdName, def: exampleDef } = exampleCmd;
|
|
262
|
+
const exampleArgs = buildExampleArgs(exampleDef.args || {});
|
|
263
|
+
relinka(
|
|
264
|
+
"log",
|
|
265
|
+
re.cyan(
|
|
266
|
+
`Example: ${pkgName}${parserOptions._isSubcommand ? ` ${cliName}` : ""} ${exampleCmdName}${exampleArgs ? ` ${exampleArgs}` : ""}`
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (subCommandNames.length > 0) {
|
|
272
|
+
relinka("info", "Available commands (run with `help` to see more):");
|
|
273
|
+
const commandItems = subCommandDefs.map(({ name, def }) => ({
|
|
274
|
+
text: `\u2022 ${name}`,
|
|
275
|
+
desc: def?.meta?.description
|
|
276
|
+
}));
|
|
277
|
+
const padding = calculatePadding(commandItems);
|
|
278
|
+
for (const { text, desc } of commandItems) {
|
|
279
|
+
relinka("log", formatTableRow(text, desc, padding));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
relinka("info", "Available options:");
|
|
284
|
+
const optionItems = [
|
|
285
|
+
{ text: "\u2022 -h, --help", desc: "Show help" },
|
|
286
|
+
{ text: "\u2022 -v, --version", desc: "Show version" },
|
|
287
|
+
{ text: "\u2022 --debug", desc: "Enable debug mode" }
|
|
288
|
+
];
|
|
289
|
+
for (const [key, def] of Object.entries(command.args || {})) {
|
|
290
|
+
if (def.type === "positional") {
|
|
291
|
+
optionItems.push({
|
|
292
|
+
text: `\u2022 <${key}>`,
|
|
293
|
+
desc: `${def.description ?? ""}${def.required ? " | required" : ""}`
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
const text = `\u2022 --${key}${"alias" in def && def.alias ? `, -${def.alias}` : ""}`;
|
|
297
|
+
const parts = [
|
|
298
|
+
def.description ?? "",
|
|
299
|
+
`type=${def.type}`,
|
|
300
|
+
def.default !== void 0 ? `default=${JSON.stringify(def.default)}` : null,
|
|
301
|
+
def.required ? "required" : null,
|
|
302
|
+
def.dependencies ? `depends on: ${def.dependencies.map((r) => `--${r}`).join(", ")}` : null
|
|
303
|
+
].filter(Boolean);
|
|
304
|
+
optionItems.push({ text, desc: parts.join(" | ") });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const optionsPadding = calculatePadding(optionItems);
|
|
308
|
+
for (const { text, desc } of optionItems) {
|
|
309
|
+
relinka("log", formatTableRow(text, desc, optionsPadding));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
export function createCli(options, legacyParserOptions) {
|
|
313
|
+
let command;
|
|
314
|
+
let parserOptions;
|
|
315
|
+
let globalCliMeta = {};
|
|
316
|
+
if (typeof options === "object" && !("run" in options) && !("meta" in options) && !("args" in options) && !("commands" in options)) {
|
|
317
|
+
command = options;
|
|
318
|
+
parserOptions = legacyParserOptions || {};
|
|
319
|
+
} else if ("mainCommand" in options) {
|
|
320
|
+
command = options.mainCommand;
|
|
321
|
+
parserOptions = {
|
|
322
|
+
fileBased: options.fileBased,
|
|
323
|
+
autoExit: options.autoExit,
|
|
324
|
+
metaSettings: options.metaSettings
|
|
325
|
+
};
|
|
326
|
+
globalCliMeta = {
|
|
327
|
+
name: options.name,
|
|
328
|
+
version: options.version,
|
|
329
|
+
description: options.description
|
|
330
|
+
};
|
|
331
|
+
} else {
|
|
332
|
+
const inlineOptions = options;
|
|
333
|
+
const {
|
|
334
|
+
name,
|
|
335
|
+
version,
|
|
336
|
+
description,
|
|
337
|
+
fileBased,
|
|
338
|
+
autoExit,
|
|
339
|
+
metaSettings,
|
|
340
|
+
mainCommand,
|
|
341
|
+
...commandOptions
|
|
342
|
+
} = inlineOptions;
|
|
343
|
+
command = {
|
|
344
|
+
meta: commandOptions.meta,
|
|
345
|
+
args: commandOptions.args,
|
|
346
|
+
run: commandOptions.run,
|
|
347
|
+
commands: commandOptions.commands,
|
|
348
|
+
onCmdInit: commandOptions.onCmdInit,
|
|
349
|
+
onCmdExit: commandOptions.onCmdExit,
|
|
350
|
+
onLauncherInit: commandOptions.onLauncherInit,
|
|
351
|
+
onLauncherExit: commandOptions.onLauncherExit
|
|
352
|
+
};
|
|
353
|
+
parserOptions = {
|
|
354
|
+
fileBased,
|
|
355
|
+
autoExit,
|
|
356
|
+
metaSettings
|
|
357
|
+
};
|
|
358
|
+
globalCliMeta = { name, version, description };
|
|
359
|
+
}
|
|
360
|
+
if (command.run && (globalCliMeta.name || globalCliMeta.version || globalCliMeta.description)) {
|
|
361
|
+
const mergedMeta = { ...command.meta };
|
|
362
|
+
if (globalCliMeta.name && !command.meta?.name) {
|
|
363
|
+
mergedMeta.name = globalCliMeta.name;
|
|
364
|
+
}
|
|
365
|
+
if (globalCliMeta.version && !command.meta?.version) {
|
|
366
|
+
mergedMeta.version = globalCliMeta.version;
|
|
367
|
+
}
|
|
368
|
+
if (globalCliMeta.description && !command.meta?.description) {
|
|
369
|
+
mergedMeta.description = globalCliMeta.description;
|
|
370
|
+
}
|
|
371
|
+
command = {
|
|
372
|
+
...command,
|
|
373
|
+
meta: mergedMeta
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const execute = async (_ctx) => {
|
|
377
|
+
if (typeof command.onLauncherInit === "function") {
|
|
378
|
+
try {
|
|
379
|
+
await command.onLauncherInit();
|
|
380
|
+
} catch (err) {
|
|
381
|
+
relinka("error", "Error in onLauncherInit:", err);
|
|
382
|
+
if (parserOptions.autoExit !== false) process.exit(1);
|
|
383
|
+
throw err;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
if (!parserOptions.fileBased && !command.commands) {
|
|
388
|
+
const mainEntry = process.argv[1] ? path.dirname(path.resolve(process.argv[1])) : process.cwd();
|
|
389
|
+
const defaultCmdsRoot = path.join(mainEntry, "src", "app");
|
|
390
|
+
const exists = await pathExists(defaultCmdsRoot);
|
|
391
|
+
const finalCmdsRoot = exists ? defaultCmdsRoot : path.join(mainEntry, "app");
|
|
392
|
+
parserOptions.fileBased = {
|
|
393
|
+
enable: true,
|
|
394
|
+
cmdsRootPath: finalCmdsRoot
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const rawArgv = process.argv.slice(2);
|
|
398
|
+
const autoExit = parserOptions.autoExit !== false;
|
|
399
|
+
if (!(parserOptions.fileBased?.enable || command.commands && Object.keys(command.commands).length > 0 || command.run)) {
|
|
400
|
+
relinka(
|
|
401
|
+
"error",
|
|
402
|
+
"Invalid CLI configuration: No file-based commands, subCommands, or run() handler are defined. This CLI will not do anything.\n\u2502 To fix: add file-based commands (./app), or provide at least one subCommand, or a run() handler."
|
|
403
|
+
);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
if (parserOptions.fileBased?.enable && rawArgv.length > 0) {
|
|
407
|
+
const commandsDir = path.resolve(parserOptions.fileBased.cmdsRootPath);
|
|
408
|
+
const resolved = await resolveFileBasedCommandPath(commandsDir, rawArgv);
|
|
409
|
+
if (resolved) {
|
|
410
|
+
const { def: subCommand, leftoverArgv, path: pathSegments } = resolved;
|
|
411
|
+
const helpIdx = leftoverArgv.findIndex(
|
|
412
|
+
(arg) => arg === "help" || arg === "--help" || arg === "-h"
|
|
413
|
+
);
|
|
414
|
+
if (helpIdx !== -1) {
|
|
415
|
+
await showUsage(
|
|
416
|
+
subCommand,
|
|
417
|
+
{
|
|
418
|
+
...parserOptions,
|
|
419
|
+
_fileBasedCurrentDir: pathSegments.length ? path.join(commandsDir, ...pathSegments) : commandsDir,
|
|
420
|
+
_fileBasedPathSegments: pathSegments
|
|
421
|
+
},
|
|
422
|
+
globalCliMeta
|
|
423
|
+
);
|
|
424
|
+
if (autoExit) process.exit(0);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const fileBasedEnabled = parserOptions.fileBased?.enable;
|
|
430
|
+
if (fileBasedEnabled && rawArgv.length > 0 && rawArgv[0] && !isFlag(rawArgv[0])) {
|
|
431
|
+
const [subName, ...subCmdArgv] = rawArgv;
|
|
432
|
+
try {
|
|
433
|
+
const ctx = getParsedContext(command, rawArgv, parserOptions);
|
|
434
|
+
if (typeof command.onCmdInit === "function") await command.onCmdInit(ctx);
|
|
435
|
+
await runFileBasedSubCmd(
|
|
436
|
+
subName,
|
|
437
|
+
subCmdArgv,
|
|
438
|
+
parserOptions.fileBased,
|
|
439
|
+
parserOptions,
|
|
440
|
+
command.onCmdExit ? async (_subCtx) => {
|
|
441
|
+
await command.onCmdExit?.(ctx);
|
|
442
|
+
} : void 0,
|
|
443
|
+
globalCliMeta
|
|
444
|
+
);
|
|
445
|
+
if (autoExit) process.exit(0);
|
|
446
|
+
return;
|
|
447
|
+
} catch (err) {
|
|
448
|
+
relinka("error", "Error loading file-based subcommand:", err.message);
|
|
449
|
+
if (autoExit) process.exit(1);
|
|
450
|
+
throw err;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (!fileBasedEnabled && command.commands && rawArgv.length > 0 && rawArgv[0] && !isFlag(rawArgv[0])) {
|
|
454
|
+
const [maybeSub, ...subCmdArgv] = rawArgv;
|
|
455
|
+
let subSpec;
|
|
456
|
+
for (const [key, spec] of Object.entries(command.commands)) {
|
|
457
|
+
if (key === maybeSub) {
|
|
458
|
+
subSpec = spec;
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const cmd = await loadSubCommand(spec);
|
|
463
|
+
if (cmd.meta?.aliases?.includes(maybeSub)) {
|
|
464
|
+
subSpec = spec;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
} catch (err) {
|
|
468
|
+
debugLog(`Error checking alias for command ${key}:`, err);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (subSpec) {
|
|
472
|
+
const helpIdx = subCmdArgv.findIndex(
|
|
473
|
+
(arg) => arg === "help" || arg === "--help" || arg === "-h"
|
|
474
|
+
);
|
|
475
|
+
if (helpIdx !== -1) {
|
|
476
|
+
const subCommandDef = await loadSubCommand(subSpec);
|
|
477
|
+
await showUsage(
|
|
478
|
+
subCommandDef,
|
|
479
|
+
{
|
|
480
|
+
...parserOptions,
|
|
481
|
+
_isSubcommand: true
|
|
482
|
+
},
|
|
483
|
+
globalCliMeta
|
|
484
|
+
);
|
|
485
|
+
if (autoExit) process.exit(0);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const ctx = getParsedContext(command, rawArgv, parserOptions);
|
|
490
|
+
if (typeof command.onCmdInit === "function") await command.onCmdInit(ctx);
|
|
491
|
+
await runSubCommand(
|
|
492
|
+
subSpec,
|
|
493
|
+
subCmdArgv,
|
|
494
|
+
{ ...parserOptions, _isSubcommand: true },
|
|
495
|
+
command.onCmdExit ? async (_subCtx) => {
|
|
496
|
+
await command.onCmdExit?.(ctx);
|
|
497
|
+
} : void 0,
|
|
498
|
+
globalCliMeta
|
|
499
|
+
);
|
|
500
|
+
if (autoExit) process.exit(0);
|
|
501
|
+
return;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
relinka("error", "Error running subcommand:", err.message);
|
|
504
|
+
if (autoExit) process.exit(1);
|
|
505
|
+
throw err;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
await relinkaConfig();
|
|
510
|
+
if (rawArgv[0] === "help" || checkHelp(rawArgv)) {
|
|
511
|
+
await showUsage(command, parserOptions, globalCliMeta);
|
|
512
|
+
if (autoExit) process.exit(0);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (checkVersion(rawArgv)) {
|
|
516
|
+
if (command.meta?.name) {
|
|
517
|
+
relinka(
|
|
518
|
+
"info",
|
|
519
|
+
`${command.meta?.name} ${command.meta?.version ? `v${command.meta?.version}` : ""}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (autoExit) process.exit(0);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
await runCommandWithArgs(command, rawArgv, parserOptions, globalCliMeta);
|
|
527
|
+
} finally {
|
|
528
|
+
}
|
|
529
|
+
await relinkaShutdown();
|
|
530
|
+
} finally {
|
|
531
|
+
if (typeof command.onLauncherExit === "function") await command.onLauncherExit();
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const promise = execute();
|
|
535
|
+
const cli = Object.assign(promise, {
|
|
536
|
+
/**
|
|
537
|
+
* @deprecated Use createCli() directly instead. This method will be removed in a future version.
|
|
538
|
+
* @example
|
|
539
|
+
* // Instead of:
|
|
540
|
+
* createCli({...}).run()
|
|
541
|
+
* // Use:
|
|
542
|
+
* await createCli({...})
|
|
543
|
+
*/
|
|
544
|
+
async run(_ctx) {
|
|
545
|
+
relinka(
|
|
546
|
+
"warn",
|
|
547
|
+
"\u26A0\uFE0F Deprecated: .run() method is deprecated. Use createCli() directly instead."
|
|
548
|
+
);
|
|
549
|
+
relinka("warn", " Instead of: createCli({...}).run()");
|
|
550
|
+
relinka("warn", " Use: await createCli({...})");
|
|
551
|
+
return execute(_ctx);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
return cli;
|
|
555
|
+
}
|
|
556
|
+
function checkHelp(argv) {
|
|
557
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
558
|
+
}
|
|
559
|
+
function checkVersion(argv) {
|
|
560
|
+
return argv.includes("--version") || argv.includes("-v");
|
|
561
|
+
}
|
|
562
|
+
async function loadSubCommand(spec) {
|
|
563
|
+
if (typeof spec === "string") {
|
|
564
|
+
const mod = await import(spec);
|
|
565
|
+
if (!mod.default) {
|
|
566
|
+
throw new Error(`Subcommand module "${spec}" has no default export`);
|
|
567
|
+
}
|
|
568
|
+
return mod.default;
|
|
569
|
+
}
|
|
570
|
+
const imported = await spec();
|
|
571
|
+
if ("default" in imported && imported.default) {
|
|
572
|
+
return imported.default;
|
|
573
|
+
}
|
|
574
|
+
if (imported) {
|
|
575
|
+
return imported;
|
|
576
|
+
}
|
|
577
|
+
throw new Error("Subcommand import did not return a valid command");
|
|
578
|
+
}
|
|
579
|
+
async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions, parentFinish, globalCliMeta) {
|
|
580
|
+
async function resolveCmdPath(baseDir, args) {
|
|
581
|
+
if (args.length === 0 || args[0] && isFlag(args[0])) {
|
|
582
|
+
const possibleFiles2 = [path.join(baseDir, "cmd.js"), path.join(baseDir, "cmd.ts")];
|
|
583
|
+
for (const file of possibleFiles2) {
|
|
584
|
+
if (await pathExists(file)) {
|
|
585
|
+
return { importPath: file, leftoverArgv: args };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
throw new Error(
|
|
589
|
+
`
|
|
590
|
+
Unknown command or arguments: ${args.join(" ")}
|
|
591
|
+
Info for this CLI's developer: No valid command file found in ${baseDir}`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const nextDir = path.join(baseDir, args[0] || "");
|
|
595
|
+
if (await pathExists(nextDir) && (await stat(nextDir)).isDirectory()) {
|
|
596
|
+
return resolveCmdPath(nextDir, args.slice(1));
|
|
597
|
+
}
|
|
598
|
+
const possibleFiles = [path.join(baseDir, "cmd.js"), path.join(baseDir, "cmd.ts")];
|
|
599
|
+
for (const file of possibleFiles) {
|
|
600
|
+
if (await pathExists(file)) {
|
|
601
|
+
return { importPath: file, leftoverArgv: args };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
throw new Error(
|
|
605
|
+
`
|
|
606
|
+
Unknown command or arguments: ${args.join(" ")}
|
|
607
|
+
Info for this CLI's developer: No valid command file found in ${baseDir}`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const startDir = path.join(fileCmdOpts.cmdsRootPath, subName);
|
|
611
|
+
if (!await pathExists(startDir) || !(await stat(startDir)).isDirectory()) {
|
|
612
|
+
const attempted = [subName, ...argv].join(" ");
|
|
613
|
+
const expectedPath = path.relative(
|
|
614
|
+
process.cwd(),
|
|
615
|
+
path.join(fileCmdOpts.cmdsRootPath, subName, "cmd.{ts,js}")
|
|
616
|
+
);
|
|
617
|
+
const allCommands = await findRecursiveFileBasedCommands(fileCmdOpts.cmdsRootPath);
|
|
618
|
+
const commandNames = allCommands.map((cmd) => cmd.path.join(" "));
|
|
619
|
+
let closestMatch = "";
|
|
620
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
621
|
+
for (const cmd of commandNames) {
|
|
622
|
+
const distance = levenshteinDistance(subName, cmd.split(" ")[0] || "");
|
|
623
|
+
if (distance < minDistance) {
|
|
624
|
+
minDistance = distance;
|
|
625
|
+
closestMatch = cmd;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const suggestion = minDistance <= 3 ? ` (Did you mean: \`${closestMatch}\`?)` : "";
|
|
629
|
+
throw new Error(
|
|
630
|
+
`
|
|
631
|
+
Unknown command or arguments: ${attempted}${suggestion}
|
|
632
|
+
Info for this CLI's developer: No valid command directory found, expected: ${expectedPath}`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const { importPath, leftoverArgv } = await resolveCmdPath(startDir, argv);
|
|
636
|
+
const imported = await import(path.resolve(importPath));
|
|
637
|
+
const subCommand = imported.default;
|
|
638
|
+
if (!subCommand) {
|
|
639
|
+
throw new Error(`File-based subcommand has no default export or is invalid: ${importPath}`);
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
const subCtx = await runCommandWithArgs(subCommand, leftoverArgv, parserOptions, globalCliMeta);
|
|
643
|
+
if (typeof parentFinish === "function" && subCtx) await parentFinish(subCtx);
|
|
644
|
+
} finally {
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function runSubCommand(spec, argv, parserOptions, parentFinish, globalCliMeta) {
|
|
648
|
+
const subCommand = await loadSubCommand(spec);
|
|
649
|
+
try {
|
|
650
|
+
const helpIdx = argv.findIndex((arg) => arg === "help" || arg === "--help" || arg === "-h");
|
|
651
|
+
if (helpIdx !== -1) {
|
|
652
|
+
await showUsage(subCommand, { ...parserOptions, _isSubcommand: true }, globalCliMeta);
|
|
653
|
+
if (parserOptions.autoExit !== false) process.exit(0);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const subCtx = await runCommandWithArgs(subCommand, argv, parserOptions, globalCliMeta, true);
|
|
657
|
+
if (typeof parentFinish === "function" && subCtx) await parentFinish(subCtx);
|
|
658
|
+
} finally {
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function runCommandWithArgs(command, argv, parserOptions, globalCliMeta, returnCtx) {
|
|
662
|
+
const autoExit = parserOptions.autoExit !== false;
|
|
663
|
+
const booleanKeys = Object.keys(command.args || {}).filter(
|
|
664
|
+
(k) => command.args?.[k]?.type === "boolean"
|
|
665
|
+
);
|
|
666
|
+
const defaultMap = {};
|
|
667
|
+
for (const [argKey, def] of Object.entries(command.args || {})) {
|
|
668
|
+
if (def.type === "boolean") {
|
|
669
|
+
defaultMap[argKey] = def.default !== void 0 ? def.default : false;
|
|
670
|
+
} else if (def.default !== void 0) {
|
|
671
|
+
defaultMap[argKey] = def.type === "array" && typeof def.default === "string" ? [def.default] : def.default;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const mergedParserOptions = {
|
|
675
|
+
...parserOptions,
|
|
676
|
+
boolean: [...parserOptions.boolean || [], ...booleanKeys],
|
|
677
|
+
defaults: { ...defaultMap, ...parserOptions.defaults || {} }
|
|
678
|
+
};
|
|
679
|
+
const parsed = reliArgParser(argv, mergedParserOptions);
|
|
680
|
+
debugLog("Parsed arguments:", parsed);
|
|
681
|
+
const finalArgs = {};
|
|
682
|
+
const positionalKeys = Object.keys(command.args || {}).filter(
|
|
683
|
+
(k) => command.args?.[k]?.type === "positional"
|
|
684
|
+
);
|
|
685
|
+
const leftoverPositionals = [...parsed._ || []];
|
|
686
|
+
for (let i = 0; i < positionalKeys.length; i++) {
|
|
687
|
+
const key = positionalKeys[i];
|
|
688
|
+
if (!key || !command.args) continue;
|
|
689
|
+
const def = command.args[key];
|
|
690
|
+
const val = leftoverPositionals[i];
|
|
691
|
+
finalArgs[key] = val != null && def ? castArgValue(def, val, key) : def?.default;
|
|
692
|
+
}
|
|
693
|
+
const otherKeys = Object.keys(command.args || {}).filter(
|
|
694
|
+
(k) => command.args?.[k]?.type !== "positional"
|
|
695
|
+
);
|
|
696
|
+
for (const key of otherKeys) {
|
|
697
|
+
const def = command.args?.[key];
|
|
698
|
+
if (!def) continue;
|
|
699
|
+
let rawVal = parsed[key];
|
|
700
|
+
if (def.type === "array" && rawVal !== void 0 && !Array.isArray(rawVal)) {
|
|
701
|
+
rawVal = [rawVal];
|
|
702
|
+
}
|
|
703
|
+
const casted = rawVal !== void 0 ? castArgValue(def, rawVal, key) : def.default;
|
|
704
|
+
const argUsed = rawVal !== void 0 && (def.type === "boolean" ? casted === true : true);
|
|
705
|
+
if (casted == null && def.required) {
|
|
706
|
+
await showUsage(command, parserOptions, globalCliMeta);
|
|
707
|
+
relinka("error", `Missing required argument: --${key}`);
|
|
708
|
+
if (autoExit) process.exit(1);
|
|
709
|
+
else throw new Error(`Missing required argument: --${key}`);
|
|
710
|
+
}
|
|
711
|
+
if (argUsed && def.dependencies?.length) {
|
|
712
|
+
const missingDeps = def.dependencies.filter((d) => {
|
|
713
|
+
const depVal = parsed[d] ?? defaultMap[d];
|
|
714
|
+
return !depVal;
|
|
715
|
+
});
|
|
716
|
+
if (missingDeps.length > 0) {
|
|
717
|
+
const depsList = missingDeps.map((d) => `--${d}`).join(", ");
|
|
718
|
+
throw new Error(
|
|
719
|
+
`Argument --${key} can only be used when ${depsList} ${missingDeps.length === 1 ? "is" : "are"} set`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
finalArgs[key] = def.type === "boolean" ? Boolean(casted) : casted;
|
|
724
|
+
}
|
|
725
|
+
const ctx = {
|
|
726
|
+
args: finalArgs,
|
|
727
|
+
raw: argv
|
|
728
|
+
};
|
|
729
|
+
try {
|
|
730
|
+
if (command.run) {
|
|
731
|
+
await command.run(ctx);
|
|
732
|
+
} else {
|
|
733
|
+
const isDispatcher = parserOptions.fileBased?.enable || command.commands && Object.keys(command.commands).length > 0;
|
|
734
|
+
const noSubcommandArgInCurrentCall = !argv.some((arg) => !isFlag(arg));
|
|
735
|
+
if (isDispatcher && noSubcommandArgInCurrentCall) {
|
|
736
|
+
relinka("warn", "Please specify a command");
|
|
737
|
+
await showUsage(command, parserOptions, globalCliMeta);
|
|
738
|
+
if (autoExit) process.exit(0);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const cmdName = command.meta?.name || "unknown";
|
|
742
|
+
const attempted = argv.length > 0 ? argv.join(" ") : "(no arguments)";
|
|
743
|
+
await showUsage(command, parserOptions, globalCliMeta);
|
|
744
|
+
relinka("error", `Unknown command or arguments: ${attempted}`);
|
|
745
|
+
if (autoExit) {
|
|
746
|
+
process.exit(1);
|
|
747
|
+
} else {
|
|
748
|
+
throw new Error(`Command "${cmdName}" is not runnable.`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} catch (err) {
|
|
752
|
+
relinka("error", `Error while executing command:
|
|
753
|
+
${String(err)}`);
|
|
754
|
+
if (autoExit) process.exit(1);
|
|
755
|
+
else throw err;
|
|
756
|
+
}
|
|
757
|
+
if (returnCtx) return ctx;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
function castArgValue(def, rawVal, argName) {
|
|
761
|
+
if (rawVal == null) {
|
|
762
|
+
if (def.type === "array" && typeof def.default === "string") {
|
|
763
|
+
return [def.default];
|
|
764
|
+
}
|
|
765
|
+
return def.default ?? void 0;
|
|
766
|
+
}
|
|
767
|
+
let castedValue;
|
|
768
|
+
switch (def.type) {
|
|
769
|
+
case "boolean":
|
|
770
|
+
if (typeof rawVal === "string") {
|
|
771
|
+
const lower = rawVal.toLowerCase();
|
|
772
|
+
if (lower === "true") castedValue = true;
|
|
773
|
+
else if (lower === "false") castedValue = false;
|
|
774
|
+
else castedValue = Boolean(rawVal);
|
|
775
|
+
} else {
|
|
776
|
+
castedValue = Boolean(rawVal);
|
|
777
|
+
}
|
|
778
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
779
|
+
throw new Error(
|
|
780
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
return castedValue;
|
|
784
|
+
case "string":
|
|
785
|
+
castedValue = typeof rawVal === "string" ? rawVal : String(rawVal);
|
|
786
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
787
|
+
throw new Error(
|
|
788
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return castedValue;
|
|
792
|
+
case "number": {
|
|
793
|
+
const n = Number(rawVal);
|
|
794
|
+
if (Number.isNaN(n)) {
|
|
795
|
+
throw new Error(`Invalid number provided for --${argName}: ${rawVal}`);
|
|
796
|
+
}
|
|
797
|
+
if (def.allowed && !def.allowed.includes(n)) {
|
|
798
|
+
throw new Error(
|
|
799
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return n;
|
|
803
|
+
}
|
|
804
|
+
case "positional":
|
|
805
|
+
castedValue = String(rawVal);
|
|
806
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
`Invalid value for <${argName}>: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
return castedValue;
|
|
812
|
+
case "array": {
|
|
813
|
+
const arrVal = Array.isArray(rawVal) ? rawVal : [String(rawVal)];
|
|
814
|
+
const result = [];
|
|
815
|
+
const arrValStr = arrVal.map(String);
|
|
816
|
+
let warned = false;
|
|
817
|
+
for (let v of arrValStr) {
|
|
818
|
+
if (!warned && (v.startsWith("[") && !v.endsWith("]") || !v.startsWith("[") && v.endsWith("]"))) {
|
|
819
|
+
relinka("error", `Don't use quotes around array elements.`);
|
|
820
|
+
relinka("error", `Also \u2014 don't use spaces \u2014 unless you wrap the whole array in quotes.`);
|
|
821
|
+
relinka(
|
|
822
|
+
"warn",
|
|
823
|
+
`Array argument --${argName}: Detected possible shell splitting of bracketed value ('${v}').`
|
|
824
|
+
);
|
|
825
|
+
relinka(
|
|
826
|
+
"warn",
|
|
827
|
+
`If you intended to pass a bracketed list, quote the whole value like: --${argName} "[a, b, c]"`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
warned = true;
|
|
831
|
+
if (v.startsWith("[") && v.endsWith("]")) {
|
|
832
|
+
v = v.slice(1, -1);
|
|
833
|
+
}
|
|
834
|
+
const parts = v.split(/\s*,\s*/).filter(Boolean);
|
|
835
|
+
parts.forEach((p) => {
|
|
836
|
+
if (p.startsWith('"') && p.endsWith('"') || p.startsWith("'") && p.endsWith("'")) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`Array argument --${argName}: Quoted values are not supported due to shell parsing limitations. Please avoid using single or double quotes around array elements.`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
if (def.allowed && !def.allowed.includes(p)) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
`Invalid value in array --${argName}: ${p}. Allowed values are: ${def.allowed.join(", ")}`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
result.push(...parts);
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
default:
|
|
852
|
+
return rawVal;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
function renderPositional(args) {
|
|
856
|
+
const positionalKeys = Object.keys(args || {}).filter((k) => args?.[k]?.type === "positional");
|
|
857
|
+
return positionalKeys.map((k) => `<${k}>`).join(" ");
|
|
858
|
+
}
|
|
859
|
+
export function defineArgs(args) {
|
|
860
|
+
return args;
|
|
861
|
+
}
|
|
862
|
+
function normalizeArgv(argv) {
|
|
863
|
+
const normalized = [];
|
|
864
|
+
for (const arg of argv) {
|
|
865
|
+
const parts = arg.split(/\s+/).filter((part) => part.length > 0);
|
|
866
|
+
normalized.push(...parts);
|
|
867
|
+
}
|
|
868
|
+
return normalized;
|
|
869
|
+
}
|
|
870
|
+
function getParsedContext(command, argv, parserOptions) {
|
|
871
|
+
const normalizedArgv = normalizeArgv(argv);
|
|
872
|
+
const booleanKeys = Object.keys(command.args || {}).filter(
|
|
873
|
+
(k) => command.args?.[k]?.type === "boolean"
|
|
874
|
+
);
|
|
875
|
+
const defaultMap = {};
|
|
876
|
+
for (const [argKey, def] of Object.entries(command.args || {})) {
|
|
877
|
+
if (def.type === "boolean") {
|
|
878
|
+
defaultMap[argKey] = def.default !== void 0 ? def.default : false;
|
|
879
|
+
} else if (def.default !== void 0) {
|
|
880
|
+
defaultMap[argKey] = def.type === "array" && typeof def.default === "string" ? [def.default] : def.default;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const mergedParserOptions = {
|
|
884
|
+
...parserOptions,
|
|
885
|
+
boolean: [...parserOptions.boolean || [], ...booleanKeys],
|
|
886
|
+
defaults: { ...defaultMap, ...parserOptions.defaults || {} }
|
|
887
|
+
};
|
|
888
|
+
const parsed = reliArgParser(normalizedArgv, mergedParserOptions);
|
|
889
|
+
const finalArgs = {};
|
|
890
|
+
const positionalKeys = Object.keys(command.args || {}).filter(
|
|
891
|
+
(k) => command.args?.[k]?.type === "positional"
|
|
892
|
+
);
|
|
893
|
+
const leftoverPositionals = [...parsed._ || []];
|
|
894
|
+
for (let i = 0; i < positionalKeys.length; i++) {
|
|
895
|
+
const key = positionalKeys[i];
|
|
896
|
+
if (!key || !command.args) continue;
|
|
897
|
+
const def = command.args[key];
|
|
898
|
+
const val = leftoverPositionals[i];
|
|
899
|
+
finalArgs[key] = val != null && def ? castArgValue(def, val, key) : def?.default;
|
|
900
|
+
}
|
|
901
|
+
const otherKeys = Object.keys(command.args || {}).filter(
|
|
902
|
+
(k) => command.args?.[k]?.type !== "positional"
|
|
903
|
+
);
|
|
904
|
+
for (const key of otherKeys) {
|
|
905
|
+
const def = command.args?.[key];
|
|
906
|
+
if (!def) continue;
|
|
907
|
+
let rawVal = parsed[key];
|
|
908
|
+
if (def.type === "array" && rawVal !== void 0 && !Array.isArray(rawVal)) {
|
|
909
|
+
rawVal = [rawVal];
|
|
910
|
+
}
|
|
911
|
+
if (def.type === "boolean") {
|
|
912
|
+
finalArgs[key] = rawVal !== void 0 ? castArgValue(def, rawVal, key) : false;
|
|
913
|
+
} else {
|
|
914
|
+
finalArgs[key] = castArgValue(def, rawVal, key);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return { args: finalArgs, raw: argv };
|
|
918
|
+
}
|
|
919
|
+
async function resolveFileBasedCommandPath(cmdsRoot, argv) {
|
|
920
|
+
let currentDir = cmdsRoot;
|
|
921
|
+
const pathSegments = [];
|
|
922
|
+
let leftover = [...argv];
|
|
923
|
+
while (leftover.length > 0 && leftover[0] && !isFlag(leftover[0])) {
|
|
924
|
+
const nextDir = path.join(currentDir, leftover[0]);
|
|
925
|
+
if (await pathExists(nextDir) && (await stat(nextDir)).isDirectory()) {
|
|
926
|
+
currentDir = nextDir;
|
|
927
|
+
pathSegments.push(leftover[0]);
|
|
928
|
+
leftover = leftover.slice(1);
|
|
929
|
+
} else {
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
for (const fname of ["cmd.ts", "cmd.js"]) {
|
|
934
|
+
const fpath = path.join(currentDir, fname);
|
|
935
|
+
if (await pathExists(fpath)) {
|
|
936
|
+
const imported = await import(path.resolve(fpath));
|
|
937
|
+
if (imported.default) {
|
|
938
|
+
return {
|
|
939
|
+
def: imported.default,
|
|
940
|
+
path: pathSegments,
|
|
941
|
+
leftoverArgv: leftover
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
function levenshteinDistance(a, b) {
|
|
949
|
+
if (a.length === 0) return b.length;
|
|
950
|
+
if (b.length === 0) return a.length;
|
|
951
|
+
const matrix = [];
|
|
952
|
+
for (let i = 0; i <= b.length; i++) {
|
|
953
|
+
matrix[i] = [i];
|
|
954
|
+
}
|
|
955
|
+
for (let j = 0; j <= a.length; j++) {
|
|
956
|
+
matrix[0][j] = j;
|
|
957
|
+
}
|
|
958
|
+
for (let i = 1; i <= b.length; i++) {
|
|
959
|
+
for (let j = 1; j <= a.length; j++) {
|
|
960
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
961
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
962
|
+
} else {
|
|
963
|
+
matrix[i][j] = Math.min(
|
|
964
|
+
matrix[i - 1][j - 1] + 1,
|
|
965
|
+
// substitution
|
|
966
|
+
matrix[i][j - 1] + 1,
|
|
967
|
+
// insertion
|
|
968
|
+
matrix[i - 1][j] + 1
|
|
969
|
+
// deletion
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return matrix[b.length][a.length];
|
|
975
|
+
}
|