erii 3.0.0-beta.1 → 3.0.0-beta.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/dist/index.d.mts +125 -49
- package/dist/index.mjs +20 -25
- package/package.json +55 -52
- package/readme.md +106 -128
package/dist/index.d.mts
CHANGED
|
@@ -1,117 +1,193 @@
|
|
|
1
1
|
import validator from "validator";
|
|
2
2
|
//#region src/types.d.ts
|
|
3
|
+
/** Raw values the CLI parser may return; generic declarations do not convert them. */
|
|
3
4
|
type ArgumentValue = string | number | boolean | ArgumentValue[] | {
|
|
4
5
|
[key: string]: ArgumentValue;
|
|
5
6
|
};
|
|
7
|
+
/** Raw options stored internally, before narrowing to a command schema. */
|
|
6
8
|
type CommandOptions = Record<string, ArgumentValue | undefined>;
|
|
7
9
|
interface ParsedArguments extends CommandOptions {
|
|
8
10
|
_: Array<string | number>;
|
|
9
11
|
}
|
|
10
|
-
type CommandHandler = (ctx: CommandCtx, options: CommandOptions) => unknown;
|
|
11
12
|
type LifecycleHandler = () => unknown;
|
|
13
|
+
/** Custom validators receive raw values and return false when validation fails. */
|
|
12
14
|
type ArgumentValidator = (value: ArgumentValue | undefined, logger: (message: string) => void) => boolean;
|
|
13
|
-
|
|
15
|
+
/** Only validator.js methods callable with a single string and returning a boolean are allowed. */
|
|
16
|
+
type ValidatorName = { [K in keyof typeof validator]: (typeof validator)[K] extends ((value: string) => boolean) ? K : never; }[keyof typeof validator];
|
|
14
17
|
interface MetaInfo {
|
|
15
18
|
version?: string;
|
|
16
19
|
name?: string;
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
name: string | string[];
|
|
20
|
-
description?: string;
|
|
21
|
-
argument?: Argument;
|
|
22
|
-
alias?: string[];
|
|
23
|
-
redirect?: string;
|
|
24
|
-
options?: Option[];
|
|
25
|
-
handler?: CommandHandler;
|
|
26
|
-
}
|
|
27
|
-
interface Option {
|
|
28
|
-
name: string | string[];
|
|
29
|
-
description?: string;
|
|
30
|
-
command?: string;
|
|
31
|
-
argument?: Argument;
|
|
32
|
-
}
|
|
33
|
-
interface CommandMap {
|
|
34
|
-
[key: string]: Command | undefined;
|
|
35
|
-
}
|
|
36
|
-
interface CommandCtx {
|
|
37
|
-
showVersion: () => void;
|
|
38
|
-
showHelp: () => void;
|
|
39
|
-
getArgument: (commandName?: string) => ArgumentValue | undefined;
|
|
40
|
-
}
|
|
21
|
+
/** Argument metadata and runtime validation rules for a command or option. */
|
|
41
22
|
interface Argument {
|
|
42
23
|
name: string;
|
|
43
24
|
description: string;
|
|
44
25
|
validate?: ValidatorName | ArgumentValidator;
|
|
45
26
|
}
|
|
27
|
+
type SchemaCommands<Schema> = Schema extends {
|
|
28
|
+
commands: infer Commands;
|
|
29
|
+
} ? Commands : never;
|
|
30
|
+
type LocalOptions<Command> = Command extends {
|
|
31
|
+
options?: infer Options;
|
|
32
|
+
} ? NonNullable<Options> : {};
|
|
33
|
+
type CommonOptions<Schema> = Schema extends {
|
|
34
|
+
commonOptions?: infer Options;
|
|
35
|
+
} ? NonNullable<Options> : {};
|
|
36
|
+
type SchemaOptionValues<Options> = { [Name in keyof Options]: ArgumentValue | undefined; };
|
|
37
|
+
type SchemaCommand<Command> = {
|
|
38
|
+
argument?: ArgumentValue;
|
|
39
|
+
options?: SchemaOptionValues<LocalOptions<Command>>;
|
|
40
|
+
aliases?: string;
|
|
41
|
+
};
|
|
42
|
+
type DefaultSchema = {
|
|
43
|
+
commands: Record<string, {
|
|
44
|
+
options?: CommandOptions;
|
|
45
|
+
}>;
|
|
46
|
+
commonOptions?: CommandOptions;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Compile-time schema constraints, with no role in runtime parsing or validation.
|
|
50
|
+
* Checks the supplied schema's own keys, supporting named interfaces without widening valid names.
|
|
51
|
+
*/
|
|
52
|
+
interface EriiSchema<Schema = DefaultSchema> {
|
|
53
|
+
commonOptions?: SchemaOptionValues<CommonOptions<Schema>>;
|
|
54
|
+
commands: { [Name in keyof SchemaCommands<Schema>]: SchemaCommand<SchemaCommands<Schema>[Name]>; };
|
|
55
|
+
}
|
|
56
|
+
type CommandName<Schema extends EriiSchema<Schema>> = Extract<keyof Schema["commands"], string>;
|
|
57
|
+
type CommandAliases<Command> = "aliases" extends keyof Command ? Extract<Command["aliases"], string> : never;
|
|
58
|
+
/** All valid command lookup names, including aliases declared in the schema. */
|
|
59
|
+
type CommandLookup<Schema extends EriiSchema<Schema>> = { [Name in CommandName<Schema>]: Name | CommandAliases<Schema["commands"][Name]>; }[CommandName<Schema>];
|
|
60
|
+
/** Resolves a primary name or alias to its command key in the schema. */
|
|
61
|
+
type ResolveCommand<Schema extends EriiSchema<Schema>, Lookup extends CommandLookup<Schema>> = { [Name in CommandName<Schema>]: Lookup extends Name | CommandAliases<Schema["commands"][Name]> ? Name : never; }[CommandName<Schema>];
|
|
62
|
+
/** Detects argument declarations by key presence, preserving the optional value type of argument?: T. */
|
|
63
|
+
type CommandArgument<Command> = "argument" extends keyof Command ? Command["argument"] : ArgumentValue;
|
|
64
|
+
type LowerLetter = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
|
|
65
|
+
/** Generates candidate camelCase names for kebab-case keys; underscores are not converted. */
|
|
66
|
+
type CamelKey<Key extends string> = Key extends `${infer Head}-${infer Letter}${infer Tail}` ? Letter extends LowerLetter ? `${Head}${Uppercase<Letter>}${CamelKey<Tail>}` : `${Head}-${CamelKey<`${Letter}${Tail}`>}` : Key;
|
|
67
|
+
type KebabLetter<Letter extends string> = Letter extends Uppercase<LowerLetter> ? `-${Lowercase<Letter>}` : Letter;
|
|
68
|
+
type KebabTail<Key extends string> = Key extends `${infer Head}${infer Tail}` ? `${KebabLetter<Head>}${KebabTail<Tail>}` : Key;
|
|
69
|
+
/** Matches runtime toKebabCase: lowercases the first letter and inserts hyphens before subsequent uppercase ASCII letters. */
|
|
70
|
+
type KebabKey<Key extends string> = Key extends `${infer Head}${infer Tail}` ? `${Lowercase<Head>}${KebabTail<Tail>}` : Key;
|
|
71
|
+
/** Only candidates that the proxy can resolve back to the original key are valid aliases. */
|
|
72
|
+
type CamelAliases<Options> = { [Name in Extract<keyof Options, string>]: KebabKey<CamelKey<Name>> extends Name ? CamelKey<Name> : never; }[Extract<keyof Options, string>];
|
|
73
|
+
type OptionValue<Options, Name> = Name extends keyof Options ? Options[Name] : never;
|
|
74
|
+
type FallbackOptionValue<Options, Name> = Name extends string ? OptionValue<Options, KebabKey<Name>> : never;
|
|
75
|
+
/**
|
|
76
|
+
* Matches proxy reads: checks the original key first, then falls back to the kebab-case key when absent.
|
|
77
|
+
* Name collisions may yield either value; any option may be omitted.
|
|
78
|
+
*/
|
|
79
|
+
type ProxiedOptions<Options> = { readonly [Name in keyof Options | CamelAliases<Options>]?: OptionValue<Options, Name> | FallbackOptionValue<Options, Name>; };
|
|
80
|
+
/** Common option names take precedence and are excluded from command-specific declarations. */
|
|
81
|
+
type ScopedOptions<Schema extends EriiSchema<Schema>, Name extends CommandName<Schema>> = Omit<LocalOptions<Schema["commands"][Name]>, keyof CommonOptions<Schema>>;
|
|
82
|
+
/** Options available to a handler: command-specific options, common options, and camelCase aliases. */
|
|
83
|
+
type TypedCommandOptions<Schema extends EriiSchema<Schema>, Name extends CommandName<Schema>> = ProxiedOptions<ScopedOptions<Schema, Name> & CommonOptions<Schema>>;
|
|
84
|
+
/** Reads the current command's argument when no name is supplied, or the argument of the named command or alias. */
|
|
85
|
+
interface TypedCommandCtx<Schema extends EriiSchema<Schema>, Name extends CommandName<Schema>> {
|
|
86
|
+
showVersion: () => void;
|
|
87
|
+
showHelp: () => void;
|
|
88
|
+
getArgument(): CommandArgument<Schema["commands"][Name]> | undefined;
|
|
89
|
+
getArgument<Lookup extends CommandLookup<Schema>>(name: Lookup): LookupArgument<Schema, Lookup> | undefined;
|
|
90
|
+
}
|
|
91
|
+
/** Provides context and option types matching the specified command for independently defined handlers. */
|
|
92
|
+
type TypedCommandHandler<Schema extends EriiSchema<Schema>, Name extends CommandName<Schema>> = (ctx: TypedCommandCtx<Schema, Name>, options: TypedCommandOptions<Schema, Name>) => unknown;
|
|
93
|
+
/** Argument metadata is required, optional, or forbidden according to the schema declaration. */
|
|
94
|
+
type ArgumentDefinition<Command> = "argument" extends keyof Command ? {} extends Pick<Command, "argument"> ? {
|
|
95
|
+
argument?: Argument;
|
|
96
|
+
} : {
|
|
97
|
+
argument: Argument;
|
|
98
|
+
} : {
|
|
99
|
+
argument?: never;
|
|
100
|
+
};
|
|
101
|
+
/** bind configuration: the first name must be the primary command name; subsequent names must be its aliases. */
|
|
102
|
+
type TypedCommand<Schema extends EriiSchema<Schema>, Name extends CommandName<Schema>> = {
|
|
103
|
+
name: Name | readonly [Name, ...CommandAliases<Schema["commands"][Name]>[]];
|
|
104
|
+
description?: string;
|
|
105
|
+
} & ArgumentDefinition<Schema["commands"][Name]>;
|
|
106
|
+
type OptionDefinition<Name extends string> = {
|
|
107
|
+
name: Name | readonly [Name, ...string[]];
|
|
108
|
+
description?: string;
|
|
109
|
+
argument?: Argument;
|
|
110
|
+
};
|
|
111
|
+
/** Generates a separate configuration for each option, preserving the association between its name and command scope. */
|
|
112
|
+
type OptionDefinitions<Options, Scope> = { [Name in Extract<keyof Options, string>]: OptionDefinition<Name> & Scope; }[Extract<keyof Options, string>];
|
|
113
|
+
type ScopedOptionDefinitions<Schema extends EriiSchema<Schema>> = { [Name in CommandName<Schema>]: OptionDefinitions<ScopedOptions<Schema, Name>, {
|
|
114
|
+
command: Name | CommandAliases<Schema["commands"][Name]>;
|
|
115
|
+
}>; }[CommandName<Schema>];
|
|
116
|
+
/** addOption configuration: common options omit command; scoped options must specify their owning command or alias. */
|
|
117
|
+
type TypedOption<Schema extends EriiSchema<Schema>> = OptionDefinitions<CommonOptions<Schema>, {
|
|
118
|
+
command?: never;
|
|
119
|
+
}> | ScopedOptionDefinitions<Schema>;
|
|
120
|
+
/** Internal lookup result for Erii.getArgument; the method signature separately accounts for missing values. */
|
|
121
|
+
type LookupArgument<Schema extends EriiSchema<Schema>, Lookup extends CommandLookup<Schema>> = CommandArgument<Schema["commands"][ResolveCommand<Schema, Lookup>]>;
|
|
46
122
|
//#endregion
|
|
47
123
|
//#region src/erii.d.ts
|
|
48
|
-
export declare class Erii {
|
|
124
|
+
export declare class Erii<S extends EriiSchema<S> = never> {
|
|
49
125
|
rawArguments: string[];
|
|
50
126
|
parsedArguments: ParsedArguments;
|
|
51
127
|
private version;
|
|
52
128
|
private name;
|
|
53
|
-
commands
|
|
54
|
-
commonOptions
|
|
129
|
+
private commands;
|
|
130
|
+
private commonOptions;
|
|
55
131
|
validator: typeof validator;
|
|
56
132
|
alwaysHandler?: LifecycleHandler;
|
|
57
133
|
defaultHandler?: LifecycleHandler;
|
|
58
|
-
constructor();
|
|
134
|
+
constructor(..._schema: [S] extends [never] ? [schema: never] : []);
|
|
59
135
|
/**
|
|
60
|
-
*
|
|
136
|
+
* Binds a command handler.
|
|
61
137
|
* @param config
|
|
62
138
|
* @param handler
|
|
63
139
|
*/
|
|
64
|
-
bind(config:
|
|
140
|
+
bind<K extends CommandName<S>>(config: TypedCommand<S, K>, handler: TypedCommandHandler<S, NoInfer<K>>): void;
|
|
141
|
+
private bindCommand;
|
|
65
142
|
/**
|
|
66
|
-
*
|
|
143
|
+
* Registers a handler that runs on every start.
|
|
67
144
|
* @param handler
|
|
68
145
|
*/
|
|
69
146
|
always(handler: LifecycleHandler): void;
|
|
70
147
|
default(handler: LifecycleHandler): void;
|
|
71
148
|
/**
|
|
72
|
-
*
|
|
149
|
+
* Registers an option.
|
|
73
150
|
* @param config
|
|
74
151
|
*/
|
|
75
|
-
addOption(config:
|
|
152
|
+
addOption(config: TypedOption<S>): void;
|
|
153
|
+
private registerOption;
|
|
76
154
|
private commandCtx;
|
|
77
155
|
/**
|
|
78
|
-
*
|
|
156
|
+
* Sets the CLI metadata.
|
|
79
157
|
* @param metaInfo
|
|
80
158
|
*/
|
|
81
159
|
setMetaInfo({ version, name }?: MetaInfo): void;
|
|
82
160
|
/**
|
|
83
|
-
*
|
|
161
|
+
* Displays help information.
|
|
84
162
|
*/
|
|
85
|
-
showHelp(command?:
|
|
163
|
+
showHelp(command?: CommandLookup<S>): void;
|
|
86
164
|
/**
|
|
87
|
-
*
|
|
165
|
+
* Displays the version number.
|
|
88
166
|
*/
|
|
89
167
|
showVersion(): void;
|
|
90
168
|
/**
|
|
91
|
-
*
|
|
169
|
+
* Starts command dispatch.
|
|
92
170
|
*/
|
|
93
171
|
start(): void;
|
|
94
172
|
/**
|
|
95
|
-
*
|
|
173
|
+
* Executes the command handler.
|
|
96
174
|
* @param command
|
|
97
175
|
*/
|
|
98
176
|
private exec;
|
|
99
177
|
validateArgument(argumentValue: ArgumentValue | undefined, argument?: Argument): boolean;
|
|
100
178
|
/**
|
|
101
|
-
*
|
|
179
|
+
* Gets the argument for a command.
|
|
102
180
|
* @param commandName
|
|
103
|
-
* @param followRedirect
|
|
181
|
+
* @param followRedirect Whether to follow alias redirects.
|
|
104
182
|
*/
|
|
105
|
-
getArgument(commandName:
|
|
183
|
+
getArgument<N extends CommandLookup<S>>(commandName: N, followRedirect?: boolean): LookupArgument<S, N> | undefined;
|
|
184
|
+
private readArgument;
|
|
106
185
|
private findArgument;
|
|
107
186
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
187
|
+
* Starts command dispatch.
|
|
188
|
+
* Erii is awake!
|
|
110
189
|
*/
|
|
111
190
|
okite(): void;
|
|
112
191
|
}
|
|
113
192
|
//#endregion
|
|
114
|
-
|
|
115
|
-
declare const _default: Erii;
|
|
116
|
-
//#endregion
|
|
117
|
-
export { type Argument, type ArgumentValidator, type ArgumentValue, type Command, type CommandCtx, type CommandHandler, type CommandMap, type CommandOptions, type LifecycleHandler, type MetaInfo, type Option, type ParsedArguments, type ValidatorName, _default as default };
|
|
193
|
+
export { type Argument, type ArgumentValidator, type ArgumentValue, Erii as default, type EriiSchema, type LifecycleHandler, type MetaInfo, type TypedCommand, type TypedCommandCtx, type TypedCommandHandler, type TypedCommandOptions, type TypedOption, type ValidatorName };
|
package/dist/index.mjs
CHANGED
|
@@ -60,18 +60,16 @@ var Erii = class {
|
|
|
60
60
|
validator;
|
|
61
61
|
alwaysHandler;
|
|
62
62
|
defaultHandler;
|
|
63
|
-
constructor() {
|
|
63
|
+
constructor(..._schema) {
|
|
64
64
|
this.rawArguments = process.argv.slice(2);
|
|
65
65
|
this.parsedArguments = yargs(process.argv.slice(2), { configuration: { "boolean-negation": false } });
|
|
66
66
|
this.validator = validator;
|
|
67
67
|
for (const key of Object.keys(this.parsedArguments)) if (key !== "_" && !this.rawArguments.includes("--" + key) && !this.rawArguments.includes("-" + key)) delete this.parsedArguments[key];
|
|
68
68
|
}
|
|
69
|
-
/**
|
|
70
|
-
* 绑定命令处理函数
|
|
71
|
-
* @param config
|
|
72
|
-
* @param handler
|
|
73
|
-
*/
|
|
74
69
|
bind(config, handler) {
|
|
70
|
+
this.bindCommand(config, handler);
|
|
71
|
+
}
|
|
72
|
+
bindCommand(config, handler) {
|
|
75
73
|
if (config.name === void 0) return console.error(chalk.red("Invalid command binding, ignored."));
|
|
76
74
|
const { name, description, argument } = config;
|
|
77
75
|
const [mainCommand, ...aliases] = Array.isArray(name) ? name : [name];
|
|
@@ -93,7 +91,7 @@ var Erii = class {
|
|
|
93
91
|
};
|
|
94
92
|
}
|
|
95
93
|
/**
|
|
96
|
-
*
|
|
94
|
+
* Registers a handler that runs on every start.
|
|
97
95
|
* @param handler
|
|
98
96
|
*/
|
|
99
97
|
always(handler) {
|
|
@@ -102,11 +100,10 @@ var Erii = class {
|
|
|
102
100
|
default(handler) {
|
|
103
101
|
this.defaultHandler = handler;
|
|
104
102
|
}
|
|
105
|
-
/**
|
|
106
|
-
* 增加设置项
|
|
107
|
-
* @param config
|
|
108
|
-
*/
|
|
109
103
|
addOption(config) {
|
|
104
|
+
this.registerOption(config);
|
|
105
|
+
}
|
|
106
|
+
registerOption(config) {
|
|
110
107
|
config.name = Array.isArray(config.name) ? config.name : [config.name];
|
|
111
108
|
if (!config.command) this.commonOptions.push(config);
|
|
112
109
|
else {
|
|
@@ -125,12 +122,12 @@ var Erii = class {
|
|
|
125
122
|
this.showHelp();
|
|
126
123
|
},
|
|
127
124
|
getArgument: (commandName = command) => {
|
|
128
|
-
return this.
|
|
125
|
+
return this.readArgument(commandName);
|
|
129
126
|
}
|
|
130
127
|
};
|
|
131
128
|
}
|
|
132
129
|
/**
|
|
133
|
-
*
|
|
130
|
+
* Sets the CLI metadata.
|
|
134
131
|
* @param metaInfo
|
|
135
132
|
*/
|
|
136
133
|
setMetaInfo({ version = "", name = "" } = {}) {
|
|
@@ -138,20 +135,20 @@ var Erii = class {
|
|
|
138
135
|
this.name = name;
|
|
139
136
|
}
|
|
140
137
|
/**
|
|
141
|
-
*
|
|
138
|
+
* Displays help information.
|
|
142
139
|
*/
|
|
143
140
|
showHelp(command) {
|
|
144
141
|
this.showVersion();
|
|
145
142
|
renderHelp(this.commands, this.commonOptions);
|
|
146
143
|
}
|
|
147
144
|
/**
|
|
148
|
-
*
|
|
145
|
+
* Displays the version number.
|
|
149
146
|
*/
|
|
150
147
|
showVersion() {
|
|
151
148
|
console.log(`${this.name} / ${this.version}`);
|
|
152
149
|
}
|
|
153
150
|
/**
|
|
154
|
-
*
|
|
151
|
+
* Starts command dispatch.
|
|
155
152
|
*/
|
|
156
153
|
start() {
|
|
157
154
|
if (this.alwaysHandler) this.alwaysHandler();
|
|
@@ -162,7 +159,7 @@ var Erii = class {
|
|
|
162
159
|
for (const key of this.parsedArguments["_"]) if (key in this.commands) this.exec(String(key));
|
|
163
160
|
}
|
|
164
161
|
/**
|
|
165
|
-
*
|
|
162
|
+
* Executes the command handler.
|
|
166
163
|
* @param command
|
|
167
164
|
*/
|
|
168
165
|
exec(command) {
|
|
@@ -207,12 +204,10 @@ var Erii = class {
|
|
|
207
204
|
console.log(chalk.red(message));
|
|
208
205
|
});
|
|
209
206
|
}
|
|
210
|
-
/**
|
|
211
|
-
* 获得命令的参数
|
|
212
|
-
* @param commandName
|
|
213
|
-
* @param followRedirect 是否遵循重定向
|
|
214
|
-
*/
|
|
215
207
|
getArgument(commandName, followRedirect = true) {
|
|
208
|
+
return this.readArgument(commandName, followRedirect);
|
|
209
|
+
}
|
|
210
|
+
readArgument(commandName, followRedirect = true) {
|
|
216
211
|
const value = this.findArgument(commandName, followRedirect);
|
|
217
212
|
if (value === void 0) console.error(chalk.red(`Command ${commandName} not found.`));
|
|
218
213
|
return value;
|
|
@@ -225,8 +220,8 @@ var Erii = class {
|
|
|
225
220
|
for (const alias of command.alias ?? []) if (Object.hasOwn(this.parsedArguments, alias)) return this.parsedArguments[alias];
|
|
226
221
|
}
|
|
227
222
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
223
|
+
* Starts command dispatch.
|
|
224
|
+
* Erii is awake!
|
|
230
225
|
*/
|
|
231
226
|
okite() {
|
|
232
227
|
return this.start();
|
|
@@ -234,6 +229,6 @@ var Erii = class {
|
|
|
234
229
|
};
|
|
235
230
|
//#endregion
|
|
236
231
|
//#region src/index.ts
|
|
237
|
-
var src_default =
|
|
232
|
+
var src_default = Erii;
|
|
238
233
|
//#endregion
|
|
239
234
|
export { Erii, src_default as default };
|
package/package.json
CHANGED
|
@@ -1,55 +1,58 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
2
|
+
"name": "erii",
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.mts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"format": "prettier --write .",
|
|
9
|
+
"format:check": "prettier --check .",
|
|
10
|
+
"build": "tsdown",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "npm run build && npm run typecheck && node --test test/*.test.mjs",
|
|
13
|
+
"test:coverage": "npm run build && npm run typecheck && node --test --experimental-test-coverage --test-coverage-include=dist/index.mjs --test-coverage-include=src/utils/convert.ts --test-coverage-lines=100 --test-coverage-functions=100 --test-coverage-branches=95 test/*.test.mjs",
|
|
14
|
+
"prepack": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Last-Order/erii.git"
|
|
19
|
+
},
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Last-Order/erii/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/Last-Order/erii#readme",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@types/validator": "^13.15.10",
|
|
28
|
+
"chalk": "^6.0.0",
|
|
29
|
+
"cli-color": "^2.0.0",
|
|
30
|
+
"clui": "^0.3.6",
|
|
31
|
+
"validator": "^13.15.35",
|
|
32
|
+
"yargs-parser": "^21.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/cli-color": "^2.0.6",
|
|
36
|
+
"@types/clui": "^0.3.5",
|
|
37
|
+
"@types/node": "^24.13.5",
|
|
38
|
+
"@types/yargs-parser": "^21.0.3",
|
|
39
|
+
"prettier": "3.9.7",
|
|
40
|
+
"tsdown": "^0.23.0",
|
|
41
|
+
"typescript": "^7.0.2"
|
|
42
|
+
},
|
|
43
|
+
"type": "module",
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.mts",
|
|
47
|
+
"import": "./dist/index.mjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"readme.md",
|
|
53
|
+
"logo.png"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.18.0"
|
|
45
57
|
}
|
|
46
|
-
},
|
|
47
|
-
"files": [
|
|
48
|
-
"dist",
|
|
49
|
-
"readme.md",
|
|
50
|
-
"logo.png"
|
|
51
|
-
],
|
|
52
|
-
"engines": {
|
|
53
|
-
"node": ">=22.18.0"
|
|
54
|
-
}
|
|
55
58
|
}
|
package/readme.md
CHANGED
|
@@ -2,166 +2,144 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
[](https://badge.fury.io/js/erii)
|
|
6
|
-
|
|
7
5
|
## Installation
|
|
8
|
-
`npm install erii --save`
|
|
9
|
-
This package is ESM-only and requires Node.js 22.18 or later. Use `import` from an ESM project (`"type": "module"` in package.json, or an `.mjs` file).
|
|
10
|
-
|
|
11
|
-
## Development
|
|
12
|
-
|
|
13
|
-
- `npm run build`: bundle ESM and TypeScript declarations with tsdown.
|
|
14
|
-
- `npm run typecheck`: check source and consumer types in strict mode.
|
|
15
|
-
- `npm test`: build, check types, and run regression tests.
|
|
16
|
-
- `npm run test:coverage`: run the same checks with coverage gates (100% lines/functions, 95% branches).
|
|
17
|
-
|
|
18
|
-
See [test/README.md](test/README.md) for the test scenario matrix. Runtime tests exercise the built ESM package; utility tests also cover source functions that are removed from the bundle when unused.
|
|
19
|
-
|
|
20
|
-
## TypeScript
|
|
21
|
-
|
|
22
|
-
The package exports `Command`, `Option`, `Argument`, `CommandCtx`, `CommandHandler`, `CommandOptions`, `ArgumentValue`, `ArgumentValidator`, `ValidatorName`, `ParsedArguments`, and `MetaInfo` types. Handler options are always provided; individual options and `getArgument()` can be `undefined`. Values retain the parser's strings, numbers, booleans, arrays, and nested objects, so narrow them before use. String validators accept only validator.js methods callable with one string and returning a boolean.
|
|
23
|
-
|
|
24
|
-
## Usage
|
|
25
6
|
|
|
26
|
-
|
|
27
|
-
import Erii from 'erii';
|
|
7
|
+
Use Node.js 22.18 or later and set `"type": "module"` in your `package.json`.
|
|
28
8
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
name: 'example'
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// Bind commands
|
|
35
|
-
Erii.bind({
|
|
36
|
-
name: ['help', 'h'], // `h` will be set as an alias
|
|
37
|
-
description: 'Show Help', // command description
|
|
38
|
-
argument: {
|
|
39
|
-
name: 'command',
|
|
40
|
-
description: 'query help of a specified command'
|
|
41
|
-
}
|
|
42
|
-
}, (ctx, options) => {
|
|
43
|
-
ctx.showHelp(); // show help text
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// add options for `help` command
|
|
47
|
-
Erii.addOption({
|
|
48
|
-
name: ['verbose', 'debug'],
|
|
49
|
-
command: 'help', // bind to command
|
|
50
|
-
description: 'debug output', // option description
|
|
51
|
-
argument: { // definition of option argument
|
|
52
|
-
name: 'level',
|
|
53
|
-
description: 'level of debug output'
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
Erii.addOption({
|
|
58
|
-
name: ['test'],
|
|
59
|
-
// without binding to a specified command,
|
|
60
|
-
// this option will be set as a common option.
|
|
61
|
-
description: 'show test information',
|
|
62
|
-
argument: {
|
|
63
|
-
name: 'test-argument',
|
|
64
|
-
description: 'test argument'
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
Erii.start(); // don't forget to start Erii.
|
|
9
|
+
```sh
|
|
10
|
+
npm install erii
|
|
69
11
|
```
|
|
70
12
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Call with
|
|
74
|
-
|
|
75
|
-
`node index.js --help xxx --debug 1`
|
|
13
|
+
## Usage
|
|
76
14
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Erii
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
15
|
+
Define your commands and options, register handlers, then call `start()`.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import Erii from "erii";
|
|
19
|
+
|
|
20
|
+
type CLI = {
|
|
21
|
+
commonOptions: {
|
|
22
|
+
verbose?: boolean;
|
|
23
|
+
};
|
|
24
|
+
commands: {
|
|
25
|
+
build: {
|
|
26
|
+
aliases: "b";
|
|
27
|
+
argument: string;
|
|
28
|
+
options: {
|
|
29
|
+
outDir?: string;
|
|
30
|
+
minify?: boolean;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
serve: {
|
|
34
|
+
options: {
|
|
35
|
+
port?: number;
|
|
36
|
+
mode?: "dev" | "prod";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const cli = new Erii<CLI>();
|
|
43
|
+
cli.setMetaInfo({ name: "example", version: "1.0.0" });
|
|
44
|
+
|
|
45
|
+
cli.bind(
|
|
46
|
+
{
|
|
47
|
+
name: ["build", "b"],
|
|
48
|
+
description: "Build the project",
|
|
49
|
+
argument: { name: "path", description: "Source directory" },
|
|
50
|
+
},
|
|
51
|
+
(ctx, options) => {
|
|
52
|
+
const path = ctx.getArgument(); // string | undefined
|
|
53
|
+
const output = options.outDir; // string | undefined
|
|
54
|
+
const verbose = options.verbose; // boolean | undefined
|
|
55
|
+
console.log({ path, output, verbose });
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
cli.bind({ name: "serve" }, (_, options) => {
|
|
60
|
+
console.log(options.port, options.mode);
|
|
91
61
|
});
|
|
92
62
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
63
|
+
cli.addOption({ name: ["verbose", "v"] });
|
|
64
|
+
cli.addOption({ command: "build", name: ["outDir", "out-dir", "o"] });
|
|
65
|
+
cli.addOption({ command: "b", name: "minify" });
|
|
66
|
+
cli.addOption({ command: "serve", name: "port" });
|
|
67
|
+
cli.addOption({
|
|
68
|
+
command: "serve",
|
|
69
|
+
name: "mode",
|
|
96
70
|
argument: {
|
|
97
|
-
name:
|
|
98
|
-
description:
|
|
99
|
-
|
|
71
|
+
name: "mode",
|
|
72
|
+
description: "Server mode",
|
|
73
|
+
validate: (value) => value === "dev" || value === "prod",
|
|
74
|
+
},
|
|
100
75
|
});
|
|
101
76
|
|
|
102
|
-
|
|
77
|
+
cli.start(); // okite() is also available.
|
|
103
78
|
```
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
80
|
+
```sh
|
|
81
|
+
node index.js --build src --out-dir dist --verbose
|
|
82
|
+
node index.js serve --port 3000 --mode dev
|
|
107
83
|
```
|
|
108
|
-
example / 0.0.1
|
|
109
|
-
|
|
110
|
-
Help:
|
|
111
|
-
Commands Description Alias
|
|
112
84
|
|
|
113
|
-
|
|
114
|
-
<command> query help of a specified comm
|
|
85
|
+
## Aliases
|
|
115
86
|
|
|
116
|
-
|
|
87
|
+
Put the primary name first and aliases after it. Read options using the primary name, such as `options.outDir`.
|
|
117
88
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<level> level of verbose output
|
|
89
|
+
```sh
|
|
90
|
+
node index.js -b src -o dist -v
|
|
121
91
|
```
|
|
122
92
|
|
|
123
|
-
|
|
93
|
+
## Reading arguments
|
|
124
94
|
|
|
125
|
-
|
|
95
|
+
Use `ctx.getArgument()` inside a handler, or look up a command by name:
|
|
126
96
|
|
|
127
|
-
|
|
97
|
+
```ts
|
|
98
|
+
const path = cli.getArgument("build");
|
|
99
|
+
if (path !== undefined) {
|
|
100
|
+
console.log(path);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
128
103
|
|
|
129
|
-
|
|
104
|
+
## Validation
|
|
130
105
|
|
|
131
|
-
|
|
106
|
+
Use `argument.validate` to check values with a validator.js method:
|
|
132
107
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
argument: {
|
|
138
|
-
name: 'level',
|
|
139
|
-
description: 'level of verbose output',
|
|
140
|
-
validate: 'isInt'
|
|
141
|
-
}
|
|
108
|
+
```ts
|
|
109
|
+
cli.addOption({
|
|
110
|
+
command: "serve",
|
|
111
|
+
name: "port",
|
|
112
|
+
argument: { name: "port", description: "Listening port", validate: "isInt" },
|
|
142
113
|
});
|
|
143
114
|
```
|
|
144
115
|
|
|
145
|
-
|
|
116
|
+
To use a custom validator, return `true` for accepted input:
|
|
146
117
|
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
118
|
+
```ts
|
|
119
|
+
cli.addOption({
|
|
120
|
+
command: "serve",
|
|
121
|
+
name: "port",
|
|
151
122
|
argument: {
|
|
152
|
-
name:
|
|
153
|
-
description:
|
|
154
|
-
validate: (value) =>
|
|
155
|
-
}
|
|
123
|
+
name: "port",
|
|
124
|
+
description: "Listening port",
|
|
125
|
+
validate: (value) => typeof value === "number" && value > 0 && value <= 65535,
|
|
126
|
+
},
|
|
156
127
|
});
|
|
157
128
|
```
|
|
158
129
|
|
|
159
|
-
|
|
130
|
+
## Help and lifecycle
|
|
160
131
|
|
|
161
|
-
|
|
132
|
+
Register lifecycle handlers before calling `start()`:
|
|
162
133
|
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
134
|
+
```ts
|
|
135
|
+
cli.always(() => {
|
|
136
|
+
console.log("Starting");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
cli.default(() => {
|
|
140
|
+
cli.showHelp(); // Show help when no arguments are supplied.
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
cli.showVersion();
|
|
144
|
+
cli.start();
|
|
167
145
|
```
|