@reliverse/rempts 1.7.7 → 1.7.9
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
|
@@ -155,7 +155,7 @@ await main();
|
|
|
155
155
|
```ts
|
|
156
156
|
import { relinka } from "@reliverse/relinka";
|
|
157
157
|
|
|
158
|
-
import { defineCommand, runMain } from "
|
|
158
|
+
import { defineCommand, runMain } from "@reliverse/rempts";
|
|
159
159
|
|
|
160
160
|
const main = defineCommand({
|
|
161
161
|
meta: {
|
|
@@ -238,7 +238,7 @@ defineCommand({
|
|
|
238
238
|
args: {
|
|
239
239
|
name: { type: "string", required: true },
|
|
240
240
|
verbose: { type: "boolean", default: false },
|
|
241
|
-
animals: { type: "array",
|
|
241
|
+
animals: { type: "array", default: ["cat","dog"] },
|
|
242
242
|
},
|
|
243
243
|
async run({ args, raw }) { // or `async run(ctx)`
|
|
244
244
|
relinka("log", args.name, args.verbose, args.animals); // or `relinka("log", ctx.args.name, ...);`
|
|
@@ -283,7 +283,7 @@ When playing with the example, you can run e.g. `bun dev:modern nested foo bar b
|
|
|
283
283
|
git clone https://github.com/reliverse/rempts
|
|
284
284
|
cd rempts
|
|
285
285
|
bun i
|
|
286
|
-
bun dev
|
|
286
|
+
bun dev
|
|
287
287
|
```
|
|
288
288
|
|
|
289
289
|
- `bun dev:prompts`: This example will show you a `multiselectPrompt()` where you can choose which CLI prompts you want to play with.
|
|
@@ -563,12 +563,12 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
563
563
|
|
|
564
564
|
- **Lifecycle Hooks:**
|
|
565
565
|
You can define optional lifecycle hooks in your main command:
|
|
566
|
-
- `
|
|
566
|
+
- `onLauncherInit` and `onLauncherExit` (global, called once per CLI process)
|
|
567
567
|
- `onCmdInit` and `onCmdExit` (per-command, called before/after each command, but NOT for the main `run()` handler)
|
|
568
568
|
|
|
569
569
|
**Global Hooks:**
|
|
570
|
-
- `
|
|
571
|
-
- `
|
|
570
|
+
- `onLauncherInit`: Called once, before any command/run() is executed.
|
|
571
|
+
- `onLauncherExit`: Called once, after all command/run() logic is finished (even if an error occurs).
|
|
572
572
|
|
|
573
573
|
**Per-Command Hooks:**
|
|
574
574
|
- `onCmdInit`: Called before each command (not for main `run()`).
|
|
@@ -578,20 +578,20 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
578
578
|
- If your CLI has multiple commands, `onCmdInit` and `onCmdExit` will be called for each command invocation, not just once for the whole CLI process.
|
|
579
579
|
- 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.
|
|
580
580
|
- This allows you to perform setup/teardown logic specific to each command execution.
|
|
581
|
-
- If you want logic to run only once for the entire CLI process, use `
|
|
581
|
+
- If you want logic to run only once for the entire CLI process, use `onLauncherInit` and `onLauncherExit`.
|
|
582
582
|
|
|
583
583
|
**Example:**
|
|
584
584
|
|
|
585
585
|
```ts
|
|
586
586
|
const main = defineCommand({
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
onLauncherInit() { relinka('info', 'Global setup (once per process)'); },
|
|
588
|
+
onLauncherExit() { relinka('info', 'Global cleanup (once per process)'); },
|
|
589
589
|
onCmdInit() { relinka('info', 'Setup for each command'); },
|
|
590
590
|
onCmdExit() { relinka('info', 'Cleanup for each command'); },
|
|
591
591
|
commands: { ... },
|
|
592
592
|
run() { relinka('info', 'Main run handler (no command)'); },
|
|
593
593
|
});
|
|
594
|
-
//
|
|
594
|
+
// onLauncherInit/onLauncherExit are called once per process
|
|
595
595
|
// onCmdInit/onCmdExit are called for every command (not for main run())
|
|
596
596
|
// If you want per-run() logic, use the run() handler or global hooks
|
|
597
597
|
```
|
|
@@ -627,7 +627,63 @@ Finally, a full-featured CLI launcher without the ceremony. `@reliverse/rempts`'
|
|
|
627
627
|
|
|
628
628
|
### Launcher Programmatic Execution
|
|
629
629
|
|
|
630
|
-
For larger CLIs or when you want to programmatically run commands (e.g.: [prompt demo](./example/prompts/mod.ts), tests, etc), you can organize your commands in a `cmds.ts` file and use the `runCmd` utility.
|
|
630
|
+
For larger CLIs or when you want to programmatically run commands (e.g.: [prompt demo](./example/prompts/mod.ts), tests, etc), you can organize your commands in a `cmds.ts` file and use the `runCmd` utility. Example:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
// example/launcher/app/runcmd/cmd.ts
|
|
634
|
+
|
|
635
|
+
import { relinka } from "@reliverse/relinka";
|
|
636
|
+
import { defineArgs, defineCommand, runCmd } from "@reliverse/rempts";
|
|
637
|
+
import { cmdMinimal } from "../cmds.js";
|
|
638
|
+
|
|
639
|
+
export default defineCommand({
|
|
640
|
+
meta: {
|
|
641
|
+
name: "runcmd",
|
|
642
|
+
description:
|
|
643
|
+
"Demonstrate how to use runCmd() to invoke another command programmatically.",
|
|
644
|
+
},
|
|
645
|
+
args: defineArgs({
|
|
646
|
+
name: {
|
|
647
|
+
type: "string",
|
|
648
|
+
description: "your name",
|
|
649
|
+
},
|
|
650
|
+
}),
|
|
651
|
+
async run({ args }) {
|
|
652
|
+
// const username = args.name ?? "Alice";
|
|
653
|
+
const username = args.name; // intentionally missing fallback
|
|
654
|
+
relinka(
|
|
655
|
+
"info",
|
|
656
|
+
`Running the 'minimal' command using runCmd() with name='${username}'`,
|
|
657
|
+
);
|
|
658
|
+
await runCmd(await cmdMinimal(), ["--name", username]);
|
|
659
|
+
relinka("log", "Done running 'minimal' via runCmd().");
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
// example/launcher/app/minimal/cmd.ts
|
|
666
|
+
|
|
667
|
+
import { relinka } from "@reliverse/relinka";
|
|
668
|
+
import { defineArgs, defineCommand } from "@reliverse/rempts";
|
|
669
|
+
|
|
670
|
+
export default defineCommand({
|
|
671
|
+
meta: {
|
|
672
|
+
name: "minimal",
|
|
673
|
+
description: "hello world",
|
|
674
|
+
},
|
|
675
|
+
args: defineArgs({
|
|
676
|
+
name: {
|
|
677
|
+
type: "string",
|
|
678
|
+
description: "your name",
|
|
679
|
+
required: true,
|
|
680
|
+
},
|
|
681
|
+
}),
|
|
682
|
+
run({ args }) {
|
|
683
|
+
relinka("success", `👋 Hello, ${args.name}!`);
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
```
|
|
631
687
|
|
|
632
688
|
**Pro Tips & Best Practices**:
|
|
633
689
|
|
|
@@ -641,7 +697,7 @@ For larger CLIs or when you want to programmatically run commands (e.g.: [prompt
|
|
|
641
697
|
```ts
|
|
642
698
|
// example/launcher/app/cmds.ts
|
|
643
699
|
|
|
644
|
-
export async function
|
|
700
|
+
export async function cmdHooks() {
|
|
645
701
|
return (await import("./hooks/cmd.js")).default;
|
|
646
702
|
}
|
|
647
703
|
|
|
@@ -657,12 +713,12 @@ Usage:
|
|
|
657
713
|
```ts
|
|
658
714
|
// example/prompts/mod.ts
|
|
659
715
|
|
|
660
|
-
import {
|
|
716
|
+
import { cmdHooks } from "@/launcher/app/cmds.js";
|
|
661
717
|
import { runCmd } from "@reliverse/rempts";
|
|
662
718
|
|
|
663
|
-
await runCmd(await
|
|
719
|
+
await runCmd(await cmdHooks(), ["--flag"]);
|
|
664
720
|
// OR:
|
|
665
|
-
// const hooksCmd = await
|
|
721
|
+
// const hooksCmd = await cmdHooks();
|
|
666
722
|
// await runCmd(hooksCmd, ["--flag"]);
|
|
667
723
|
```
|
|
668
724
|
|
|
@@ -698,7 +754,7 @@ await runCmd(hooksCmd, ["--flag"]); // argv as array of strings
|
|
|
698
754
|
Or with lazy loading:
|
|
699
755
|
|
|
700
756
|
```ts
|
|
701
|
-
const hooksCmd = await
|
|
757
|
+
const hooksCmd = await cmdHooks();
|
|
702
758
|
await runCmd(hooksCmd, ["--flag"]);
|
|
703
759
|
```
|
|
704
760
|
|
|
@@ -711,6 +767,162 @@ await runCmd(hooksCmd, ["--flag"]);
|
|
|
711
767
|
|
|
712
768
|
Choose the pattern that best fits your CLI's size and usage!
|
|
713
769
|
|
|
770
|
+
## Argument Types: Usage Comparison
|
|
771
|
+
|
|
772
|
+
Below is a demonstration of how to define and use all supported argument types in rempts: positional, boolean, string, number, and array. This includes example CLI invocations and the resulting parsed output.
|
|
773
|
+
|
|
774
|
+
```ts
|
|
775
|
+
import { defineCommand, runMain } from "@reliverse/rempts";
|
|
776
|
+
|
|
777
|
+
const main = defineCommand({
|
|
778
|
+
meta: {
|
|
779
|
+
name: "mycli",
|
|
780
|
+
version: "1.0.0",
|
|
781
|
+
description: "Demo of all argument types",
|
|
782
|
+
},
|
|
783
|
+
args: {
|
|
784
|
+
// Positional argument (required)
|
|
785
|
+
input: {
|
|
786
|
+
type: "positional",
|
|
787
|
+
required: true,
|
|
788
|
+
description: "Input file path",
|
|
789
|
+
},
|
|
790
|
+
// Boolean flag (default: false)
|
|
791
|
+
verbose: {
|
|
792
|
+
type: "boolean",
|
|
793
|
+
default: false,
|
|
794
|
+
description: "Enable verbose output",
|
|
795
|
+
},
|
|
796
|
+
// String option (optional)
|
|
797
|
+
name: {
|
|
798
|
+
type: "string",
|
|
799
|
+
description: "Your name",
|
|
800
|
+
},
|
|
801
|
+
// Number option (optional, with default)
|
|
802
|
+
count: {
|
|
803
|
+
type: "number",
|
|
804
|
+
default: 1,
|
|
805
|
+
description: "How many times to run",
|
|
806
|
+
},
|
|
807
|
+
// Array option (can be repeated, accepts any value)
|
|
808
|
+
tags: {
|
|
809
|
+
type: "array",
|
|
810
|
+
default: ["demo"],
|
|
811
|
+
description: "Tags for this run (repeatable)",
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
run({ args }) {
|
|
815
|
+
console.log("Parsed args:", args);
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
await runMain(main);
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Example CLI Invocations
|
|
823
|
+
|
|
824
|
+
#### 1. Positional argument
|
|
825
|
+
|
|
826
|
+
```bash
|
|
827
|
+
mycli input.txt
|
|
828
|
+
# → args.input = "input.txt"
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
#### 2. Boolean flag
|
|
832
|
+
|
|
833
|
+
```bash
|
|
834
|
+
mycli input.txt --verbose
|
|
835
|
+
# → args.verbose = true
|
|
836
|
+
mycli input.txt --no-verbose
|
|
837
|
+
# → args.verbose = false
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
#### 3. String option
|
|
841
|
+
|
|
842
|
+
```bash
|
|
843
|
+
mycli input.txt --name Alice
|
|
844
|
+
# → args.name = "Alice"
|
|
845
|
+
mycli input.txt
|
|
846
|
+
# → args.name = undefined
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
#### 4. Number option
|
|
850
|
+
|
|
851
|
+
```bash
|
|
852
|
+
mycli input.txt --count 5
|
|
853
|
+
# → args.count = 5
|
|
854
|
+
mycli input.txt
|
|
855
|
+
# → args.count = 1 (default)
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
#### 5. Array option (repeatable, accepts any value)
|
|
859
|
+
|
|
860
|
+
You can provide array values using any of the following syntaxes (mix and match as needed):
|
|
861
|
+
|
|
862
|
+
- Repeated flags:
|
|
863
|
+
|
|
864
|
+
```bash
|
|
865
|
+
mycli input.txt --tags foo --tags bar --tags baz
|
|
866
|
+
# → args.tags = ["foo", "bar", "baz"]
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
- Comma-separated values (with or without spaces):
|
|
870
|
+
|
|
871
|
+
```bash
|
|
872
|
+
mycli input.txt --tags foo,bar,baz
|
|
873
|
+
mycli input.txt --tags foo, bar, baz
|
|
874
|
+
# → args.tags = ["foo", "bar", "baz"]
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
- Bracketed values (must be passed as a single argument!):
|
|
878
|
+
|
|
879
|
+
```bash
|
|
880
|
+
mycli input.txt --tags "[foo,bar,baz]"
|
|
881
|
+
# → args.tags = ["foo", "bar", "baz"]
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
- Mix and match:
|
|
885
|
+
|
|
886
|
+
```bash
|
|
887
|
+
mycli input.txt --tags foo --tags "[bar,bar2,bar3]" --tags baz
|
|
888
|
+
# → args.tags = ["foo", "bar", "bar2", "bar3", "baz"]
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
> **Important:**
|
|
892
|
+
>
|
|
893
|
+
> - **Quoted values (single or double quotes around elements) are NOT supported and will throw an error.**
|
|
894
|
+
> - Example: `--tags 'foo'` or `--tags "[\"bar\",'baz']"` will throw an error.
|
|
895
|
+
> - **Bracketed or comma-separated lists must be passed as a single argument.**
|
|
896
|
+
> - Example: `--tags "[foo,bar]"` (quotes around the whole value, not around elements)
|
|
897
|
+
> - If you split a bracketed value across arguments, you will get a warning or incorrect parsing.
|
|
898
|
+
> - **Shells remove quotes before passing arguments to the CLI.** If you want to pass a value with commas or brackets, always quote the whole value.
|
|
899
|
+
> - **Troubleshooting:**
|
|
900
|
+
> - If you see a warning about possible shell splitting, try quoting the whole value: `--tags "[a,b,c]"`
|
|
901
|
+
> - If you see an error about quoted values, remove quotes around individual elements.
|
|
902
|
+
|
|
903
|
+
**Example error:**
|
|
904
|
+
|
|
905
|
+
```bash
|
|
906
|
+
$ bun example/launcher/modern.ts build --entry "[foo.ts," "bar.ts]"
|
|
907
|
+
✖ Don't use quotes around array elements.
|
|
908
|
+
✖ Also — don't use spaces — unless you wrap the whole array in quotes.
|
|
909
|
+
⚠ Array argument --entry: Detected possible shell splitting of bracketed value ('[foo.ts,').
|
|
910
|
+
⚠ If you intended to pass a bracketed list, quote the whole value like: --entry "[a, b, c]"
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
#### 6. All together
|
|
914
|
+
|
|
915
|
+
```bash
|
|
916
|
+
mycli input.txt --verbose --name Alice --count 3 --tags foo --tags bar
|
|
917
|
+
# → args = {
|
|
918
|
+
# input: "input.txt",
|
|
919
|
+
# verbose: true,
|
|
920
|
+
# name: "Alice",
|
|
921
|
+
# count: 3,
|
|
922
|
+
# tags: ["foo", "bar"]
|
|
923
|
+
# }
|
|
924
|
+
```
|
|
925
|
+
|
|
714
926
|
## Contributing
|
|
715
927
|
|
|
716
928
|
Bug report? Prompt idea? Want to build the best DX possible?
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import type { ReliArgParserOptions } from "@reliverse/reliarg";
|
|
2
|
-
type InvalidDefaultError<O extends readonly string[]> = {
|
|
3
|
-
__error__: "Default value(s) must be a subset of options";
|
|
4
|
-
options: O;
|
|
5
|
-
};
|
|
6
2
|
type EmptyArgs = Record<string, never>;
|
|
7
3
|
type BaseArgProps = {
|
|
8
4
|
description?: string;
|
|
9
5
|
required?: boolean;
|
|
6
|
+
allowed?: string[];
|
|
10
7
|
};
|
|
11
8
|
type PositionalArgDefinition = {
|
|
12
9
|
type: "positional";
|
|
@@ -15,6 +12,7 @@ type PositionalArgDefinition = {
|
|
|
15
12
|
type BooleanArgDefinition = {
|
|
16
13
|
type: "boolean";
|
|
17
14
|
default?: boolean;
|
|
15
|
+
allowed?: boolean[];
|
|
18
16
|
} & BaseArgProps;
|
|
19
17
|
type StringArgDefinition = {
|
|
20
18
|
type: "string";
|
|
@@ -23,11 +21,11 @@ type StringArgDefinition = {
|
|
|
23
21
|
type NumberArgDefinition = {
|
|
24
22
|
type: "number";
|
|
25
23
|
default?: number;
|
|
24
|
+
allowed?: number[];
|
|
26
25
|
} & BaseArgProps;
|
|
27
26
|
type ArrayArgDefinition = {
|
|
28
27
|
type: "array";
|
|
29
28
|
default?: string | readonly string[];
|
|
30
|
-
options: readonly string[];
|
|
31
29
|
} & BaseArgProps;
|
|
32
30
|
export type ArgDefinition = PositionalArgDefinition | BooleanArgDefinition | StringArgDefinition | NumberArgDefinition | ArrayArgDefinition;
|
|
33
31
|
export type ArgDefinitions = Record<string, ArgDefinition>;
|
|
@@ -85,11 +83,11 @@ type DefineCommandOptions<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
85
83
|
/**
|
|
86
84
|
* Called once per CLI process, before any command/run() is executed
|
|
87
85
|
*/
|
|
88
|
-
|
|
86
|
+
onLauncherInit?: () => void | Promise<void>;
|
|
89
87
|
/**
|
|
90
88
|
* Called once per CLI process, after all command/run() logic is finished
|
|
91
89
|
*/
|
|
92
|
-
|
|
90
|
+
onLauncherExit?: () => void | Promise<void>;
|
|
93
91
|
};
|
|
94
92
|
export type Command<A extends ArgDefinitions = EmptyArgs> = {
|
|
95
93
|
meta?: CommandMeta;
|
|
@@ -122,28 +120,16 @@ export type Command<A extends ArgDefinitions = EmptyArgs> = {
|
|
|
122
120
|
/**
|
|
123
121
|
* Called once per CLI process, before any command/run() is executed
|
|
124
122
|
*/
|
|
125
|
-
|
|
123
|
+
onLauncherInit?: () => void | Promise<void>;
|
|
126
124
|
/**
|
|
127
125
|
* Called once per CLI process, after all command/run() logic is finished
|
|
128
126
|
*/
|
|
129
|
-
|
|
127
|
+
onLauncherExit?: () => void | Promise<void>;
|
|
130
128
|
};
|
|
131
129
|
export type InferArgTypes<A extends ArgDefinitions> = {
|
|
132
130
|
[K in keyof A]: A[K] extends PositionalArgDefinition ? string : A[K] extends BooleanArgDefinition ? boolean : A[K] extends StringArgDefinition ? string : A[K] extends NumberArgDefinition ? number : A[K] extends {
|
|
133
131
|
type: "array";
|
|
134
|
-
|
|
135
|
-
} ? O[number][] : never;
|
|
136
|
-
};
|
|
137
|
-
type ValidateArrayDefaults<A extends ArgDefinitions> = {
|
|
138
|
-
[K in keyof A]: A[K] extends {
|
|
139
|
-
type: "array";
|
|
140
|
-
options: infer O extends readonly string[];
|
|
141
|
-
default?: infer D;
|
|
142
|
-
} ? D extends undefined ? A[K] : D extends O[number] ? A[K] : D extends readonly (infer E)[] ? E extends O[number] ? A[K] : Omit<A[K], "default"> & {
|
|
143
|
-
default: InvalidDefaultError<O>;
|
|
144
|
-
} : Omit<A[K], "default"> & {
|
|
145
|
-
default: InvalidDefaultError<O>;
|
|
146
|
-
} : A[K];
|
|
132
|
+
} ? string[] : never;
|
|
147
133
|
};
|
|
148
134
|
export type FileBasedCmdsOptions = {
|
|
149
135
|
enable: boolean;
|
|
@@ -187,11 +173,8 @@ export declare function runMain<A extends ArgDefinitions = EmptyArgs>(command: C
|
|
|
187
173
|
/**
|
|
188
174
|
* Helper to define argument definitions with improved type inference
|
|
189
175
|
* for IntelliSense and validation for array defaults against options.
|
|
190
|
-
*
|
|
191
|
-
* **Note:** For array types, use `as const` on the `options` array to enable
|
|
192
|
-
* precise default value validation (e.g., `options: ["a", "b"] as const`).
|
|
193
176
|
*/
|
|
194
|
-
export declare function defineArgs<A extends ArgDefinitions>(args: A
|
|
177
|
+
export declare function defineArgs<A extends ArgDefinitions>(args: A): A;
|
|
195
178
|
/**
|
|
196
179
|
* Programmatically run a command's run() handler with parsed arguments.
|
|
197
180
|
* Does not handle subcommands, file-based commands, or global hooks.
|
|
@@ -36,11 +36,7 @@ function buildExampleArgs(args) {
|
|
|
36
36
|
parts.push(`--${key}=${String(def.default ?? 42)}`);
|
|
37
37
|
break;
|
|
38
38
|
case "array":
|
|
39
|
-
|
|
40
|
-
parts.push(`--${key}=${String(def.options[0])}`);
|
|
41
|
-
} else {
|
|
42
|
-
parts.push(`--${key}=${String(key)}`);
|
|
43
|
-
}
|
|
39
|
+
parts.push(`--${key}=${String(def.default ?? key)}`);
|
|
44
40
|
break;
|
|
45
41
|
}
|
|
46
42
|
}
|
|
@@ -59,8 +55,8 @@ function isFlag(str) {
|
|
|
59
55
|
export function defineCommand(options) {
|
|
60
56
|
const onCmdInit = options.onCmdInit || options.setup;
|
|
61
57
|
const onCmdExit = options.onCmdExit || options.cleanup;
|
|
62
|
-
const
|
|
63
|
-
const
|
|
58
|
+
const onLauncherInit = options.onLauncherInit;
|
|
59
|
+
const onLauncherExit = options.onLauncherExit;
|
|
64
60
|
let commands = options.commands;
|
|
65
61
|
if (!commands) {
|
|
66
62
|
commands = options.subCommands;
|
|
@@ -72,8 +68,8 @@ export function defineCommand(options) {
|
|
|
72
68
|
commands,
|
|
73
69
|
onCmdInit,
|
|
74
70
|
onCmdExit,
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
onLauncherInit,
|
|
72
|
+
onLauncherExit,
|
|
77
73
|
// Backward-compatible aliases
|
|
78
74
|
setup: onCmdInit,
|
|
79
75
|
cleanup: onCmdExit
|
|
@@ -245,15 +241,13 @@ export async function showUsage(command, parserOptions = {}) {
|
|
|
245
241
|
if (def.default !== void 0)
|
|
246
242
|
parts.push(`default=${JSON.stringify(def.default)}`);
|
|
247
243
|
if (def.required) parts.push("required");
|
|
248
|
-
if (def.type === "array" && def.options)
|
|
249
|
-
parts.push(`options: ${def.options.join(", ")}`);
|
|
250
244
|
relinka("log", parts.filter(Boolean).join(" | "));
|
|
251
245
|
}
|
|
252
246
|
}
|
|
253
247
|
}
|
|
254
248
|
export async function runMain(command, parserOptions = {}) {
|
|
255
|
-
if (typeof command.
|
|
256
|
-
await command.
|
|
249
|
+
if (typeof command.onLauncherInit === "function")
|
|
250
|
+
await command.onLauncherInit();
|
|
257
251
|
try {
|
|
258
252
|
if (!parserOptions.fileBasedCmds && !command.commands) {
|
|
259
253
|
let callerDir = process.cwd();
|
|
@@ -430,8 +424,8 @@ This can cause recursion or unexpected behavior.`
|
|
|
430
424
|
}
|
|
431
425
|
await relinkaShutdown();
|
|
432
426
|
} finally {
|
|
433
|
-
if (typeof command.
|
|
434
|
-
await command.
|
|
427
|
+
if (typeof command.onLauncherExit === "function")
|
|
428
|
+
await command.onLauncherExit();
|
|
435
429
|
}
|
|
436
430
|
}
|
|
437
431
|
function checkHelp(argv) {
|
|
@@ -583,8 +577,8 @@ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
|
|
|
583
577
|
const def = command.args?.[key];
|
|
584
578
|
const val = leftoverPositionals[i];
|
|
585
579
|
if (val == null && def.required) {
|
|
586
|
-
relinka("error", `Missing required positional argument: <${key}>`);
|
|
587
580
|
await showUsage(command, parserOptions);
|
|
581
|
+
relinka("error", `Missing required positional argument: <${key}>`);
|
|
588
582
|
if (autoExit) process.exit(1);
|
|
589
583
|
else throw new Error(`Missing required positional argument: <${key}>`);
|
|
590
584
|
}
|
|
@@ -601,8 +595,8 @@ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
|
|
|
601
595
|
}
|
|
602
596
|
const valueOrDefault = rawVal ?? defaultMap[key];
|
|
603
597
|
if (valueOrDefault == null && def.required) {
|
|
604
|
-
relinka("error", `Missing required argument: --${key}`);
|
|
605
598
|
await showUsage(command, parserOptions);
|
|
599
|
+
relinka("error", `Missing required argument: --${key}`);
|
|
606
600
|
if (autoExit) process.exit(1);
|
|
607
601
|
else throw new Error(`Missing required argument: --${key}`);
|
|
608
602
|
}
|
|
@@ -612,17 +606,6 @@ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
|
|
|
612
606
|
} else {
|
|
613
607
|
finalArgs[key] = castArgValue(def, rawVal, key);
|
|
614
608
|
}
|
|
615
|
-
if (def.type === "array" && def.options && finalArgs[key]) {
|
|
616
|
-
const values = finalArgs[key];
|
|
617
|
-
const invalidOptions = values.filter(
|
|
618
|
-
(v) => def.options && !def.options.includes(v)
|
|
619
|
-
);
|
|
620
|
-
if (invalidOptions.length > 0) {
|
|
621
|
-
throw new Error(
|
|
622
|
-
`Invalid choice(s) for --${key}: ${invalidOptions.join(", ")}. Allowed options: ${def.options.join(", ")}`
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
609
|
} catch (err) {
|
|
627
610
|
relinka("error", String(err));
|
|
628
611
|
if (autoExit) process.exit(1);
|
|
@@ -647,8 +630,8 @@ async function runCommandWithArgs(command, argv, parserOptions, returnCtx) {
|
|
|
647
630
|
}
|
|
648
631
|
const cmdName = command.meta?.name || "unknown";
|
|
649
632
|
const attempted = argv.length > 0 ? argv.join(" ") : "(no arguments)";
|
|
650
|
-
relinka("error", `Unknown command or arguments: ${attempted}`);
|
|
651
633
|
await showUsage(command, parserOptions);
|
|
634
|
+
relinka("error", `Unknown command or arguments: ${attempted}`);
|
|
652
635
|
if (autoExit) {
|
|
653
636
|
process.exit(1);
|
|
654
637
|
} else {
|
|
@@ -671,28 +654,92 @@ function castArgValue(def, rawVal, argName) {
|
|
|
671
654
|
}
|
|
672
655
|
return def.default ?? void 0;
|
|
673
656
|
}
|
|
657
|
+
let castedValue;
|
|
674
658
|
switch (def.type) {
|
|
675
659
|
case "boolean":
|
|
676
660
|
if (typeof rawVal === "string") {
|
|
677
661
|
const lower = rawVal.toLowerCase();
|
|
678
|
-
if (lower === "true")
|
|
679
|
-
if (lower === "false")
|
|
662
|
+
if (lower === "true") castedValue = true;
|
|
663
|
+
else if (lower === "false") castedValue = false;
|
|
664
|
+
else castedValue = Boolean(rawVal);
|
|
665
|
+
} else {
|
|
666
|
+
castedValue = Boolean(rawVal);
|
|
667
|
+
}
|
|
668
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
671
|
+
);
|
|
680
672
|
}
|
|
681
|
-
return
|
|
673
|
+
return castedValue;
|
|
682
674
|
case "string":
|
|
683
|
-
|
|
675
|
+
castedValue = typeof rawVal === "string" ? rawVal : String(rawVal);
|
|
676
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
677
|
+
throw new Error(
|
|
678
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
return castedValue;
|
|
684
682
|
case "number": {
|
|
685
683
|
const n = Number(rawVal);
|
|
686
684
|
if (Number.isNaN(n)) {
|
|
687
685
|
throw new Error(`Invalid number provided for --${argName}: ${rawVal}`);
|
|
688
686
|
}
|
|
687
|
+
if (def.allowed && !def.allowed.includes(n)) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`Invalid value for --${argName}: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
689
692
|
return n;
|
|
690
693
|
}
|
|
691
694
|
case "positional":
|
|
692
|
-
|
|
695
|
+
castedValue = String(rawVal);
|
|
696
|
+
if (def.allowed && !def.allowed.includes(castedValue)) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Invalid value for <${argName}>: ${rawVal}. Allowed values are: ${def.allowed.join(", ")}`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return castedValue;
|
|
693
702
|
case "array": {
|
|
694
703
|
const arrVal = Array.isArray(rawVal) ? rawVal : [String(rawVal)];
|
|
695
|
-
|
|
704
|
+
const result = [];
|
|
705
|
+
const arrValStr = arrVal.map(String);
|
|
706
|
+
let warned = false;
|
|
707
|
+
for (let v of arrValStr) {
|
|
708
|
+
if (!warned && (v.startsWith("[") && !v.endsWith("]") || !v.startsWith("[") && v.endsWith("]"))) {
|
|
709
|
+
relinka("error", `Don't use quotes around array elements.`);
|
|
710
|
+
relinka(
|
|
711
|
+
"error",
|
|
712
|
+
`Also \u2014 don't use spaces \u2014 unless you wrap the whole array in quotes.`
|
|
713
|
+
);
|
|
714
|
+
relinka(
|
|
715
|
+
"warn",
|
|
716
|
+
`Array argument --${argName}: Detected possible shell splitting of bracketed value ('${v}').`
|
|
717
|
+
);
|
|
718
|
+
relinka(
|
|
719
|
+
"warn",
|
|
720
|
+
`If you intended to pass a bracketed list, quote the whole value like: --${argName} "[a, b, c]"`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
warned = true;
|
|
724
|
+
if (v.startsWith("[") && v.endsWith("]")) {
|
|
725
|
+
v = v.slice(1, -1);
|
|
726
|
+
}
|
|
727
|
+
const parts = v.split(/\s*,\s*/).filter(Boolean);
|
|
728
|
+
parts.forEach((p) => {
|
|
729
|
+
if (p.startsWith('"') && p.endsWith('"') || p.startsWith("'") && p.endsWith("'")) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`Array argument --${argName}: Quoted values are not supported due to shell parsing limitations. Please avoid using single or double quotes around array elements.`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
if (def.allowed && !def.allowed.includes(p)) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`Invalid value in array --${argName}: ${p}. Allowed values are: ${def.allowed.join(", ")}`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
result.push(...parts);
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
696
743
|
}
|
|
697
744
|
default:
|
|
698
745
|
return rawVal;
|
|
@@ -757,17 +804,15 @@ export async function runCmd(command, argv = [], parserOptions = {}) {
|
|
|
757
804
|
if (valueOrDefault == null && def.required) {
|
|
758
805
|
throw new Error(`Missing required argument: --${key}`);
|
|
759
806
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
);
|
|
766
|
-
if (invalidOptions.length > 0) {
|
|
767
|
-
throw new Error(
|
|
768
|
-
`Invalid choice(s) for --${key}: ${invalidOptions.join(", ")}. Allowed options: ${def.options.join(", ")}`
|
|
769
|
-
);
|
|
807
|
+
try {
|
|
808
|
+
if (def.type === "boolean") {
|
|
809
|
+
finalArgs[key] = rawVal !== void 0 ? castArgValue(def, rawVal, key) : false;
|
|
810
|
+
} else {
|
|
811
|
+
finalArgs[key] = castArgValue(def, rawVal, key);
|
|
770
812
|
}
|
|
813
|
+
} catch (err) {
|
|
814
|
+
relinka("error", String(err));
|
|
815
|
+
throw err;
|
|
771
816
|
}
|
|
772
817
|
}
|
|
773
818
|
const ctx = {
|
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.
|
|
31
|
+
"version": "1.7.9",
|
|
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.
|
|
47
|
+
"@reliverse/dler": "^1.2.4",
|
|
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",
|