@reliverse/rempts 1.6.1 โ†’ 1.6.2

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright (c) 2025 blefnk Nazar Kornienko
3
+ Copyright (c) 2025 Nazar Kornienko (blefnk), Reliverse
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [๐Ÿ’ฌ Discord](https://discord.gg/3GawfWfAPe) โ€ข [๐Ÿ“ฆ NPM](https://npmjs.com/package/@reliverse/rempts) โ€ข [๐Ÿง  Docs](https://docs.reliverse.org/reliverse/rempts) โ€ข [๐ŸŒ JSR](https://jsr.io/@reliverse/rempts) โ€ข [โœจ GitHub](https://github.com/reliverse/rempts)
4
4
 
5
- > @reliverse/rempts is a prompt library built for modern CLI devs. Fast. Type-safe. Unreasonably delightful.
5
+ > @reliverse/rempts is a prompt library built with developer joy in mind. File-based commands. Flexible. Fast. Forget the clutter, it just works. This is a typesafe toolkit for building delightful CLI experiences.
6
6
 
7
7
  ## Stop Fighting Your CLI
8
8
 
@@ -12,6 +12,7 @@
12
12
 
13
13
  ## What Makes It Different?
14
14
 
15
+ - ๐Ÿ“‚ File-based commands (optional)
15
16
  - ๐Ÿง  Type-safe from args to prompts
16
17
  - ๐ŸŽจ Customizable themes, styled output
17
18
  - ๐Ÿงฉ Router + argument parser built-in
@@ -20,6 +21,7 @@
20
21
  - ๐ŸŽ๏ธ Prompt engine that *feels* modern, actually is
21
22
  - ๐Ÿงช Scriptable for testing, stable for production
22
23
  - ๐Ÿšจ Crash-safe (Ctrl+C, SIGINT, errors)
24
+ - ๐Ÿ†• Automatic commands creation via `rempts init --cmd my-cool-cmd`
23
25
 
24
26
  ## Screenshot
25
27
 
@@ -371,12 +373,12 @@ await runMain(mainCommand, {
371
373
  v: "verbose", // Maps shorthand flag -v to --verbose.
372
374
  },
373
375
  strict: false, // Do not throw errors for unknown flags.
374
- warnOnUnknown: true, // Warn when encountering unknown flags.
376
+ warnOnUnknown: false, // Warn when encountering unknown flags.
375
377
  negatedBoolean: true, // Support for negated booleans (e.g., --no-verbose).
376
- unknown: (flagName) => {
377
- relinka("warn", "Unknown flag encountered:", flagName);
378
- return false;
379
- },
378
+ // unknown: (flagName) => {
379
+ // relinka("warn", "Unknown flag encountered:", flagName);
380
+ // return false;
381
+ // },
380
382
  });
