@reliverse/rempts 1.7.2 โ†’ 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  - ๐Ÿšจ Crash-safe (Ctrl+C, SIGINT, errors)
19
19
  - ๐Ÿช„ Minimal API surface, max expressiveness
20
20
  - ๐Ÿงช Scriptable for testing, stable for production
21
- - ๐Ÿ†• Automatic commands creation (via `dler init --cmd my-cool-cmd`)
21
+ - ๐Ÿ†• Automatic commands creation (via `dler rempts init --cmd my-cool-cmd`)
22
22
  - ๐Ÿž๏ธ No more hacking together `inquirer`, `citty`, `commander`, `chalk`
23
23
 
24
24
  ## Installation
@@ -94,7 +94,7 @@ import {
94
94
 
95
95
  ### Notices
96
96
 
97
- - `setup`/`cleanup` are now `onCmdStart`/`onCmdEnd` (old names still work for now).
97
+ - `setup`/`cleanup` are now `onCmdInit`/`onCmdExit` (old names still work for now).
98
98
 
99
99
  ### Prompts Usage Example
100
100
 
@@ -145,6 +145,8 @@ await main();
145
145
 
146
146
  #### Launcher Usage Example
147
147
 
148
+ **Important**: Ensure your commands don't have `await main();`, `await runMain();`, or something like that โ€” to prevent any unexpected behavior. Only main command should have it.
149
+
148
150
  ```ts
149
151
  import { relinka } from "@reliverse/relinka";
150
152
 
@@ -156,10 +158,10 @@ const main = defineCommand({
156
158
  version: "1.0.0",
157
159
  description: "Rempts Launcher Playground CLI",
158
160
  },
159
- onCmdStart() {
161
+ onCmdInit() {
160
162
  relinka("success", "Setup");
161
163
  },
162
- onCmdEnd() {
164
+ onCmdExit() {
163
165
  relinka("success", "Cleanup");
164
166
  },
165
167
  commands: {
@@ -220,7 +222,7 @@ export default defineCommand({
220
222
  **Hint**:
221
223
 
222
224
  - Install `bun add -D @reliverse/dler`
223
- - Use `dler init --cmd cmd1 cmd2` to init commands for rempts launcher's automatically
225
+ - Use `dler rempts init --cmd cmd1 cmd2` to init commands for rempts launcher's automatically
224
226
 
225
227
  ### Advanced Minimal API
226
228
 
@@ -287,7 +289,7 @@ await runMain(defineCommand({}));
287
289
 
288
290
  ```bash
289
291
  bun add -D @reliverse/dler # or: bun i -g @reliverse/dler
290
- bun dler init --cmd my-cmd-1 # or: dler init my-cmd-1 my-cmd-2 --main src/mod.ts
292
+ bun dler rempts init --cmd my-cmd-1 # or: dler rempts init my-cmd-1 my-cmd-2 --main src/mod.ts
291
293
  # * `--main` is optional, default is `./src/mod.ts`
292
294
  # * you can specify multiple commands at once
293
295
  ```
@@ -545,18 +547,18 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
545
547
  - **Lifecycle Hooks:**
546
548
  You can define optional lifecycle hooks in your main command:
547
549
  - `onLauncherStart` and `onLauncherEnd` (global, called once per CLI process)
548
- - `onCmdStart` and `onCmdEnd` (per-command, called before/after each command, but NOT for the main `run()` handler)
550
+ - `onCmdInit` and `onCmdExit` (per-command, called before/after each command, but NOT for the main `run()` handler)
549
551
 
550
552
  **Global Hooks:**
551
553
  - `onLauncherStart`: Called once, before any command/run() is executed.
552
554
  - `onLauncherEnd`: Called once, after all command/run() logic is finished (even if an error occurs).
553
555
 
554
556
  **Per-Command Hooks:**
555
- - `onCmdStart`: Called before each command (not for main `run()`).
556
- - `onCmdEnd`: Called after each command (not for main `run()`).
557
+ - `onCmdInit`: Called before each command (not for main `run()`).
558
+ - `onCmdExit`: Called after each command (not for main `run()`).
557
559
 
558
560
  This means:
559
- - If your CLI has multiple commands, `onCmdStart` and `onCmdEnd` will be called for each command invocation, not just once for the whole CLI process.
561
+ - If your CLI has multiple commands, `onCmdInit` and `onCmdExit` will be called for each command invocation, not just once for the whole CLI process.
560
562
  - If your main command has a `run()` handler (and no command is invoked), these hooks are **not** called; use the `run()` handler itself or the global hooks for such logic.
561
563
  - This allows you to perform setup/teardown logic specific to each command execution.
562
564
  - If you want logic to run only once for the entire CLI process, use `onLauncherStart` and `onLauncherEnd`.
@@ -567,18 +569,18 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
567
569
  const main = defineCommand({
568
570
  onLauncherStart() { relinka('info', 'Global setup (once per process)'); },
569
571
  onLauncherEnd() { relinka('info', 'Global cleanup (once per process)'); },
570
- onCmdStart() { relinka('info', 'Setup for each command'); },
571
- onCmdEnd() { relinka('info', 'Cleanup for each command'); },
572
+ onCmdInit() { relinka('info', 'Setup for each command'); },
573
+ onCmdExit() { relinka('info', 'Cleanup for each command'); },
572
574
  commands: { ... },
573
575
  run() { relinka('info', 'Main run handler (no command)'); },
574
576
  });
575
577
  // onLauncherStart/onLauncherEnd are called once per process
576
- // onCmdStart/onCmdEnd are called for every command (not for main run())
578
+ // onCmdInit/onCmdExit are called for every command (not for main run())
577
579
  // If you want per-run() logic, use the run() handler or global hooks
578
580
  ```
579
581
 
580
582
  - **Deprecation Notice**
581
- - The legacy `setup` and `cleanup` names are still supported as aliases for per-command hooks, but will be removed in a future major version. Prefer `onCmdStart` and `onCmdEnd` going forward.
583
+ - The legacy `setup` and `cleanup` names are still supported as aliases for per-command hooks, but will be removed in a future major version. Prefer `onCmdInit` and `onCmdExit` going forward.
582
584
  - The `subCommands` property is deprecated as well. Please use `commands` instead. `subCommands` will be removed in a future major version.
583
585
 
584
586
  - **Dynamic Usage Examples:**
@@ -53,6 +53,7 @@ type CommandContext<ARGS> = {
53
53
  raw: string[];
54
54
  };
55
55
  type CommandRun<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
56
+ type CommandHook<ARGS> = (ctx: CommandContext<ARGS>) => void | Promise<void>;
56
57
  type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
57
58
  meta?: CommandMeta;
58
59
  args?: A;
@@ -66,21 +67,21 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
66
67
  */
67
68
  subCommands?: CommandsMap;
68
69
  /**
69
- * Called before the command runs
70
+ * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
70
71
  */
71
- onCmdStart?: () => void | Promise<void>;
72
+ onCmdInit?: CommandHook<InferArgTypes<A>>;
72
73
  /**
73
- * Called after the command finishes
74
+ * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
74
75
  */
75
- onCmdEnd?: () => void | Promise<void>;
76
+ onCmdExit?: CommandHook<InferArgTypes<A>>;
76
77
  /**
77
- * @deprecated Use onCmdStart instead
78
+ * @deprecated Use onCmdInit instead
78
79
  */
79
- setup?: () => void | Promise<void>;
80
+ setup?: CommandHook<InferArgTypes<A>>;
80
81
  /**
81
- * @deprecated Use onCmdEnd instead
82
+ * @deprecated Use onCmdExit instead
82
83
  */
83
- cleanup?: () => void | Promise<void>;
84
+ cleanup?: CommandHook<InferArgTypes<A>>;
84
85
  /**
85
86
  * Called once per CLI process, before any command/run() is executed
86
87
  */
@@ -103,21 +104,21 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
103
104
  */
104
105
  subCommands?: CommandsMap;
105
106
  /**
106
- * Called before the command runs
107
+ * Called before the command runs. Receives `{ args, raw }` (parsed args and raw argv).
107
108
  */
108
- onCmdStart?: () => void | Promise<void>;
109
+ onCmdInit?: CommandHook<InferArgTypes<A>>;
109
110
  /**
110
- * Called after the command finishes
111
+ * Called after the command finishes. Receives `{ args, raw }` (parsed args and raw argv).
111
112
  */
112
- onCmdEnd?: () => void | Promise<void>;
113
+ onCmdExit?: CommandHook<InferArgTypes<A>>;
113
114
  /**
114
- * @deprecated Use onCmdStart instead
115
+ * @deprecated Use onCmdInit instead
115
116
  */
116
- setup?: () => void | Promise<void>;
117
+ setup?: CommandHook<InferArgTypes<A>>;
117
118
  /**
118
- * @deprecated Use onCmdEnd instead
119
+ * @deprecated Use onCmdExit instead
119
120
  */
120
- cleanup?: () => void | Promise<void>;
121
+ cleanup?: CommandHook<InferArgTypes<A>>;
121
122
  /**
122
123
  * Called once per CLI process, before any command/run() is executed
123
124
  */
@@ -57,8 +57,8 @@ function isFlag(str) {
57
57
  return str.startsWith("-");
58
58
  }
59
59
  export function defineCommand(options) {
60
- const onCmdStart = options.onCmdStart || options.setup;
61
- const onCmdEnd = options.onCmdEnd || options.cleanup;
60
+ const onCmdInit = options.onCmdInit || options.setup;
61
+ const onCmdExit = options.onCmdExit || options.cleanup;
62
62
  const onLauncherStart = options.onLauncherStart;
63
63
  const onLauncherEnd = options.onLauncherEnd;
64
64
  let commands = options.commands;
@@ -70,13 +70,13 @@ export function defineCommand(options) {
70
70
  args: options.args || {},
71
71
  run: options.run,
72
72
  commands,
73
- onCmdStart,
74
- onCmdEnd,
73
+ onCmdInit,
74
+ onCmdExit,
75
75
  onLauncherStart,
76
76
  onLauncherEnd,
77
77
  // Backward-compatible aliases
78
- setup: onCmdStart,
79
- cleanup: onCmdEnd
78
+ setup: onCmdInit,
79
+ cleanup: onCmdExit
80
80
  };
81
81
  Object.defineProperty(cmdObj, "subCommands", {
82
82
  get() {
@@ -167,7 +167,7 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
167
167
  );
168
168
  }
169
169
  if (subCommandNames.length > 0) {
170
- relinka("log", "Available commands (run with `help` to see more):");
170
+ relinka("info", "Available commands (run with `help` to see more):");
171
171
  subCommandDefs.forEach(({ name, def }) => {
172
172
  const desc = def?.meta?.description ?? "";
173
173
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
@@ -217,14 +217,14 @@ export async function showUsage(command, parserOptions = {}, displayNotFoundMess
217
217
  );
218
218
  }
219
219
  if (subCommandNames.length > 0) {
220
- relinka("log", "Available commands (run with `help` to see more):");
220
+ relinka("info", "Available commands (run with `help` to see more):");
221
221
  subCommandDefs.forEach(({ name, def }) => {
222
222
  const desc = def?.meta?.description ?? "";
223
223
  relinka("log", `\u2022 ${name}${desc ? ` | ${desc}` : ""}`);
224
224
  });
225
225
  }
226
226
  }
227
- relinka("log", "Available options:");
227
+ relinka("info", "Available options:");
228
228
  relinka("log", "\u2022 -h, --help | Show help");
229
229
  relinka("log", "\u2022 -v, --version | Show version");
230
230
  relinka("log", "\u2022 --debug | Enable debug mode");
@@ -334,14 +334,17 @@ This can cause recursion or unexpected behavior.`
334
334
  if (fileBasedEnabled && rawArgv.length > 0 && !isFlag(rawArgv[0])) {
335
335
  const [subName, ...subCmdArgv] = rawArgv;
336
336
  try {
337
- if (typeof command.onCmdStart === "function")
338
- await command.onCmdStart();
337
+ const ctx = getParsedContext(command, rawArgv, parserOptions);
338
+ if (typeof command.onCmdInit === "function")
339
+ await command.onCmdInit(ctx);
339
340
  await runFileBasedSubCmd(
340
341
  subName,
341
342
  subCmdArgv,
342
343
  parserOptions.fileBasedCmds,
343
344
  parserOptions,
344
- command.onCmdEnd
345
+ command.onCmdExit ? async (_subCtx) => {
346
+ await command.onCmdExit?.(ctx);
347
+ } : void 0
345
348
  );
346
349
  if (autoExit) process.exit(0);
347
350
  return;
@@ -371,13 +374,16 @@ This can cause recursion or unexpected behavior.`
371
374
  }
372
375
  if (subSpec) {
373
376
  try {
374
- if (typeof command.onCmdStart === "function")
375
- await command.onCmdStart();
377
+ const ctx = getParsedContext(command, rawArgv, parserOptions);
378
+ if (typeof command.onCmdInit === "function")
379
+ await command.onCmdInit(ctx);
376
380
  await runSubCommand(
377
381
  subSpec,
378
382
  subCmdArgv,
379
383
  parserOptions,
380
- command.onCmdEnd
384
+ command.onCmdExit ? async (_subCtx) => {
385
+ await command.onCmdExit?.(ctx);
386
+ } : void 0
381
387
  );
382
388
  if (autoExit) process.exit(0);
383
389
  return;
@@ -474,27 +480,41 @@ Info for this CLI's developer: No valid command directory found, expected: ${exp
474
480
  );
475
481
  }
476
482
  try {
477
- await runCommandWithArgs(subCommand, argv, parserOptions);
483
+ const subCtx = await runCommandWithArgs(
484
+ subCommand,
485
+ argv,
486
+ parserOptions,
487
+ true
488
+ );
489
+ if (typeof parentFinish === "function" && subCtx)
490
+ await parentFinish(subCtx);
478
491
  } finally {
479
- if (typeof parentFinish === "function") await parentFinish();
480
492
  }
481
493
  }
482
494
  async function runSubCommand(spec, argv, parserOptions, parentFinish) {
483
495
  const subCommand = await loadSubCommand(spec);
484
496
  try {
485
- await runCommandWithArgs(subCommand, argv, parserOptions);
497
+ const subCtx = await runCommandWithArgs(
498
+ subCommand,
499
+ argv,
500
+ parserOptions,
501
+ true
502
+ );
503
+ if (typeof parentFinish === "function" && subCtx)
504
+ await parentFinish(subCtx);
486
505
  } finally {
487
- if (typeof parentFinish === "function") await parentFinish();
488
506
  }
489
507
  }
490
- async function runCommandWithArgs(command, argv, parserOptions) {
508
+ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
491
509
  const autoExit = parserOptions.autoExit !== false;
492
510
  const booleanKeys = Object.keys(command.args || {}).filter(
493
511
  (k) => command.args[k].type === "boolean"
494
512
  );
495
513
  const defaultMap = {};
496
514
  for (const [argKey, def] of Object.entries(command.args || {})) {
497
- if (def.default !== void 0) {
515
+ if (def.type === "boolean") {
516
+ defaultMap[argKey] = def.default !== void 0 ? def.default : false;
517
+ } else if (def.default !== void 0) {
498
518
  if (def.type === "array" && typeof def.default === "string") {
499
519
  defaultMap[argKey] = [def.default];
500
520
  } else {
@@ -543,7 +563,11 @@ async function runCommandWithArgs(command, argv, parserOptions) {
543
563
  else throw new Error(`Missing required argument: --${key}`);
544
564
  }
545
565
  try {
546
- finalArgs[key] = castArgValue(def, rawVal, key);
566
+ if (def.type === "boolean") {
567
+ finalArgs[key] = rawVal !== void 0 ? castArgValue(def, rawVal, key) : false;
568
+ } else {
569
+ finalArgs[key] = castArgValue(def, rawVal, key);
570
+ }
547
571
  if (def.type === "array" && def.options && finalArgs[key]) {
548
572
  const values = finalArgs[key];
549
573
  const invalidOptions = values.filter(
@@ -593,6 +617,8 @@ ${String(err)}`);
593
617
  if (autoExit) process.exit(1);
594
618
  else throw err;
595
619
  }
620
+ if (returnCtx) return ctx;
621
+ return void 0;
596
622
  }
597
623
  function castArgValue(def, rawVal, argName) {
598
624
  if (rawVal == null) {
@@ -643,7 +669,9 @@ export async function runCmd(command, argv = [], parserOptions = {}) {
643
669
  );
644
670
  const defaultMap = {};
645
671
  for (const [argKey, def] of Object.entries(command.args || {})) {
646
- if (def.default !== void 0) {
672
+ if (def.type === "boolean") {
673
+ defaultMap[argKey] = def.default !== void 0 ? def.default : false;
674
+ } else if (def.default !== void 0) {
647
675
  if (def.type === "array" && typeof def.default === "string") {
648
676
  defaultMap[argKey] = [def.default];
649
677
  } else {
@@ -708,3 +736,53 @@ export async function runCmd(command, argv = [], parserOptions = {}) {
708
736
  throw new Error("Command has no run() handler.");
709
737
  }
710
738
  }
739
+ function getParsedContext(command, argv, parserOptions) {
740
+ const booleanKeys = Object.keys(command.args || {}).filter(
741
+ (k) => command.args[k].type === "boolean"
742
+ );
743
+ const defaultMap = {};
744
+ for (const [argKey, def] of Object.entries(command.args || {})) {
745
+ if (def.type === "boolean") {
746
+ defaultMap[argKey] = def.default !== void 0 ? def.default : false;
747
+ } else if (def.default !== void 0) {
748
+ if (def.type === "array" && typeof def.default === "string") {
749
+ defaultMap[argKey] = [def.default];
750
+ } else {
751
+ defaultMap[argKey] = def.default;
752
+ }
753
+ }
754
+ }
755
+ const mergedParserOptions = {
756
+ ...parserOptions,
757
+ boolean: [...parserOptions.boolean || [], ...booleanKeys],
758
+ defaults: { ...defaultMap, ...parserOptions.defaults || {} }
759
+ };
760
+ const parsed = reliArgParser(argv, mergedParserOptions);
761
+ const finalArgs = {};
762
+ const positionalKeys = Object.keys(command.args || {}).filter(
763
+ (k) => command.args[k].type === "positional"
764
+ );
765
+ const leftoverPositionals = [...parsed._ || []];
766
+ for (let i = 0; i < positionalKeys.length; i++) {
767
+ const key = positionalKeys[i];
768
+ const def = command.args?.[key];
769
+ const val = leftoverPositionals[i];
770
+ finalArgs[key] = val != null ? castArgValue(def, val, key) : def.default;
771
+ }
772
+ const otherKeys = Object.keys(command.args || {}).filter(
773
+ (k) => command.args[k].type !== "positional"
774
+ );
775
+ for (const key of otherKeys) {
776
+ const def = command.args?.[key];
777
+ let rawVal = parsed[key];
778
+ if (def.type === "array" && rawVal !== void 0 && !Array.isArray(rawVal)) {
779
+ rawVal = [rawVal];
780
+ }
781
+ if (def.type === "boolean") {
782
+ finalArgs[key] = rawVal !== void 0 ? castArgValue(def, rawVal, key) : false;
783
+ } else {
784
+ finalArgs[key] = castArgValue(def, rawVal, key);
785
+ }
786
+ }
787
+ return { args: finalArgs, raw: argv };
788
+ }
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.2",
31
+ "version": "1.7.4",
32
32
  "author": "reliverse",
33
33
  "bugs": {
34
34
  "email": "blefnk@gmail.com",