@voryx/cli 0.0.0

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.
@@ -0,0 +1,22 @@
1
+
2
+ 
3
+ > @voryx/cli@0.0.0 build /Users/cal/Documents/dev/voryx/packages/cli
4
+ > rslib build
5
+
6
+ Rslib v0.19.6
7
+ 
8
+ (node:97408) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///Users/cal/Documents/dev/voryx/packages/cli/rslib.config.ts?t=1788725586056 is not specified and it doesn't parse as CommonJS.
9
+ Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
10
+ To eliminate this warning, add "type": "module" to /Users/cal/Documents/dev/voryx/packages/cli/package.json.
11
+ (Use `node --trace-warnings ...` to show where the warning was created)
12
+ info  build started...
13
+ ready  built in 0.13 s
14
+ start  generating declaration files... (esm)
15
+ ready  declaration files generated in 0.26 s (esm)
16
+
17
+ File (esm) Size 
18
+ dist/manifest.json 0.17 kB
19
+ dist/index.mjs 11.0 kB
20
+
21
+ Total: 11.1 kB
22
+
@@ -0,0 +1,10 @@
1
+ import { FlagSchema } from './FlagSchema';
2
+ import type { FlagDefinition } from './types';
3
+ export declare class BooleanFlag<D extends boolean | undefined = boolean | undefined> extends FlagSchema<'boolean', D> {
4
+ constructor(options: Omit<FlagDefinition<'boolean', D>, 'kind'>);
5
+ resolveValue(raw: string | undefined): {
6
+ value: boolean;
7
+ usedValue: boolean;
8
+ };
9
+ }
10
+ //# sourceMappingURL=BooleanFlag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BooleanFlag.d.ts","sourceRoot":"","sources":["../src/BooleanFlag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,qBAAa,WAAW,CACtB,CAAC,SAAS,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CACnD,SAAQ,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;gBACnB,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAIhE,YAAY,CAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;QACtC,KAAK,EAAE,OAAO,CAAA;QACd,SAAS,EAAE,OAAO,CAAA;KACnB;CAMF"}
package/dist/Cli.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { Schema } from './Schema';
2
+ import type { CommandCtx, SchemaOptions } from './types';
3
+ export interface Command<S extends SchemaOptions = SchemaOptions> {
4
+ name: string;
5
+ description: string;
6
+ usage: string;
7
+ schema: Schema<S>;
8
+ run: (ctx: CommandCtx<S>) => void | Promise<void>;
9
+ }
10
+ declare function createCommandClass(cli: Cli): {
11
+ new <S extends SchemaOptions = SchemaOptions>(options: {
12
+ name: string;
13
+ description: string;
14
+ usage: string;
15
+ schema: S;
16
+ run: (ctx: CommandCtx<S>) => void | Promise<void>;
17
+ }): {
18
+ name: string;
19
+ description: string;
20
+ usage: string;
21
+ schema: Schema<S>;
22
+ run: (ctx: CommandCtx<S>) => void | Promise<void>;
23
+ };
24
+ };
25
+ export declare class Cli {
26
+ name: string;
27
+ commands: Map<string, Command>;
28
+ Command: ReturnType<typeof createCommandClass>;
29
+ constructor(name: string);
30
+ run(): Promise<void>;
31
+ warn(msg: string): void;
32
+ error(msg: string): void;
33
+ log(msg: string): void;
34
+ exit(code: number): void;
35
+ }
36
+ export {};
37
+ //# sourceMappingURL=Cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cli.d.ts","sourceRoot":"","sources":["../src/Cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAgExD,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IACjB,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAED,iBAAS,kBAAkB,CAAE,GAAG,EAAE,GAAG;SAEjC,CAAC,SAAS,aAAa,2BAOD;QACpB,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,CAAC,CAAA;QACT,GAAG,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAClD;cAXG,MAAM;qBACC,MAAM;eACZ,MAAM;gBACL,MAAM,CAAC,CAAC,CAAC;aACZ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;;EAgBlD;AAED,qBAAa,GAAG;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;gBAEjC,IAAI,EAAE,MAAM;IAMnB,GAAG;IAqDT,IAAI,CAAE,GAAG,EAAE,MAAM;IAIjB,KAAK,CAAE,GAAG,EAAE,MAAM;IAKlB,GAAG,CAAE,GAAG,EAAE,MAAM;IAIhB,IAAI,CAAE,IAAI,EAAE,MAAM;CAGnB"}
@@ -0,0 +1,14 @@
1
+ import type { FlagDefinition, FlagKind, TypeFlag } from './types';
2
+ export declare abstract class FlagSchema<T extends FlagKind = FlagKind, D extends TypeFlag<T> | undefined = TypeFlag<T> | undefined> {
3
+ kind: T;
4
+ long: string;
5
+ short?: string;
6
+ description: string;
7
+ default: D;
8
+ constructor(options: FlagDefinition<T, D>);
9
+ abstract resolveValue(raw: string | undefined): {
10
+ value: TypeFlag<T> | D;
11
+ usedValue: boolean;
12
+ };
13
+ }
14
+ //# sourceMappingURL=FlagSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlagSchema.d.ts","sourceRoot":"","sources":["../src/FlagSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEjE,8BAAsB,UAAU,CAC9B,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAC7B,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS;IAE3D,IAAI,EAAE,CAAC,CAAA;IACP,IAAI,EAAE,MAAM,CAAK;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,CAAC,CAAA;gBAEG,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IAO1C,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;QAC9C,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACtB,SAAS,EAAE,OAAO,CAAA;KACnB;CACF"}
@@ -0,0 +1,10 @@
1
+ import { FlagSchema } from "./FlagSchema";
2
+ import type { FlagDefinition } from "./types";
3
+ export declare class NumberFlag<D extends number | undefined = number | undefined> extends FlagSchema<'number', D> {
4
+ constructor(options: Omit<FlagDefinition<'number', D>, 'kind'>);
5
+ resolveValue(raw: string | undefined): {
6
+ value: number;
7
+ usedValue: boolean;
8
+ };
9
+ }
10
+ //# sourceMappingURL=NumberFlag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NumberFlag.d.ts","sourceRoot":"","sources":["../src/NumberFlag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,qBAAa,UAAU,CACrB,CAAC,SAAS,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CACjD,SAAQ,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAClB,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAI/D,YAAY,CAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE;CAY9E"}
@@ -0,0 +1,14 @@
1
+ import type { Flags, SchemaOptions } from "./types";
2
+ export declare class Schema<S extends SchemaOptions = SchemaOptions> {
3
+ private byLong;
4
+ private byShort;
5
+ private names;
6
+ constructor(schema: S);
7
+ map<T>(fn: (flag: S[keyof S]) => T): T[];
8
+ forEach(fn: (flag: S[keyof S], index: number) => void): void;
9
+ parse(argv: string[]): {
10
+ flags: Flags<S>;
11
+ unknown: string[];
12
+ };
13
+ }
14
+ //# sourceMappingURL=Schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Schema.d.ts","sourceRoot":"","sources":["../src/Schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnD,qBAAa,MAAM,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa;IACzD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,KAAK,CAAa;gBAEb,MAAM,EAAE,CAAC;IAatB,GAAG,CAAC,CAAC,EAAG,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;IAQzC,OAAO,CAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAQ7D,KAAK,CAAE,IAAI,EAAE,MAAM,EAAE,GAAG;QAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE;CAwE/D"}
@@ -0,0 +1,10 @@
1
+ import { FlagSchema } from "./FlagSchema";
2
+ import type { FlagDefinition } from "./types";
3
+ export declare class StringFlag<D extends string | undefined = string | undefined> extends FlagSchema<'string', D> {
4
+ constructor(options: Omit<FlagDefinition<'string', D>, 'kind'>);
5
+ resolveValue(raw: string | undefined): {
6
+ value: string;
7
+ usedValue: boolean;
8
+ };
9
+ }
10
+ //# sourceMappingURL=StringFlag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StringFlag.d.ts","sourceRoot":"","sources":["../src/StringFlag.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAE7C,qBAAa,UAAU,CACrB,CAAC,SAAS,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CACjD,SAAQ,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAClB,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAI/D,YAAY,CAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE;CAS9E"}
@@ -0,0 +1,20 @@
1
+ export declare class TerminalColor {
2
+ static red(text: string): string;
3
+ static green(text: string): string;
4
+ static yellow(text: string): string;
5
+ static blue(text: string): string;
6
+ static magenta(text: string): string;
7
+ static cyan(text: string): string;
8
+ static purple(text: string): string;
9
+ static gray(text: string): string;
10
+ static bold(text: string): string;
11
+ static backgroundRed(text: string): string;
12
+ static backgroundGreen(text: string): string;
13
+ static backgroundYellow(text: string): string;
14
+ static backgroundBlue(text: string): string;
15
+ static backgroundMagenta(text: string): string;
16
+ static backgroundCyan(text: string): string;
17
+ static backgroundPurple(text: string): string;
18
+ static backgroundGray(text: string): string;
19
+ }
20
+ //# sourceMappingURL=TerminalColor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalColor.d.ts","sourceRoot":"","sources":["../src/TerminalColor.ts"],"names":[],"mappings":"AACA,qBAAa,aAAa;IACxB,MAAM,CAAC,GAAG,CAAE,IAAI,EAAE,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAE,IAAI,EAAE,MAAM;IAI1B,MAAM,CAAC,MAAM,CAAE,IAAI,EAAE,MAAM;IAI3B,MAAM,CAAC,IAAI,CAAE,IAAI,EAAE,MAAM;IAIzB,MAAM,CAAC,OAAO,CAAE,IAAI,EAAE,MAAM;IAI5B,MAAM,CAAC,IAAI,CAAE,IAAI,EAAE,MAAM;IAIzB,MAAM,CAAC,MAAM,CAAE,IAAI,EAAE,MAAM;IAI3B,MAAM,CAAC,IAAI,CAAE,IAAI,EAAE,MAAM;IAIzB,MAAM,CAAC,IAAI,CAAE,IAAI,EAAE,MAAM;IAIzB,MAAM,CAAC,aAAa,CAAE,IAAI,EAAE,MAAM;IAIlC,MAAM,CAAC,eAAe,CAAE,IAAI,EAAE,MAAM;IAIpC,MAAM,CAAC,gBAAgB,CAAE,IAAI,EAAE,MAAM;IAIrC,MAAM,CAAC,cAAc,CAAE,IAAI,EAAE,MAAM;IAInC,MAAM,CAAC,iBAAiB,CAAE,IAAI,EAAE,MAAM;IAItC,MAAM,CAAC,cAAc,CAAE,IAAI,EAAE,MAAM;IAInC,MAAM,CAAC,gBAAgB,CAAE,IAAI,EAAE,MAAM;IAIrC,MAAM,CAAC,cAAc,CAAE,IAAI,EAAE,MAAM;CAGpC"}
@@ -0,0 +1,9 @@
1
+ export * from './Cli';
2
+ export * from './Schema';
3
+ export * from './types';
4
+ export * from './TerminalColor';
5
+ export * from './BooleanFlag';
6
+ export * from './StringFlag';
7
+ export * from './NumberFlag';
8
+ export * from './FlagSchema';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,OAAO,CAAA;AACrB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,345 @@
1
+ class Schema {
2
+ byLong;
3
+ byShort;
4
+ names;
5
+ constructor(schema){
6
+ this.byLong = new Map();
7
+ this.byShort = new Map();
8
+ this.names = new Set();
9
+ for (const [key, s] of Object.entries(schema)){
10
+ s.long = key;
11
+ this.byLong.set(s.long, s);
12
+ if (s.short) this.byShort.set(s.short, s);
13
+ this.names.add(s.long);
14
+ }
15
+ }
16
+ map(fn) {
17
+ const result = [];
18
+ for (const flag of this.byLong.values())result.push(fn(flag));
19
+ return result;
20
+ }
21
+ forEach(fn) {
22
+ let index = 0;
23
+ for (const flag of this.byLong.values()){
24
+ fn(flag, index);
25
+ index++;
26
+ }
27
+ }
28
+ parse(argv) {
29
+ const flags = {};
30
+ const notFound = new Set(this.names);
31
+ const unknown = [];
32
+ for(let i = 0; i < argv.length; i++){
33
+ const a = argv[i];
34
+ if ('--' === a) break;
35
+ if (a.startsWith('--')) {
36
+ const eq = a.indexOf('=');
37
+ const name = -1 === eq ? a.slice(2) : a.slice(2, eq);
38
+ const flag = this.byLong.get(name);
39
+ if (!flag) {
40
+ unknown.push(a);
41
+ continue;
42
+ }
43
+ notFound.delete(name);
44
+ const value = -1 === eq ? void 0 : a.slice(eq + 1);
45
+ const raw = value ?? argv[i + 1];
46
+ if (void 0 === value) {
47
+ const { value: resolved, usedValue } = flag.resolveValue(raw);
48
+ flags[flag.long] = resolved;
49
+ if (usedValue) i++;
50
+ } else {
51
+ const resolved = flag.resolveValue(value);
52
+ flags[flag.long] = resolved.value;
53
+ }
54
+ } else if (a.startsWith('-') && '-' !== a) {
55
+ const eq = a.indexOf('=');
56
+ const name = -1 === eq ? a.slice(1) : a.slice(1, eq);
57
+ const flag = this.byShort.get(name);
58
+ if (!flag) {
59
+ unknown.push(a);
60
+ continue;
61
+ }
62
+ notFound.delete(name);
63
+ const value = -1 === eq ? void 0 : a.slice(eq + 1);
64
+ const raw = value ?? argv[i + 1];
65
+ if (void 0 === value) {
66
+ const { value: resolved, usedValue } = flag.resolveValue(raw);
67
+ flags[flag.long] = resolved;
68
+ if (usedValue) i++;
69
+ } else {
70
+ const resolved = flag.resolveValue(value);
71
+ flags[flag.long] = resolved.value;
72
+ }
73
+ }
74
+ }
75
+ for (const name of notFound){
76
+ const flag = this.byLong.get(name);
77
+ if (void 0 !== flag.default) flags[flag.long] = flag.default;
78
+ else if ('boolean' === flag.kind) flags[flag.long] = false;
79
+ else throw new Error(`Missing required flag --${flag.long}`);
80
+ }
81
+ return {
82
+ flags,
83
+ unknown
84
+ };
85
+ }
86
+ }
87
+ class TerminalColor {
88
+ static red(text) {
89
+ return `\x1b[38;5;196m${text}\x1b[0m`;
90
+ }
91
+ static green(text) {
92
+ return `\x1b[38;5;47m${text}\x1b[0m`;
93
+ }
94
+ static yellow(text) {
95
+ return `\x1b[38;5;226m${text}\x1b[0m`;
96
+ }
97
+ static blue(text) {
98
+ return `\x1b[38;5;27m${text}\x1b[0m`;
99
+ }
100
+ static magenta(text) {
101
+ return `\x1b[38;5;200m${text}\x1b[0m`;
102
+ }
103
+ static cyan(text) {
104
+ return `\x1b[38;5;51m${text}\x1b[0m`;
105
+ }
106
+ static purple(text) {
107
+ return `\x1b[38;5;141m${text}\x1b[0m`;
108
+ }
109
+ static gray(text) {
110
+ return `\x1b[38;5;245m${text}\x1b[0m`;
111
+ }
112
+ static bold(text) {
113
+ return `\x1b[1m${text}\x1b[0m`;
114
+ }
115
+ static backgroundRed(text) {
116
+ return `\x1b[48;5;196m${text}\x1b[0m`;
117
+ }
118
+ static backgroundGreen(text) {
119
+ return `\x1b[48;5;47m${text}\x1b[0m`;
120
+ }
121
+ static backgroundYellow(text) {
122
+ return `\x1b[48;5;226m${text}\x1b[0m`;
123
+ }
124
+ static backgroundBlue(text) {
125
+ return `\x1b[48;5;27m${text}\x1b[0m`;
126
+ }
127
+ static backgroundMagenta(text) {
128
+ return `\x1b[48;5;200m${text}\x1b[0m`;
129
+ }
130
+ static backgroundCyan(text) {
131
+ return `\x1b[48;5;51m${text}\x1b[0m`;
132
+ }
133
+ static backgroundPurple(text) {
134
+ return `\x1b[48;5;141m${text}\x1b[0m`;
135
+ }
136
+ static backgroundGray(text) {
137
+ return `\x1b[48;5;245m${text}\x1b[0m`;
138
+ }
139
+ }
140
+ function exit(code) {
141
+ process.exitCode = code;
142
+ throw code;
143
+ }
144
+ function formatError(msg) {
145
+ return `Error: ${msg}`;
146
+ }
147
+ function padRight(s, n) {
148
+ return s.length >= n ? s : s + ' '.repeat(n - s.length);
149
+ }
150
+ function printHelp(appName, commands) {
151
+ const lines = [];
152
+ lines.push(`${TerminalColor.backgroundPurple(appName)} — CLI`);
153
+ lines.push('');
154
+ lines.push('Usage:');
155
+ lines.push(` ${appName} ${TerminalColor.cyan('<command>')} [options]`);
156
+ lines.push('');
157
+ lines.push('Commands:');
158
+ const w = Math.max(...commands.map((c)=>c.name.length), 7);
159
+ for (const c of commands)lines.push(` ${padRight(c.name, w)} ${c.description}`);
160
+ lines.push('');
161
+ lines.push(`Run "${appName} <command> --help" for command options.`);
162
+ process.stdout.write(lines.join('\n') + '\n');
163
+ }
164
+ function printCommandHelp(appName, cmd) {
165
+ const lines = [];
166
+ lines.push(`${TerminalColor.backgroundPurple(appName)} ${TerminalColor.cyan(cmd.name)}`);
167
+ lines.push('');
168
+ lines.push(cmd.description);
169
+ lines.push('');
170
+ lines.push('Usage:');
171
+ lines.push(` ${appName} ${cmd.usage}`);
172
+ if (cmd.schema) {
173
+ lines.push('');
174
+ lines.push('Options:');
175
+ const left = cmd.schema.map((f)=>{
176
+ const short = 'short' in f && f.short ? `-${f.short}, ` : ' ';
177
+ const long = `--${f.long}`;
178
+ const value = 'boolean' === f.kind ? '' : `=<${'placeholder' in f && f.placeholder ? f.placeholder : f.kind}>`;
179
+ return `${short}${long}${value}`;
180
+ });
181
+ const w = Math.max(...left.map((s)=>s.length), 10);
182
+ cmd.schema.forEach((f, i)=>{
183
+ lines.push(` ${padRight(left[i], w)} ${f.description}`);
184
+ });
185
+ }
186
+ lines.push('');
187
+ process.stdout.write(lines.join('\n') + '\n');
188
+ }
189
+ function createCommandClass(cli) {
190
+ return class {
191
+ name;
192
+ description;
193
+ usage;
194
+ schema;
195
+ run;
196
+ constructor(options){
197
+ this.name = options.name;
198
+ this.description = options.description;
199
+ this.usage = options.usage;
200
+ this.schema = new Schema(options.schema);
201
+ this.run = options.run;
202
+ cli.commands.set(this.name, this);
203
+ }
204
+ };
205
+ }
206
+ class Cli {
207
+ name;
208
+ commands;
209
+ Command;
210
+ constructor(name){
211
+ this.name = name;
212
+ this.commands = new Map();
213
+ this.Command = createCommandClass(this);
214
+ }
215
+ async run() {
216
+ const argv = process.argv.slice(2);
217
+ const [maybeCommand, ...rest] = argv;
218
+ if (!maybeCommand || '-h' === maybeCommand || '--help' === maybeCommand) return void printHelp(this.name, [
219
+ ...this.commands.values()
220
+ ]);
221
+ const cmd = this.commands.get(maybeCommand);
222
+ if (!cmd) {
223
+ process.stderr.write(formatError(`Unknown command "${maybeCommand}".`) + '\n\n');
224
+ printHelp(this.name, [
225
+ ...this.commands.values()
226
+ ]);
227
+ exit(1);
228
+ }
229
+ if (rest.includes('--help') || rest.includes('-h')) return void printCommandHelp(this.name, cmd);
230
+ const parsed = cmd.schema.parse(rest);
231
+ if (parsed.unknown.length) {
232
+ process.stderr.write(formatError(`Unknown option(s): ${parsed.unknown.join(', ')}`) + '\n');
233
+ process.stderr.write(`Run "${this.name} ${cmd.name} --help" for options.\n`);
234
+ exit(1);
235
+ }
236
+ process.stdout.write(`${TerminalColor.backgroundPurple(this.name)} - <${TerminalColor.cyan(cmd.name)}>...\n\n`);
237
+ const timeNow = performance.now();
238
+ await cmd.run({
239
+ argv: rest,
240
+ flags: parsed.flags,
241
+ stdout: process.stdout,
242
+ stderr: process.stderr,
243
+ exit: exit
244
+ });
245
+ const timeEnd = performance.now();
246
+ process.stdout.write(`\n${TerminalColor.backgroundPurple(this.name)} - <${TerminalColor.cyan(cmd.name)}>\n completed in ${TerminalColor.green((timeEnd - timeNow).toFixed(2))}ms\n\n`);
247
+ }
248
+ warn(msg) {
249
+ process.stdout.write(` ${TerminalColor.yellow('Warning:')} ${msg.split('\n').join('\n ')}\n`);
250
+ }
251
+ error(msg) {
252
+ process.stderr.write(`${TerminalColor.red('Error:')} ${msg}\n`);
253
+ exit(1);
254
+ }
255
+ log(msg) {
256
+ process.stdout.write(` ${msg.split('\n').join('\n ')}\n`);
257
+ }
258
+ exit(code) {
259
+ exit(code);
260
+ }
261
+ }
262
+ class FlagSchema {
263
+ kind;
264
+ long = '';
265
+ short;
266
+ description;
267
+ default;
268
+ constructor(options){
269
+ this.kind = options.kind;
270
+ this.short = options.short;
271
+ this.description = options.description;
272
+ this.default = options.default;
273
+ }
274
+ }
275
+ class BooleanFlag extends FlagSchema {
276
+ constructor(options){
277
+ super({
278
+ ...options,
279
+ kind: 'boolean'
280
+ });
281
+ }
282
+ resolveValue(raw) {
283
+ if (void 0 === raw) return {
284
+ value: true,
285
+ usedValue: false
286
+ };
287
+ if ('true' === raw.toLowerCase()) return {
288
+ value: true,
289
+ usedValue: true
290
+ };
291
+ if ('false' === raw.toLowerCase()) return {
292
+ value: false,
293
+ usedValue: true
294
+ };
295
+ return {
296
+ value: true,
297
+ usedValue: false
298
+ };
299
+ }
300
+ }
301
+ class StringFlag extends FlagSchema {
302
+ constructor(options){
303
+ super({
304
+ ...options,
305
+ kind: 'string'
306
+ });
307
+ }
308
+ resolveValue(raw) {
309
+ if (void 0 === raw) {
310
+ if (void 0 !== this.default) return {
311
+ value: this.default,
312
+ usedValue: false
313
+ };
314
+ throw new Error(`Missing value for --${this.long}`);
315
+ }
316
+ return {
317
+ value: raw,
318
+ usedValue: true
319
+ };
320
+ }
321
+ }
322
+ class NumberFlag extends FlagSchema {
323
+ constructor(options){
324
+ super({
325
+ ...options,
326
+ kind: 'number'
327
+ });
328
+ }
329
+ resolveValue(raw) {
330
+ if (void 0 === raw) {
331
+ if (void 0 !== this.default) return {
332
+ value: this.default,
333
+ usedValue: false
334
+ };
335
+ throw new Error(`Missing value for --${this.long}`);
336
+ }
337
+ const n = Number(raw);
338
+ if (!Number.isFinite(n)) throw new Error(`Invalid number for --${this.long}: ${raw}`);
339
+ return {
340
+ value: n,
341
+ usedValue: true
342
+ };
343
+ }
344
+ }
345
+ export { BooleanFlag, Cli, FlagSchema, NumberFlag, Schema, StringFlag, TerminalColor };
@@ -0,0 +1,15 @@
1
+ {
2
+ "allFiles": [
3
+ "index.mjs"
4
+ ],
5
+ "entries": {
6
+ "index": {
7
+ "initial": {
8
+ "js": [
9
+ "index.mjs"
10
+ ]
11
+ }
12
+ }
13
+ },
14
+ "integrity": {}
15
+ }
@@ -0,0 +1,26 @@
1
+ import type { FlagSchema } from "./FlagSchema";
2
+ export type TypeFlag<T extends 'boolean' | 'string' | 'number'> = T extends 'boolean' ? boolean : T extends 'string' ? string : T extends 'number' ? number : never;
3
+ export type FlagKind = 'boolean' | 'string' | 'number';
4
+ export type FlagDefinition<T extends FlagKind, D extends TypeFlag<T> | undefined = TypeFlag<T> | undefined> = {
5
+ kind: T;
6
+ short?: string;
7
+ description: string;
8
+ default: D;
9
+ };
10
+ export type ShouldRequire<T extends FlagDefinition<FlagKind>> = T['default'] extends undefined ? T['kind'] extends 'boolean' ? true : false : true;
11
+ export type Flags<S extends SchemaOptions> = {
12
+ [K in keyof S as ShouldRequire<S[K]> extends true ? K : never]: TypeFlag<S[K]['kind']>;
13
+ } & {
14
+ [K in keyof S as ShouldRequire<S[K]> extends false ? K : never]?: TypeFlag<S[K]['kind']>;
15
+ };
16
+ export interface SchemaOptions {
17
+ [key: string]: FlagSchema;
18
+ }
19
+ export type CommandCtx<S extends SchemaOptions = SchemaOptions> = {
20
+ argv: string[];
21
+ flags: Flags<S>;
22
+ stdout: NodeJS.WriteStream;
23
+ stderr: NodeJS.WriteStream;
24
+ exit: (code: number) => never;
25
+ };
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,SAAS,GAAG,QAAQ,GAAG,QAAQ,IAAI,CAAC,SAAS,SAAS,GACjF,OAAO,GACP,CAAC,SAAS,QAAQ,GAClB,MAAM,GACN,CAAC,SAAS,QAAQ,GAClB,MAAM,GACN,KAAK,CAAA;AAET,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEtD,MAAM,MAAM,cAAc,CACxB,CAAC,SAAS,QAAQ,EAClB,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,IACzD;IACF,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,CAAC,CAAA;CACX,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,cAAc,CAAC,QAAQ,CAAC,IAC1D,CAAC,CAAC,SAAS,CAAC,SAAS,SAAS,GAC1B,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GACzB,IAAI,GACJ,KAAK,GACP,IAAI,CAAA;AAEV,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,aAAa,IAAI;KAC1C,CAAC,IAAI,MAAM,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,GAAG,QAAQ,CACtE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CACb;CACF,GAAG;KACD,CAAC,IAAI,MAAM,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,EAAE,QAAQ,CACxE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CACb;CACF,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;CAC1B;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,IAAI;IAChE,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;IACf,MAAM,EAAE,MAAM,CAAC,WAAW,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAC,WAAW,CAAA;IAC1B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAA;CAC9B,CAAA"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@voryx/cli",
3
+ "version": "0.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "devDependencies": {
14
+ "@rslib/core": "^0.19.6",
15
+ "@types/node": "^22.15.3",
16
+ "ts-standard": "^12.0.2",
17
+ "typescript": "5.9.2",
18
+ "@voryx/tsconfig": "0.0.0"
19
+ },
20
+ "scripts": {
21
+ "lint": "ts-standard",
22
+ "build": "rslib build",
23
+ "check-types": "tsc --noEmit"
24
+ }
25
+ }
@@ -0,0 +1,22 @@
1
+
2
+ import { defineConfig } from '@rslib/core'
3
+
4
+ export default defineConfig({
5
+ lib: [
6
+ {
7
+ format: 'esm',
8
+ syntax: 'es2023',
9
+ bundle: true,
10
+ dts: true
11
+ }
12
+ ],
13
+ output: {
14
+ target: 'node',
15
+ manifest: true
16
+ },
17
+ source: {
18
+ entry: {
19
+ index: 'src/index.ts'
20
+ }
21
+ }
22
+ })
@@ -0,0 +1,20 @@
1
+ import { FlagSchema } from './FlagSchema'
2
+ import type { FlagDefinition } from './types'
3
+
4
+ export class BooleanFlag<
5
+ D extends boolean | undefined = boolean | undefined
6
+ > extends FlagSchema<'boolean', D> {
7
+ constructor (options: Omit<FlagDefinition<'boolean', D>, 'kind'>) {
8
+ super({ ...options, kind: 'boolean' })
9
+ }
10
+
11
+ resolveValue (raw: string | undefined): {
12
+ value: boolean
13
+ usedValue: boolean
14
+ } {
15
+ if (raw === undefined) return { value: true, usedValue: false }
16
+ if (raw.toLowerCase() === 'true') return { value: true, usedValue: true }
17
+ if (raw.toLowerCase() === 'false') return { value: false, usedValue: true }
18
+ return { value: true, usedValue: false }
19
+ }
20
+ }
package/src/Cli.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { Schema } from './Schema'
2
+ import type { CommandCtx, SchemaOptions } from './types'
3
+ import { TerminalColor } from './TerminalColor'
4
+
5
+ function exit (code: number): never {
6
+ process.exitCode = code
7
+ // eslint-disable-next-line no-throw-literal
8
+ throw code // allows early exits inside async without extra plumbing
9
+ }
10
+
11
+ function formatError (msg: string) {
12
+ return `Error: ${msg}`
13
+ }
14
+
15
+ function padRight (s: string, n: number) {
16
+ return s.length >= n ? s : s + ' '.repeat(n - s.length)
17
+ }
18
+
19
+ function printHelp (appName: string, commands: Command[]) {
20
+ const lines: string[] = []
21
+ lines.push(`${TerminalColor.backgroundPurple(appName)} — CLI`)
22
+ lines.push('')
23
+ lines.push('Usage:')
24
+ lines.push(` ${appName} ${TerminalColor.cyan('<command>')} [options]`)
25
+ lines.push('')
26
+ lines.push('Commands:')
27
+ const w = Math.max(...commands.map(c => c.name.length), 7)
28
+ for (const c of commands) {
29
+ lines.push(` ${padRight(c.name, w)} ${c.description}`)
30
+ }
31
+ lines.push('')
32
+ lines.push(`Run "${appName} <command> --help" for command options.`)
33
+ process.stdout.write(lines.join('\n') + '\n')
34
+ }
35
+
36
+ function printCommandHelp (appName: string, cmd: Command) {
37
+ const lines: string[] = []
38
+ lines.push(`${TerminalColor.backgroundPurple(appName)} ${TerminalColor.cyan(cmd.name)}`)
39
+ lines.push('')
40
+ lines.push(cmd.description)
41
+ lines.push('')
42
+ lines.push('Usage:')
43
+ lines.push(` ${appName} ${cmd.usage}`)
44
+ if (cmd.schema) {
45
+ lines.push('')
46
+ lines.push('Options:')
47
+ const left = cmd.schema.map(f => {
48
+ const short = 'short' in f && f.short ? `-${f.short}, ` : ' '
49
+ const long = `--${f.long}`
50
+ const value =
51
+ f.kind === 'boolean'
52
+ ? ''
53
+ : `=<${'placeholder' in f && f.placeholder ? f.placeholder : f.kind}>`
54
+ return `${short}${long}${value}`
55
+ })
56
+ const w = Math.max(...left.map(s => s.length), 10)
57
+
58
+ cmd.schema.forEach((f, i) => {
59
+ lines.push(` ${padRight(left[i]!, w)} ${f.description}`)
60
+ })
61
+ }
62
+ lines.push('')
63
+ process.stdout.write(lines.join('\n') + '\n')
64
+ }
65
+
66
+ export interface Command<S extends SchemaOptions = SchemaOptions> {
67
+ name: string
68
+ description: string
69
+ usage: string
70
+ schema: Schema<S>
71
+ run: (ctx: CommandCtx<S>) => void | Promise<void>
72
+ }
73
+
74
+ function createCommandClass (cli: Cli) {
75
+ return class CliCommand<
76
+ S extends SchemaOptions = SchemaOptions
77
+ > implements Command<S> {
78
+ name: string
79
+ description: string
80
+ usage: string
81
+ schema: Schema<S>
82
+ run: (ctx: CommandCtx<S>) => void | Promise<void>
83
+ constructor (options: {
84
+ name: string
85
+ description: string
86
+ usage: string
87
+ schema: S
88
+ run: (ctx: CommandCtx<S>) => void | Promise<void>
89
+ }) {
90
+ this.name = options.name
91
+ this.description = options.description
92
+ this.usage = options.usage
93
+ this.schema = new Schema(options.schema)
94
+ this.run = options.run
95
+ cli.commands.set(this.name, this as Command<any>)
96
+ }
97
+ }
98
+ }
99
+
100
+ export class Cli {
101
+ name: string
102
+ commands: Map<string, Command>
103
+ Command: ReturnType<typeof createCommandClass>
104
+
105
+ constructor (name: string) {
106
+ this.name = name
107
+ this.commands = new Map()
108
+ this.Command = createCommandClass(this)
109
+ }
110
+
111
+ async run () {
112
+ // remove node + script path
113
+ const argv = process.argv.slice(2)
114
+
115
+ const [maybeCommand, ...rest] = argv
116
+
117
+ if (!maybeCommand || maybeCommand === '-h' || maybeCommand === '--help') {
118
+ printHelp(this.name, [...this.commands.values()])
119
+ return
120
+ }
121
+
122
+ const cmd = this.commands.get(maybeCommand)
123
+ if (!cmd) {
124
+ process.stderr.write(
125
+ formatError(`Unknown command "${maybeCommand}".`) + '\n\n'
126
+ )
127
+ printHelp(this.name, [...this.commands.values()])
128
+ exit(1)
129
+ }
130
+
131
+ // command-level help
132
+ if (rest.includes('--help') || rest.includes('-h')) {
133
+ printCommandHelp(this.name, cmd)
134
+ return
135
+ }
136
+
137
+ const parsed = cmd.schema.parse(rest)
138
+
139
+ if (parsed.unknown.length) {
140
+ process.stderr.write(
141
+ formatError(`Unknown option(s): ${parsed.unknown.join(', ')}`) + '\n'
142
+ )
143
+ process.stderr.write(
144
+ `Run "${this.name} ${cmd.name} --help" for options.\n`
145
+ )
146
+ exit(1)
147
+ }
148
+
149
+ process.stdout.write(`${TerminalColor.backgroundPurple(this.name)} - <${TerminalColor.cyan(cmd.name)}>...\n\n`)
150
+ const timeNow = performance.now()
151
+
152
+ await cmd.run({
153
+ argv: rest,
154
+ flags: parsed.flags,
155
+ stdout: process.stdout,
156
+ stderr: process.stderr,
157
+ exit
158
+ })
159
+
160
+ const timeEnd = performance.now()
161
+ process.stdout.write(`\n${TerminalColor.backgroundPurple(this.name)} - <${TerminalColor.cyan(cmd.name)}>\n completed in ${TerminalColor.green((timeEnd - timeNow).toFixed(2))}ms\n\n`)
162
+ }
163
+
164
+ warn (msg: string) {
165
+ process.stdout.write(` ${TerminalColor.yellow('Warning:')} ${msg.split('\n').join('\n ')}\n`)
166
+ }
167
+
168
+ error (msg: string) {
169
+ process.stderr.write(`${TerminalColor.red('Error:')} ${msg}\n`)
170
+ exit(1)
171
+ }
172
+
173
+ log (msg: string) {
174
+ process.stdout.write(` ${msg.split('\n').join('\n ')}\n`)
175
+ }
176
+
177
+ exit (code: number) {
178
+ exit(code)
179
+ }
180
+ }
@@ -0,0 +1,24 @@
1
+ import type { FlagDefinition, FlagKind, TypeFlag } from './types'
2
+
3
+ export abstract class FlagSchema<
4
+ T extends FlagKind = FlagKind,
5
+ D extends TypeFlag<T> | undefined = TypeFlag<T> | undefined
6
+ > {
7
+ kind: T
8
+ long: string = ''
9
+ short?: string
10
+ description: string
11
+ default: D
12
+
13
+ constructor (options: FlagDefinition<T, D>) {
14
+ this.kind = options.kind
15
+ this.short = options.short
16
+ this.description = options.description
17
+ this.default = options.default
18
+ }
19
+
20
+ abstract resolveValue(raw: string | undefined): {
21
+ value: TypeFlag<T> | D
22
+ usedValue: boolean
23
+ }
24
+ }
@@ -0,0 +1,23 @@
1
+ import { FlagSchema } from "./FlagSchema"
2
+ import type { FlagDefinition } from "./types"
3
+
4
+ export class NumberFlag<
5
+ D extends number | undefined = number | undefined
6
+ > extends FlagSchema<'number', D> {
7
+ constructor (options: Omit<FlagDefinition<'number', D>, 'kind'>) {
8
+ super({ ...options, kind: 'number' })
9
+ }
10
+
11
+ resolveValue (raw: string | undefined): { value: number; usedValue: boolean } {
12
+ if (raw === undefined) {
13
+ if (this.default !== undefined)
14
+ return { value: this.default, usedValue: false }
15
+ throw new Error(`Missing value for --${this.long}`)
16
+ }
17
+ const n = Number(raw)
18
+ if (!Number.isFinite(n)) {
19
+ throw new Error(`Invalid number for --${this.long}: ${raw}`)
20
+ }
21
+ return { value: n, usedValue: true }
22
+ }
23
+ }
package/src/Schema.ts ADDED
@@ -0,0 +1,109 @@
1
+ import type { Flags, SchemaOptions } from "./types"
2
+
3
+ export class Schema<S extends SchemaOptions = SchemaOptions> {
4
+ private byLong: Map<string, S[keyof S]>
5
+ private byShort: Map<string, S[keyof S]>
6
+ private names: Set<string>
7
+
8
+ constructor (schema: S) {
9
+ this.byLong = new Map()
10
+ this.byShort = new Map()
11
+ this.names = new Set()
12
+
13
+ for (const [key, s] of Object.entries(schema)) {
14
+ s.long = key
15
+ this.byLong.set(s.long, s as S[keyof S])
16
+ if (s.short) this.byShort.set(s.short, s as S[keyof S])
17
+ this.names.add(s.long)
18
+ }
19
+ }
20
+
21
+ map<T> (fn: (flag: S[keyof S]) => T): T[] {
22
+ const result: T[] = []
23
+ for (const flag of this.byLong.values()) {
24
+ result.push(fn(flag))
25
+ }
26
+ return result
27
+ }
28
+
29
+ forEach (fn: (flag: S[keyof S], index: number) => void): void {
30
+ let index = 0
31
+ for (const flag of this.byLong.values()) {
32
+ fn(flag, index)
33
+ index++
34
+ }
35
+ }
36
+
37
+ parse (argv: string[]): { flags: Flags<S>; unknown: string[] } {
38
+ const flags = {} as Flags<S>
39
+ const notFound: Set<string> = new Set(this.names)
40
+ const unknown: string[] = []
41
+
42
+ for (let i = 0; i < argv.length; i++) {
43
+ const a = argv[i]!
44
+
45
+ if (a === '--') {
46
+ break
47
+ }
48
+
49
+ if (a.startsWith('--')) {
50
+ const eq = a.indexOf('=')
51
+ const name = eq === -1 ? a.slice(2) : a.slice(2, eq)
52
+ const flag = this.byLong.get(name)
53
+
54
+ if (!flag) {
55
+ unknown.push(a)
56
+ continue
57
+ }
58
+
59
+ notFound.delete(name)
60
+ const value = eq === -1 ? undefined : a.slice(eq + 1)
61
+ const raw = value ?? argv[i + 1]
62
+
63
+ if (value === undefined) {
64
+ const { value: resolved, usedValue } = flag.resolveValue(raw)
65
+ flags[flag.long as keyof Flags<S>] = resolved as any
66
+ if (usedValue) i++
67
+ } else {
68
+ const resolved = flag.resolveValue(value)
69
+ flags[flag.long as keyof Flags<S>] = resolved.value as any
70
+ }
71
+ } else if (a.startsWith('-') && a !== '-') {
72
+ const eq = a.indexOf('=')
73
+ const name = eq === -1 ? a.slice(1) : a.slice(1, eq)
74
+ const flag = this.byShort.get(name)
75
+
76
+ if (!flag) {
77
+ unknown.push(a)
78
+ continue
79
+ }
80
+
81
+ notFound.delete(name)
82
+ const value = eq === -1 ? undefined : a.slice(eq + 1)
83
+ const raw = value ?? argv[i + 1]
84
+
85
+ if (value === undefined) {
86
+ const { value: resolved, usedValue } = flag.resolveValue(raw)
87
+ flags[flag.long as keyof Flags<S>] = resolved as any
88
+ if (usedValue) i++
89
+ } else {
90
+ const resolved = flag.resolveValue(value)
91
+ flags[flag.long as keyof Flags<S>] = resolved.value as any
92
+ }
93
+ }
94
+ }
95
+
96
+ for (const name of notFound) {
97
+ const flag = this.byLong.get(name)!
98
+ if (flag.default !== undefined) {
99
+ flags[flag.long as keyof Flags<S>] = flag.default as any
100
+ } else if (flag.kind === 'boolean') {
101
+ flags[flag.long as keyof Flags<S>] = false as any
102
+ } else {
103
+ throw new Error(`Missing required flag --${flag.long}`)
104
+ }
105
+ }
106
+
107
+ return { flags, unknown }
108
+ }
109
+ }
@@ -0,0 +1,20 @@
1
+ import { FlagSchema } from "./FlagSchema"
2
+ import type { FlagDefinition } from "./types"
3
+
4
+ export class StringFlag<
5
+ D extends string | undefined = string | undefined
6
+ > extends FlagSchema<'string', D> {
7
+ constructor (options: Omit<FlagDefinition<'string', D>, 'kind'>) {
8
+ super({ ...options, kind: 'string' })
9
+ }
10
+
11
+ resolveValue (raw: string | undefined): { value: string; usedValue: boolean } {
12
+ if (raw === undefined) {
13
+ if (this.default !== undefined)
14
+ return { value: this.default, usedValue: false }
15
+ throw new Error(`Missing value for --${this.long}`)
16
+ }
17
+
18
+ return { value: raw, usedValue: true }
19
+ }
20
+ }
@@ -0,0 +1,70 @@
1
+
2
+ export class TerminalColor {
3
+ static red (text: string) {
4
+ return `\x1b[38;5;196m${text}\x1b[0m`
5
+ }
6
+
7
+ static green (text: string) {
8
+ return `\x1b[38;5;47m${text}\x1b[0m`
9
+ }
10
+
11
+ static yellow (text: string) {
12
+ return `\x1b[38;5;226m${text}\x1b[0m`
13
+ }
14
+
15
+ static blue (text: string) {
16
+ return `\x1b[38;5;27m${text}\x1b[0m`
17
+ }
18
+
19
+ static magenta (text: string) {
20
+ return `\x1b[38;5;200m${text}\x1b[0m`
21
+ }
22
+
23
+ static cyan (text: string) {
24
+ return `\x1b[38;5;51m${text}\x1b[0m`
25
+ }
26
+
27
+ static purple (text: string) {
28
+ return `\x1b[38;5;141m${text}\x1b[0m`
29
+ }
30
+
31
+ static gray (text: string) {
32
+ return `\x1b[38;5;245m${text}\x1b[0m`
33
+ }
34
+
35
+ static bold (text: string) {
36
+ return `\x1b[1m${text}\x1b[0m`
37
+ }
38
+
39
+ static backgroundRed (text: string) {
40
+ return `\x1b[48;5;196m${text}\x1b[0m`
41
+ }
42
+
43
+ static backgroundGreen (text: string) {
44
+ return `\x1b[48;5;47m${text}\x1b[0m`
45
+ }
46
+
47
+ static backgroundYellow (text: string) {
48
+ return `\x1b[48;5;226m${text}\x1b[0m`
49
+ }
50
+
51
+ static backgroundBlue (text: string) {
52
+ return `\x1b[48;5;27m${text}\x1b[0m`
53
+ }
54
+
55
+ static backgroundMagenta (text: string) {
56
+ return `\x1b[48;5;200m${text}\x1b[0m`
57
+ }
58
+
59
+ static backgroundCyan (text: string) {
60
+ return `\x1b[48;5;51m${text}\x1b[0m`
61
+ }
62
+
63
+ static backgroundPurple (text: string) {
64
+ return `\x1b[48;5;141m${text}\x1b[0m`
65
+ }
66
+
67
+ static backgroundGray (text: string) {
68
+ return `\x1b[48;5;245m${text}\x1b[0m`
69
+ }
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+
2
+ export * from './Cli'
3
+ export * from './Schema'
4
+ export * from './types'
5
+ export * from './TerminalColor'
6
+ export * from './BooleanFlag'
7
+ export * from './StringFlag'
8
+ export * from './NumberFlag'
9
+ export * from './FlagSchema'
package/src/types.ts ADDED
@@ -0,0 +1,50 @@
1
+ import type { FlagSchema } from "./FlagSchema"
2
+
3
+ export type TypeFlag<T extends 'boolean' | 'string' | 'number'> = T extends 'boolean'
4
+ ? boolean
5
+ : T extends 'string'
6
+ ? string
7
+ : T extends 'number'
8
+ ? number
9
+ : never
10
+
11
+ export type FlagKind = 'boolean' | 'string' | 'number'
12
+
13
+ export type FlagDefinition<
14
+ T extends FlagKind,
15
+ D extends TypeFlag<T> | undefined = TypeFlag<T> | undefined
16
+ > = {
17
+ kind: T
18
+ short?: string
19
+ description: string
20
+ default: D
21
+ }
22
+
23
+ export type ShouldRequire<T extends FlagDefinition<FlagKind>> =
24
+ T['default'] extends undefined
25
+ ? T['kind'] extends 'boolean'
26
+ ? true
27
+ : false
28
+ : true
29
+
30
+ export type Flags<S extends SchemaOptions> = {
31
+ [K in keyof S as ShouldRequire<S[K]> extends true ? K : never]: TypeFlag<
32
+ S[K]['kind']
33
+ >
34
+ } & {
35
+ [K in keyof S as ShouldRequire<S[K]> extends false ? K : never]?: TypeFlag<
36
+ S[K]['kind']
37
+ >
38
+ }
39
+
40
+ export interface SchemaOptions {
41
+ [key: string]: FlagSchema
42
+ }
43
+
44
+ export type CommandCtx<S extends SchemaOptions = SchemaOptions> = {
45
+ argv: string[]
46
+ flags: Flags<S>
47
+ stdout: NodeJS.WriteStream
48
+ stderr: NodeJS.WriteStream
49
+ exit: (code: number) => never
50
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@voryx/tsconfig/nodelib.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src"],
7
+ "exclude": ["node_modules", "dist"]
8
+ }