@runium/cli 0.0.2 → 0.0.3
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/.eslintrc.json +31 -0
- package/.prettierrc.json +10 -0
- package/README.md +3 -0
- package/build.js +125 -0
- package/lib/app.js +6 -0
- package/{commands → lib/commands}/index.js +0 -0
- package/lib/commands/plugin/plugin-add.js +1 -0
- package/lib/commands/plugin/plugin-disable.js +1 -0
- package/lib/commands/plugin/plugin-enable.js +1 -0
- package/lib/commands/plugin/plugin-remove.js +1 -0
- package/lib/commands/plugin/plugin.js +1 -0
- package/lib/commands/project/project-add.js +1 -0
- package/lib/commands/project/project-command.js +1 -0
- package/lib/commands/project/project-start.js +1 -0
- package/lib/commands/project/project-state-command.js +1 -0
- package/lib/commands/project/project-status.js +1 -0
- package/lib/commands/project/project-stop.js +1 -0
- package/lib/commands/project/project-validate.js +4 -0
- package/lib/commands/project/project.js +1 -0
- package/lib/commands/runium-command.js +1 -0
- package/lib/constants/error-code.js +1 -0
- package/{constants → lib/constants}/index.js +0 -0
- package/lib/index.js +2 -0
- package/lib/macros/date.js +1 -0
- package/lib/macros/index.js +1 -0
- package/lib/macros/path.js +1 -0
- package/lib/package.json +22 -0
- package/lib/services/command.js +1 -0
- package/lib/services/config.js +1 -0
- package/lib/services/file.js +1 -0
- package/lib/services/index.js +1 -0
- package/lib/services/output.js +3 -0
- package/lib/services/plugin-context.js +1 -0
- package/lib/services/plugin.js +1 -0
- package/lib/services/profile.js +1 -0
- package/lib/services/project.js +1 -0
- package/lib/services/shutdown.js +1 -0
- package/lib/utils/get-version.js +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/validation/create-validator.js +1 -0
- package/lib/validation/get-config-schema.js +1 -0
- package/lib/validation/get-error-messages.js +1 -0
- package/lib/validation/get-plugin-schema.js +1 -0
- package/lib/validation/index.js +1 -0
- package/package.json +33 -7
- package/src/app.ts +190 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/plugin/plugin-add.ts +48 -0
- package/src/commands/plugin/plugin-command.ts +36 -0
- package/src/commands/plugin/plugin-disable.ts +46 -0
- package/src/commands/plugin/plugin-enable.ts +50 -0
- package/src/commands/plugin/plugin-list.ts +61 -0
- package/src/commands/plugin/plugin-remove.ts +42 -0
- package/src/commands/plugin/plugin.ts +36 -0
- package/src/commands/project/project-add.ts +64 -0
- package/src/commands/project/project-command.ts +43 -0
- package/src/commands/project/project-list.ts +32 -0
- package/src/commands/project/project-remove.ts +41 -0
- package/src/commands/project/project-start.ts +158 -0
- package/src/commands/project/project-state-command.ts +53 -0
- package/src/commands/project/project-status.ts +116 -0
- package/src/commands/project/project-stop.ts +59 -0
- package/src/commands/project/project-validate.ts +56 -0
- package/src/commands/project/project.ts +40 -0
- package/src/commands/runium-command.ts +52 -0
- package/src/constants/error-code.ts +28 -0
- package/src/constants/index.ts +1 -0
- package/src/global.d.ts +6 -0
- package/src/index.ts +24 -0
- package/src/macros/conditional.ts +31 -0
- package/src/macros/date.ts +15 -0
- package/src/macros/empty.ts +6 -0
- package/src/macros/env.ts +8 -0
- package/src/macros/index.ts +17 -0
- package/src/macros/path.ts +24 -0
- package/src/services/command.ts +171 -0
- package/src/services/config.ts +119 -0
- package/src/services/file.ts +272 -0
- package/src/services/index.ts +9 -0
- package/src/services/output.ts +205 -0
- package/src/services/plugin-context.ts +140 -0
- package/src/services/plugin.ts +248 -0
- package/src/services/profile.ts +199 -0
- package/src/services/project.ts +142 -0
- package/src/services/shutdown.ts +147 -0
- package/src/utils/convert-path-to-valid-file-name.ts +39 -0
- package/src/utils/debounce.ts +23 -0
- package/src/utils/format-timestamp.ts +17 -0
- package/src/utils/get-version.ts +13 -0
- package/src/utils/index.ts +4 -0
- package/src/validation/create-validator.ts +27 -0
- package/src/validation/get-config-schema.ts +59 -0
- package/src/validation/get-error-messages.ts +35 -0
- package/src/validation/get-plugin-schema.ts +137 -0
- package/src/validation/index.ts +4 -0
- package/tsconfig.json +38 -0
- package/app.js +0 -6
- package/commands/plugin/plugin-add.js +0 -1
- package/commands/plugin/plugin-disable.js +0 -1
- package/commands/plugin/plugin-enable.js +0 -1
- package/commands/plugin/plugin-remove.js +0 -1
- package/commands/plugin/plugin.js +0 -1
- package/commands/project/project-add.js +0 -1
- package/commands/project/project-command.js +0 -1
- package/commands/project/project-start.js +0 -1
- package/commands/project/project-state-command.js +0 -1
- package/commands/project/project-status.js +0 -1
- package/commands/project/project-stop.js +0 -1
- package/commands/project/project-validate.js +0 -1
- package/commands/project/project.js +0 -1
- package/commands/runium-command.js +0 -1
- package/constants/error-code.js +0 -1
- package/index.js +0 -2
- package/macros/index.js +0 -1
- package/macros/path.js +0 -1
- package/services/config.js +0 -1
- package/services/index.js +0 -1
- package/services/output.js +0 -3
- package/services/plugin-context.js +0 -1
- package/services/plugin.js +0 -1
- package/services/profile.js +0 -1
- package/services/project.js +0 -1
- package/services/shutdown.js +0 -1
- package/utils/index.js +0 -1
- /package/{commands → lib/commands}/plugin/plugin-command.js +0 -0
- /package/{commands → lib/commands}/plugin/plugin-list.js +0 -0
- /package/{commands → lib/commands}/project/project-list.js +0 -0
- /package/{commands → lib/commands}/project/project-remove.js +0 -0
- /package/{macros → lib/macros}/conditional.js +0 -0
- /package/{macros → lib/macros}/empty.js +0 -0
- /package/{macros → lib/macros}/env.js +0 -0
- /package/{utils → lib/utils}/convert-path-to-valid-file-name.js +0 -0
- /package/{utils → lib/utils}/debounce.js +0 -0
- /package/{utils → lib/utils}/format-timestamp.js +0 -0
package/src/app.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { Container } from 'typedi';
|
|
5
|
+
import { RuniumError } from '@runium/core';
|
|
6
|
+
import * as commands from '@commands';
|
|
7
|
+
import {
|
|
8
|
+
CommandService,
|
|
9
|
+
ConfigService,
|
|
10
|
+
ProfileService,
|
|
11
|
+
PluginService,
|
|
12
|
+
ShutdownService,
|
|
13
|
+
OutputService,
|
|
14
|
+
OutputLevel,
|
|
15
|
+
PluginContextService,
|
|
16
|
+
} from '@services';
|
|
17
|
+
import { getVersion } from '@utils';
|
|
18
|
+
|
|
19
|
+
const RUNIUM_DESCRIPTION = `╔═══════════════════════╗
|
|
20
|
+
║ ╔═╗ ╦ ╦ ╔╗╔ ╦ ╦ ╦ ╔╦╗ ║
|
|
21
|
+
║ ╠╦╝ ║ ║ ║║║ ║ ║ ║ ║║║ ║
|
|
22
|
+
║ ╩╚═ ╚═╝ ╝╚╝ ╩ ╚═╝ ╩ ╩ ║
|
|
23
|
+
╚═══════════════════════╝
|
|
24
|
+
One Tool to Run Them All!`;
|
|
25
|
+
|
|
26
|
+
export class RuniumCliApp {
|
|
27
|
+
private readonly program: Command;
|
|
28
|
+
|
|
29
|
+
private commandService: CommandService;
|
|
30
|
+
private configService: ConfigService;
|
|
31
|
+
private profileService: ProfileService;
|
|
32
|
+
private pluginService: PluginService;
|
|
33
|
+
private shutdownService: ShutdownService;
|
|
34
|
+
private outputService: OutputService;
|
|
35
|
+
private pluginContextService: PluginContextService;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.program = new Command('runium');
|
|
39
|
+
this.configService = Container.get(ConfigService);
|
|
40
|
+
this.profileService = Container.get(ProfileService);
|
|
41
|
+
this.pluginService = Container.get(PluginService);
|
|
42
|
+
this.shutdownService = Container.get(ShutdownService);
|
|
43
|
+
this.outputService = Container.get(OutputService);
|
|
44
|
+
this.pluginContextService = Container.get(PluginContextService);
|
|
45
|
+
this.commandService = Container.get(CommandService);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start the application
|
|
50
|
+
*/
|
|
51
|
+
async start(): Promise<Command> {
|
|
52
|
+
await this.configService.init().catch(error => {
|
|
53
|
+
this.initOutput();
|
|
54
|
+
throw error;
|
|
55
|
+
});
|
|
56
|
+
await this.shutdownService.init();
|
|
57
|
+
await this.profileService.init();
|
|
58
|
+
await this.pluginContextService.init();
|
|
59
|
+
|
|
60
|
+
this.initOutput();
|
|
61
|
+
this.initEnv();
|
|
62
|
+
|
|
63
|
+
await this.loadPlugins();
|
|
64
|
+
await this.initProgram();
|
|
65
|
+
await this.initPlugins();
|
|
66
|
+
|
|
67
|
+
return this.program.parseAsync();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load plugins
|
|
72
|
+
*/
|
|
73
|
+
private async loadPlugins(): Promise<void> {
|
|
74
|
+
const plugins = this.profileService.getPlugins();
|
|
75
|
+
for (const plugin of plugins) {
|
|
76
|
+
if (plugin.disabled !== true) {
|
|
77
|
+
try {
|
|
78
|
+
const pluginPath = this.pluginService.resolvePath(
|
|
79
|
+
plugin.path,
|
|
80
|
+
plugin.file
|
|
81
|
+
);
|
|
82
|
+
await this.pluginService.loadPlugin(pluginPath, plugin.options);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.outputService.error(`Failed to load plugin "${plugin.name}"`);
|
|
85
|
+
const { code, message, payload } = error as RuniumError;
|
|
86
|
+
this.outputService.debug('Error details:', {
|
|
87
|
+
message,
|
|
88
|
+
code,
|
|
89
|
+
payload,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the program
|
|
98
|
+
*/
|
|
99
|
+
private async initProgram(): Promise<void> {
|
|
100
|
+
const program = this.program;
|
|
101
|
+
|
|
102
|
+
program.option('-D, --debug', 'enable debug mode');
|
|
103
|
+
program.option('-E, --env [paths...]', 'load env files');
|
|
104
|
+
program.version(getVersion());
|
|
105
|
+
program.description(RUNIUM_DESCRIPTION);
|
|
106
|
+
|
|
107
|
+
program.on('option:debug', () => {
|
|
108
|
+
this.setDebugMode();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
program.on('option:env', path => {
|
|
112
|
+
this.loadEnvFiles([path]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
Object.values(commands).forEach(CommandConstructor => {
|
|
116
|
+
this.commandService.registerCommand(CommandConstructor, program);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Initialize the output
|
|
122
|
+
*/
|
|
123
|
+
private initOutput(): void {
|
|
124
|
+
const output = this.configService.get('output');
|
|
125
|
+
if (
|
|
126
|
+
output.debug ||
|
|
127
|
+
process.argv.includes('-D') ||
|
|
128
|
+
process.argv.includes('--debug')
|
|
129
|
+
) {
|
|
130
|
+
this.setDebugMode();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Set debug mode
|
|
136
|
+
*/
|
|
137
|
+
private setDebugMode(): void {
|
|
138
|
+
if (this.outputService.getLevel() !== OutputLevel.DEBUG) {
|
|
139
|
+
this.outputService.setLevel(OutputLevel.DEBUG);
|
|
140
|
+
this.outputService.debug('Debug mode enabled');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Initialize the environment
|
|
146
|
+
*/
|
|
147
|
+
private initEnv(): void {
|
|
148
|
+
const env = this.configService.get('env');
|
|
149
|
+
if (env.path.length > 0) {
|
|
150
|
+
this.loadEnvFiles(env.path);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Set environment variables
|
|
156
|
+
* @param path
|
|
157
|
+
*/
|
|
158
|
+
private loadEnvFiles(path: string[]): void {
|
|
159
|
+
for (const p of path) {
|
|
160
|
+
const envPath = resolve((p && p.trim()) || '.env');
|
|
161
|
+
if (existsSync(envPath)) {
|
|
162
|
+
process.loadEnvFile(envPath);
|
|
163
|
+
} else {
|
|
164
|
+
this.outputService.error(`Env file "${envPath}" not found`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Initialize plugins
|
|
171
|
+
*/
|
|
172
|
+
private async initPlugins(): Promise<void> {
|
|
173
|
+
const plugins = this.pluginService.getAllPlugins();
|
|
174
|
+
for (const plugin of plugins) {
|
|
175
|
+
const commands = plugin.app?.commands;
|
|
176
|
+
if (commands) {
|
|
177
|
+
for (const command of commands) {
|
|
178
|
+
this.commandService.registerCommand(
|
|
179
|
+
command,
|
|
180
|
+
this.program,
|
|
181
|
+
plugin.name
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
await this.pluginService.runHook('app.afterInit', {
|
|
187
|
+
profilePath: this.configService.get('profile').path,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RuniumError } from '@runium/core';
|
|
2
|
+
import { ErrorCode } from '@constants';
|
|
3
|
+
import { PluginCommand } from './plugin-command.js';
|
|
4
|
+
/**
|
|
5
|
+
* Plugin add command
|
|
6
|
+
*/
|
|
7
|
+
export class PluginAddCommand extends PluginCommand {
|
|
8
|
+
/**
|
|
9
|
+
* Config command
|
|
10
|
+
*/
|
|
11
|
+
protected config(): void {
|
|
12
|
+
this.command
|
|
13
|
+
.name('add')
|
|
14
|
+
.description('add plugin')
|
|
15
|
+
.option('-f, --file', 'use file path instead of plugin package name')
|
|
16
|
+
.argument('<plugin>', 'plugin package name or absolute file path');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handle command
|
|
21
|
+
* @param path
|
|
22
|
+
* @param file
|
|
23
|
+
*/
|
|
24
|
+
protected async handle(
|
|
25
|
+
path: string,
|
|
26
|
+
{ file: isFile }: { file: boolean }
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
const pluginPath = this.pluginService.resolvePath(path, isFile);
|
|
29
|
+
const name = await this.pluginService.loadPlugin(pluginPath, {});
|
|
30
|
+
const plugin = this.pluginService.getPluginByName(name);
|
|
31
|
+
if (plugin) {
|
|
32
|
+
await this.profileService.addPlugin({
|
|
33
|
+
name,
|
|
34
|
+
path: isFile ? pluginPath : path,
|
|
35
|
+
file: isFile,
|
|
36
|
+
disabled: false,
|
|
37
|
+
options: plugin.options ?? {},
|
|
38
|
+
});
|
|
39
|
+
this.outputService.success(`Plugin "%s" successfully added`, name);
|
|
40
|
+
} else {
|
|
41
|
+
throw new RuniumError(
|
|
42
|
+
`Failed to add plugin "${path}"`,
|
|
43
|
+
ErrorCode.PLUGIN_NOT_FOUND,
|
|
44
|
+
{ name, path }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { Container } from 'typedi';
|
|
3
|
+
import { RuniumError } from '@runium/core';
|
|
4
|
+
import { RuniumCommand } from '@commands/runium-command.js';
|
|
5
|
+
import { ErrorCode } from '@constants';
|
|
6
|
+
import { PluginService, ProfileService, ProfilePlugin } from '@services';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base plugin command
|
|
10
|
+
*/
|
|
11
|
+
export abstract class PluginCommand extends RuniumCommand {
|
|
12
|
+
protected pluginService: PluginService;
|
|
13
|
+
protected profileService: ProfileService;
|
|
14
|
+
|
|
15
|
+
constructor(parent: Command) {
|
|
16
|
+
super(parent);
|
|
17
|
+
this.pluginService = Container.get(PluginService);
|
|
18
|
+
this.profileService = Container.get(ProfileService);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensure plugin exists
|
|
23
|
+
* @param name
|
|
24
|
+
*/
|
|
25
|
+
protected ensureProfilePlugin(name: string): ProfilePlugin {
|
|
26
|
+
const plugin = this.profileService.getPluginByName(name);
|
|
27
|
+
if (!plugin) {
|
|
28
|
+
throw new RuniumError(
|
|
29
|
+
`Plugin "${name}" not found`,
|
|
30
|
+
ErrorCode.PLUGIN_NOT_FOUND,
|
|
31
|
+
{ name }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return plugin;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PluginCommand } from './plugin-command.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin disable command
|
|
5
|
+
*/
|
|
6
|
+
export class PluginDisableCommand extends PluginCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Config command
|
|
9
|
+
*/
|
|
10
|
+
protected config(): void {
|
|
11
|
+
this.command
|
|
12
|
+
.name('disable')
|
|
13
|
+
.description('disable plugin')
|
|
14
|
+
.option('-a, --all', 'disable all plugins')
|
|
15
|
+
.argument('[name...]', 'plugin names');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle command
|
|
20
|
+
* @param names
|
|
21
|
+
* @param all
|
|
22
|
+
*/
|
|
23
|
+
protected async handle(
|
|
24
|
+
names: string[],
|
|
25
|
+
{ all }: { all: boolean }
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
if (names.length === 0 && !all) {
|
|
28
|
+
this.outputService.warn('No plugins specified to disable');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (all) {
|
|
32
|
+
names = this.profileService.getPlugins().map(p => p.name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
const plugin = this.ensureProfilePlugin(name);
|
|
37
|
+
if (plugin.disabled) {
|
|
38
|
+
this.outputService.info(`Plugin "%s" is already disabled`, name);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
await this.profileService.updatePlugin(name, { disabled: true });
|
|
42
|
+
await this.pluginService.unloadPlugin(name);
|
|
43
|
+
this.outputService.success(`Plugin "%s" successfully disabled`, name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PluginCommand } from './plugin-command.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin enable command
|
|
5
|
+
*/
|
|
6
|
+
export class PluginEnableCommand extends PluginCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Config command
|
|
9
|
+
*/
|
|
10
|
+
protected config(): void {
|
|
11
|
+
this.command
|
|
12
|
+
.name('enable')
|
|
13
|
+
.description('enable plugin')
|
|
14
|
+
.option('-a, --all', 'enable all plugins')
|
|
15
|
+
.argument('[name...]', 'plugin names');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle command
|
|
20
|
+
* @param names
|
|
21
|
+
* @param all
|
|
22
|
+
*/
|
|
23
|
+
protected async handle(
|
|
24
|
+
names: string[],
|
|
25
|
+
{ all }: { all: boolean }
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
if (names.length === 0 && !all) {
|
|
28
|
+
this.outputService.warn('No plugins specified to enable');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (all) {
|
|
32
|
+
names = this.profileService.getPlugins().map(p => p.name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
const plugin = this.ensureProfilePlugin(name);
|
|
37
|
+
if (!plugin.disabled) {
|
|
38
|
+
this.outputService.info(`Plugin "%s" is already enabled`, name);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
await this.profileService.updatePlugin(name, { disabled: false });
|
|
42
|
+
const pluginPath = this.pluginService.resolvePath(
|
|
43
|
+
plugin.path,
|
|
44
|
+
plugin.file
|
|
45
|
+
);
|
|
46
|
+
await this.pluginService.loadPlugin(pluginPath, plugin.options);
|
|
47
|
+
this.outputService.success(`Plugin "%s" successfully enabled`, name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
2
|
+
import { PluginCommand } from './plugin-command.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plugin list command
|
|
6
|
+
*/
|
|
7
|
+
export class PluginListCommand extends PluginCommand {
|
|
8
|
+
/**
|
|
9
|
+
* Config command
|
|
10
|
+
*/
|
|
11
|
+
protected config(): void {
|
|
12
|
+
this.command
|
|
13
|
+
.name('list')
|
|
14
|
+
.addOption(
|
|
15
|
+
new Option('-d, --disabled', 'show only disabled plugins').conflicts(
|
|
16
|
+
'enabled'
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
.addOption(
|
|
20
|
+
new Option('-e, --enabled', 'show only enabled plugins').conflicts(
|
|
21
|
+
'disabled'
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
.option('-s, --sort', 'sort by name')
|
|
25
|
+
.description('list plugins');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Handle command
|
|
30
|
+
*/
|
|
31
|
+
protected async handle({
|
|
32
|
+
disabled,
|
|
33
|
+
enabled,
|
|
34
|
+
sort,
|
|
35
|
+
}: {
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
sort: boolean;
|
|
39
|
+
}): Promise<void> {
|
|
40
|
+
let plugins = this.profileService.getPlugins();
|
|
41
|
+
if (disabled) {
|
|
42
|
+
plugins = plugins.filter(p => p.disabled === true);
|
|
43
|
+
}
|
|
44
|
+
if (enabled) {
|
|
45
|
+
plugins = plugins.filter(p => p.disabled === false);
|
|
46
|
+
}
|
|
47
|
+
if (sort) {
|
|
48
|
+
plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (plugins.length !== 0) {
|
|
52
|
+
this.outputService.table(plugins, [
|
|
53
|
+
'name',
|
|
54
|
+
'path',
|
|
55
|
+
...(disabled || enabled ? [] : ['disabled']),
|
|
56
|
+
]);
|
|
57
|
+
} else {
|
|
58
|
+
this.outputService.warn('No plugins found');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PluginCommand } from './plugin-command.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin remove command
|
|
5
|
+
*/
|
|
6
|
+
export class PluginRemoveCommand extends PluginCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Config command
|
|
9
|
+
*/
|
|
10
|
+
protected config(): void {
|
|
11
|
+
this.command
|
|
12
|
+
.name('remove')
|
|
13
|
+
.description('remove plugin')
|
|
14
|
+
.option('-a, --all', 'remove all plugins')
|
|
15
|
+
.argument('[name...]', 'plugin names');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle command
|
|
20
|
+
* @param names
|
|
21
|
+
* @param all
|
|
22
|
+
*/
|
|
23
|
+
protected async handle(
|
|
24
|
+
names: string[],
|
|
25
|
+
{ all }: { all: boolean }
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
if (names.length === 0 && !all) {
|
|
28
|
+
this.outputService.warn('No plugins specified to remove');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (all) {
|
|
32
|
+
names = this.profileService.getPlugins().map(p => p.name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
this.ensureProfilePlugin(name);
|
|
37
|
+
await this.profileService.removePlugin(name);
|
|
38
|
+
await this.pluginService.unloadPlugin(name);
|
|
39
|
+
this.outputService.success(`Plugin "%s" successfully removed`, name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RuniumCommand } from '@commands/runium-command.js';
|
|
2
|
+
import { PluginAddCommand } from './plugin-add.js';
|
|
3
|
+
import { PluginDisableCommand } from './plugin-disable.js';
|
|
4
|
+
import { PluginEnableCommand } from './plugin-enable.js';
|
|
5
|
+
import { PluginListCommand } from './plugin-list.js';
|
|
6
|
+
import { PluginRemoveCommand } from './plugin-remove.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Plugin group command
|
|
10
|
+
*/
|
|
11
|
+
export class PluginCommand extends RuniumCommand {
|
|
12
|
+
/**
|
|
13
|
+
* Subcommands
|
|
14
|
+
*/
|
|
15
|
+
subcommands = [
|
|
16
|
+
PluginListCommand,
|
|
17
|
+
PluginAddCommand,
|
|
18
|
+
PluginRemoveCommand,
|
|
19
|
+
PluginDisableCommand,
|
|
20
|
+
PluginEnableCommand,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Config command
|
|
25
|
+
*/
|
|
26
|
+
protected config(): void {
|
|
27
|
+
this.command.name('plugin').description('manage plugins');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handle command
|
|
32
|
+
*/
|
|
33
|
+
protected async handle(): Promise<void> {
|
|
34
|
+
this.command.help();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ID_REGEX, RuniumError } from '@runium/core';
|
|
2
|
+
import { ErrorCode } from '@constants';
|
|
3
|
+
import { ProjectCommand } from './project-command.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Project add command
|
|
7
|
+
*/
|
|
8
|
+
export class ProjectAddCommand extends ProjectCommand {
|
|
9
|
+
/**
|
|
10
|
+
* Config command
|
|
11
|
+
*/
|
|
12
|
+
protected config(): void {
|
|
13
|
+
this.command
|
|
14
|
+
.name('add')
|
|
15
|
+
.description('add project')
|
|
16
|
+
.argument('<path>', 'project file path')
|
|
17
|
+
.argument('[name]', 'project name (default: project config id)');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Handle command
|
|
22
|
+
* @param path
|
|
23
|
+
* @param name
|
|
24
|
+
*/
|
|
25
|
+
protected async handle(path: string, name?: string): Promise<void> {
|
|
26
|
+
const projectPath = this.projectService.resolvePath(path);
|
|
27
|
+
const project = await this.projectService.initProject(projectPath);
|
|
28
|
+
if (project) {
|
|
29
|
+
const projectName = name ?? project.getConfig().id;
|
|
30
|
+
|
|
31
|
+
this.validateProjectName(projectName);
|
|
32
|
+
|
|
33
|
+
await this.profileService.addProject({
|
|
34
|
+
name: projectName,
|
|
35
|
+
path: projectPath,
|
|
36
|
+
});
|
|
37
|
+
this.outputService.success(
|
|
38
|
+
`Project "%s" successfully added`,
|
|
39
|
+
projectName
|
|
40
|
+
);
|
|
41
|
+
} else {
|
|
42
|
+
throw new RuniumError(
|
|
43
|
+
`Failed to add project "${projectPath}"`,
|
|
44
|
+
ErrorCode.PROJECT_NOT_FOUND,
|
|
45
|
+
{ path: projectPath }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validate project name
|
|
52
|
+
* @param name - project name to validate
|
|
53
|
+
* @throws RuniumError if name contains invalid characters
|
|
54
|
+
*/
|
|
55
|
+
private validateProjectName(name: string): void {
|
|
56
|
+
if (!ID_REGEX.test(name)) {
|
|
57
|
+
throw new RuniumError(
|
|
58
|
+
`Invalid project name "${name}". Only letters, digits, underscores (_), and hyphens (-) are allowed.`,
|
|
59
|
+
ErrorCode.INVALID_ARGUMENT,
|
|
60
|
+
{ name }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { Container } from 'typedi';
|
|
3
|
+
import { RuniumError } from '@runium/core';
|
|
4
|
+
import { RuniumCommand } from '@commands/runium-command.js';
|
|
5
|
+
import { ErrorCode } from '@constants';
|
|
6
|
+
import {
|
|
7
|
+
ProfileService,
|
|
8
|
+
ProjectService,
|
|
9
|
+
ProfileProject,
|
|
10
|
+
FileService,
|
|
11
|
+
} from '@services';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base project command
|
|
15
|
+
*/
|
|
16
|
+
export abstract class ProjectCommand extends RuniumCommand {
|
|
17
|
+
protected projectService: ProjectService;
|
|
18
|
+
protected profileService: ProfileService;
|
|
19
|
+
protected fileService: FileService;
|
|
20
|
+
|
|
21
|
+
constructor(parent: Command) {
|
|
22
|
+
super(parent);
|
|
23
|
+
this.projectService = Container.get(ProjectService);
|
|
24
|
+
this.profileService = Container.get(ProfileService);
|
|
25
|
+
this.fileService = Container.get(FileService);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Ensure project exists
|
|
30
|
+
* @param name
|
|
31
|
+
*/
|
|
32
|
+
protected ensureProfileProject(name: string): ProfileProject {
|
|
33
|
+
const project = this.profileService.getProjectByName(name);
|
|
34
|
+
if (!project) {
|
|
35
|
+
throw new RuniumError(
|
|
36
|
+
`Project "${name}" not found`,
|
|
37
|
+
ErrorCode.PROJECT_NOT_FOUND,
|
|
38
|
+
{ name }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return project;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ProjectCommand } from './project-command.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Project list command
|
|
5
|
+
*/
|
|
6
|
+
export class ProjectListCommand extends ProjectCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Config command
|
|
9
|
+
*/
|
|
10
|
+
protected config(): void {
|
|
11
|
+
this.command
|
|
12
|
+
.name('list')
|
|
13
|
+
.description('list projects')
|
|
14
|
+
.option('-s, --sort', 'sort by name');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handle command
|
|
19
|
+
*/
|
|
20
|
+
protected async handle({ sort }: { sort: boolean }): Promise<void> {
|
|
21
|
+
const projects = this.profileService.getProjects();
|
|
22
|
+
if (sort) {
|
|
23
|
+
projects.sort((a, b) => a.name.localeCompare(b.name));
|
|
24
|
+
}
|
|
25
|
+
// handle empty list
|
|
26
|
+
if (projects.length !== 0) {
|
|
27
|
+
this.outputService.table(projects, ['name', 'path']);
|
|
28
|
+
} else {
|
|
29
|
+
this.outputService.warn('No projects found');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ProjectCommand } from './project-command.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Project remove command
|
|
5
|
+
*/
|
|
6
|
+
export class ProjectRemoveCommand extends ProjectCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Config command
|
|
9
|
+
*/
|
|
10
|
+
protected config(): void {
|
|
11
|
+
this.command
|
|
12
|
+
.name('remove')
|
|
13
|
+
.description('remove project')
|
|
14
|
+
.option('-a, --all', 'remove all projects')
|
|
15
|
+
.argument('[name...]', 'project names');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle command
|
|
20
|
+
* @param names
|
|
21
|
+
* @param all
|
|
22
|
+
*/
|
|
23
|
+
protected async handle(
|
|
24
|
+
names: string[],
|
|
25
|
+
{ all }: { all: boolean }
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
if (names.length === 0 && !all) {
|
|
28
|
+
this.outputService.warn('No projects specified to remove');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (all) {
|
|
32
|
+
names = this.profileService.getProjects().map(p => p.name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
this.ensureProfileProject(name);
|
|
37
|
+
await this.profileService.removeProject(name);
|
|
38
|
+
this.outputService.success(`Project "%s" successfully removed`, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|