381
383
  ```
382
384
 
@@ -392,6 +394,10 @@ You're in the right place:
392
394
 
393
395
  > *No classes. No magic. Just clean, composable tools for CLI devs.*
394
396
 
397
+ ### Helpful Links
398
+
399
+ - [CLI application with the Node.js Readline module](https://dev.to/camptocamp-geo/cli-application-with-the-nodejs-readline-module-48ic)
400
+
395
401
  ## Related
396
402
 
397
403
  - [`@reliverse/cli`](https://npmjs.com/package/@reliverse/cli) โ€“ CLI-first toolkit for fullstack workflows
@@ -31,8 +31,9 @@ type CommandMeta = {
31
31
  };
32
32
  /**
33
33
  * A subcommand can be either:
34
- * 1) A string path to a module with a default export of type Command.
35
- * 2) A lazy import function returning a Promise that resolves to { default: Command<any> } or directly to a Command instance.
34
+ * 1) A string path to a module with a default export of type Command.
35
+ * 2) A lazy import function returning a Promise that resolves to
36
+ * { default: Command<any> } or directly to a Command instance.
36
37
  */
37
38
  type SubCommandSpec = string | (() => Promise<{
38
39
  default: Command<any>;
@@ -64,7 +65,7 @@ export type FileBasedCmdsOptions = {
64
65
  };
65
66
  /**
66
67
  * Defines a command with metadata, argument definitions,
67
- * an execution function, and subCommands.
68
+ * an execution function, and (optional) subCommands.
68
69
  */
69
70
  export declare function defineCommand<A extends ArgDefinitions = EmptyArgs>(options: DefineCommandOptions<A>): Command<A>;
70
71
  /**
@@ -75,10 +76,14 @@ export declare function showUsage<A extends ArgDefinitions>(command: Command<A>,
75
76
  autoExit?: boolean;
76
77
  }): Promise<void>;
77
78
  /**
78
- * Primary entry point to run a command, with support for subcommands or file-based commands.
79
+ * Primary entry point to run a command. This function supports:
79
80
  *
80
- * If the user does not provide any subCommands (or they are empty) AND no fileBasedCmds options,
81
- * then file-based command detection is automatically enabled with a default cmdsRootPath.
81
+ * - File-based Subcommands: scanning for subcommands within a given commands root.
82
+ * - SubCommands defined within the command object.
83
+ * - Standard flags like --help, --version, and --debug.
84
+ *
85
+ * This function passes along remaining arguments to subcommand runners to ensure
86
+ * consistent parsing.
82
87
  */
83
88
  export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: Command<A>, parserOptions?: ReliArgParserOptions & {
84
89
  fileBasedCmds?: FileBasedCmdsOptions;
@@ -1,5 +1,5 @@
1
1
  import { reliArgParser } from "@reliverse/reliarg";
2
- import { relinka } from "@reliverse/relinka";
2
+ import { relinka, relinkaConfig, relinkaShutdown } from "@reliverse/relinka";
3
3
  import fs from "fs-extra";
4
4
  import process from "node:process";
5
5
  import path from "pathe";
@@ -9,6 +9,9 @@ function debugLog(...args) {
9
9
  relinka("info", "[DEBUG]", ...args);
10
10
  }
11
11
  }
12
+ function isFlag(str) {
13
+ return str.startsWith("-");
14
+ }
12
15
  export function defineCommand(options) {
13
16
  return {
14
17
  meta: options.meta,
@@ -18,7 +21,7 @@ export function defineCommand(options) {
18
21
  };
19
22
  }
20
23
  export async function showUsage(command, parserOptions = {}) {
21
- relinka("info", `${command.meta.name} v${command.meta.version || ""}`);
24
+ relinka("info", `${command.meta.name} v${command.meta.version ?? ""}`);
22
25
  if (command.meta.description) {
23
26
  relinka("info", command.meta.description);
24
27
  }
@@ -28,7 +31,9 @@ export async function showUsage(command, parserOptions = {}) {
28
31
  relinka("info", usageLine);
29
32
  try {
30
33
  const commandsDir = path.resolve(fileCmds.cmdsRootPath);
31
- const items = await fs.readdir(commandsDir, { withFileTypes: true });
34
+ const items = await fs.readdir(commandsDir, {
35
+ withFileTypes: true
36
+ });
32
37
  const subCommandNames = [];
33
38
  for (const dirent of items) {
34
39
  if (dirent.isDirectory() || dirent.isFile()) {
@@ -36,14 +41,14 @@ export async function showUsage(command, parserOptions = {}) {
36
41
  try {
37
42
  const importPath = path.join(
38
43
  commandsDir,
39
- dirent.isDirectory() ? `${name}/index.js` : `${name}.js`
44
+ dirent.isDirectory() ? `${name}/index.ts` : `${name}.ts`
40
45
  );
41
46
  const imported = await import(path.resolve(importPath));
42
47
  if (imported.default && !imported.default.meta?.hidden) {
43
48
  subCommandNames.push(name);
44
49
  }
45
- } catch (_err) {
46
- debugLog(`Skipping file ${dirent.name}:`, _err);
50
+ } catch (err) {
51
+ debugLog(`Skipping file ${dirent.name}:`, err);
47
52
  }
48
53
  }
49
54
  }
@@ -51,13 +56,13 @@ export async function showUsage(command, parserOptions = {}) {
51
56
  relinka("info", "\nAvailable file-based subCommands:");
52
57
  subCommandNames.forEach((sc) => relinka("info", ` ${sc}`));
53
58
  }
54
- } catch (_err) {
59
+ } catch (err) {
55
60
  relinka(
56
61
  "info",
57
62
  `
