gunshi 0.26.3 → 0.27.0-alpha.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/README.md +3 -2
- package/lib/bone.d.ts +36 -0
- package/lib/bone.js +18 -0
- package/lib/cli-CkDncXPO.js +107 -0
- package/lib/context-DZ06drtF.js +87 -0
- package/lib/context.d.ts +30 -13
- package/lib/context.js +2 -2
- package/lib/core-6RK6f9Jg.js +46 -0
- package/lib/core-ClAxkDQL.js +285 -0
- package/lib/definition-BHhWgThC.js +29 -0
- package/lib/definition-FnIxIQf2.d.ts +102 -0
- package/lib/definition.d.ts +3 -3
- package/lib/definition.js +1 -1
- package/lib/generator.d.ts +3 -8
- package/lib/generator.js +6 -4
- package/lib/index.d.ts +87 -21
- package/lib/index.js +50 -6
- package/lib/plugin.d.ts +2 -0
- package/lib/plugin.js +3 -0
- package/lib/renderer.d.ts +7 -16
- package/lib/renderer.js +3 -2
- package/lib/{renderer-BzRfaLdJ.js → src-Dgf8uyR8.js} +180 -47
- package/lib/types-ClnyW35e.d.ts +621 -0
- package/lib/utils-DPIUzHzX.js +52 -0
- package/lib/utils.d.ts +12 -0
- package/lib/utils.js +3 -0
- package/package.json +26 -10
- package/lib/cli-DVGNVw3h.js +0 -147
- package/lib/context-D_EmfRNA.js +0 -187
- package/lib/definition-CKKX4Yvy.d.ts +0 -26
- package/lib/definition-wq1Kmbvq.js +0 -29
- package/lib/locales/en-US.json +0 -15
- package/lib/locales/ja-JP.json +0 -15
- package/lib/types-DtEE4hKC.d.ts +0 -441
- package/lib/utils-D41C8Abf.js +0 -103
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img width="196" src="./assets/logo.png">
|
|
2
|
+
<img alt="logo" width="196" src="./assets/logo.png">
|
|
3
3
|
</p>
|
|
4
4
|
<h1 align="center">🏯 Gunshi</h1>
|
|
5
5
|
|
|
@@ -102,6 +102,7 @@ About more details and usage, see [documentations](https://gunshi.dev)
|
|
|
102
102
|
- [sourcemap-publisher](https://github.com/es-tooling/sourcemap-publisher): A tool to publish sourcemaps externally and rewrite sourcemap URLs at pre-publish time
|
|
103
103
|
- [curxy](https://github.com/ryoppippi/curxy): An proxy worker for using ollama in cursor
|
|
104
104
|
- [SiteMCP](https://github.com/ryoppippi/sitemcp): Fetch an entire site and use it as an MCP Server
|
|
105
|
+
- [ccusage](https://github.com/ryoppippi/ccusage): A CLI tool for analyzing Claude Code usage from local JSONL files.
|
|
105
106
|
|
|
106
107
|
## 🙌 Contributing guidelines
|
|
107
108
|
|
|
@@ -123,7 +124,7 @@ The development of Gunshi is supported by my OSS sponsors!
|
|
|
123
124
|
|
|
124
125
|
<p align="center">
|
|
125
126
|
<a href="https://cdn.jsdelivr.net/gh/kazupon/sponsors/sponsors.svg">
|
|
126
|
-
<img src='https://cdn.jsdelivr.net/gh/kazupon/sponsors/sponsors.svg'/>
|
|
127
|
+
<img alt="sponsor" src='https://cdn.jsdelivr.net/gh/kazupon/sponsors/sponsors.svg'/>
|
|
127
128
|
</a>
|
|
128
129
|
</p>
|
|
129
130
|
|
package/lib/bone.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ArgSchema, ArgToken, ArgValues, Args, Awaitable, CliOptions, Command, CommandCallMode, CommandContext, CommandContextCore, CommandContextExtension, CommandDecorator, CommandEnvironment, CommandExamplesFetcher, CommandLoader, CommandRunner, Commandable, DefaultGunshiParams, ExtendContext, ExtractArgs, ExtractExtensions, GunshiParams, GunshiParamsConstraint, LazyCommand, NormalizeToGunshiParams, RendererDecorator, ValidationErrorsDecorator } from "./types-ClnyW35e.js";
|
|
2
|
+
|
|
3
|
+
//#region src/cli/bone.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run the command.
|
|
7
|
+
* @param args Command line arguments
|
|
8
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
9
|
+
* @param options A {@link CliOptions | CLI options}
|
|
10
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
11
|
+
*/
|
|
12
|
+
declare function cli<A extends Args = Args, G extends GunshiParams = {
|
|
13
|
+
args: A;
|
|
14
|
+
extensions: {};
|
|
15
|
+
}>(argv: string[], entry: Command<G> | CommandRunner<G> | LazyCommand<G>, options?: CliOptions<G>): Promise<string | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Run the command.
|
|
18
|
+
* @param args Command line arguments
|
|
19
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
20
|
+
* @param options A {@link CliOptions | CLI options}
|
|
21
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
22
|
+
*/
|
|
23
|
+
declare function cli<E extends ExtendContext = ExtendContext, G extends GunshiParams = {
|
|
24
|
+
args: Args;
|
|
25
|
+
extensions: E;
|
|
26
|
+
}>(argv: string[], entry: Command<G> | CommandRunner<G> | LazyCommand<G>, options?: CliOptions<G>): Promise<string | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Run the command.
|
|
29
|
+
* @param args Command line arguments
|
|
30
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
31
|
+
* @param options A {@link CliOptions | CLI options}
|
|
32
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
33
|
+
*/
|
|
34
|
+
declare function cli<G extends GunshiParams = DefaultGunshiParams>(argv: string[], entry: Command<G> | CommandRunner<G> | LazyCommand<G>, options?: CliOptions<G>): Promise<string | undefined>;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ArgSchema, ArgToken, ArgValues, Args, Awaitable, CliOptions, Command, CommandCallMode, CommandContext, CommandContextCore, CommandContextExtension, CommandDecorator, CommandEnvironment, CommandExamplesFetcher, CommandLoader, CommandRunner, Commandable, DefaultGunshiParams, ExtendContext, ExtractArgs, ExtractExtensions, GunshiParams, GunshiParamsConstraint, LazyCommand, NormalizeToGunshiParams, RendererDecorator, ValidationErrorsDecorator, cli };
|
package/lib/bone.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "./context-DZ06drtF.js";
|
|
2
|
+
import "./utils-DPIUzHzX.js";
|
|
3
|
+
import { cliCore } from "./core-ClAxkDQL.js";
|
|
4
|
+
|
|
5
|
+
//#region src/cli/bone.ts
|
|
6
|
+
/**
|
|
7
|
+
* Run the command.
|
|
8
|
+
* @param args Command line arguments
|
|
9
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
10
|
+
* @param options A {@link CliOptions | CLI options}
|
|
11
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
12
|
+
*/
|
|
13
|
+
async function cli(argv, entry, options = {}) {
|
|
14
|
+
return cliCore(argv, entry, options, []);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { cli };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { cliCore } from "./core-ClAxkDQL.js";
|
|
2
|
+
import { plugin } from "./core-6RK6f9Jg.js";
|
|
3
|
+
import { COMMON_ARGS, namespacedId, renderer } from "./src-Dgf8uyR8.js";
|
|
4
|
+
|
|
5
|
+
//#region ../plugin-global/src/types.ts
|
|
6
|
+
/**
|
|
7
|
+
* The unique identifier for the global options plugin.
|
|
8
|
+
*/
|
|
9
|
+
const pluginId = namespacedId("global");
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region ../plugin-global/src/decorator.ts
|
|
13
|
+
/**
|
|
14
|
+
* Decorator function to extend the command with global options.
|
|
15
|
+
*/
|
|
16
|
+
const decorator = (baseRunner) => async (ctx) => {
|
|
17
|
+
const { values, validationError, extensions: { [pluginId]: { showVersion, showHeader, showUsage, showValidationErrors } } } = ctx;
|
|
18
|
+
if (values.version) return showVersion();
|
|
19
|
+
const buf = [];
|
|
20
|
+
const header = await showHeader();
|
|
21
|
+
if (header) buf.push(header);
|
|
22
|
+
if (values.help) {
|
|
23
|
+
const usage = await showUsage();
|
|
24
|
+
if (usage) {
|
|
25
|
+
buf.push(usage);
|
|
26
|
+
return buf.join("\n");
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (validationError) return await showValidationErrors(validationError);
|
|
31
|
+
return baseRunner(ctx);
|
|
32
|
+
};
|
|
33
|
+
var decorator_default = decorator;
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region ../plugin-global/src/extension.ts
|
|
37
|
+
function extension(ctx) {
|
|
38
|
+
return {
|
|
39
|
+
showVersion: () => {
|
|
40
|
+
const version = ctx.env.version || "unknown";
|
|
41
|
+
if (!ctx.env.usageSilent) ctx.log(version);
|
|
42
|
+
return version;
|
|
43
|
+
},
|
|
44
|
+
showHeader: async () => {
|
|
45
|
+
let header;
|
|
46
|
+
if (ctx.env.renderHeader != null) {
|
|
47
|
+
header = await ctx.env.renderHeader(ctx);
|
|
48
|
+
if (header) {
|
|
49
|
+
ctx.log(header);
|
|
50
|
+
ctx.log();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return header;
|
|
54
|
+
},
|
|
55
|
+
showUsage: async () => {
|
|
56
|
+
if (ctx.env.renderUsage != null) {
|
|
57
|
+
const usage = await ctx.env.renderUsage(ctx);
|
|
58
|
+
if (usage) {
|
|
59
|
+
ctx.log(usage);
|
|
60
|
+
return usage;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
showValidationErrors: async (error) => {
|
|
65
|
+
if (ctx.env.renderValidationErrors === null) return;
|
|
66
|
+
if (ctx.env.renderValidationErrors !== void 0) {
|
|
67
|
+
const message = await ctx.env.renderValidationErrors(ctx, error);
|
|
68
|
+
ctx.log(message);
|
|
69
|
+
return message;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region ../plugin-global/src/index.ts
|
|
77
|
+
/**
|
|
78
|
+
* global options plugin
|
|
79
|
+
*/
|
|
80
|
+
function global() {
|
|
81
|
+
return plugin({
|
|
82
|
+
id: pluginId,
|
|
83
|
+
name: "global options",
|
|
84
|
+
extension,
|
|
85
|
+
setup(ctx) {
|
|
86
|
+
for (const [name, schema] of Object.entries(COMMON_ARGS)) ctx.addGlobalOption(name, schema);
|
|
87
|
+
ctx.decorateCommand(decorator_default);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/cli/builtin.ts
|
|
94
|
+
/**
|
|
95
|
+
* Run the command.
|
|
96
|
+
* @param args Command line arguments
|
|
97
|
+
* @param entry A {@link Command | entry command}, an {@link CommandRunner | inline command runner}, or a {@link LazyCommand | lazily-loaded command}
|
|
98
|
+
* @param options A {@link CliOptions | CLI options}
|
|
99
|
+
* @returns A rendered usage or undefined. if you will use {@link CliOptions.usageSilent} option, it will return rendered usage string.
|
|
100
|
+
*/
|
|
101
|
+
async function cli(argv, entry, options = {}) {
|
|
102
|
+
const builtInPlugins = [global(), renderer()];
|
|
103
|
+
return cliCore(argv, entry, options, builtInPlugins);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { cli };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { create, deepFreeze, isLazyCommand, log } from "./utils-DPIUzHzX.js";
|
|
2
|
+
|
|
3
|
+
//#region src/constants.ts
|
|
4
|
+
const ANONYMOUS_COMMAND_NAME = "(anonymous)";
|
|
5
|
+
const NOOP = () => {};
|
|
6
|
+
const COMMAND_OPTIONS_DEFAULT = {
|
|
7
|
+
name: void 0,
|
|
8
|
+
description: void 0,
|
|
9
|
+
version: void 0,
|
|
10
|
+
cwd: void 0,
|
|
11
|
+
usageSilent: false,
|
|
12
|
+
subCommands: void 0,
|
|
13
|
+
leftMargin: 2,
|
|
14
|
+
middleMargin: 10,
|
|
15
|
+
usageOptionType: false,
|
|
16
|
+
usageOptionValue: true,
|
|
17
|
+
renderHeader: void 0,
|
|
18
|
+
renderUsage: void 0,
|
|
19
|
+
renderValidationErrors: void 0,
|
|
20
|
+
plugins: void 0
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/context.ts
|
|
25
|
+
/**
|
|
26
|
+
* Create a {@link CommandContext | command context}
|
|
27
|
+
* @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context}
|
|
28
|
+
* @returns A {@link CommandContext | command context}, which is readonly
|
|
29
|
+
*/
|
|
30
|
+
async function createCommandContext({ args, values, positionals, rest, argv, tokens, command, extensions = {}, cliOptions, callMode = "entry", omitted = false, validationError }) {
|
|
31
|
+
/**
|
|
32
|
+
* normailize the options schema and values, to avoid prototype pollution
|
|
33
|
+
*/
|
|
34
|
+
const _args = Object.entries(args).reduce((acc, [key, value]) => {
|
|
35
|
+
acc[key] = Object.assign(create(), value);
|
|
36
|
+
return acc;
|
|
37
|
+
}, create());
|
|
38
|
+
/**
|
|
39
|
+
* setup the environment
|
|
40
|
+
*/
|
|
41
|
+
const env = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, cliOptions);
|
|
42
|
+
/**
|
|
43
|
+
* create the command context
|
|
44
|
+
*/
|
|
45
|
+
const core = Object.assign(create(), {
|
|
46
|
+
name: getCommandName(command),
|
|
47
|
+
description: command.description,
|
|
48
|
+
omitted,
|
|
49
|
+
callMode,
|
|
50
|
+
env,
|
|
51
|
+
args: _args,
|
|
52
|
+
values,
|
|
53
|
+
positionals,
|
|
54
|
+
rest,
|
|
55
|
+
_: argv,
|
|
56
|
+
tokens,
|
|
57
|
+
toKebab: command.toKebab,
|
|
58
|
+
log: cliOptions.usageSilent ? NOOP : log,
|
|
59
|
+
validationError
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* extend the command context with extensions
|
|
63
|
+
*/
|
|
64
|
+
if (Object.keys(extensions).length > 0) {
|
|
65
|
+
const ext = create(null);
|
|
66
|
+
Object.defineProperty(core, "extensions", {
|
|
67
|
+
value: ext,
|
|
68
|
+
writable: false,
|
|
69
|
+
enumerable: true,
|
|
70
|
+
configurable: true
|
|
71
|
+
});
|
|
72
|
+
for (const [key, extension] of Object.entries(extensions)) {
|
|
73
|
+
ext[key] = await extension.factory(core, command);
|
|
74
|
+
if (extension.onFactory) await extension.onFactory(core, command);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const ctx = deepFreeze(core, ["extensions"]);
|
|
78
|
+
return ctx;
|
|
79
|
+
}
|
|
80
|
+
function getCommandName(cmd) {
|
|
81
|
+
if (isLazyCommand(cmd)) return cmd.commandName || cmd.name || ANONYMOUS_COMMAND_NAME;
|
|
82
|
+
else if (typeof cmd === "object") return cmd.name || ANONYMOUS_COMMAND_NAME;
|
|
83
|
+
else return ANONYMOUS_COMMAND_NAME;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { ANONYMOUS_COMMAND_NAME, COMMAND_OPTIONS_DEFAULT, NOOP, createCommandContext };
|
package/lib/context.d.ts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { CliOptions, Command, CommandCallMode, CommandContext, LazyCommand } from "./types-
|
|
2
|
-
import { ArgToken, ArgValues, Args } from "args-tokens";
|
|
1
|
+
import { ArgToken, ArgValues, CliOptions, Command, CommandCallMode, CommandContext, CommandContextExtension, DefaultGunshiParams, ExtendContext, ExtractArgs, GunshiParams, GunshiParamsConstraint, LazyCommand } from "./types-ClnyW35e.js";
|
|
3
2
|
|
|
4
3
|
//#region src/context.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Parameters of {@link createCommandContext}
|
|
7
|
-
*/
|
|
8
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Extract extension return types from extensions record
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
type ExtractExtensions<E extends Record<string, CommandContextExtension>> = { [K in keyof E]: E[K] extends CommandContextExtension<infer T> ? T : never };
|
|
9
10
|
/**
|
|
10
11
|
* Parameters of {@link createCommandContext}
|
|
11
12
|
*/
|
|
12
|
-
interface CommandContextParams<
|
|
13
|
+
interface CommandContextParams<G extends GunshiParams | {
|
|
14
|
+
extensions: ExtendContext;
|
|
15
|
+
}, V extends ArgValues<ExtractArgs<G>>, C extends Command<G> | LazyCommand<G> = Command<G>, E extends Record<string, CommandContextExtension> = Record<string, CommandContextExtension>> {
|
|
13
16
|
/**
|
|
14
17
|
* An arguments of target command
|
|
15
18
|
*/
|
|
16
|
-
args:
|
|
19
|
+
args: ExtractArgs<G>;
|
|
17
20
|
/**
|
|
18
21
|
* A values of target command
|
|
19
22
|
*/
|
|
@@ -45,18 +48,26 @@ interface CommandContextParams<A extends Args, V> {
|
|
|
45
48
|
/**
|
|
46
49
|
* A target command
|
|
47
50
|
*/
|
|
48
|
-
command:
|
|
51
|
+
command: C;
|
|
52
|
+
/**
|
|
53
|
+
* Plugin extensions to apply as the command context extension.
|
|
54
|
+
*/
|
|
55
|
+
extensions?: E;
|
|
49
56
|
/**
|
|
50
57
|
* A command options, which is spicialized from `cli` function
|
|
51
58
|
*/
|
|
52
|
-
cliOptions: CliOptions<
|
|
59
|
+
cliOptions: CliOptions<G>;
|
|
60
|
+
/**
|
|
61
|
+
* Validation error from argument parsing.
|
|
62
|
+
*/
|
|
63
|
+
validationError?: AggregateError;
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
66
|
* Create a {@link CommandContext | command context}
|
|
56
67
|
* @param param A {@link CommandContextParams | parameters} to create a {@link CommandContext | command context}
|
|
57
68
|
* @returns A {@link CommandContext | command context}, which is readonly
|
|
58
69
|
*/
|
|
59
|
-
declare function createCommandContext<
|
|
70
|
+
declare function createCommandContext<G extends GunshiParamsConstraint = DefaultGunshiParams, V extends ArgValues<ExtractArgs<G>> = ArgValues<ExtractArgs<G>>, C extends Command<G> | LazyCommand<G> = Command<G>, E extends Record<string, CommandContextExtension> = {}>({
|
|
60
71
|
args,
|
|
61
72
|
values,
|
|
62
73
|
positionals,
|
|
@@ -64,8 +75,14 @@ declare function createCommandContext<A extends Args = Args, V extends ArgValues
|
|
|
64
75
|
argv,
|
|
65
76
|
tokens,
|
|
66
77
|
command,
|
|
78
|
+
extensions,
|
|
67
79
|
cliOptions,
|
|
68
80
|
callMode,
|
|
69
|
-
omitted
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
omitted,
|
|
82
|
+
validationError
|
|
83
|
+
}: CommandContextParams<G, V, C, E>): Promise<{} extends ExtractExtensions<E> ? Readonly<CommandContext<G>> : Readonly<CommandContext<GunshiParams<{
|
|
84
|
+
args: ExtractArgs<G>;
|
|
85
|
+
extensions: ExtractExtensions<E>;
|
|
86
|
+
}>>>>;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { ExtractExtensions, createCommandContext };
|
package/lib/context.js
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/plugin/core.ts
|
|
2
|
+
/**
|
|
3
|
+
* Define a plugin
|
|
4
|
+
* @param options - {@link PluginOptions | plugin options}
|
|
5
|
+
* @returns A defined plugin.
|
|
6
|
+
* @since v0.27.0
|
|
7
|
+
*/
|
|
8
|
+
function plugin(options) {
|
|
9
|
+
const { id, name, setup, extension, onExtension, dependencies } = options;
|
|
10
|
+
const pluginFn = async (ctx) => {
|
|
11
|
+
if (setup) await setup(ctx);
|
|
12
|
+
};
|
|
13
|
+
return Object.defineProperties(pluginFn, {
|
|
14
|
+
id: {
|
|
15
|
+
value: id,
|
|
16
|
+
writable: false,
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true
|
|
19
|
+
},
|
|
20
|
+
...name && { name: {
|
|
21
|
+
value: name,
|
|
22
|
+
writable: false,
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true
|
|
25
|
+
} },
|
|
26
|
+
...dependencies && { dependencies: {
|
|
27
|
+
value: dependencies,
|
|
28
|
+
writable: false,
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true
|
|
31
|
+
} },
|
|
32
|
+
...extension && { extension: {
|
|
33
|
+
value: {
|
|
34
|
+
key: Symbol(id),
|
|
35
|
+
factory: extension,
|
|
36
|
+
onFactory: onExtension
|
|
37
|
+
},
|
|
38
|
+
writable: false,
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true
|
|
41
|
+
} }
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { plugin };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { ANONYMOUS_COMMAND_NAME, COMMAND_OPTIONS_DEFAULT, NOOP, createCommandContext } from "./context-DZ06drtF.js";
|
|
2
|
+
import { create, isLazyCommand, resolveLazyCommand } from "./utils-DPIUzHzX.js";
|
|
3
|
+
import { parseArgs, resolveArgs } from "args-tokens";
|
|
4
|
+
|
|
5
|
+
//#region src/decorators.ts
|
|
6
|
+
const EMPTY_RENDERER = async () => "";
|
|
7
|
+
/**
|
|
8
|
+
* Factory function for creating a decorators manager.
|
|
9
|
+
* @returns A new decorators manager instance
|
|
10
|
+
*/
|
|
11
|
+
function createDecorators() {
|
|
12
|
+
/**
|
|
13
|
+
* private states
|
|
14
|
+
*/
|
|
15
|
+
const headerDecorators = [];
|
|
16
|
+
const usageDecorators = [];
|
|
17
|
+
const validationDecorators = [];
|
|
18
|
+
const commandDecorators = [];
|
|
19
|
+
/**
|
|
20
|
+
* helper function for building renderers
|
|
21
|
+
*/
|
|
22
|
+
function buildRenderer(decorators, defaultRenderer) {
|
|
23
|
+
if (decorators.length === 0) return defaultRenderer;
|
|
24
|
+
let renderer = defaultRenderer;
|
|
25
|
+
for (const decorator of decorators) {
|
|
26
|
+
const previousRenderer = renderer;
|
|
27
|
+
renderer = (ctx) => decorator(previousRenderer, ctx);
|
|
28
|
+
}
|
|
29
|
+
return renderer;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* public interfaces
|
|
33
|
+
*/
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
addHeaderDecorator(decorator) {
|
|
36
|
+
headerDecorators.push(decorator);
|
|
37
|
+
},
|
|
38
|
+
addUsageDecorator(decorator) {
|
|
39
|
+
usageDecorators.push(decorator);
|
|
40
|
+
},
|
|
41
|
+
addValidationErrorsDecorator(decorator) {
|
|
42
|
+
validationDecorators.push(decorator);
|
|
43
|
+
},
|
|
44
|
+
addCommandDecorator(decorator) {
|
|
45
|
+
commandDecorators.push(decorator);
|
|
46
|
+
},
|
|
47
|
+
get commandDecorators() {
|
|
48
|
+
return [...commandDecorators];
|
|
49
|
+
},
|
|
50
|
+
getHeaderRenderer() {
|
|
51
|
+
return buildRenderer(headerDecorators, EMPTY_RENDERER);
|
|
52
|
+
},
|
|
53
|
+
getUsageRenderer() {
|
|
54
|
+
return buildRenderer(usageDecorators, EMPTY_RENDERER);
|
|
55
|
+
},
|
|
56
|
+
getValidationErrorsRenderer() {
|
|
57
|
+
if (validationDecorators.length === 0) return EMPTY_RENDERER;
|
|
58
|
+
let renderer = EMPTY_RENDERER;
|
|
59
|
+
for (const decorator of validationDecorators) {
|
|
60
|
+
const previousRenderer = renderer;
|
|
61
|
+
renderer = (ctx, error) => decorator(previousRenderer, ctx, error);
|
|
62
|
+
}
|
|
63
|
+
return renderer;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/plugin/context.ts
|
|
70
|
+
/**
|
|
71
|
+
* Factory function for creating a plugin context.
|
|
72
|
+
* @param decorators - A {@link Decorators} instance.
|
|
73
|
+
* @returns A new {@link PluginContext} instance.
|
|
74
|
+
*/
|
|
75
|
+
function createPluginContext(decorators) {
|
|
76
|
+
/**
|
|
77
|
+
* private states
|
|
78
|
+
*/
|
|
79
|
+
const globalOptions = new Map();
|
|
80
|
+
/**
|
|
81
|
+
* public interfaces
|
|
82
|
+
*/
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
get globalOptions() {
|
|
85
|
+
return new Map(globalOptions);
|
|
86
|
+
},
|
|
87
|
+
addGlobalOption(name, schema) {
|
|
88
|
+
if (!name) throw new Error("Option name must be a non-empty string");
|
|
89
|
+
if (globalOptions.has(name)) throw new Error(`Global option '${name}' is already registered`);
|
|
90
|
+
globalOptions.set(name, schema);
|
|
91
|
+
},
|
|
92
|
+
decorateHeaderRenderer(decorator) {
|
|
93
|
+
decorators.addHeaderDecorator(decorator);
|
|
94
|
+
},
|
|
95
|
+
decorateUsageRenderer(decorator) {
|
|
96
|
+
decorators.addUsageDecorator(decorator);
|
|
97
|
+
},
|
|
98
|
+
decorateValidationErrorsRenderer(decorator) {
|
|
99
|
+
decorators.addValidationErrorsDecorator(decorator);
|
|
100
|
+
},
|
|
101
|
+
decorateCommand(decorator) {
|
|
102
|
+
decorators.addCommandDecorator(decorator);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/plugin/dependency.ts
|
|
109
|
+
/**
|
|
110
|
+
* Resolve plugin dependencies using topological sort
|
|
111
|
+
* @param plugins - Array of plugins to resolve
|
|
112
|
+
* @returns Array of plugins sorted by dependencies
|
|
113
|
+
* @throws Error if circular dependency is detected or required dependency is missing
|
|
114
|
+
*/
|
|
115
|
+
function resolveDependencies(plugins) {
|
|
116
|
+
const sorted = [];
|
|
117
|
+
const visited = new Set();
|
|
118
|
+
const visiting = new Set();
|
|
119
|
+
const pluginMap = new Map();
|
|
120
|
+
for (const plugin of plugins) if (plugin.id) {
|
|
121
|
+
if (pluginMap.has(plugin.id)) console.warn(`Duplicate plugin id detected: \`${plugin.id}\``);
|
|
122
|
+
pluginMap.set(plugin.id, plugin);
|
|
123
|
+
}
|
|
124
|
+
function visit(plugin) {
|
|
125
|
+
if (!plugin.id) return;
|
|
126
|
+
if (visited.has(plugin.id)) return;
|
|
127
|
+
if (visiting.has(plugin.id)) throw new Error(`Circular dependency detected: \`${[...visiting].join(` -> `) + " -> " + plugin.id}\``);
|
|
128
|
+
visiting.add(plugin.id);
|
|
129
|
+
const deps = plugin.dependencies || [];
|
|
130
|
+
for (const dep of deps) {
|
|
131
|
+
const depId = typeof dep === "string" ? dep : dep.id;
|
|
132
|
+
const isOptional = typeof dep === "string" ? false : dep.optional || false;
|
|
133
|
+
const depPlugin = pluginMap.get(depId);
|
|
134
|
+
if (!depPlugin && !isOptional) throw new Error(`Missing required dependency: \`${depId}\` on \`${plugin.id}\``);
|
|
135
|
+
if (depPlugin) visit(depPlugin);
|
|
136
|
+
}
|
|
137
|
+
visiting.delete(plugin.id);
|
|
138
|
+
visited.add(plugin.id);
|
|
139
|
+
sorted.push(plugin);
|
|
140
|
+
}
|
|
141
|
+
for (const plugin of plugins) visit(plugin);
|
|
142
|
+
return sorted;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/cli/core.ts
|
|
147
|
+
async function cliCore(argv, entry, options, plugins) {
|
|
148
|
+
const decorators = createDecorators();
|
|
149
|
+
const pluginContext = createPluginContext(decorators);
|
|
150
|
+
const resolvedPlugins = await applyPlugins(pluginContext, [...plugins, ...options.plugins || []]);
|
|
151
|
+
const cliOptions = normalizeCliOptions(options, entry, decorators);
|
|
152
|
+
const tokens = parseArgs(argv);
|
|
153
|
+
const subCommand = getSubCommand(tokens);
|
|
154
|
+
const { commandName: name, command, callMode } = await resolveCommand(subCommand, entry, cliOptions);
|
|
155
|
+
if (!command) throw new Error(`Command not found: ${name || ""}`);
|
|
156
|
+
const args = resolveArguments(pluginContext, getCommandArgs(command));
|
|
157
|
+
const { values, positionals, rest, error } = resolveArgs(args, tokens, {
|
|
158
|
+
shortGrouping: true,
|
|
159
|
+
toKebab: command.toKebab,
|
|
160
|
+
skipPositional: cliOptions.subCommands.size > 0 ? 0 : -1
|
|
161
|
+
});
|
|
162
|
+
const omitted = !subCommand;
|
|
163
|
+
const commandContext = await createCommandContext({
|
|
164
|
+
args,
|
|
165
|
+
values,
|
|
166
|
+
positionals,
|
|
167
|
+
rest,
|
|
168
|
+
argv,
|
|
169
|
+
tokens,
|
|
170
|
+
omitted,
|
|
171
|
+
callMode,
|
|
172
|
+
command,
|
|
173
|
+
extensions: getPluginExtensions(resolvedPlugins),
|
|
174
|
+
validationError: error,
|
|
175
|
+
cliOptions
|
|
176
|
+
});
|
|
177
|
+
return await executeCommand(command, commandContext, name || "", decorators.commandDecorators);
|
|
178
|
+
}
|
|
179
|
+
async function applyPlugins(pluginContext, plugins) {
|
|
180
|
+
const sortedPlugins = resolveDependencies(plugins);
|
|
181
|
+
try {
|
|
182
|
+
for (const plugin of sortedPlugins)
|
|
183
|
+
/**
|
|
184
|
+
* NOTE(kazupon):
|
|
185
|
+
* strictly `Args` are not required for plugin installation.
|
|
186
|
+
* because the strictly `Args` required by each plugin are unknown,
|
|
187
|
+
* and the plugin side can not know what the user will specify.
|
|
188
|
+
*/
|
|
189
|
+
await plugin(pluginContext);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error("Error loading plugin:", error.message);
|
|
192
|
+
}
|
|
193
|
+
return sortedPlugins;
|
|
194
|
+
}
|
|
195
|
+
function getCommandArgs(cmd) {
|
|
196
|
+
if (isLazyCommand(cmd)) return cmd.args || create();
|
|
197
|
+
else if (typeof cmd === "object") return cmd.args || create();
|
|
198
|
+
else return create();
|
|
199
|
+
}
|
|
200
|
+
function resolveArguments(pluginContext, args) {
|
|
201
|
+
return Object.assign(create(), Object.fromEntries(pluginContext.globalOptions), args);
|
|
202
|
+
}
|
|
203
|
+
function normalizeCliOptions(options, entry, decorators) {
|
|
204
|
+
const subCommands = new Map(options.subCommands);
|
|
205
|
+
if (options.subCommands) {
|
|
206
|
+
if (isLazyCommand(entry)) subCommands.set(entry.commandName, entry);
|
|
207
|
+
else if (typeof entry === "object" && entry.name) subCommands.set(entry.name, entry);
|
|
208
|
+
}
|
|
209
|
+
const resolvedOptions = Object.assign(create(), COMMAND_OPTIONS_DEFAULT, options, { subCommands });
|
|
210
|
+
if (resolvedOptions.renderHeader === void 0) resolvedOptions.renderHeader = decorators.getHeaderRenderer();
|
|
211
|
+
if (resolvedOptions.renderUsage === void 0) resolvedOptions.renderUsage = decorators.getUsageRenderer();
|
|
212
|
+
if (resolvedOptions.renderValidationErrors === void 0) resolvedOptions.renderValidationErrors = decorators.getValidationErrorsRenderer();
|
|
213
|
+
return resolvedOptions;
|
|
214
|
+
}
|
|
215
|
+
function getSubCommand(tokens) {
|
|
216
|
+
const firstToken = tokens[0];
|
|
217
|
+
return firstToken && firstToken.kind === "positional" && firstToken.index === 0 && firstToken.value ? firstToken.value : "";
|
|
218
|
+
}
|
|
219
|
+
const CANNOT_RESOLVE_COMMAND = { callMode: "unexpected" };
|
|
220
|
+
async function resolveCommand(sub, entry, options) {
|
|
221
|
+
const omitted = !sub;
|
|
222
|
+
async function doResolveCommand() {
|
|
223
|
+
if (typeof entry === "function") if ("commandName" in entry && entry.commandName) return {
|
|
224
|
+
commandName: entry.commandName,
|
|
225
|
+
command: entry,
|
|
226
|
+
callMode: "entry"
|
|
227
|
+
};
|
|
228
|
+
else return {
|
|
229
|
+
command: { run: entry },
|
|
230
|
+
callMode: "entry"
|
|
231
|
+
};
|
|
232
|
+
else if (typeof entry === "object") return {
|
|
233
|
+
commandName: resolveEntryName(entry),
|
|
234
|
+
command: entry,
|
|
235
|
+
callMode: "entry"
|
|
236
|
+
};
|
|
237
|
+
else return CANNOT_RESOLVE_COMMAND;
|
|
238
|
+
}
|
|
239
|
+
if (omitted || options.subCommands?.size === 0) return doResolveCommand();
|
|
240
|
+
const cmd = options.subCommands?.get(sub);
|
|
241
|
+
if (cmd == null) return {
|
|
242
|
+
commandName: sub,
|
|
243
|
+
callMode: "unexpected"
|
|
244
|
+
};
|
|
245
|
+
if (isLazyCommand(cmd) && cmd.commandName == null) cmd.commandName = sub;
|
|
246
|
+
else if (typeof cmd === "object" && cmd.name == null) cmd.name = sub;
|
|
247
|
+
return {
|
|
248
|
+
commandName: sub,
|
|
249
|
+
command: cmd,
|
|
250
|
+
callMode: "subCommand"
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function resolveEntryName(entry) {
|
|
254
|
+
return entry.name || ANONYMOUS_COMMAND_NAME;
|
|
255
|
+
}
|
|
256
|
+
function getPluginExtensions(plugins) {
|
|
257
|
+
const extensions = create();
|
|
258
|
+
for (const plugin of plugins) if (plugin.extension) {
|
|
259
|
+
const key = plugin.id;
|
|
260
|
+
if (extensions[key]) console.warn(`Plugin "${key}" is already installed. ignore it for command context extending.`);
|
|
261
|
+
else extensions[key] = plugin.extension;
|
|
262
|
+
}
|
|
263
|
+
return extensions;
|
|
264
|
+
}
|
|
265
|
+
async function executeCommand(cmd, ctx, name, decorators) {
|
|
266
|
+
const resolved = isLazyCommand(cmd) ? await resolveLazyCommand(cmd, name, true) : cmd;
|
|
267
|
+
const baseRunner = resolved.run || NOOP;
|
|
268
|
+
const decoratedRunner = decorators.reduceRight((runner, decorator) => decorator(runner), baseRunner);
|
|
269
|
+
try {
|
|
270
|
+
if (ctx.env.onBeforeCommand) await ctx.env.onBeforeCommand(ctx);
|
|
271
|
+
const result = await decoratedRunner(ctx);
|
|
272
|
+
if (ctx.env.onAfterCommand) await ctx.env.onAfterCommand(ctx, result);
|
|
273
|
+
return typeof result === "string" ? result : void 0;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (ctx.env.onErrorCommand) try {
|
|
276
|
+
await ctx.env.onErrorCommand(ctx, error);
|
|
277
|
+
} catch (hookError) {
|
|
278
|
+
console.error("Error in onErrorCommand hook:", hookError);
|
|
279
|
+
}
|
|
280
|
+
throw error;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
//#endregion
|
|
285
|
+
export { cliCore };
|