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 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
- type ValidatorName = { [K in keyof typeof validator]: typeof validator[K] extends ((value: string) => boolean) ? K : never; }[keyof typeof validator];
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
- 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
- }
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: CommandMap;
54
- commonOptions: Option[];
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: Command, handler: CommandHandler): void;
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: Option): void;
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?: string): void;
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: string, followRedirect?: boolean): ArgumentValue | undefined;
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
- //#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 };
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.getArgument(commandName);
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 = new Erii();
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
- "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"
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
  ![](./logo.png)
4
4
 
5
- [![npm version](https://badge.fury.io/js/erii.svg)](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
- ```JavaScript
27
- import Erii from 'erii';
7
+ Use Node.js 22.18 or later and set `"type": "module"` in your `package.json`.
28
8
 
29
- Erii.setMetaInfo({
30
- version: '0.0.1',
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
- **Example**
72
-
73
- Call with
74
-
75
- `node index.js --help xxx --debug 1`
13
+ ## Usage
76
14
 
77
- ```Javascript
78
- // ...
79
- // PART OF CODE
80
- Erii.bind({
81
- name: ['help', 'h'],
82
- description: 'Show Help',
83
- argument: {
84
- name: 'command',
85
- description: 'query help of a specified command'
86
- }
87
- }, (ctx, options) => {
88
- const { verbose } = options; // option aliases use the primary name
89
- console.log(verbose); // 1
90
- console.log(ctx.getArgument()); // 'xxx'
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
- Erii.addOption({
94
- name: ['verbose', 'debug'],
95
- description: 'show verbose output',
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: 'level',
98
- description: 'level of verbose output'
99
- }
71
+ name: "mode",
72
+ description: "Server mode",
73
+ validate: (value) => value === "dev" || value === "prod",
74
+ },
100
75
  });
101
76
 
102
- Erii.start();
77
+ cli.start(); // okite() is also available.
103
78
  ```
104
79
 
105
-
106
- **Help Text**
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
- --help <command> Show Help --h
114
- <command> query help of a specified comm
85
+ ## Aliases
115
86
 
116
- Options:
87
+ Put the primary name first and aliases after it. Read options using the primary name, such as `options.outDir`.
117
88
 
118
- Options Description
119
- --verbose, debug <level> show verbose output
120
- <level> level of verbose output
89
+ ```sh
90
+ node index.js -b src -o dist -v
121
91
  ```
122
92
 
123
- **Argument Validation**
93
+ ## Reading arguments
124
94
 
125
- Argument validation are based on [validator.js](https://github.com/chriso/validator.js/).
95
+ Use `ctx.getArgument()` inside a handler, or look up a command by name:
126
96
 
127
- `Erii.validator` points to a `validator` exported by `validator.js`.
97
+ ```ts
98
+ const path = cli.getArgument("build");
99
+ if (path !== undefined) {
100
+ console.log(path);
101
+ }
102
+ ```
128
103
 
129
- Erii can validate arguments automatically.
104
+ ## Validation
130
105
 
131
- Define the validate methods in `argument` parameter.
106
+ Use `argument.validate` to check values with a validator.js method:
132
107
 
133
- ```JavaScript
134
- Erii.addOption({
135
- name: ['verbose', 'debug'],
136
- description: 'show verbose output',
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
- `validate` can also be a function, for example:
116
+ To use a custom validator, return `true` for accepted input:
146
117
 
147
- ```JavaScript
148
- Erii.addOption({
149
- name: ['verbose', 'debug'],
150
- description: 'show verbose output',
118
+ ```ts
119
+ cli.addOption({
120
+ command: "serve",
121
+ name: "port",
151
122
  argument: {
152
- name: 'level',
153
- description: 'level of verbose output',
154
- validate: (value) => Erii.validator.isInt(String(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
- `argument.validate` works in both command and option definitions.
130
+ ## Help and lifecycle
160
131
 
161
- **Example Output for Argument Validation**
132
+ Register lifecycle handlers before calling `start()`:
162
133
 
163
- ```
164
- PS D:\Git\erii.test> node index.js --help --verbose f
165
- Argument validation failed for option 'verbose'.
166
- <level> should be a/an Int.
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
  ```