58
63
  (No file-based subcommands found in ${fileCmds.cmdsRootPath}.)`
59
64
  );
60
- debugLog("Error reading file-based commands:", _err);
65
+ debugLog("Error reading file-based commands:", err);
61
66
  }
62
67
  } else {
63
68
  const subCommandNames = [];
@@ -69,8 +74,8 @@ export async function showUsage(command, parserOptions = {}) {
69
74
  const aliasDisplay = cmd.meta.aliases ? ` (aliases: ${cmd.meta.aliases.join(", ")})` : "";
70
75
  subCommandNames.push(`${name}${aliasDisplay}`);
71
76
  }
72
- } catch (_err) {
73
- debugLog(`Error loading subcommand ${name}:`, _err);
77
+ } catch (err) {
78
+ debugLog(`Error loading subcommand ${name}:`, err);
74
79
  }
75
80
  }
76
81
  }
@@ -94,12 +99,12 @@ export async function showUsage(command, parserOptions = {}) {
94
99
  if (def.type === "positional") {
95
100
  relinka(
96
101
  "info",
97
- ` <${key}> : ${def.description || ""} ${def.required ? "(required)" : ""}`
102
+ ` <${key}> : ${def.description ?? ""} ${def.required ? "(required)" : ""}`
98
103
  );
99
104
  } else {
100
105
  relinka(
101
106
  "info",
102
- ` --${key} : ${def.description || ""} (type=${def.type})${def.required ? " (required)" : ""}${def.default !== void 0 ? ` [default: ${def.default}]` : ""}`
107
+ ` --${key} : ${def.description ?? ""} (type=${def.type})${def.required ? " (required)" : ""}${def.default !== void 0 ? ` [default: ${def.default}]` : ""}`
103
108
  );
104
109
  }
105
110
  }
@@ -112,24 +117,26 @@ export async function runMain(command, parserOptions = {}) {
112
117
  // default path
113
118
  };
114
119
  }
115
- const argv = process.argv.slice(2);
120
+ const rawArgv = process.argv.slice(2);
116
121
  const autoExit = parserOptions.autoExit !== false;
117
- if (checkHelp(argv)) {
122
+ await relinkaConfig;
123
+ if (checkHelp(rawArgv)) {
118
124
  await showUsage(command, parserOptions);
119
125
  if (autoExit) process.exit(0);
120
126
  return;
121
127
  }
122
- if (checkVersion(argv)) {
123
- relinka("info", `${command.meta.name} v${command.meta.version || ""}`);
128
+ if (checkVersion(rawArgv)) {
129
+ relinka("info", `${command.meta.name} v${command.meta.version ?? ""}`);
124
130
  if (autoExit) process.exit(0);
125
131
  return;
126
132
  }
127
133
  const fileBasedEnabled = parserOptions.fileBasedCmds?.enable;
128
- if (fileBasedEnabled && argv.length > 0) {
129
- const subName = argv.shift();
134
+ if (fileBasedEnabled && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
135
+ const [subName, ...subCmdArgv] = rawArgv;
130
136
  try {
131
137
  await runFileBasedSubCmd(
132
138
  subName,
139
+ subCmdArgv,
133
140
  parserOptions.fileBasedCmds,
134
141
  parserOptions
135
142
  );
@@ -141,8 +148,8 @@ export async function runMain(command, parserOptions = {}) {
141
148
  throw err;
142
149
  }
143
150
  }
144
- if (!fileBasedEnabled && command.subCommands && argv.length > 0) {
145
- const maybeSub = argv[0];
151
+ if (!fileBasedEnabled && command.subCommands && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
152
+ const [maybeSub, ...subCmdArgv] = rawArgv;
146
153
  let subSpec;
147
154
  for (const [key, spec] of Object.entries(command.subCommands)) {
148
155
  if (key === maybeSub) {
@@ -155,14 +162,13 @@ export async function runMain(command, parserOptions = {}) {
155
162
  subSpec = spec;
156
163
  break;
157
164
  }
158
- } catch (_err) {
159
- debugLog(`Error checking alias for subcommand ${key}:`, _err);
165
+ } catch (err) {
166
+ debugLog(`Error checking alias for subcommand ${key}:`, err);
160
167
  }
161
168
  }
162
169
  if (subSpec) {
163
- argv.shift();
164
170
  try {
165
- await runSubCommand(subSpec, parserOptions);
171
+ await runSubCommand(subSpec, subCmdArgv, parserOptions);
166
172
  if (autoExit) process.exit(0);
167
173
  return;
168
174
  } catch (err) {
@@ -172,7 +178,8 @@ export async function runMain(command, parserOptions = {}) {
172
178
  }
173
179
  }
174
180
  }
175
- await runCommandWithArgs(command, argv, parserOptions);
181
+ await runCommandWithArgs(command, rawArgv, parserOptions);
182
+ await relinkaShutdown();
176
183
  }
177
184
  function checkHelp(argv) {
178
185
  return argv.includes("--help") || argv.includes("-h");
@@ -197,14 +204,16 @@ async function loadSubCommand(spec) {
197
204
  }
198
205
  throw new Error("Subcommand import did not return a valid command");
199
206
  }
200
- async function runFileBasedSubCmd(subName, fileCmdOpts, parserOptions) {
207
+ async function runFileBasedSubCmd(subName, argv, fileCmdOpts, parserOptions) {
201
208
  const subPathDir = path.join(fileCmdOpts.cmdsRootPath, subName);
202
209
  let importPath;
203
210
  const possibleFiles = [
211
+ // When command is inside a directory
204
212
  path.join(subPathDir, "index.js"),
205
213
  path.join(subPathDir, "index.ts"),
206
214
  path.join(subPathDir, `${subName}-mod.js`),
207
215
  path.join(subPathDir, `${subName}-mod.ts`),
216
+ // When command is a file in the root directory
208
217
  path.join(fileCmdOpts.cmdsRootPath, `${subName}.js`),
209
218
  path.join(fileCmdOpts.cmdsRootPath, `${subName}.ts`),
210
219
  path.join(fileCmdOpts.cmdsRootPath, `arg-${subName}.js`),
@@ -248,13 +257,11 @@ async function runFileBasedSubCmd(subName, fileCmdOpts, parserOptions) {
248
257
  `File-based subcommand "${subName}" has no default export or is invalid.`
249
258
  );
250
259
  }
251
- const argvRemaining = process.argv.slice(2);
252
- await runCommandWithArgs(subCommand, argvRemaining, parserOptions);
260
+ await runCommandWithArgs(subCommand, argv, parserOptions);
253
261
  }
254
- async function runSubCommand(spec, parserOptions) {
262
+ async function runSubCommand(spec, argv, parserOptions) {
255
263
  const subCommand = await loadSubCommand(spec);
256
- const argvRemaining = process.argv.slice(2);
257
- await runCommandWithArgs(subCommand, argvRemaining, parserOptions);
264
+ await runCommandWithArgs(subCommand, argv, parserOptions);
258
265
  }
259
266
  async function runCommandWithArgs(command, argv, parserOptions) {
260
267
  const autoExit = parserOptions.autoExit !== false;
@@ -156,7 +156,7 @@ export type StreamTextOptions = {
156
156
  */
157
157
  color?: ColorName;
158
158
  /**
159
- * Whether to add a newline at the end
159
+ * Whether to inject a newline at the end
160
160
  * @default true
161
161
  */
162
162
  newline?: boolean;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "description": "@reliverse/rempts is your modern, type-safe toolkit for building delightful CLI experiences. It's fast, flexible, and built with developer joy in mind. Forget the clutter โ€” this is how CLI should feel.",
2
+ "description": "@reliverse/rempts is a prompt library built with developer joy in mind. File-based commands. Flexible. Fast. Forget the clutter, it just works. This is a typesafe toolkit for building delightful CLI experiences.",
3
3
  "license": "MIT",
4
4
  "name": "@reliverse/rempts",
5
5
  "type": "module",
6
- "version": "1.6.1",
6
+ "version": "1.6.2",
7
7
  "author": "reliverse",
8
8
  "bugs": {
9
9
  "email": "blefnk@gmail.com",
@@ -21,13 +21,13 @@
21
21
  "@figliolia/chalk-animation": "^1.0.4",
22
22
  "@reliverse/reliarg": "^1.0.3",
23
23
  "@reliverse/relico": "^1.1.0",
24
- "@reliverse/relinka": "^1.3.8",
24
+ "@reliverse/relinka": "^1.4.1",
25
25
  "@reliverse/runtime": "^1.0.3",
26
26
  "ansi-escapes": "^7.0.0",
27
27
  "c12": "^3.0.3",
28
28
  "cli-spinners": "^3.2.0",
29
29
  "detect-package-manager": "^3.0.2",
30
- "figlet": "^1.8.0",
30
+ "figlet": "^1.8.1",
31
31
  "fs-extra": "^11.3.0",
32
32
  "gradient-string": "^3.0.0",
33
33
  "log-update": "^6.1.0",