@reliverse/rempts 1.7.10 → 1.7.12

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.
@@ -1,4 +1,5 @@
1
1
  import { reliArgParser } from "@reliverse/reliarg";
2
+ import { re } from "@reliverse/relico";
2
3
  import { relinka, relinkaConfig, relinkaShutdown } from "@reliverse/relinka";
3
4
  import fs from "fs-extra";
4
5
  import process from "node:process";
@@ -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")
@@ -1,20 +1,64 @@
1
+ import { type Color, type Options as OraOptions } from "ora";
1
2
  type SpinnerOptions = {
2
3
  text: string;
4
+ color?: Color;
5
+ spinner?: OraOptions["spinner"];
6
+ successText?: string;
7
+ failText?: string;
3
8
  };
4
9
  type SpinnerControls = {
5
- start: () => SpinnerControls;
10
+ start: (text?: string) => SpinnerControls;
6
11
  stop: () => void;
7
12
  setText: (text: string) => void;
13
+ succeed: (text?: string) => void;
14
+ fail: (text?: string) => void;
15
+ warn: (text?: string) => void;
16
+ info: (text?: string) => void;
17
+ isSpinning: () => boolean;
18
+ clear: () => void;
8
19
  };
9
20
  /**
10
- * Creates a terminal spinner.
21
+ * Creates a terminal spinner with enhanced controls and styling options.
11
22
  *
12
23
  * @example
13
- * \`\`\`typescript
14
- * const spinner = useSpinner({ text: "preparing something..." }).start();
15
- * // user's code
24
+ * ```typescript
25
+ * // Basic usage
26
+ * const spinner = useSpinner({ text: "Loading..." }).start();
16
27
  * spinner.stop();
17
- * \`\`\`
28
+ *
29
+ * // With custom color and spinner
30
+ * const spinner = useSpinner({
31
+ * text: "Processing...",
32
+ * color: "cyan",
33
+ * spinner: "dots"
34
+ * }).start();
35
+ *
36
+ * // With success/failure states
37
+ * const spinner = useSpinner({
38
+ * text: "Uploading...",
39
+ * successText: "Upload complete!",
40
+ * failText: "Upload failed!"
41
+ * }).start();
42
+ * try {
43
+ * await uploadFile();
44
+ * spinner.succeed();
45
+ * } catch (error) {
46
+ * spinner.fail();
47
+ * }
48
+ *
49
+ * // Using the wrapper for async operations
50
+ * await useSpinner.promise(
51
+ * async () => { await longOperation(); },
52
+ * {
53
+ * text: "Working...",
54
+ * successText: "Done!",
55
+ * failText: "Failed!"
56
+ * }
57
+ * );
58
+ * ```
18
59
  */
19
60
  export declare function useSpinner(options: SpinnerOptions): SpinnerControls;
61
+ export declare namespace useSpinner {
62
+ var promise: <T>(operation: () => Promise<T>, options: SpinnerOptions) => Promise<T>;
63
+ }
20
64
  export {};
@@ -2,9 +2,18 @@ import ora from "ora";
2
2
  export function useSpinner(options) {
3
3
  let spinnerInstance = null;
4
4
  const controls = {
5
- start: () => {
5
+ start: (text) => {
6
+ if (text) {
7
+ options.text = text;
8
+ }
6
9
  if (!spinnerInstance) {
7
- spinnerInstance = ora(options.text);
10
+ spinnerInstance = ora({
11
+ text: options.text,
12
+ color: options.color,
13
+ spinner: options.spinner
14
+ });
15
+ } else {
16
+ spinnerInstance.text = options.text;
8
17
  }
9
18
  spinnerInstance.start();
10
19
  return controls;
@@ -20,7 +29,46 @@ export function useSpinner(options) {
20
29
  } else {
21
30
  options.text = text;
22
31
  }
32
+ },
33
+ succeed: (text) => {
34
+ if (spinnerInstance) {
35
+ spinnerInstance.succeed(text ?? options.successText);
36
+ }
37
+ },
38
+ fail: (text) => {
39
+ if (spinnerInstance) {
40
+ spinnerInstance.fail(text ?? options.failText);
41
+ }
42
+ },
43
+ warn: (text) => {
44
+ if (spinnerInstance) {
45
+ spinnerInstance.warn(text);
46
+ }
47
+ },
48
+ info: (text) => {
49
+ if (spinnerInstance) {
50
+ spinnerInstance.info(text);
51
+ }
52
+ },
53
+ isSpinning: () => {
54
+ return spinnerInstance?.isSpinning ?? false;
55
+ },
56
+ clear: () => {
57
+ if (spinnerInstance) {
58
+ spinnerInstance.clear();
59
+ }
23
60
  }
24
61
  };
25
62
  return controls;
26
63
  }
64
+ useSpinner.promise = async (operation, options) => {
65
+ const spinner = useSpinner(options).start();
66
+ try {
67
+ const result = await operation();
68
+ spinner.succeed();
69
+ return result;
70
+ } catch (error) {
71
+ spinner.fail();
72
+ throw error;
73
+ }
74
+ };
package/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "name": "@reliverse/rempts",
30
30
  "type": "module",
31
- "version": "1.7.10",
31
+ "version": "1.7.12",
32
32
  "author": "reliverse",
33
33
  "bugs": {
34
34
  "email": "blefnk@gmail.com",
@@ -44,7 +44,7 @@
44
44
  "devDependencies": {
45
45
  "@biomejs/biome": "1.9.4",
46
46
  "@eslint/js": "^9.26.0",
47
- "@reliverse/dler": "^1.2.4",
47
+ "@reliverse/dler": "^1.2.5",
48
48
  "@reliverse/relidler-cfg": "^1.1.3",
49
49
  "@stylistic/eslint-plugin": "^4.2.0",
50
50
  "@total-typescript/ts-reset": "^0.6.1",