erii 2.0.6 → 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 +193 -0
- package/dist/index.mjs +234 -0
- package/package.json +58 -30
- package/readme.md +145 -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/src/index.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
const yargs = require('yargs-parser');
|
|
2
|
-
const CLI = require('clui'), clc = require('cli-color');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const validator = require('validator');
|
|
5
|
-
import { createCamelProxifiedObject } from "./utils/convert";
|
|
6
|
-
export interface Command {
|
|
7
|
-
name: string | string[];
|
|
8
|
-
description?: string;
|
|
9
|
-
argument?: Argument,
|
|
10
|
-
alias?: string[],
|
|
11
|
-
redirect?: string;
|
|
12
|
-
options?: Option[];
|
|
13
|
-
handler?: (ctx: CommandCtx, options?: object) => any
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface Option {
|
|
17
|
-
name: string | string[];
|
|
18
|
-
description?: string;
|
|
19
|
-
command?: string;
|
|
20
|
-
argument?: Argument,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface CommandMap {
|
|
24
|
-
[key: string]: Command;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export interface CommandCtx {
|
|
28
|
-
showVersion: () => void;
|
|
29
|
-
showHelp: () => void;
|
|
30
|
-
getArgument: (commandName?: string) => string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface Argument {
|
|
34
|
-
name: string;
|
|
35
|
-
description: string;
|
|
36
|
-
validate?: string | ((value: any, logger: (message: string) => void) => boolean);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class Erii {
|
|
40
|
-
rawArguments: string[];
|
|
41
|
-
parsedArguments: {
|
|
42
|
-
_?: string[],
|
|
43
|
-
[key: string]: string | string[];
|
|
44
|
-
} = {};
|
|
45
|
-
private version: string = "1.0.0";
|
|
46
|
-
private name: string = "Erii";
|
|
47
|
-
|
|
48
|
-
commands: CommandMap = {};
|
|
49
|
-
commonOptions: Option[] = [];
|
|
50
|
-
validator: any;
|
|
51
|
-
alwaysHandler: () => void;
|
|
52
|
-
defaultHandler: () => void;
|
|
53
|
-
|
|
54
|
-
constructor() {
|
|
55
|
-
this.rawArguments = process.argv.slice(2);
|
|
56
|
-
this.parsedArguments = yargs(process.argv.slice(2), {
|
|
57
|
-
configuration: {
|
|
58
|
-
"boolean-negation": false
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
this.validator = validator;
|
|
62
|
-
|
|
63
|
-
// 不响应-后第二个字符起的命令
|
|
64
|
-
for (const key of Object.keys(this.parsedArguments)) {
|
|
65
|
-
if (key !== "_" && (!this.rawArguments.includes('--' + key) && !this.rawArguments.includes('-' + key))) {
|
|
66
|
-
delete this.parsedArguments[key];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 绑定命令处理函数
|
|
73
|
-
* @param config
|
|
74
|
-
* @param handler
|
|
75
|
-
*/
|
|
76
|
-
bind(config: Command, handler: (ctx: CommandCtx, ...extraArguments) => any) {
|
|
77
|
-
if (config.name === undefined) {
|
|
78
|
-
return console.error(chalk.red("Invalid command binding, ignored."));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { name, description, argument } = config;
|
|
82
|
-
|
|
83
|
-
const mainCommand = Array.isArray(name) ? name.shift() : name;
|
|
84
|
-
|
|
85
|
-
this.commands[mainCommand] = {
|
|
86
|
-
name: mainCommand,
|
|
87
|
-
description,
|
|
88
|
-
argument,
|
|
89
|
-
alias: [],
|
|
90
|
-
options: [],
|
|
91
|
-
handler
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
if (Array.isArray(name)) {
|
|
95
|
-
for (const alias of name) {
|
|
96
|
-
this.commands[mainCommand].alias.push(alias);
|
|
97
|
-
this.commands[alias] = {
|
|
98
|
-
name: alias,
|
|
99
|
-
redirect: mainCommand
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 总是执行
|
|
107
|
-
* @param handler
|
|
108
|
-
*/
|
|
109
|
-
always(handler: () => any) {
|
|
110
|
-
this.alwaysHandler = handler;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
*
|
|
115
|
-
*/
|
|
116
|
-
default(handler: () => any) {
|
|
117
|
-
this.defaultHandler = handler;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 增加设置项
|
|
122
|
-
* @param config
|
|
123
|
-
*/
|
|
124
|
-
addOption(config: Option) {
|
|
125
|
-
config.name = Array.isArray(config.name) ? config.name : [config.name];
|
|
126
|
-
if (!config.command) {
|
|
127
|
-
this.commonOptions.push(config);
|
|
128
|
-
} else {
|
|
129
|
-
if (!(config.command in this.commands)){
|
|
130
|
-
return console.error(chalk.red(`Command for option [${config.name.join(', ')}] not found, ignored.`));
|
|
131
|
-
}
|
|
132
|
-
this.commands[config.command].options.push(config);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
*
|
|
138
|
-
* @param command
|
|
139
|
-
*/
|
|
140
|
-
private commandCtx(command: string): CommandCtx {
|
|
141
|
-
return {
|
|
142
|
-
showVersion: () => {
|
|
143
|
-
this.showVersion();
|
|
144
|
-
},
|
|
145
|
-
showHelp: () => {
|
|
146
|
-
this.showHelp();
|
|
147
|
-
},
|
|
148
|
-
getArgument: (commandName: string = command) => {
|
|
149
|
-
return this.getArgument(commandName);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* 设定基础信息
|
|
156
|
-
* @param metaInfo
|
|
157
|
-
*/
|
|
158
|
-
setMetaInfo({
|
|
159
|
-
version = "",
|
|
160
|
-
name = ""
|
|
161
|
-
} = {}) {
|
|
162
|
-
this.version = version;
|
|
163
|
-
this.name = name;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 显示帮助信息
|
|
168
|
-
*/
|
|
169
|
-
showHelp(command?: string) {
|
|
170
|
-
this.showVersion();
|
|
171
|
-
console.log('\nHelp:');
|
|
172
|
-
const Line = CLI.Line;
|
|
173
|
-
|
|
174
|
-
new Line()
|
|
175
|
-
.padding(5)
|
|
176
|
-
.column('Commands', 30, [clc.cyan])
|
|
177
|
-
.column('Description', 30, [clc.cyan])
|
|
178
|
-
.column('Alias', 20, [clc.cyan])
|
|
179
|
-
.output();
|
|
180
|
-
new Line().output();
|
|
181
|
-
for (const key of Object.keys(this.commands)) {
|
|
182
|
-
if (this.commands[key].redirect) {
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
let commandText = '--' + key;
|
|
186
|
-
let aliasText = '';
|
|
187
|
-
if (this.commands[key].argument) {
|
|
188
|
-
commandText += ` <${this.commands[key].argument.name}>`;
|
|
189
|
-
}
|
|
190
|
-
if (this.commands[key].alias.length > 0) {
|
|
191
|
-
const aliasWithPrefixes = this.commands[key].alias.map(alias => alias.length === 1 ? `-${alias}` : `--${alias}`);
|
|
192
|
-
aliasText += aliasWithPrefixes.join(' / ');
|
|
193
|
-
}
|
|
194
|
-
new Line()
|
|
195
|
-
.padding(5)
|
|
196
|
-
.column(commandText, 30)
|
|
197
|
-
.column(this.commands[key].description, 30)
|
|
198
|
-
.column(aliasText, 20)
|
|
199
|
-
.output();
|
|
200
|
-
if (this.commands[key].argument) {
|
|
201
|
-
// 参数说明
|
|
202
|
-
new Line()
|
|
203
|
-
.padding(9)
|
|
204
|
-
.column(`<${this.commands[key].argument.name}>`, 26)
|
|
205
|
-
.column(this.commands[key].argument.description, 30)
|
|
206
|
-
.output();
|
|
207
|
-
}
|
|
208
|
-
if (this.commands[key].options.length > 0) {
|
|
209
|
-
// 设置项说明
|
|
210
|
-
for (const option of this.commands[key].options) {
|
|
211
|
-
let optionsText = '';
|
|
212
|
-
const optionName = Array.isArray(option.name) ? option.name : [option.name];
|
|
213
|
-
if (option.argument) {
|
|
214
|
-
optionsText += `--${optionName.join(', ')} <${option.argument.name}>`;
|
|
215
|
-
} else {
|
|
216
|
-
optionsText += `--${optionName.join(', ')}`
|
|
217
|
-
};
|
|
218
|
-
new Line()
|
|
219
|
-
.padding(9)
|
|
220
|
-
.column(optionsText, 26)
|
|
221
|
-
.column(option.description || '')
|
|
222
|
-
.output();
|
|
223
|
-
if (option.argument) {
|
|
224
|
-
// 设置项参数说明
|
|
225
|
-
new Line()
|
|
226
|
-
.padding(13)
|
|
227
|
-
.column(`<${option.argument.name}>`, 22)
|
|
228
|
-
.column(option.argument.description || '')
|
|
229
|
-
.output();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
new Line().output();
|
|
235
|
-
if (this.commonOptions.length > 0) {
|
|
236
|
-
// 通用设置项说明
|
|
237
|
-
console.log('Options:\n');
|
|
238
|
-
new Line()
|
|
239
|
-
.padding(5)
|
|
240
|
-
.column('Options', 30, [clc.cyan])
|
|
241
|
-
.column('Description', 30, [clc.cyan])
|
|
242
|
-
.output();
|
|
243
|
-
for (const option of this.commonOptions) {
|
|
244
|
-
let optionsText = '';
|
|
245
|
-
const optionName = Array.isArray(option.name) ? option.name : [option.name];
|
|
246
|
-
if (option.argument) {
|
|
247
|
-
optionsText += `--${optionName.join(', ')} <${option.argument.name}>`;
|
|
248
|
-
} else {
|
|
249
|
-
optionsText += `--${optionName.join(', ')}`
|
|
250
|
-
};
|
|
251
|
-
new Line()
|
|
252
|
-
.padding(5)
|
|
253
|
-
.column(optionsText, 30)
|
|
254
|
-
.column(option.description || '')
|
|
255
|
-
.output();
|
|
256
|
-
if (option.argument) {
|
|
257
|
-
// 设置项参数说明
|
|
258
|
-
new Line()
|
|
259
|
-
.padding(9)
|
|
260
|
-
.column(`<${option.argument.name}>`, 26)
|
|
261
|
-
.column(option.argument.description || '')
|
|
262
|
-
.output();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 显示版本号
|
|
270
|
-
*/
|
|
271
|
-
showVersion() {
|
|
272
|
-
console.log(`${this.name} / ${this.version}`);
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* 启动
|
|
276
|
-
*/
|
|
277
|
-
start() {
|
|
278
|
-
if (this.alwaysHandler) {
|
|
279
|
-
this.alwaysHandler();
|
|
280
|
-
}
|
|
281
|
-
if (this.defaultHandler) {
|
|
282
|
-
if (this.parsedArguments['_'].length === 0 && Object.keys(this.parsedArguments).length === 1) {
|
|
283
|
-
this.defaultHandler();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
for (const key of Object.keys(this.parsedArguments)) {
|
|
287
|
-
if (key in this.commands) {
|
|
288
|
-
this.exec(key);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
for (const key of this.parsedArguments['_']) {
|
|
292
|
-
if (key in this.commands) {
|
|
293
|
-
this.exec(key);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* 执行命令担当函数
|
|
300
|
-
* @param command
|
|
301
|
-
* @param extraArguments
|
|
302
|
-
*/
|
|
303
|
-
private exec(command) {
|
|
304
|
-
if (this.commands[command].redirect) {
|
|
305
|
-
// 别名重定向
|
|
306
|
-
this.exec(this.commands[command].redirect);
|
|
307
|
-
} else {
|
|
308
|
-
// 传递绑定的设置及通用设置
|
|
309
|
-
const options = {};
|
|
310
|
-
const bandOptions = this.commands[command].options.concat(this.commonOptions);
|
|
311
|
-
for (const option of bandOptions) {
|
|
312
|
-
for (const name of option.name) {
|
|
313
|
-
if (name in this.parsedArguments) {
|
|
314
|
-
// do option argument validation
|
|
315
|
-
if (this.validateArgument(this.parsedArguments[name], option.argument)) {
|
|
316
|
-
options[option.name[0]] = this.parsedArguments[name];
|
|
317
|
-
} else {
|
|
318
|
-
console.error(chalk.red(`Argument validation failed for option '${name}'.`));
|
|
319
|
-
if (typeof option.argument.validate === 'string') {
|
|
320
|
-
console.error(chalk.red(`<${option.argument.name}> should be a/an ${option.argument.validate.slice(2)}.`))
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
// do command argument validation
|
|
327
|
-
if (this.validateArgument(this.parsedArguments[command], this.commands[command].argument)) {
|
|
328
|
-
this.commands[command].handler(this.commandCtx(command), createCamelProxifiedObject(options));
|
|
329
|
-
} else {
|
|
330
|
-
console.error(chalk.red(`Argument validation failed for command ${command}`));
|
|
331
|
-
const validateMethod = this.commands[command].argument.validate;
|
|
332
|
-
if (typeof validateMethod === 'string') {
|
|
333
|
-
console.error(chalk.red(`<${this.commands[command].argument.name}> should be a/an ${validateMethod.slice(2)}.`))
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
validateArgument(argumentValue: any, argument: Argument) {
|
|
340
|
-
if (!argument || !argument.validate) {
|
|
341
|
-
// no need to validate
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
if (typeof argument.validate === "string") {
|
|
345
|
-
if (argument.validate in this.validator) {
|
|
346
|
-
return this.validator[argument.validate](argumentValue.toString());
|
|
347
|
-
} else {
|
|
348
|
-
console.error(chalk.red(`Unknown validate method for ${argument.name}.`));
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
} else {
|
|
352
|
-
// custom validator
|
|
353
|
-
return argument.validate(argumentValue, (message: string) => {
|
|
354
|
-
console.log(chalk.red(message))
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* 获得命令的参数
|
|
361
|
-
* @param commandName
|
|
362
|
-
* @param followRedirect 是否遵循重定向
|
|
363
|
-
*/
|
|
364
|
-
getArgument(commandName: string, followRedirect = true): string {
|
|
365
|
-
if (this.commands[commandName]) {
|
|
366
|
-
if (this.commands[commandName].redirect) {
|
|
367
|
-
return this.getArgument(this.commands[commandName].redirect);
|
|
368
|
-
} else {
|
|
369
|
-
if (Object.keys(this.parsedArguments).includes(commandName) && commandName !== '_') {
|
|
370
|
-
return this.parsedArguments[commandName] as string;
|
|
371
|
-
} else {
|
|
372
|
-
for (const alias of this.commands[commandName].alias) {
|
|
373
|
-
if (Object.keys(this.parsedArguments).includes(alias)) {
|
|
374
|
-
return this.parsedArguments[alias] as string;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
console.error(chalk.red(`Command ${commandName} not found.`));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
} else {
|
|
381
|
-
console.error(chalk.red(`Command ${commandName} not found.`));
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* 启动
|
|
387
|
-
* エリイ 起きてます❤
|
|
388
|
-
*/
|
|
389
|
-
okite() {
|
|
390
|
-
return this.start();
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export default new Erii();
|
package/src/utils/convert.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export function toCamelCase(str: string) {
|
|
2
|
-
return str.replace(/([-_][a-z])/gi, ($1) => {
|
|
3
|
-
return $1.toUpperCase().replace("-", "");
|
|
4
|
-
});
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function toKebabCase(str: string) {
|
|
8
|
-
return (
|
|
9
|
-
str[0].toLowerCase() +
|
|
10
|
-
str
|
|
11
|
-
.slice(1, str.length)
|
|
12
|
-
.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function convertObjectKey(before: { [index: string]: any }) {
|
|
17
|
-
return Object.fromEntries(
|
|
18
|
-
Object.entries(before).map(([key, value]) => [toCamelCase(key), value])
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function createCamelProxifiedObject(before: { [index: string]: any }) {
|
|
23
|
-
const originalObject = before;
|
|
24
|
-
return new Proxy(before, {
|
|
25
|
-
get: (target: typeof before, key: string) => {
|
|
26
|
-
return originalObject[key] ?? originalObject[toKebabCase(key)];
|
|
27
|
-
},
|
|
28
|
-
set: (target: typeof before, key: string, value: any) => {
|
|
29
|
-
// 如果赋值的是camel case的key,而驼峰形式key是从kebab case的key proxy来的 则赋值其kebab case形式的key的值
|
|
30
|
-
if (
|
|
31
|
-
!key.includes("-") &&
|
|
32
|
-
(originalObject[key] === undefined ||
|
|
33
|
-
originalObject[key] === null) &&
|
|
34
|
-
originalObject[toKebabCase(key)] !== undefined &&
|
|
35
|
-
originalObject[toKebabCase(key)] !== null
|
|
36
|
-
) {
|
|
37
|
-
target[toKebabCase(key)] = value;
|
|
38
|
-
} else {
|
|
39
|
-
target[key] = value;
|
|
40
|
-
}
|
|
41
|
-
return value;
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es6",
|
|
4
|
-
"outDir": "dist",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"module": "commonjs",
|
|
7
|
-
"sourceMap": true,
|
|
8
|
-
"lib": ["es7", "esnext"],
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"newLine": "lf"
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*"],
|
|
13
|
-
"exclude": ["dist"]
|
|
14
|
-
}
|