erii 2.0.6 → 3.0.0-beta.1
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 +117 -0
- package/dist/index.mjs +239 -0
- package/package.json +55 -30
- package/readme.md +167 -152
- package/dist/index.d.ts +0 -108
- package/dist/index.js +0 -349
- package/dist/index.js.map +0 -1
- package/dist/utils/convert.d.ts +0 -12
- package/dist/utils/convert.js +0 -45
- package/dist/utils/convert.js.map +0 -1
- package/src/index.ts +0 -394
- package/src/utils/convert.ts +0 -44
- package/tsconfig.json +0 -14
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import validator from "validator";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
type ArgumentValue = string | number | boolean | ArgumentValue[] | {
|
|
4
|
+
[key: string]: ArgumentValue;
|
|
5
|
+
};
|
|
6
|
+
type CommandOptions = Record<string, ArgumentValue | undefined>;
|
|
7
|
+
interface ParsedArguments extends CommandOptions {
|
|
8
|
+
_: Array<string | number>;
|
|
9
|
+
}
|
|
10
|
+
type CommandHandler = (ctx: CommandCtx, options: CommandOptions) => unknown;
|
|
11
|
+
type LifecycleHandler = () => unknown;
|
|
12
|
+
type ArgumentValidator = (value: ArgumentValue | undefined, logger: (message: string) => void) => boolean;
|
|
13
|
+
type ValidatorName = { [K in keyof typeof validator]: typeof validator[K] extends ((value: string) => boolean) ? K : never; }[keyof typeof validator];
|
|
14
|
+
interface MetaInfo {
|
|
15
|
+
version?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
18
|
+
interface Command {
|
|
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
|
+
}
|
|
41
|
+
interface Argument {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
validate?: ValidatorName | ArgumentValidator;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/erii.d.ts
|
|
48
|
+
export declare class Erii {
|
|
49
|
+
rawArguments: string[];
|
|
50
|
+
parsedArguments: ParsedArguments;
|
|
51
|
+
private version;
|
|
52
|
+
private name;
|
|
53
|
+
commands: CommandMap;
|
|
54
|
+
commonOptions: Option[];
|
|
55
|
+
validator: typeof validator;
|
|
56
|
+
alwaysHandler?: LifecycleHandler;
|
|
57
|
+
defaultHandler?: LifecycleHandler;
|
|
58
|
+
constructor();
|
|
59
|
+
/**
|
|
60
|
+
* 绑定命令处理函数
|
|
61
|
+
* @param config
|
|
62
|
+
* @param handler
|
|
63
|
+
*/
|
|
64
|
+
bind(config: Command, handler: CommandHandler): void;
|
|
65
|
+
/**
|
|
66
|
+
* 总是执行
|
|
67
|
+
* @param handler
|
|
68
|
+
*/
|
|
69
|
+
always(handler: LifecycleHandler): void;
|
|
70
|
+
default(handler: LifecycleHandler): void;
|
|
71
|
+
/**
|
|
72
|
+
* 增加设置项
|
|
73
|
+
* @param config
|
|
74
|
+
*/
|
|
75
|
+
addOption(config: Option): void;
|
|
76
|
+
private commandCtx;
|
|
77
|
+
/**
|
|
78
|
+
* 设定基础信息
|
|
79
|
+
* @param metaInfo
|
|
80
|
+
*/
|
|
81
|
+
setMetaInfo({ version, name }?: MetaInfo): void;
|
|
82
|
+
/**
|
|
83
|
+
* 显示帮助信息
|
|
84
|
+
*/
|
|
85
|
+
showHelp(command?: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* 显示版本号
|
|
88
|
+
*/
|
|
89
|
+
showVersion(): void;
|
|
90
|
+
/**
|
|
91
|
+
* 启动
|
|
92
|
+
*/
|
|
93
|
+
start(): void;
|
|
94
|
+
/**
|
|
95
|
+
* 执行命令担当函数
|
|
96
|
+
* @param command
|
|
97
|
+
*/
|
|
98
|
+
private exec;
|
|
99
|
+
validateArgument(argumentValue: ArgumentValue | undefined, argument?: Argument): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* 获得命令的参数
|
|
102
|
+
* @param commandName
|
|
103
|
+
* @param followRedirect 是否遵循重定向
|
|
104
|
+
*/
|
|
105
|
+
getArgument(commandName: string, followRedirect?: boolean): ArgumentValue | undefined;
|
|
106
|
+
private findArgument;
|
|
107
|
+
/**
|
|
108
|
+
* 启动
|
|
109
|
+
* エリイ 起きてます❤
|
|
110
|
+
*/
|
|
111
|
+
okite(): void;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/index.d.ts
|
|
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 };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import yargs from "yargs-parser";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import validator from "validator";
|
|
4
|
+
import CLI from "clui";
|
|
5
|
+
import clc from "cli-color";
|
|
6
|
+
//#region src/help.ts
|
|
7
|
+
/** Render the help body; the caller prints the version first. */
|
|
8
|
+
function renderHelp(commands, commonOptions) {
|
|
9
|
+
console.log("\nHelp:");
|
|
10
|
+
const Line = CLI.Line;
|
|
11
|
+
new Line().padding(5).column("Commands", 30, [clc.cyan]).column("Description", 30, [clc.cyan]).column("Alias", 20, [clc.cyan]).output();
|
|
12
|
+
new Line().output();
|
|
13
|
+
for (const [key, command] of Object.entries(commands)) {
|
|
14
|
+
if (!command || command.redirect) continue;
|
|
15
|
+
const commandText = "--" + key + (command.argument ? ` <${command.argument.name}>` : "");
|
|
16
|
+
const aliasText = (command.alias ?? []).map((alias) => alias.length === 1 ? `-${alias}` : `--${alias}`).join(" / ");
|
|
17
|
+
new Line().padding(5).column(commandText, 30).column(command.description ?? "", 30).column(aliasText, 20).output();
|
|
18
|
+
if (command.argument) new Line().padding(9).column(`<${command.argument.name}>`, 26).column(command.argument.description, 30).output();
|
|
19
|
+
for (const option of command.options ?? []) renderOption(option, 9, 26);
|
|
20
|
+
}
|
|
21
|
+
new Line().output();
|
|
22
|
+
if (commonOptions.length > 0) {
|
|
23
|
+
console.log("Options:\n");
|
|
24
|
+
new Line().padding(5).column("Options", 30, [clc.cyan]).column("Description", 30, [clc.cyan]).output();
|
|
25
|
+
for (const option of commonOptions) renderOption(option, 5, 30);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Scoped and common options share formatting, with different indentation. */
|
|
29
|
+
function renderOption(option, padding, width) {
|
|
30
|
+
const text = `--${(Array.isArray(option.name) ? option.name : [option.name]).join(", ")}` + (option.argument ? ` <${option.argument.name}>` : "");
|
|
31
|
+
new CLI.Line().padding(padding).column(text, width).column(option.description || "", 30).output();
|
|
32
|
+
if (option.argument) new CLI.Line().padding(padding + 4).column(`<${option.argument.name}>`, width - 4).column(option.argument.description || "", 30).output();
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/utils/convert.ts
|
|
36
|
+
function toKebabCase(str) {
|
|
37
|
+
return (str[0]?.toLowerCase() ?? "") + str.slice(1).replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
38
|
+
}
|
|
39
|
+
function createCamelProxifiedObject(before) {
|
|
40
|
+
return new Proxy(before, {
|
|
41
|
+
get(target, key, receiver) {
|
|
42
|
+
const value = Reflect.get(target, key, receiver);
|
|
43
|
+
return typeof key === "string" ? value ?? Reflect.get(target, toKebabCase(key), receiver) : value;
|
|
44
|
+
},
|
|
45
|
+
set(target, key, value, receiver) {
|
|
46
|
+
if (typeof key === "string" && !key.includes("-") && Reflect.get(target, key, receiver) == null && Reflect.get(target, toKebabCase(key), receiver) != null) return Reflect.set(target, toKebabCase(key), value, receiver);
|
|
47
|
+
return Reflect.set(target, key, value, receiver);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/erii.ts
|
|
53
|
+
var Erii = class {
|
|
54
|
+
rawArguments;
|
|
55
|
+
parsedArguments;
|
|
56
|
+
version = "1.0.0";
|
|
57
|
+
name = "Erii";
|
|
58
|
+
commands = {};
|
|
59
|
+
commonOptions = [];
|
|
60
|
+
validator;
|
|
61
|
+
alwaysHandler;
|
|
62
|
+
defaultHandler;
|
|
63
|
+
constructor() {
|
|
64
|
+
this.rawArguments = process.argv.slice(2);
|
|
65
|
+
this.parsedArguments = yargs(process.argv.slice(2), { configuration: { "boolean-negation": false } });
|
|
66
|
+
this.validator = validator;
|
|
67
|
+
for (const key of Object.keys(this.parsedArguments)) if (key !== "_" && !this.rawArguments.includes("--" + key) && !this.rawArguments.includes("-" + key)) delete this.parsedArguments[key];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 绑定命令处理函数
|
|
71
|
+
* @param config
|
|
72
|
+
* @param handler
|
|
73
|
+
*/
|
|
74
|
+
bind(config, handler) {
|
|
75
|
+
if (config.name === void 0) return console.error(chalk.red("Invalid command binding, ignored."));
|
|
76
|
+
const { name, description, argument } = config;
|
|
77
|
+
const [mainCommand, ...aliases] = Array.isArray(name) ? name : [name];
|
|
78
|
+
if (!mainCommand) {
|
|
79
|
+
console.error(chalk.red("Invalid command binding, ignored."));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.commands[mainCommand] = {
|
|
83
|
+
name: mainCommand,
|
|
84
|
+
description,
|
|
85
|
+
argument,
|
|
86
|
+
alias: aliases,
|
|
87
|
+
options: [],
|
|
88
|
+
handler
|
|
89
|
+
};
|
|
90
|
+
if (Array.isArray(name)) for (const alias of aliases) this.commands[alias] = {
|
|
91
|
+
name: alias,
|
|
92
|
+
redirect: mainCommand
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 总是执行
|
|
97
|
+
* @param handler
|
|
98
|
+
*/
|
|
99
|
+
always(handler) {
|
|
100
|
+
this.alwaysHandler = handler;
|
|
101
|
+
}
|
|
102
|
+
default(handler) {
|
|
103
|
+
this.defaultHandler = handler;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 增加设置项
|
|
107
|
+
* @param config
|
|
108
|
+
*/
|
|
109
|
+
addOption(config) {
|
|
110
|
+
config.name = Array.isArray(config.name) ? config.name : [config.name];
|
|
111
|
+
if (!config.command) this.commonOptions.push(config);
|
|
112
|
+
else {
|
|
113
|
+
if (!(config.command in this.commands)) return console.error(chalk.red(`Command for option [${config.name.join(", ")}] not found, ignored.`));
|
|
114
|
+
const command = this.commands[config.command];
|
|
115
|
+
const target = command?.redirect ? this.commands[command.redirect] : command;
|
|
116
|
+
if (target) (target.options ??= []).push(config);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
commandCtx(command) {
|
|
120
|
+
return {
|
|
121
|
+
showVersion: () => {
|
|
122
|
+
this.showVersion();
|
|
123
|
+
},
|
|
124
|
+
showHelp: () => {
|
|
125
|
+
this.showHelp();
|
|
126
|
+
},
|
|
127
|
+
getArgument: (commandName = command) => {
|
|
128
|
+
return this.getArgument(commandName);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 设定基础信息
|
|
134
|
+
* @param metaInfo
|
|
135
|
+
*/
|
|
136
|
+
setMetaInfo({ version = "", name = "" } = {}) {
|
|
137
|
+
this.version = version;
|
|
138
|
+
this.name = name;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 显示帮助信息
|
|
142
|
+
*/
|
|
143
|
+
showHelp(command) {
|
|
144
|
+
this.showVersion();
|
|
145
|
+
renderHelp(this.commands, this.commonOptions);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 显示版本号
|
|
149
|
+
*/
|
|
150
|
+
showVersion() {
|
|
151
|
+
console.log(`${this.name} / ${this.version}`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 启动
|
|
155
|
+
*/
|
|
156
|
+
start() {
|
|
157
|
+
if (this.alwaysHandler) this.alwaysHandler();
|
|
158
|
+
if (this.defaultHandler) {
|
|
159
|
+
if (this.parsedArguments["_"].length === 0 && Object.keys(this.parsedArguments).length === 1) this.defaultHandler();
|
|
160
|
+
}
|
|
161
|
+
for (const key of Object.keys(this.parsedArguments)) if (key in this.commands) this.exec(key);
|
|
162
|
+
for (const key of this.parsedArguments["_"]) if (key in this.commands) this.exec(String(key));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 执行命令担当函数
|
|
166
|
+
* @param command
|
|
167
|
+
*/
|
|
168
|
+
exec(command) {
|
|
169
|
+
const boundCommand = this.commands[command];
|
|
170
|
+
if (!boundCommand) return;
|
|
171
|
+
if (boundCommand.redirect) {
|
|
172
|
+
this.exec(boundCommand.redirect);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const options = {};
|
|
176
|
+
for (const option of [...boundCommand.options ?? [], ...this.commonOptions]) {
|
|
177
|
+
const names = Array.isArray(option.name) ? option.name : [option.name];
|
|
178
|
+
const primaryName = names[0];
|
|
179
|
+
if (!primaryName) continue;
|
|
180
|
+
for (const name of names) {
|
|
181
|
+
if (!(name in this.parsedArguments)) continue;
|
|
182
|
+
if (this.validateArgument(this.parsedArguments[name], option.argument)) options[primaryName] = this.parsedArguments[name];
|
|
183
|
+
else {
|
|
184
|
+
console.error(chalk.red(`Argument validation failed for option '${name}'.`));
|
|
185
|
+
if (typeof option.argument?.validate === "string") console.error(chalk.red(`<${option.argument.name}> should be a/an ${option.argument.validate.slice(2)}.`));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const argumentValue = this.findArgument(command);
|
|
190
|
+
if (this.validateArgument(argumentValue, boundCommand.argument)) boundCommand.handler?.(this.commandCtx(command), createCamelProxifiedObject(options));
|
|
191
|
+
else {
|
|
192
|
+
console.error(chalk.red(`Argument validation failed for command ${command}`));
|
|
193
|
+
if (typeof boundCommand.argument?.validate === "string") console.error(chalk.red(`<${boundCommand.argument.name}> should be a/an ${boundCommand.argument.validate.slice(2)}.`));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
validateArgument(argumentValue, argument) {
|
|
197
|
+
if (!argument || !argument.validate) return true;
|
|
198
|
+
if (typeof argument.validate === "string") {
|
|
199
|
+
if (argument.validate in this.validator) {
|
|
200
|
+
if (argumentValue === void 0) return false;
|
|
201
|
+
return this.validator[argument.validate](String(argumentValue));
|
|
202
|
+
} else {
|
|
203
|
+
console.error(chalk.red(`Unknown validate method for ${argument.name}.`));
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
} else return argument.validate(argumentValue, (message) => {
|
|
207
|
+
console.log(chalk.red(message));
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 获得命令的参数
|
|
212
|
+
* @param commandName
|
|
213
|
+
* @param followRedirect 是否遵循重定向
|
|
214
|
+
*/
|
|
215
|
+
getArgument(commandName, followRedirect = true) {
|
|
216
|
+
const value = this.findArgument(commandName, followRedirect);
|
|
217
|
+
if (value === void 0) console.error(chalk.red(`Command ${commandName} not found.`));
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
findArgument(commandName, followRedirect = true) {
|
|
221
|
+
const command = this.commands[commandName];
|
|
222
|
+
if (!command) return void 0;
|
|
223
|
+
if (command.redirect && followRedirect) return this.findArgument(command.redirect);
|
|
224
|
+
if (commandName !== "_" && Object.hasOwn(this.parsedArguments, commandName)) return this.parsedArguments[commandName];
|
|
225
|
+
for (const alias of command.alias ?? []) if (Object.hasOwn(this.parsedArguments, alias)) return this.parsedArguments[alias];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 启动
|
|
229
|
+
* エリイ 起きてます❤
|
|
230
|
+
*/
|
|
231
|
+
okite() {
|
|
232
|
+
return this.start();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/index.ts
|
|
237
|
+
var src_default = new Erii();
|
|
238
|
+
//#endregion
|
|
239
|
+
export { Erii, src_default as default };
|
package/package.json
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "erii",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "./dist/index.
|
|
6
|
-
"types": "./dist/index.d.
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
},
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "erii",
|
|
3
|
+
"version": "3.0.0-beta.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.mts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsdown",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"test": "npm run build && npm run typecheck && node --test test/*.test.mjs",
|
|
11
|
+
"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",
|
|
12
|
+
"prepack": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/Last-Order/erii.git"
|
|
17
|
+
},
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/Last-Order/erii/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/Last-Order/erii#readme",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@types/validator": "^13.15.10",
|
|
26
|
+
"chalk": "^6.0.0",
|
|
27
|
+
"cli-color": "^2.0.0",
|
|
28
|
+
"clui": "^0.3.6",
|
|
29
|
+
"validator": "^13.15.35",
|
|
30
|
+
"yargs-parser": "^21.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/cli-color": "^2.0.6",
|
|
34
|
+
"@types/clui": "^0.3.5",
|
|
35
|
+
"@types/node": "^24.13.5",
|
|
36
|
+
"@types/yargs-parser": "^21.0.3",
|
|
37
|
+
"tsdown": "^0.23.0",
|
|
38
|
+
"typescript": "^7.0.2"
|
|
39
|
+
},
|
|
40
|
+
"type": "module",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.mts",
|
|
44
|
+
"import": "./dist/index.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"readme.md",
|
|
50
|
+
"logo.png"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22.18.0"
|
|
54
|
+
}
|
|
55
|
+
}
|