bob-core 2.0.0-beta.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -33
- package/dist/cjs/package-Blqq-jZJ.cjs +1 -0
- package/dist/cjs/src/Cli.d.ts +10 -11
- package/dist/cjs/src/Command.d.ts +11 -18
- package/dist/cjs/src/CommandIO.d.ts +5 -2
- package/dist/cjs/src/CommandParser.d.ts +13 -5
- package/dist/cjs/src/CommandRegistry.d.ts +13 -6
- package/dist/cjs/src/CommandSignatureParser.d.ts +5 -4
- package/dist/cjs/src/CommandWithSignature.d.ts +3 -3
- package/dist/cjs/src/ExceptionHandler.d.ts +1 -1
- package/dist/cjs/src/Logger.d.ts +1 -1
- package/dist/cjs/src/StringSimilarity.d.ts +26 -0
- package/dist/cjs/src/commands/HelpCommand.d.ts +1 -1
- package/dist/cjs/src/contracts/CommandOption.d.ts +3 -3
- package/dist/cjs/src/errors/BadCommandOption.d.ts +2 -1
- package/dist/cjs/src/errors/BadCommandParameter.d.ts +2 -1
- package/dist/cjs/src/errors/CommandNotFoundError.d.ts +2 -1
- package/dist/cjs/src/errors/InvalidOption.d.ts +2 -1
- package/dist/cjs/src/errors/MissingRequiredArgumentValue.d.ts +2 -1
- package/dist/cjs/src/errors/MissingRequiredOptionValue.d.ts +2 -1
- package/dist/cjs/src/index.d.ts +1 -0
- package/dist/cjs/src/index.js +17 -0
- package/dist/cjs/src/lib/types.d.ts +4 -2
- package/dist/cjs/src/options/HelpOption.d.ts +1 -1
- package/dist/esm/package-DbMvpGfM.js +33 -0
- package/dist/esm/src/Cli.d.ts +10 -11
- package/dist/esm/src/Command.d.ts +11 -18
- package/dist/esm/src/CommandIO.d.ts +5 -2
- package/dist/esm/src/CommandParser.d.ts +13 -5
- package/dist/esm/src/CommandRegistry.d.ts +13 -6
- package/dist/esm/src/CommandSignatureParser.d.ts +5 -4
- package/dist/esm/src/CommandWithSignature.d.ts +3 -3
- package/dist/esm/src/ExceptionHandler.d.ts +1 -1
- package/dist/esm/src/Logger.d.ts +1 -1
- package/dist/esm/src/StringSimilarity.d.ts +26 -0
- package/dist/esm/src/commands/HelpCommand.d.ts +1 -1
- package/dist/esm/src/contracts/CommandOption.d.ts +3 -3
- package/dist/esm/src/errors/BadCommandOption.d.ts +2 -1
- package/dist/esm/src/errors/BadCommandParameter.d.ts +2 -1
- package/dist/esm/src/errors/CommandNotFoundError.d.ts +2 -1
- package/dist/esm/src/errors/InvalidOption.d.ts +2 -1
- package/dist/esm/src/errors/MissingRequiredArgumentValue.d.ts +2 -1
- package/dist/esm/src/errors/MissingRequiredOptionValue.d.ts +2 -1
- package/dist/esm/src/index.d.ts +1 -0
- package/dist/esm/src/index.js +1029 -0
- package/dist/esm/src/lib/types.d.ts +4 -2
- package/dist/esm/src/options/HelpOption.d.ts +1 -1
- package/package.json +28 -21
- package/dist/cjs/index.d.ts +0 -2
- package/dist/cjs/index.js +0 -21
- package/dist/cjs/package-BpbpSdBI.cjs +0 -1
- package/dist/cjs/src/testFixtures.d.ts +0 -11
- package/dist/esm/index.d.ts +0 -2
- package/dist/esm/index.js +0 -1247
- package/dist/esm/package-CjHUTRfV.js +0 -31
- package/dist/esm/src/testFixtures.d.ts +0 -11
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
[](https://www.npmjs.com/package/bob-core)
|
|
18
18
|
[](https://opensource.org/licenses/ISC)
|
|
19
|
-
[](https://www.typescriptlang.org/)
|
|
20
20
|
|
|
21
21
|
[Features](#features) • [Installation](#installation) • [Quick Start](#quick-start) • [Documentation](#documentation) • [Examples](#examples)
|
|
22
22
|
|
|
@@ -187,38 +187,44 @@ Options:
|
|
|
187
187
|
Build beautiful interactive CLIs with built-in prompts:
|
|
188
188
|
|
|
189
189
|
```typescript
|
|
190
|
-
import { Command } from 'bob-core';
|
|
190
|
+
import { Command, CommandHandlerOptions, OptionsSchema } from 'bob-core';
|
|
191
191
|
|
|
192
|
-
export default
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
192
|
+
export default class SetupCommand extends Command<any, OptionsSchema, OptionsSchema> {
|
|
193
|
+
constructor() {
|
|
194
|
+
super('setup', {
|
|
195
|
+
description: 'Interactive project setup'
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async handle(ctx: any, opts: CommandHandlerOptions<OptionsSchema, OptionsSchema>) {
|
|
200
|
+
// Text input
|
|
201
|
+
const name = await this.io.askForInput('Project name:');
|
|
202
|
+
|
|
203
|
+
// Confirmation
|
|
204
|
+
const useTypeScript = await this.io.askForConfirmation('Use TypeScript?', true);
|
|
205
|
+
|
|
206
|
+
// Selection
|
|
207
|
+
const framework = await this.io.askForSelect('Framework:', [
|
|
208
|
+
{ title: 'React', value: 'react' },
|
|
209
|
+
{ title: 'Vue', value: 'vue' },
|
|
210
|
+
{ title: 'Svelte', value: 'svelte' }
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
// Multi-select
|
|
214
|
+
const features = await this.io.askForSelect(
|
|
215
|
+
'Features:',
|
|
216
|
+
['ESLint', 'Prettier', 'Testing'],
|
|
217
|
+
{ type: 'multiselect' }
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Spinner/loader
|
|
221
|
+
using loader = this.io.newLoader('Creating project...');
|
|
222
|
+
await createProject({ name, framework, features });
|
|
223
|
+
loader.stop();
|
|
224
|
+
|
|
225
|
+
this.io.info('✅ Project created!');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
222
228
|
```
|
|
223
229
|
|
|
224
230
|
---
|
|
@@ -383,10 +389,28 @@ BOB Core makes CLI development in TypeScript a breeze:
|
|
|
383
389
|
| `'string'` | Text value | `'hello'` |
|
|
384
390
|
| `'number'` | Numeric value | `42` |
|
|
385
391
|
| `'boolean'` | True/false | `true` |
|
|
386
|
-
| `'secret'` | Masked input | `'****'` |
|
|
387
392
|
| `['string']` | String array | `['a', 'b']` |
|
|
388
393
|
| `['number']` | Number array | `[1, 2, 3]` |
|
|
389
394
|
|
|
395
|
+
**Note:** The `secret` flag is not a type but a property of OptionDefinition.
|
|
396
|
+
|
|
397
|
+
### Secret/Masked Input
|
|
398
|
+
|
|
399
|
+
For sensitive input like passwords, use the `secret: true` flag in the option definition:
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
options: {
|
|
403
|
+
password: {
|
|
404
|
+
type: 'string', // Type is still 'string'
|
|
405
|
+
secret: true, // Flag to mask input in interactive prompts
|
|
406
|
+
required: true,
|
|
407
|
+
description: 'User password'
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The `secret` flag masks the input when prompting interactively, making it perfect for passwords and API keys.
|
|
413
|
+
|
|
390
414
|
---
|
|
391
415
|
|
|
392
416
|
## Contributing
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e="bob-core",t="2.0.0",s="BOB Core",i="module",n="./dist/cjs/src/index.js",r="./dist/esm/src/index.js",c="./dist/esm/src/index.d.ts",o=["dist"],d={".":{import:{types:"./dist/esm/src/index.d.ts",default:"./dist/esm/src/index.js"},require:{types:"./dist/cjs/src/index.d.ts",default:"./dist/cjs/src/index.js"}}},p={start:"node -r @swc-node/register debug/main.ts",build:"rimraf ./dist && vite build",typecheck:"tsc --noEmit",prepack:"npm run build",test:"vitest run",lint:"eslint .","lint:fix":"eslint . --fix"},l="Léo Hubert",m="ISC",a={"@eslint/js":"^9.37.0","@faker-js/faker":"^10.0.0","@swc-node/register":"^1.11.1","@trivago/prettier-plugin-sort-imports":"^5.2.2","@types/minimist":"^1.2.5","@types/node":"^20.14.5","@types/prompts":"^2.4.9","@types/string-similarity":"^4.0.2","@vitest/coverage-v8":"^3.2.4",eslint:"^9.37.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4",prettier:"^3.6.2",rimraf:"^6.0.1",tsx:"^4.20.6",typescript:"^5.9.3","typescript-eslint":"^8.46.0",vite:"^7.2.7","vite-plugin-dts":"^4.5.4",vitest:"^3.2.4"},u={chalk:"^4.1.2",minimist:"^1.2.8",prompts:"^2.4.2"},y={name:e,version:t,description:s,type:i,main:n,module:r,types:c,files:o,exports:d,scripts:p,author:l,license:m,devDependencies:a,dependencies:u};exports.author=l;exports.default=y;exports.dependencies=u;exports.description=s;exports.devDependencies=a;exports.exports=d;exports.files=o;exports.license=m;exports.main=n;exports.module=r;exports.name=e;exports.scripts=p;exports.type=i;exports.types=c;exports.version=t;
|
package/dist/cjs/src/Cli.d.ts
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { CommandRegistry, CommandResolver, FileImporter } from './CommandRegistry.js';
|
|
2
|
-
import { default as HelpCommand, HelpCommandOptions } from './commands/HelpCommand.js';
|
|
3
|
-
import { ExceptionHandler } from './ExceptionHandler.js';
|
|
4
1
|
import { Command } from './Command.js';
|
|
2
|
+
import { CommandRegistry, CommandRegistryOptions, CommandResolver, FileImporter } from './CommandRegistry.js';
|
|
3
|
+
import { ExceptionHandler } from './ExceptionHandler.js';
|
|
5
4
|
import { Logger } from './Logger.js';
|
|
6
|
-
|
|
5
|
+
import { default as HelpCommand, HelpCommandOptions } from './commands/HelpCommand.js';
|
|
6
|
+
import { ContextDefinition, OptionsSchema } from './lib/types.js';
|
|
7
|
+
export type CliOptions<C extends ContextDefinition = ContextDefinition> = {
|
|
7
8
|
ctx?: C;
|
|
8
9
|
name?: string;
|
|
9
10
|
version?: string;
|
|
10
11
|
logger?: Logger;
|
|
11
12
|
};
|
|
12
|
-
export declare class Cli<C =
|
|
13
|
+
export declare class Cli<C extends ContextDefinition = ContextDefinition> {
|
|
13
14
|
private readonly ctx?;
|
|
14
15
|
private readonly logger;
|
|
15
16
|
readonly commandRegistry: CommandRegistry;
|
|
16
17
|
private readonly exceptionHandler;
|
|
17
18
|
private readonly helpCommand;
|
|
18
|
-
protected newCommandRegistry(opts:
|
|
19
|
-
logger: Logger;
|
|
20
|
-
}): CommandRegistry;
|
|
19
|
+
protected newCommandRegistry(opts: CommandRegistryOptions): CommandRegistry;
|
|
21
20
|
protected newHelpCommand(opts: HelpCommandOptions): HelpCommand;
|
|
22
21
|
protected newExceptionHandler(opts: {
|
|
23
22
|
logger: Logger;
|
|
@@ -25,10 +24,10 @@ export declare class Cli<C = any> {
|
|
|
25
24
|
constructor(opts?: CliOptions<C>);
|
|
26
25
|
withCommandResolver(resolver: CommandResolver): this;
|
|
27
26
|
withFileImporter(importer: FileImporter): this;
|
|
28
|
-
withCommands(...commands: Array<Command<C,
|
|
27
|
+
withCommands(...commands: Array<Command<C, OptionsSchema, OptionsSchema> | {
|
|
29
28
|
new (): Command<C>;
|
|
30
29
|
} | string>): Promise<void>;
|
|
31
|
-
runCommand(command: string | Command | undefined, ...args:
|
|
30
|
+
runCommand(command: string | Command | undefined, ...args: string[]): Promise<number>;
|
|
32
31
|
runHelpCommand(): Promise<number>;
|
|
33
|
-
protected registerCommand(command: Command<C>): void;
|
|
32
|
+
protected registerCommand(command: Command<C, OptionsSchema, OptionsSchema>): void;
|
|
34
33
|
}
|
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CommandIO, CommandIOOptions } from './CommandIO.js';
|
|
2
2
|
import { CommandParser } from './CommandParser.js';
|
|
3
|
-
import { CommandOption } from './contracts/index.js';
|
|
4
|
-
import { CommandIO } from './CommandIO.js';
|
|
5
3
|
import { Logger } from './Logger.js';
|
|
4
|
+
import { CommandOption } from './contracts/index.js';
|
|
5
|
+
import { ArgumentsObject, ArgumentsSchema, ContextDefinition, OptionsObject, OptionsSchema } from './lib/types.js';
|
|
6
6
|
export type CommandHandlerOptions<Options extends OptionsSchema, Arguments extends ArgumentsSchema> = {
|
|
7
7
|
options: OptionsObject<Options>;
|
|
8
8
|
arguments: ArgumentsObject<Arguments>;
|
|
9
9
|
};
|
|
10
|
-
export type CommandHandler<C, Options extends OptionsSchema, Arguments extends ArgumentsSchema> = (ctx: C, opts: CommandHandlerOptions<Options, Arguments>) => Promise<number | void> | number | void;
|
|
11
|
-
type
|
|
10
|
+
export type CommandHandler<C extends ContextDefinition, Options extends OptionsSchema, Arguments extends ArgumentsSchema> = (ctx: C, opts: CommandHandlerOptions<Options, Arguments>) => Promise<number | void> | number | void;
|
|
11
|
+
export type CommandRunOption<C, Options extends OptionsSchema, Arguments extends ArgumentsSchema> = {
|
|
12
12
|
logger: Logger;
|
|
13
13
|
ctx: C;
|
|
14
|
-
}
|
|
15
|
-
export type CommandRunArgsOption<C> = {
|
|
14
|
+
} & ({
|
|
16
15
|
args: string[];
|
|
17
|
-
}
|
|
18
|
-
export type CommandRunParsedOption<C, Options extends OptionsSchema, Arguments extends ArgumentsSchema> = {
|
|
16
|
+
} | {
|
|
19
17
|
options: OptionsObject<Options>;
|
|
20
18
|
arguments: ArgumentsObject<Arguments>;
|
|
21
|
-
}
|
|
22
|
-
export type CommandRunOption<C, Options extends OptionsSchema, Arguments extends ArgumentsSchema> = CommandRunArgsOption<C> | CommandRunParsedOption<C, Options, Arguments>;
|
|
19
|
+
});
|
|
23
20
|
export type CommandExample = {
|
|
24
21
|
description: string;
|
|
25
22
|
command: string;
|
|
26
23
|
};
|
|
27
|
-
export declare class Command<C =
|
|
24
|
+
export declare class Command<C extends ContextDefinition = ContextDefinition, Options extends OptionsSchema = OptionsSchema, Arguments extends ArgumentsSchema = ArgumentsSchema> {
|
|
28
25
|
readonly _command: string;
|
|
29
26
|
readonly description: string;
|
|
30
27
|
readonly group?: string;
|
|
@@ -32,7 +29,6 @@ export declare class Command<C = any, Options extends OptionsSchema = {}, Argume
|
|
|
32
29
|
get command(): string;
|
|
33
30
|
protected ctx: C;
|
|
34
31
|
protected io: CommandIO;
|
|
35
|
-
protected logger: Logger;
|
|
36
32
|
protected parser: CommandParser<Options, Arguments>;
|
|
37
33
|
protected disablePromptingFlag: boolean;
|
|
38
34
|
protected preHandle?(): Promise<void | number>;
|
|
@@ -40,15 +36,13 @@ export declare class Command<C = any, Options extends OptionsSchema = {}, Argume
|
|
|
40
36
|
protected handle?(ctx: C, opts: CommandHandlerOptions<Options, Arguments>): Promise<number | void> | number | void;
|
|
41
37
|
protected _handler?: CommandHandler<C, Options, Arguments>;
|
|
42
38
|
private tmp?;
|
|
43
|
-
protected defaultOptions(): CommandOption<Command
|
|
39
|
+
protected defaultOptions(): CommandOption<Command>[];
|
|
44
40
|
protected newCommandParser(opts: {
|
|
45
41
|
io: CommandIO;
|
|
46
42
|
options: Options;
|
|
47
43
|
arguments: Arguments;
|
|
48
44
|
}): CommandParser<Options, Arguments>;
|
|
49
|
-
protected newCommandIO(opts:
|
|
50
|
-
logger: Logger;
|
|
51
|
-
}): CommandIO;
|
|
45
|
+
protected newCommandIO(opts: CommandIOOptions): CommandIO;
|
|
52
46
|
constructor(command: string, opts?: {
|
|
53
47
|
description?: string;
|
|
54
48
|
group?: string;
|
|
@@ -62,4 +56,3 @@ export declare class Command<C = any, Options extends OptionsSchema = {}, Argume
|
|
|
62
56
|
arguments<Args extends ArgumentsSchema>(args: Args): Command<C, Options, Arguments & Args>;
|
|
63
57
|
run(opts: CommandRunOption<C, Options, Arguments>): Promise<number | void>;
|
|
64
58
|
}
|
|
65
|
-
export {};
|
|
@@ -6,9 +6,12 @@ export type SelectOption = {
|
|
|
6
6
|
selected?: boolean | undefined;
|
|
7
7
|
description?: string | undefined;
|
|
8
8
|
};
|
|
9
|
+
export type CommandIOOptions = {
|
|
10
|
+
logger: Logger;
|
|
11
|
+
};
|
|
9
12
|
export declare class CommandIO {
|
|
10
13
|
private logger;
|
|
11
|
-
constructor(
|
|
14
|
+
constructor(opts: CommandIOOptions);
|
|
12
15
|
/**
|
|
13
16
|
* Logger methods
|
|
14
17
|
*/
|
|
@@ -32,7 +35,7 @@ export declare class CommandIO {
|
|
|
32
35
|
mask?: string;
|
|
33
36
|
}): Promise<Date | null>;
|
|
34
37
|
askForList(message: string, defaultValue?: string | number, opts?: {
|
|
35
|
-
validate?: (value: string
|
|
38
|
+
validate?: (value: string) => boolean | string;
|
|
36
39
|
format?: (value: string) => string;
|
|
37
40
|
separator?: string;
|
|
38
41
|
}): Promise<string[] | null>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { OptionsSchema, OptionReturnType, OptionsObject, OptionDefinition } from './lib/types.js';
|
|
2
|
-
import { OptionDetails } from './lib/optionHelpers.js';
|
|
3
1
|
import { CommandIO } from './CommandIO.js';
|
|
2
|
+
import { OptionDetails } from './lib/optionHelpers.js';
|
|
3
|
+
import { OptionDefinition, OptionReturnType, OptionsObject, OptionsSchema } from './lib/types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Parses command-line arguments into typed options and arguments
|
|
6
6
|
* Handles validation, type conversion, and default values
|
|
@@ -21,7 +21,7 @@ export declare class CommandParser<Options extends OptionsSchema, Arguments exte
|
|
|
21
21
|
* Parses raw command-line arguments into structured options and arguments
|
|
22
22
|
* @param args - Raw command line arguments (typically from process.argv.slice(2))
|
|
23
23
|
* @returns Object containing parsed options and arguments
|
|
24
|
-
* @throws {InvalidOption} If an
|
|
24
|
+
* @throws {InvalidOption} If an naan option is provided
|
|
25
25
|
* @throws {BadCommandOption} If a value cannot be converted to the expected type
|
|
26
26
|
*/
|
|
27
27
|
init(args: string[]): {
|
|
@@ -36,19 +36,27 @@ export declare class CommandParser<Options extends OptionsSchema, Arguments exte
|
|
|
36
36
|
/**
|
|
37
37
|
* Retrieves a parsed option value by name
|
|
38
38
|
* @param name - The option name
|
|
39
|
+
* @param defaultValue - Optional default value if option is not set
|
|
39
40
|
* @returns The typed option value
|
|
40
41
|
* @throws {Error} If init() has not been called yet
|
|
41
42
|
*/
|
|
42
|
-
option<OptsName extends keyof Options>(name: OptsName): OptionReturnType<Options[OptsName]>;
|
|
43
|
+
option<OptsName extends keyof Options>(name: OptsName, defaultValue?: OptionReturnType<Options[OptsName]>): OptionReturnType<Options[OptsName]>;
|
|
43
44
|
setOption<OptsName extends keyof Options>(name: OptsName, value: OptionReturnType<Options[OptsName]>): void;
|
|
44
45
|
/**
|
|
45
46
|
* Retrieves a parsed argument value by name
|
|
46
47
|
* @param name - The argument name
|
|
48
|
+
* @param defaultValue - Optional default value if argument is not set
|
|
47
49
|
* @returns The typed argument value
|
|
48
50
|
* @throws {Error} If init() has not been called yet
|
|
49
51
|
*/
|
|
50
|
-
argument<ArgName extends keyof Arguments>(name: ArgName): OptionReturnType<Arguments[ArgName]>;
|
|
52
|
+
argument<ArgName extends keyof Arguments>(name: ArgName, defaultValue?: OptionReturnType<Arguments[ArgName]>): OptionReturnType<Arguments[ArgName]>;
|
|
51
53
|
setArgument<ArgName extends keyof Arguments>(name: ArgName, value: OptionReturnType<Arguments[ArgName]>): void;
|
|
54
|
+
/**
|
|
55
|
+
* Checks if a value should be considered "empty" for default value purposes
|
|
56
|
+
* @param value - The value to check
|
|
57
|
+
* @returns true if the value is null, undefined, or an empty array
|
|
58
|
+
*/
|
|
59
|
+
private isEmptyValue;
|
|
52
60
|
/**
|
|
53
61
|
* Validates that all provided options are recognized
|
|
54
62
|
* @throws {InvalidOption} If an unknown option is found
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import { CommandIO } from './CommandIO.js';
|
|
2
1
|
import { Command } from './Command.js';
|
|
2
|
+
import { CommandIO, CommandIOOptions } from './CommandIO.js';
|
|
3
3
|
import { Logger } from './Logger.js';
|
|
4
|
+
import { StringSimilarity } from './StringSimilarity.js';
|
|
5
|
+
import { ArgumentsSchema, ContextDefinition, OptionsSchema } from './lib/types.js';
|
|
4
6
|
export type CommandResolver = (path: string) => Promise<Command | null>;
|
|
5
|
-
export type FileImporter = (filePath: string) => Promise<
|
|
7
|
+
export type FileImporter = (filePath: string) => Promise<unknown>;
|
|
8
|
+
export type CommandRegistryOptions = {
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
stringSimilarity?: StringSimilarity;
|
|
11
|
+
};
|
|
6
12
|
export declare class CommandRegistry {
|
|
7
13
|
private readonly commands;
|
|
8
14
|
protected readonly io: CommandIO;
|
|
9
15
|
protected readonly logger: Logger;
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
private readonly stringSimilarity;
|
|
17
|
+
protected newCommandIO(opts: CommandIOOptions): CommandIO;
|
|
18
|
+
constructor(opts?: CommandRegistryOptions);
|
|
12
19
|
getAvailableCommands(): string[];
|
|
13
20
|
getCommands(): Array<Command>;
|
|
14
21
|
private importFile;
|
|
15
22
|
private commandResolver;
|
|
16
23
|
withCommandResolver(resolver: CommandResolver): this;
|
|
17
24
|
withFileImporter(importer: FileImporter): this;
|
|
18
|
-
registerCommand(command: Command<
|
|
25
|
+
registerCommand<C extends ContextDefinition = ContextDefinition, Opts extends OptionsSchema = OptionsSchema, Args extends ArgumentsSchema = ArgumentsSchema>(command: Command<C, Opts, Args>, force?: boolean): void;
|
|
19
26
|
loadCommandsPath(commandsPath: string): Promise<void>;
|
|
20
|
-
runCommand(ctx:
|
|
27
|
+
runCommand(ctx: ContextDefinition, command: string | Command, ...args: string[]): Promise<number>;
|
|
21
28
|
private suggestCommand;
|
|
22
29
|
private askRunSimilarCommand;
|
|
23
30
|
private listCommandsFiles;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Command } from './Command.js';
|
|
2
2
|
import { CommandIO } from './CommandIO.js';
|
|
3
|
-
import { OptionsSchema } from './lib/types.js';
|
|
4
3
|
import { CommandParser } from './CommandParser.js';
|
|
4
|
+
import { CommandOption } from './contracts/index.js';
|
|
5
|
+
import { OptionsSchema } from './lib/types.js';
|
|
5
6
|
/**
|
|
6
7
|
* Extends CommandParser to parse command signatures like "command {arg} {--option}"
|
|
7
8
|
* Handles interactive prompting for missing required arguments via CommandIO
|
|
8
9
|
*/
|
|
9
|
-
export declare class CommandSignatureParser<Opts extends OptionsSchema =
|
|
10
|
+
export declare class CommandSignatureParser<Opts extends OptionsSchema = OptionsSchema, Args extends OptionsSchema = OptionsSchema> extends CommandParser<Opts, Args> {
|
|
10
11
|
readonly command: string;
|
|
11
12
|
constructor(opts: {
|
|
12
13
|
io: CommandIO;
|
|
@@ -14,7 +15,7 @@ export declare class CommandSignatureParser<Opts extends OptionsSchema = any, Ar
|
|
|
14
15
|
helperDefinitions: {
|
|
15
16
|
[key: string]: string;
|
|
16
17
|
};
|
|
17
|
-
defaultOptions: CommandOption<
|
|
18
|
+
defaultOptions: CommandOption<Command>[];
|
|
18
19
|
});
|
|
19
20
|
/**
|
|
20
21
|
* Parses command signature string into command name and parameter schemas
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Command, CommandHandlerOptions } from './Command.js';
|
|
2
2
|
import { CommandIO } from './CommandIO.js';
|
|
3
3
|
import { CommandSignatureParser } from './CommandSignatureParser.js';
|
|
4
|
-
import { OptionsSchema } from './lib/types.js';
|
|
5
|
-
export declare abstract class CommandWithSignature<C =
|
|
4
|
+
import { ContextDefinition, OptionsSchema } from './lib/types.js';
|
|
5
|
+
export declare abstract class CommandWithSignature<C extends ContextDefinition = ContextDefinition, Opts extends OptionsSchema = OptionsSchema, Args extends OptionsSchema = OptionsSchema> extends Command<C, Opts, Args> {
|
|
6
6
|
abstract signature: string;
|
|
7
7
|
abstract description: string;
|
|
8
8
|
protected helperDefinitions: {
|
|
9
9
|
[key: string]: string;
|
|
10
10
|
};
|
|
11
|
+
protected parser: CommandSignatureParser<Opts, Args>;
|
|
11
12
|
get command(): string;
|
|
12
|
-
parser: CommandSignatureParser<Opts, Args>;
|
|
13
13
|
protected newCommandParser(opts: {
|
|
14
14
|
io: CommandIO;
|
|
15
15
|
options: Opts;
|
package/dist/cjs/src/Logger.d.ts
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SimilarityResult {
|
|
2
|
+
rating: number;
|
|
3
|
+
target: string;
|
|
4
|
+
}
|
|
5
|
+
export interface BestMatchResult {
|
|
6
|
+
bestMatch: SimilarityResult;
|
|
7
|
+
bestMatchIndex: number;
|
|
8
|
+
ratings: SimilarityResult[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* String similarity calculator using Dice's Coefficient algorithm
|
|
12
|
+
*/
|
|
13
|
+
export declare class StringSimilarity {
|
|
14
|
+
/**
|
|
15
|
+
* Generate bigrams (character pairs) from a string
|
|
16
|
+
*/
|
|
17
|
+
private getBigrams;
|
|
18
|
+
/**
|
|
19
|
+
* Calculate Dice's Coefficient similarity between two strings (0-1 scale)
|
|
20
|
+
*/
|
|
21
|
+
calculateSimilarity(str1: string, str2: string): number;
|
|
22
|
+
/**
|
|
23
|
+
* Find best matching string and ratings for all candidates
|
|
24
|
+
*/
|
|
25
|
+
findBestMatch(target: string, candidates: string[]): BestMatchResult;
|
|
26
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { OptionDefinition } from '../lib/types.js';
|
|
2
1
|
import { Command } from '../Command.js';
|
|
3
|
-
|
|
2
|
+
import { OptionDefinition } from '../lib/types.js';
|
|
3
|
+
export interface CommandOption<Cmd extends Command> extends OptionDefinition {
|
|
4
4
|
option: string;
|
|
5
|
-
handler(this:
|
|
5
|
+
handler(this: Cmd): Promise<number | void>;
|
|
6
6
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
export type OptionProps = {
|
|
3
4
|
option: string;
|
|
@@ -7,5 +8,5 @@ export type OptionProps = {
|
|
|
7
8
|
export declare class BadCommandOption extends BobError {
|
|
8
9
|
readonly param: OptionProps;
|
|
9
10
|
constructor(param: OptionProps);
|
|
10
|
-
pretty(
|
|
11
|
+
pretty(logger: Logger): void;
|
|
11
12
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
export type ParameterProps = {
|
|
3
4
|
param: string;
|
|
@@ -7,5 +8,5 @@ export type ParameterProps = {
|
|
|
7
8
|
export declare class BadCommandParameter extends BobError {
|
|
8
9
|
readonly param: ParameterProps;
|
|
9
10
|
constructor(param: ParameterProps);
|
|
10
|
-
pretty(
|
|
11
|
+
pretty(logger: Logger): void;
|
|
11
12
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
export declare class CommandNotFoundError extends BobError {
|
|
3
4
|
readonly command: string;
|
|
4
5
|
constructor(command: string);
|
|
5
|
-
pretty(
|
|
6
|
+
pretty(logger: Logger): void;
|
|
6
7
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
import { OptionsSchema } from '../lib/types.js';
|
|
3
4
|
export declare class InvalidOption extends BobError {
|
|
4
5
|
private option;
|
|
5
6
|
private optionsSchema;
|
|
6
7
|
constructor(option: string, optionsSchema?: OptionsSchema);
|
|
7
|
-
pretty(
|
|
8
|
+
pretty(logger: Logger): void;
|
|
8
9
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
export declare class MissingRequiredArgumentValue extends BobError {
|
|
3
4
|
readonly argument: string;
|
|
4
5
|
constructor(argument: string);
|
|
5
|
-
pretty(
|
|
6
|
+
pretty(logger: Logger): void;
|
|
6
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
1
2
|
import { BobError } from './BobError.js';
|
|
2
3
|
export declare class MissingRequiredOptionValue extends BobError {
|
|
3
4
|
readonly option: string;
|
|
4
5
|
constructor(option: string);
|
|
5
|
-
pretty(
|
|
6
|
+
pretty(logger: Logger): void;
|
|
6
7
|
}
|
package/dist/cjs/src/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './CommandRegistry.js';
|
|
|
7
7
|
export * from './Cli.js';
|
|
8
8
|
export * from './Logger.js';
|
|
9
9
|
export * from './ExceptionHandler.js';
|
|
10
|
+
export * from './StringSimilarity.js';
|
|
10
11
|
export * from './lib/types.js';
|
|
11
12
|
export * from './errors/index.js';
|
|
12
13
|
export * from './options/index.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("prompts"),a=require("chalk"),M=require("minimist"),j=require("node:fs"),F=require("node:path");class R{logger;constructor(t){this.logger=t.logger}log(...t){this.logger.log(...t)}info(...t){this.logger.info(...t)}warn(...t){this.logger.warn(...t)}error(...t){this.logger.error(...t)}debug(...t){this.logger.debug(...t)}async askForConfirmation(t="Do you want to continue?",e){return(await y({type:"confirm",name:"value",message:t,initial:e??!1})).value}async askForInput(t,e,i){return(await y({type:"text",name:"value",message:t,initial:e,...i}))?.value??null}async askForDate(t,e,i){return(await y({type:"date",name:"value",message:t,initial:e,...i}))?.value??null}async askForList(t,e,i){return(await y({type:"list",name:"value",message:t,initial:e,...i}))?.value??null}async askForToggle(t,e,i){return(await y({type:"toggle",name:"value",message:t,initial:e,...i}))?.value??null}async askForSelect(t,e,i){if(e.length===0)throw new Error("No options provided");const n=[];for(const o of e)typeof o=="string"?n.push({title:o,value:o}):n.push(o);return(await y({type:"select",name:"value",message:t,choices:n,...i}))?.value??null}newLoader(t="",e=["⠙","⠘","⠰","⠴","⠤","⠦","⠆","⠃","⠋","⠉"],i=100){let n=t,r=null,o=0;const u=setInterval(function(){r&&(process.stdout.write(new TextEncoder().encode("\r"+" ".repeat(r.length+5)+"\r")),r=null),process.stdout.write(new TextEncoder().encode("\r"+e[o++]+" "+n)),o=o%e.length},i),m=()=>{clearInterval(u),process.stdout.write(new TextEncoder().encode("\r"+" ".repeat(n.length+5)+"\r"))};return{[Symbol.dispose]:m,[Symbol.asyncDispose]:m,updateText:h=>{r=n,n=h},stop:m}}}class f extends Error{}function q(s){if(s==="string"||s==="number")return null;if(s==="boolean")return!1;if(Array.isArray(s)&&s.length===1){if(s[0]==="string")return[];if(s[0]==="number")return[]}throw new Error("Invalid option type: "+s)}function k(s){return typeof s=="string"||Array.isArray(s)?q(s):s.default!==void 0?s.default:q(s.type)}function g(s){return typeof s=="string"||Array.isArray(s)?{alias:[],default:k(s),description:"",required:!1,secret:!1,type:s,variadic:!1}:{alias:s.alias?Array.isArray(s.alias)?s.alias:[s.alias]:[],default:s.default??k(s.type),description:s.description??"",required:s.required??!1,secret:s.secret??!1,type:s.type,variadic:s.variadic??!1}}class b extends f{constructor(t,e={}){super(`Invalid option ${t} in not recognized`),this.option=t,this.optionsSchema=e}pretty(t){const e=Object.entries(this.optionsSchema);if(e.length>0){t.log(`
|
|
2
|
+
${a.yellow("Available options")}:`);for(const[i,n]of e){const r=g(n),o=r.alias?typeof r.alias=="string"?[r.alias]:r.alias:[],u=Array.isArray(r.type)?`[${r.type[0]}]`:r.type,m=`--${i}${o.length>0?o.map(l=>`, -${l}`).join(""):""}`,h=" ".repeat(30-m.length);t.log(` ${a.green(m)} ${h} ${r.description||"\b"} ${a.white(`(${u})`)}`)}t.log("")}t.log(`${a.white.bgRed(" ERROR ")} Option ${a.bold.yellow(this.option)} is not recognized.`)}}class A extends f{constructor(t){super(`Argument "${t}" is required.`),this.argument=t}pretty(t){t.log(`${a.white.bgRed(" ERROR ")} Argument ${a.bold.yellow(this.argument)} is required.`)}}class N extends f{constructor(t){super(`Argument "${t}" is required.`),this.option=t}pretty(t){t.log(`${a.white.bgRed(" ERROR ")} Option ${a.bold.yellow(this.option)} is required.`)}}class W extends f{constructor(t){let e=`Argument "${t.param}" value is invalid.`;t.reason?e+=` Reason: ${t.reason}`:e+=` Value: "${t.value}"`,super(e),this.param=t}pretty(t){t.log(` ${a.white.bgRed(" ERROR ")} Argument ${a.bold.yellow(this.param.param)} value is invalid. `),(this.param.value||this.param.reason)&&t.log(""),this.param.value&&t.log(` ${a.blue("Value")}: ${this.param.value}`),this.param.reason&&t.log(` ${a.yellow("Reason")}: ${this.param.reason}`)}}class C extends f{constructor(t){let e=`Option "${t.option}" value is invalid.`;t.reason?e+=` Reason: ${t.reason}`:e+=` Value: "${t.value}"`,super(e),this.param=t}pretty(t){t.log(` ${a.white.bgRed(" ERROR ")} Option ${a.bold.yellow(this.param.option)} value is invalid. `),(this.param.value||this.param.reason)&&t.log(""),this.param.value&&t.log(` ${a.blue("Value")}: ${this.param.value}`),this.param.reason&&t.log(` ${a.yellow("Reason")}: ${this.param.reason}`)}}class P extends f{constructor(t){super(`Command "${t}" not found.`),this.command=t}pretty(t){t.log(`${a.bgRed(" ERROR ")} Command ${a.yellow(this.command)} not found.`)}}function w(s,t,e,i){if(s==null)return i??null;if(t==="string")return String(s);if(t==="number"){const n=Number(s);if(isNaN(n))throw new C({option:e,reason:`Expected a number, got "${s}"`});return n}if(t==="boolean")return typeof s=="boolean"?s:s==="true"||s==="1"?!0:s==="false"||s==="0"?!1:!!s;if(Array.isArray(t)){const n=t[0],r=Array.isArray(s)?s:[s];if(n==="string")return r.map(o=>String(o));if(n==="number")return r.map(o=>{const u=Number(o);if(isNaN(u))throw new C({option:e,reason:`Expected array of numbers, got "${o}" in array`});return u})}return s}class x{options;parsedOptions=null;arguments;parsedArguments=null;io;shouldPromptForMissingOptions=!0;constructor(t){this.options=t.options,this.arguments=t.arguments,this.io=t.io}init(t){const{_:e,...i}=M(t);return this.validateUnknownOptions(i),this.parsedOptions=this.handleOptions(i),this.parsedArguments=this.handleArguments(e),{options:this.parsedOptions,arguments:this.parsedArguments}}async validate(){for(const t in this.options)if(g(this.options[t]).required&&(this.parsedOptions?.[t]===void 0||this.parsedOptions?.[t]===null))throw new N(t);for(const t in this.arguments){const e=g(this.arguments[t]),i=this.parsedArguments?.[t];if(e.required&&i==null){if(this.shouldPromptForMissingOptions){const n=await this.promptForArgument(t,e);if(n&&this.parsedArguments){this.parsedArguments[t]=w(n,e.type,t);continue}}throw new A(t)}if(e.required&&e.variadic&&Array.isArray(i)&&i.length===0){if(this.shouldPromptForMissingOptions){const n=await this.promptForArgument(t,e);if(n&&this.parsedArguments){this.parsedArguments[t]=w(n,e.type,t);continue}}throw new A(t)}}}option(t,e){if(!this.parsedOptions)throw new Error("Options have not been parsed yet. Call init() first.");return this.isEmptyValue(this.parsedOptions[t])&&e!==void 0?e:this.parsedOptions[t]}setOption(t,e){if(!this.parsedOptions)throw new Error("Options have not been parsed yet. Call init() first.");if(!(t in this.options))throw new b(t,this.options);this.parsedOptions[t]=e}argument(t,e){if(!this.parsedArguments)throw new Error("Arguments have not been parsed yet. Call init() first.");return this.isEmptyValue(this.parsedArguments[t])&&e!==void 0?e:this.parsedArguments[t]}setArgument(t,e){if(!this.parsedArguments)throw new Error("Arguments have not been parsed yet. Call init() first.");if(!(t in this.arguments))throw new b(t,this.arguments);this.parsedArguments[t]=e}isEmptyValue(t){return t==null||Array.isArray(t)&&t.length===0}validateUnknownOptions(t){const e=new Set;for(const i in this.options){e.add(i);const n=g(this.options[i]);for(const r of n.alias)e.add(r)}for(const i in t)if(!e.has(i))throw new b(i,this.options)}handleOptions(t){const e={};for(const i in this.options){const n=g(this.options[i]);e[i]=this.resolveOptionValue(i,n,t)}return e}handleArguments(t){const e={},i=[...t];for(const n in this.arguments){const r=g(this.arguments[n]);if(r.variadic){e[n]=this.handleVariadicArgument(n,r,i);continue}e[n]=this.resolveArgumentValue(n,r,i.shift())}return e}handleVariadicArgument(t,e,i){return i.length?w(i,e.type,t,e.default):e.default}resolveArgumentValue(t,e,i){return i===void 0?e.default:w(i,e.type,t,e.default)}resolveOptionValue(t,e,i){let n;const r=[t,...e.alias];for(const o of r)if(o in i){n=i[o];break}if(n===void 0){if(e.required)throw new C({option:t,reason:"Required option is missing"});return e.default}return w(n,e.type,t,e.default)}optionDefinitions(){const t={};for(const e in this.options)t[e]=g(this.options[e]);return t}argumentDefinitions(){const t={};for(const e in this.arguments)t[e]=g(this.arguments[e]);return t}availableOptions(){return Object.keys(this.options)}availableArguments(){return Object.keys(this.arguments)}disablePrompting(){return this.shouldPromptForMissingOptions=!1,this}async promptForArgument(t,e){if(!Array.isArray(e.type)&&!["string","number","secret"].includes(e.type))return null;let i=`${a.yellow.bold(t)} is required`;return e.description&&(i+=`: ${a.gray(`(${e.description})`)}`),i+=` ${a.green(`(${e.type}${e.variadic==!0?"[]":""})`)}
|
|
3
|
+
`,Array.isArray(e.type)?(i+=`Please provide one or more values, separated by commas:
|
|
4
|
+
`,await this.io.askForList(i,void 0,{separator:",",validate:n=>{if(!n.length)return"Please enter at least one value";if(e.type[0]==="number"){for(const r of n.split(","))if(isNaN(Number(r)))return"Please enter only valid numbers"}return!0}})):await this.io.askForInput(i,void 0,{type:e.type==="number"?"number":e.secret?"password":"text",validate:n=>{if(n==null||typeof n=="string"&&!n.length)return"This value is required";if(e.type==="number"){const r=Number(n);if(isNaN(r))return"Please enter a valid number"}else if(e.type==="string"&&(typeof n!="string"||!n.length))return"Please enter a valid text";return!0}})}}function O(s){return new Array(s+5).join(" ")}class L{type="boolean";option="help";alias=["h"];default=!1;description=`Display help for the given command. When no command is given display help for the ${a.green("list")} command`;async handler(){const t=this.parser.argumentDefinitions(),e=this.parser.optionDefinitions(),i=Object.entries(t),n=Object.entries(e),r=n.map(([l,d])=>{const c=Array.isArray(d.alias)?d.alias:d.alias?[d.alias]:[];return{name:l,...d,optionWithAlias:`--${l}${c.map(p=>`, -${p}`).join("")}`}}),o=i.filter(([,l])=>l.required);this.io.log(a.yellow("Description:")),this.io.log(` ${this.description}
|
|
5
|
+
`),this.io.log(a.yellow("Usage:")),this.io.log(` ${this.command} ${o.length>0?o.map(([l])=>`<${l}>`).join(" "):"\b"} [options]`);const u=Math.max(...r.map(l=>l.optionWithAlias.length),0),m=Math.max(...i.map(([l])=>l.length),0),h=m>u?m:u;if(i.length>0){this.io.log(`
|
|
6
|
+
${a.yellow("Arguments")}:`);for(const[l,d]of i){const c=O(h-l.length);let p=` ${a.green(l)} ${c} ${d.description??"\b"}`;if(d.default!==void 0&&!d.required){const D=(Array.isArray(d.type)?`[${d.type[0]}]`:d.type)==="array"||Array.isArray(d.type)?JSON.stringify(d.default):d.default;p+=` ${a.yellow(`[default: ${D}]`)}`}d.variadic&&(p+=` ${a.white("(variadic)")}`),this.io.log(p)}}if(n.length>0){this.io.log(`
|
|
7
|
+
${a.yellow("Options")}:`);for(const l of r){const d=O(h-l.optionWithAlias.length);let c=`${a.green(l.optionWithAlias)} ${d} ${l.description??"\b"}`;if(l.type){const p=Array.isArray(l.type)?`[${l.type[0]}]`:l.type;c+=` ${a.white(`(${p})`)}`}if(l.default!==void 0&&!l.required){const E=(Array.isArray(l.type)?`[${l.type[0]}]`:l.type)==="array"||Array.isArray(l.type)?JSON.stringify(l.default):l.default;c+=` ${a.yellow(`[default: ${E}]`)}`}this.io.log(c)}}if(this.commandsExamples.length>0){this.io.log(`
|
|
8
|
+
${a.yellow("Examples")}:`);let l=process.argv[0].split("/").pop();l==="node"&&(l+=" "+process.argv[1].split("/").pop());for(const[d,c]of this.commandsExamples.entries())d>0&&this.io.log(""),this.io.log(` ${c.description}
|
|
9
|
+
`),this.io.log(` ${a.green(`${l} ${c.command}`)}`)}return-1}}class v{_command;description;group;commandsExamples=[];get command(){return this._command}ctx;io;parser;disablePromptingFlag=!1;_preHandler;_handler;tmp;defaultOptions(){return[new L]}newCommandParser(t){return new x({io:t.io,options:t.options,arguments:t.arguments})}newCommandIO(t){return new R(t)}constructor(t,e){this._command=t,this.description=e?.description??"",this.group=e?.group,this.tmp={options:e?.options??{},arguments:e?.arguments??{}};const i=this.defaultOptions();if(i.length>0)for(const n of i)this.tmp.options[n.option]=n}disablePrompting(){return this.disablePromptingFlag=!0,this}preHandler(t){return this._preHandler=t,this}handler(t){return this._handler=t,this}options(t){return this.tmp={options:{...this.tmp?.options??{},...t},arguments:this.tmp?.arguments??{}},this}arguments(t){return this.tmp={options:this.tmp?.options??{},arguments:{...this.tmp?.arguments??{},...t}},this}async run(t){if(!this._handler&&!this.handle)throw new Error(`No handler defined for command ${this.command||"(unknown)"}`);let e;if(this.ctx=t.ctx,this.io=this.newCommandIO({logger:t.logger}),t&&"args"in t){const n=this.tmp?.options??{};for(const r of this.defaultOptions())r.option in n||(n[r.option]=r);this.parser=this.newCommandParser({io:this.io,options:n,arguments:this.tmp?.arguments??{}}),e=this.parser.init(t.args);for(const r of this.defaultOptions())if(e.options[r.option]===!0){const o=await r.handler.call(this);if(o&&o!==0)return o}this.disablePromptingFlag&&this.parser.disablePrompting(),await this.parser.validate()}else e={options:t.options,arguments:t.arguments};if(!this._preHandler&&this.preHandle&&(this._preHandler=this.preHandle.bind(this)),this._preHandler){const n=await this._preHandler(t.ctx,e);if(n&&n!==0)return n}if(!this._handler&&this.handle)this._handler=this.handle.bind(this);else if(!this._handler)throw new Error(`No handler defined for command ${this.command||"(unknown)"}`);return await this._handler(t.ctx,e)??0}}class $ extends x{command;constructor(t){const e=$.parseSignature(t.signature,t.helperDefinitions,t.defaultOptions);super({io:t.io,options:e.options,arguments:e.arguments}),this.command=e.command}static parseSignature(t,e,i){const[n,...r]=t.split(/\{(.*?)\}/g).map(m=>m.trim()).filter(Boolean),o={},u={};for(const m of r){const{name:h,isOption:l,definition:d}=$.parseParamSignature(m,e);l?o[h]=d:u[h]=d}for(const m of i)o[m.option]={type:m.type,required:m.required,alias:m.alias,variadic:m.variadic??!1,description:m.description,default:m.default??null};return{command:n,options:o,arguments:u}}static parseParamSignature(t,e){let i=t,n=!1;const r={required:!0,type:"string",description:void 0,default:null,variadic:!1};if(i.includes(":")){const[o,u]=i.split(":");i=o.trim(),r.description=u.trim()}if(i.includes("=")){const[o,u]=i.split("=");i=o.trim(),r.default=u.trim(),r.required=!1,typeof r.default=="string"&&!r.default.length?r.default=null:r.default==="true"?(r.default=!0,r.type="boolean"):r.default==="false"&&(r.default=!1,r.type="boolean")}else i.startsWith("--")&&(r.required=!1,r.default=!1,r.type="boolean");if(i.includes("|")){const[o,...u]=i.split("|");i=o.trim(),r.alias=u.map(m=>m.trim())}return i.startsWith("--")&&(n=!0,i=i.slice(2)),r.default==="*"&&(r.default=[],r.type=["string"]),i.endsWith("?")&&(r.required=!1,i=i.slice(0,-1)),i.endsWith("*")&&(r.type=["string"],r.variadic=!0,r.default=[],i=i.slice(0,-1)),r.description=r.description??e[i]??e[`--${i}`],{name:i,isOption:n,definition:r}}}class B extends v{helperDefinitions={};get command(){return this.parser?this.parser.command:this.signature.split(" ")[0]}newCommandParser(t){return new $({io:t.io,signature:this.signature,helperDefinitions:this.helperDefinitions,defaultOptions:this.defaultOptions()})}constructor(){super("")}option(t,e=null){return this.parser.option(t,e)}argument(t,e=null){return this.parser.argument(t,e)}async askForConfirmation(...t){return this.io.askForConfirmation(...t)}async askForInput(...t){return this.io.askForInput(...t)}async askForSelect(...t){return this.io.askForSelect(...t)}newLoader(...t){return this.io.newLoader(...t)}}class S{level;constructor(t={}){this.level=t.level??"info"}shouldLog(t){const e=["debug","info","warn","error"],i=e.indexOf(this.level);return e.indexOf(t)>=i}setLevel(t){this.level=t}getLevel(){return this.level}log(...t){console.log(...t)}info(...t){this.shouldLog("info")&&console.log(...t)}warn(...t){this.shouldLog("warn")&&console.warn(...t)}error(...t){this.shouldLog("error")&&console.error(...t)}debug(...t){this.shouldLog("debug")&&console.log(...t)}}class I{getBigrams(t){const e=[],i=t.toLowerCase();for(let n=0;n<i.length-1;n++)e.push(i.slice(n,n+2));return e}calculateSimilarity(t,e){if(t===e)return 1;if(t.length<2||e.length<2)return 0;const i=this.getBigrams(t),n=this.getBigrams(e),r=new Set(n);let o=0;for(const u of i)r.has(u)&&(o++,r.delete(u));return 2*o/(i.length+n.length)}findBestMatch(t,e){const i=e.map(o=>({target:o,rating:this.calculateSimilarity(t,o)}));let n=0,r=i[0]?.rating??0;for(let o=1;o<i.length;o++)i[o].rating>r&&(r=i[o].rating,n=o);return{ratings:i,bestMatch:i[n],bestMatchIndex:n}}}class V{commands={};io;logger;stringSimilarity;newCommandIO(t){return new R(t)}constructor(t){this.logger=t?.logger??new S,this.io=this.newCommandIO({logger:this.logger}),this.stringSimilarity=t?.stringSimilarity??new I}getAvailableCommands(){return Object.keys(this.commands)}getCommands(){return Object.values(this.commands)}importFile=async t=>(await import(t)).default;commandResolver=async t=>{let e=await this.importFile(t);return e?(e&&typeof e=="object"&&"default"in e&&(e=e.default),typeof e=="function"?new e:e instanceof v?e:null):null};withCommandResolver(t){return this.commandResolver=t,this}withFileImporter(t){return this.importFile=t,this}registerCommand(t,e=!1){const i=t.command;if(!i)throw new Error("Command signature is invalid, it must have a command name.");if(!e&&this.commands[i])throw new Error(`Command ${i} already registered.`);this.commands[i]=t}async loadCommandsPath(t){for await(const e of this.listCommandsFiles(t))try{const i=await this.commandResolver(e);i instanceof v&&this.registerCommand(i)}catch(i){throw new Error(`Command ${e} failed to load. ${i}`,{cause:i})}}async runCommand(t,e,...i){const n=typeof e=="string"?this.commands[e]:e,r=typeof e=="string"?e:n.command;if(!n){const o=await this.suggestCommand(r);return o?await this.runCommand(t,o,...i):1}return await n.run({ctx:t,logger:this.logger,args:i})??0}async suggestCommand(t){const e=this.getAvailableCommands(),{bestMatch:i,bestMatchIndex:n,ratings:r}=this.stringSimilarity.findBestMatch(t,e),o=r.filter(u=>u.rating>.3).map(u=>u.target);if(i.rating>0&&o.length<=1||i.rating>.7&&o.length>1){const u=e[n];return await this.askRunSimilarCommand(t,u)?u:null}if(o.length){this.io.error(`${a.bgRed(" ERROR ")} Command ${a.yellow(t)} not found.
|
|
10
|
+
`);const u=await this.io.askForSelect(a.green("Did you mean to run one of these commands instead?"),o);if(u)return u}throw new P(t)}async askRunSimilarCommand(t,e){return this.io.error(`${a.bgRed(" ERROR ")} Command ${a.yellow(t)} not found.
|
|
11
|
+
`),this.io.askForConfirmation(`${a.green(`Do you want to run ${a.yellow(e)} instead?`)} `)}async*listCommandsFiles(t){const e=j.readdirSync(t,{withFileTypes:!0});for(const i of e){const n=F.resolve(t,i.name);if(i.isDirectory())yield*this.listCommandsFiles(F.resolve(t,i.name));else{if(!n.endsWith(".ts")&&!n.endsWith(".js")&&!n.endsWith(".mjs")&&!n.endsWith(".cjs"))continue;yield n}}}}class H{logger;constructor(t){this.logger=t}handle(t){if(t instanceof f)return t.pretty(this.logger),-1;throw t}}class T extends v{constructor(t){super("help",{description:a.bold("Show help information about the CLI and its commands")}),this.opts=t}async handle(){const t=this.opts.commandRegistry.getCommands(),e=this.opts.cliName??"Bob CLI",i=this.opts.cliVersion??"0.0.0",n=(await Promise.resolve().then(()=>require("../package-Blqq-jZJ.cjs")))?.default?.version??"0.0.0";this.io.log(`${e} ${a.green(i)} (core: ${a.yellow(n)})
|
|
12
|
+
|
|
13
|
+
${a.yellow("Usage")}:
|
|
14
|
+
command [options] [arguments]
|
|
15
|
+
|
|
16
|
+
${a.yellow("Available commands")}:
|
|
17
|
+
`);const r=Math.max(...t.map(m=>m.command.length))??0,o={};for(const m of t){const h=m.group??m.command.split(":")[0];o[h]||(o[h]=[]),o[h].push(m)}const u=Object.entries(o).sort(([m],[h])=>m.toLowerCase().localeCompare(h.toLowerCase())).sort(([,m],[,h])=>m.length-h.length);for(const[m,h]of u){const l=h.length>1;l&&this.io.log(a.yellow(`${m}:`));const d=h.sort((c,p)=>c.command.toLowerCase().localeCompare(p.command.toLowerCase()));for(const c of d){let p=O(r-c.command.length);l&&(p=p.slice(2)),this.io.log(`${l?" ":""}${a.green(c.command)} ${p} ${c.description}`)}}}}class _{ctx;logger;commandRegistry;exceptionHandler;helpCommand;newCommandRegistry(t){return new V(t)}newHelpCommand(t){return new T(t)}newExceptionHandler(t){return new H(t.logger)}constructor(t={}){this.ctx=t.ctx,this.logger=t.logger??new S,this.commandRegistry=this.newCommandRegistry({logger:this.logger}),this.exceptionHandler=this.newExceptionHandler({logger:this.logger}),this.helpCommand=this.newHelpCommand({cliName:t.name,cliVersion:t.version,commandRegistry:this.commandRegistry})}withCommandResolver(t){return this.commandRegistry.withCommandResolver(t),this}withFileImporter(t){return this.commandRegistry.withFileImporter(t),this}async withCommands(...t){for(const e of t)typeof e=="string"?await this.commandRegistry.loadCommandsPath(e):typeof e=="function"?this.registerCommand(new e):this.registerCommand(e)}async runCommand(t,...e){return t?await this.commandRegistry.runCommand(this.ctx??{},t,...e).catch(this.exceptionHandler.handle.bind(this.exceptionHandler)):await this.runHelpCommand()}async runHelpCommand(){return await this.runCommand(this.helpCommand)}registerCommand(t){this.commandRegistry.registerCommand(t)}}exports.BadCommandOption=C;exports.BadCommandParameter=W;exports.BobError=f;exports.Cli=_;exports.Command=v;exports.CommandIO=R;exports.CommandNotFoundError=P;exports.CommandParser=x;exports.CommandRegistry=V;exports.CommandSignatureParser=$;exports.CommandWithSignature=B;exports.ExceptionHandler=H;exports.HelpOption=L;exports.InvalidOption=b;exports.Logger=S;exports.MissingRequiredArgumentValue=A;exports.MissingRequiredOptionValue=N;exports.StringSimilarity=I;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
export type OptionPrimitive = '
|
|
1
|
+
export type OptionPrimitive = 'string' | 'number' | 'boolean' | ['string'] | ['number'];
|
|
2
2
|
export type OptionDefinition = {
|
|
3
3
|
type: OptionPrimitive;
|
|
4
4
|
description?: string;
|
|
5
5
|
alias?: string | Array<string>;
|
|
6
6
|
required?: boolean;
|
|
7
|
+
secret?: boolean;
|
|
7
8
|
default?: any;
|
|
8
9
|
variadic?: boolean;
|
|
9
10
|
};
|
|
10
11
|
export type Option = OptionPrimitive | OptionDefinition;
|
|
11
|
-
export type OptionType<O extends Option> = O extends '
|
|
12
|
+
export type OptionType<O extends Option> = O extends 'string' ? string : O extends 'number' ? number : O extends 'boolean' ? boolean : O extends Array<'string'> ? Array<string> : O extends Array<'number'> ? Array<number> : O extends {
|
|
12
13
|
type: infer T extends Option;
|
|
13
14
|
} ? OptionType<T> : never;
|
|
14
15
|
export type IsRequired<O extends Option> = O extends {
|
|
@@ -27,3 +28,4 @@ export type ArgumentsSchema = {
|
|
|
27
28
|
export type ArgumentsObject<Arguments extends ArgumentsSchema> = {
|
|
28
29
|
[Key in keyof Arguments]: OptionReturnType<Arguments[Key]>;
|
|
29
30
|
};
|
|
31
|
+
export type ContextDefinition = any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CommandOption } from '../contracts/index.js';
|
|
2
1
|
import { Command } from '../Command.js';
|
|
2
|
+
import { CommandOption } from '../contracts/index.js';
|
|
3
3
|
import { OptionPrimitive } from '../lib/types.js';
|
|
4
4
|
export declare class HelpOption implements CommandOption<Command> {
|
|
5
5
|
type: OptionPrimitive;
|