@runium/cli 0.0.1 → 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/build.js +21 -0
- package/lib/app.js +2 -2
- package/lib/commands/plugin/plugin-add.js +1 -1
- package/lib/commands/plugin/plugin-disable.js +1 -1
- package/lib/commands/plugin/plugin-enable.js +1 -1
- package/lib/commands/plugin/plugin-remove.js +1 -1
- package/lib/commands/plugin/plugin.js +1 -1
- package/lib/commands/project/project-add.js +1 -1
- package/lib/commands/project/project-command.js +1 -1
- package/lib/commands/project/project-start.js +1 -1
- package/lib/commands/project/project-state-command.js +1 -1
- package/lib/commands/project/project-status.js +1 -1
- package/lib/commands/project/project-stop.js +1 -1
- package/lib/commands/project/project-validate.js +4 -1
- package/lib/commands/project/project.js +1 -1
- package/lib/commands/runium-command.js +1 -1
- package/lib/constants/error-code.js +1 -1
- package/lib/index.js +1 -1
- package/lib/macros/date.js +1 -0
- package/lib/macros/index.js +1 -1
- package/lib/macros/path.js +1 -1
- package/lib/package.json +3 -2
- package/lib/services/command.js +1 -0
- package/lib/services/config.js +1 -1
- package/lib/services/file.js +1 -0
- package/lib/services/index.js +1 -1
- package/lib/services/output.js +3 -3
- package/lib/services/plugin-context.js +1 -1
- package/lib/services/plugin.js +1 -1
- package/lib/services/profile.js +1 -1
- package/lib/services/project.js +1 -1
- package/lib/services/shutdown.js +1 -1
- package/lib/utils/get-version.js +1 -0
- package/lib/utils/index.js +1 -1
- 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 +5 -2
- package/src/app.ts +35 -20
- package/src/commands/plugin/plugin-add.ts +2 -2
- package/src/commands/plugin/plugin-disable.ts +1 -1
- package/src/commands/plugin/plugin-enable.ts +2 -2
- package/src/commands/plugin/plugin-remove.ts +1 -1
- package/src/commands/plugin/plugin.ts +11 -16
- package/src/commands/project/project-add.ts +23 -5
- package/src/commands/project/project-command.ts +8 -1
- package/src/commands/project/project-start.ts +18 -12
- package/src/commands/project/project-state-command.ts +4 -19
- package/src/commands/project/project-status.ts +16 -3
- package/src/commands/project/project-stop.ts +5 -1
- package/src/commands/project/project-validate.ts +23 -10
- package/src/commands/project/project.ts +13 -18
- package/src/commands/runium-command.ts +18 -16
- package/src/constants/error-code.ts +15 -2
- package/src/global.d.ts +6 -0
- package/src/index.ts +5 -2
- package/src/macros/date.ts +15 -0
- package/src/macros/index.ts +6 -1
- package/src/macros/path.ts +17 -2
- package/src/services/command.ts +171 -0
- package/src/services/config.ts +47 -4
- package/src/services/file.ts +272 -0
- package/src/services/index.ts +2 -0
- package/src/services/output.ts +5 -1
- package/src/services/plugin-context.ts +68 -9
- package/src/services/plugin.ts +122 -18
- package/src/services/profile.ts +37 -49
- package/src/services/project.ts +31 -3
- package/src/services/shutdown.ts +25 -8
- package/src/utils/get-version.ts +13 -0
- package/src/utils/index.ts +1 -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 +8 -10
|
@@ -26,7 +26,7 @@ export class PluginAddCommand extends PluginCommand {
|
|
|
26
26
|
{ file: isFile }: { file: boolean }
|
|
27
27
|
): Promise<void> {
|
|
28
28
|
const pluginPath = this.pluginService.resolvePath(path, isFile);
|
|
29
|
-
const name = await this.pluginService.loadPlugin(pluginPath);
|
|
29
|
+
const name = await this.pluginService.loadPlugin(pluginPath, {});
|
|
30
30
|
const plugin = this.pluginService.getPluginByName(name);
|
|
31
31
|
if (plugin) {
|
|
32
32
|
await this.profileService.addPlugin({
|
|
@@ -34,7 +34,7 @@ export class PluginAddCommand extends PluginCommand {
|
|
|
34
34
|
path: isFile ? pluginPath : path,
|
|
35
35
|
file: isFile,
|
|
36
36
|
disabled: false,
|
|
37
|
-
options: {},
|
|
37
|
+
options: plugin.options ?? {},
|
|
38
38
|
});
|
|
39
39
|
this.outputService.success(`Plugin "%s" successfully added`, name);
|
|
40
40
|
} else {
|
|
@@ -12,7 +12,7 @@ export class PluginDisableCommand extends PluginCommand {
|
|
|
12
12
|
.name('disable')
|
|
13
13
|
.description('disable plugin')
|
|
14
14
|
.option('-a, --all', 'disable all plugins')
|
|
15
|
-
.argument('[
|
|
15
|
+
.argument('[name...]', 'plugin names');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -12,7 +12,7 @@ export class PluginEnableCommand extends PluginCommand {
|
|
|
12
12
|
.name('enable')
|
|
13
13
|
.description('enable plugin')
|
|
14
14
|
.option('-a, --all', 'enable all plugins')
|
|
15
|
-
.argument('[
|
|
15
|
+
.argument('[name...]', 'plugin names');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -43,7 +43,7 @@ export class PluginEnableCommand extends PluginCommand {
|
|
|
43
43
|
plugin.path,
|
|
44
44
|
plugin.file
|
|
45
45
|
);
|
|
46
|
-
await this.pluginService.loadPlugin(pluginPath);
|
|
46
|
+
await this.pluginService.loadPlugin(pluginPath, plugin.options);
|
|
47
47
|
this.outputService.success(`Plugin "%s" successfully enabled`, name);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -9,6 +9,17 @@ import { PluginRemoveCommand } from './plugin-remove.js';
|
|
|
9
9
|
* Plugin group command
|
|
10
10
|
*/
|
|
11
11
|
export class PluginCommand extends RuniumCommand {
|
|
12
|
+
/**
|
|
13
|
+
* Subcommands
|
|
14
|
+
*/
|
|
15
|
+
subcommands = [
|
|
16
|
+
PluginListCommand,
|
|
17
|
+
PluginAddCommand,
|
|
18
|
+
PluginRemoveCommand,
|
|
19
|
+
PluginDisableCommand,
|
|
20
|
+
PluginEnableCommand,
|
|
21
|
+
];
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* Config command
|
|
14
25
|
*/
|
|
@@ -22,20 +33,4 @@ export class PluginCommand extends RuniumCommand {
|
|
|
22
33
|
protected async handle(): Promise<void> {
|
|
23
34
|
this.command.help();
|
|
24
35
|
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Add subcommands
|
|
28
|
-
*/
|
|
29
|
-
protected addSubcommands(): void {
|
|
30
|
-
const constructors = [
|
|
31
|
-
PluginListCommand,
|
|
32
|
-
PluginAddCommand,
|
|
33
|
-
PluginRemoveCommand,
|
|
34
|
-
PluginDisableCommand,
|
|
35
|
-
PluginEnableCommand,
|
|
36
|
-
];
|
|
37
|
-
for (const CommandConstructor of constructors) {
|
|
38
|
-
this.subcommands.push(new CommandConstructor(this.command));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
36
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { RuniumError } from '@runium/core';
|
|
1
|
+
import { ID_REGEX, RuniumError } from '@runium/core';
|
|
2
2
|
import { ErrorCode } from '@constants';
|
|
3
3
|
import { ProjectCommand } from './project-command.js';
|
|
4
|
+
|
|
4
5
|
/**
|
|
5
6
|
* Project add command
|
|
6
7
|
*/
|
|
@@ -25,15 +26,17 @@ export class ProjectAddCommand extends ProjectCommand {
|
|
|
25
26
|
const projectPath = this.projectService.resolvePath(path);
|
|
26
27
|
const project = await this.projectService.initProject(projectPath);
|
|
27
28
|
if (project) {
|
|
29
|
+
const projectName = name ?? project.getConfig().id;
|
|
30
|
+
|
|
31
|
+
this.validateProjectName(projectName);
|
|
32
|
+
|
|
28
33
|
await this.profileService.addProject({
|
|
29
|
-
|
|
30
|
-
// optionally save project file to profile
|
|
31
|
-
name: name ?? project.getConfig().id,
|
|
34
|
+
name: projectName,
|
|
32
35
|
path: projectPath,
|
|
33
36
|
});
|
|
34
37
|
this.outputService.success(
|
|
35
38
|
`Project "%s" successfully added`,
|
|
36
|
-
|
|
39
|
+
projectName
|
|
37
40
|
);
|
|
38
41
|
} else {
|
|
39
42
|
throw new RuniumError(
|
|
@@ -43,4 +46,19 @@ export class ProjectAddCommand extends ProjectCommand {
|
|
|
43
46
|
);
|
|
44
47
|
}
|
|
45
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
|
+
}
|
|
46
64
|
}
|
|
@@ -3,7 +3,12 @@ import { Container } from 'typedi';
|
|
|
3
3
|
import { RuniumError } from '@runium/core';
|
|
4
4
|
import { RuniumCommand } from '@commands/runium-command.js';
|
|
5
5
|
import { ErrorCode } from '@constants';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ProfileService,
|
|
8
|
+
ProjectService,
|
|
9
|
+
ProfileProject,
|
|
10
|
+
FileService,
|
|
11
|
+
} from '@services';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Base project command
|
|
@@ -11,11 +16,13 @@ import { ProfileService, ProjectService, ProfileProject } from '@services';
|
|
|
11
16
|
export abstract class ProjectCommand extends RuniumCommand {
|
|
12
17
|
protected projectService: ProjectService;
|
|
13
18
|
protected profileService: ProfileService;
|
|
19
|
+
protected fileService: FileService;
|
|
14
20
|
|
|
15
21
|
constructor(parent: Command) {
|
|
16
22
|
super(parent);
|
|
17
23
|
this.projectService = Container.get(ProjectService);
|
|
18
24
|
this.profileService = Container.get(ProfileService);
|
|
25
|
+
this.fileService = Container.get(FileService);
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
/**
|
|
@@ -9,17 +9,15 @@ import {
|
|
|
9
9
|
TaskState,
|
|
10
10
|
} from '@runium/core';
|
|
11
11
|
import { ErrorCode } from '@constants';
|
|
12
|
-
import { ShutdownService } from '@services';
|
|
13
|
-
import { debounce } from '@utils';
|
|
12
|
+
import { AtomicWriter, ShutdownService } from '@services';
|
|
14
13
|
import { ProjectData, ProjectStateCommand } from './project-state-command.js';
|
|
15
14
|
|
|
16
|
-
const WRITE_PROJECT_DATA_DEBOUNCE_WAIT = 100;
|
|
17
|
-
|
|
18
15
|
/**
|
|
19
16
|
* Project start command
|
|
20
17
|
*/
|
|
21
18
|
export class ProjectStartCommand extends ProjectStateCommand {
|
|
22
19
|
protected shutdownService: ShutdownService;
|
|
20
|
+
protected fileWriter: AtomicWriter | null = null;
|
|
23
21
|
|
|
24
22
|
constructor(parent: Command) {
|
|
25
23
|
super(parent);
|
|
@@ -63,9 +61,13 @@ export class ProjectStartCommand extends ProjectStateCommand {
|
|
|
63
61
|
: this.ensureProfileProject(name).path;
|
|
64
62
|
|
|
65
63
|
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
64
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
65
|
+
'projects',
|
|
66
|
+
projectDataFileName
|
|
67
|
+
);
|
|
66
68
|
|
|
67
69
|
// check if project is started
|
|
68
|
-
const projectData = await this.readProjectData(
|
|
70
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
69
71
|
if (projectData && this.isProjectProcessStarted(projectData.pid)) {
|
|
70
72
|
throw new RuniumError(
|
|
71
73
|
`Project "${name}" is already started`,
|
|
@@ -82,14 +84,18 @@ export class ProjectStartCommand extends ProjectStateCommand {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
const project = await this.projectService.initProject(path);
|
|
85
|
-
this.shutdownService.addBlocker(() => project.stop());
|
|
87
|
+
this.shutdownService.addBlocker((reason?: string) => project.stop(reason));
|
|
88
|
+
|
|
89
|
+
await this.fileService.ensureDirExists(dirname(projectDataFilePath));
|
|
90
|
+
this.fileWriter = this.fileService.createAtomicWriter(projectDataFilePath);
|
|
86
91
|
|
|
87
92
|
this.addProjectListeners(project, {
|
|
88
|
-
dataFileName: projectDataFileName,
|
|
89
93
|
projectPath: path,
|
|
90
94
|
output,
|
|
91
95
|
});
|
|
92
96
|
|
|
97
|
+
await this.projectService.runHook('project.beforeStart', project);
|
|
98
|
+
|
|
93
99
|
await project.start();
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -100,9 +106,9 @@ export class ProjectStartCommand extends ProjectStateCommand {
|
|
|
100
106
|
*/
|
|
101
107
|
protected addProjectListeners(
|
|
102
108
|
project: Project,
|
|
103
|
-
options: {
|
|
109
|
+
options: { projectPath: string; output?: boolean }
|
|
104
110
|
): void {
|
|
105
|
-
const {
|
|
111
|
+
const { projectPath, output } = options;
|
|
106
112
|
|
|
107
113
|
const projectData: ProjectData = {
|
|
108
114
|
id: project.getConfig().id,
|
|
@@ -115,9 +121,9 @@ export class ProjectStartCommand extends ProjectStateCommand {
|
|
|
115
121
|
},
|
|
116
122
|
};
|
|
117
123
|
|
|
118
|
-
const writeProjectData =
|
|
119
|
-
this.
|
|
120
|
-
}
|
|
124
|
+
const writeProjectData = () => {
|
|
125
|
+
this.fileWriter!.writeJson(projectData).then();
|
|
126
|
+
};
|
|
121
127
|
|
|
122
128
|
// write initial data
|
|
123
129
|
writeProjectData();
|
|
@@ -31,34 +31,19 @@ export abstract class ProjectStateCommand extends ProjectCommand {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Read project data
|
|
34
|
-
* @param
|
|
34
|
+
* @param path
|
|
35
35
|
*/
|
|
36
|
-
protected async readProjectData(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return this.profileService
|
|
40
|
-
.readJsonFile('projects', fileName)
|
|
36
|
+
protected async readProjectData(path: string): Promise<ProjectData | null> {
|
|
37
|
+
return this.fileService
|
|
38
|
+
.readJson(path)
|
|
41
39
|
.catch(() => null) as Promise<ProjectData | null>;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
/**
|
|
45
|
-
* Write project data
|
|
46
|
-
* @param data
|
|
47
|
-
* @param fileName
|
|
48
|
-
*/
|
|
49
|
-
protected async writeProjectData(
|
|
50
|
-
data: ProjectData,
|
|
51
|
-
fileName: string
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
return this.profileService.writeJsonFile(data, 'projects', fileName);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
42
|
/**
|
|
57
43
|
* Checks if a project process is started
|
|
58
44
|
* @param pid
|
|
59
45
|
*/
|
|
60
46
|
protected isProjectProcessStarted(pid: number): boolean {
|
|
61
|
-
// and process started
|
|
62
47
|
try {
|
|
63
48
|
return process.kill(Number(pid), 0);
|
|
64
49
|
} catch (ex) {
|
|
@@ -6,6 +6,8 @@ interface StateRecord {
|
|
|
6
6
|
status: string;
|
|
7
7
|
time: string;
|
|
8
8
|
timestamp: number;
|
|
9
|
+
reason?: string;
|
|
10
|
+
exitCode?: number;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -20,7 +22,7 @@ export class ProjectStatusCommand extends ProjectStateCommand {
|
|
|
20
22
|
.name('status')
|
|
21
23
|
.description('get project status')
|
|
22
24
|
.option('-f, --file', 'use file path instead of project name')
|
|
23
|
-
.option('-t, --tasks', 'show
|
|
25
|
+
.option('-t, --tasks', 'show tasks status')
|
|
24
26
|
.option('-a, --all', 'show status change history')
|
|
25
27
|
.argument('<name>', 'project name');
|
|
26
28
|
}
|
|
@@ -41,8 +43,12 @@ export class ProjectStatusCommand extends ProjectStateCommand {
|
|
|
41
43
|
: this.ensureProfileProject(name).path;
|
|
42
44
|
|
|
43
45
|
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
46
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
47
|
+
'projects',
|
|
48
|
+
projectDataFileName
|
|
49
|
+
);
|
|
44
50
|
|
|
45
|
-
const projectData = await this.readProjectData(
|
|
51
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
46
52
|
if (projectData) {
|
|
47
53
|
let { project: projectState = [] } = projectData.state;
|
|
48
54
|
if (!all) {
|
|
@@ -59,6 +65,7 @@ export class ProjectStatusCommand extends ProjectStateCommand {
|
|
|
59
65
|
status: state.status,
|
|
60
66
|
time: formatTimestamp(state.timestamp),
|
|
61
67
|
timestamp: state.timestamp,
|
|
68
|
+
reason: state.reason || '',
|
|
62
69
|
};
|
|
63
70
|
});
|
|
64
71
|
|
|
@@ -75,6 +82,7 @@ export class ProjectStatusCommand extends ProjectStateCommand {
|
|
|
75
82
|
status: task.status,
|
|
76
83
|
time: formatTimestamp(task.timestamp),
|
|
77
84
|
timestamp: task.timestamp,
|
|
85
|
+
reason: [task.reason, task.exitCode].filter(Boolean).join(' '),
|
|
78
86
|
});
|
|
79
87
|
});
|
|
80
88
|
});
|
|
@@ -85,7 +93,12 @@ export class ProjectStatusCommand extends ProjectStateCommand {
|
|
|
85
93
|
];
|
|
86
94
|
mappedState.sort((a, b) => a.timestamp - b.timestamp);
|
|
87
95
|
|
|
88
|
-
this.outputService.table(mappedState, [
|
|
96
|
+
this.outputService.table(mappedState, [
|
|
97
|
+
'time',
|
|
98
|
+
'name',
|
|
99
|
+
'status',
|
|
100
|
+
'reason',
|
|
101
|
+
]);
|
|
89
102
|
} else {
|
|
90
103
|
const mappedState = projectState.map(state => {
|
|
91
104
|
return {
|
|
@@ -31,9 +31,13 @@ export class ProjectStopCommand extends ProjectStateCommand {
|
|
|
31
31
|
: this.ensureProfileProject(name).path;
|
|
32
32
|
|
|
33
33
|
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
34
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
35
|
+
'projects',
|
|
36
|
+
projectDataFileName
|
|
37
|
+
);
|
|
34
38
|
|
|
35
39
|
// check if project is started
|
|
36
|
-
const projectData = await this.readProjectData(
|
|
40
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
37
41
|
if (!projectData || !this.isProjectProcessStarted(projectData.pid)) {
|
|
38
42
|
throw new RuniumError(
|
|
39
43
|
`Project "${name}" is not started`,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
import * as ajv from 'ajv';
|
|
1
2
|
import { ProjectCommand } from './project-command.js';
|
|
3
|
+
import { getErrorMessages } from '@validation';
|
|
4
|
+
|
|
5
|
+
interface ValidationError {
|
|
6
|
+
code: string;
|
|
7
|
+
payload: { errors: ajv.ErrorObject[] };
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* Project validate command
|
|
@@ -12,31 +19,37 @@ export class ProjectValidateCommand extends ProjectCommand {
|
|
|
12
19
|
.name('validate')
|
|
13
20
|
.description('validate project')
|
|
14
21
|
.option('-f, --file', 'use file path instead of project name')
|
|
15
|
-
.argument('<
|
|
22
|
+
.argument('<name>', 'project name');
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/**
|
|
19
26
|
* Handle command
|
|
20
|
-
* @param
|
|
27
|
+
* @param name
|
|
21
28
|
* @param file
|
|
22
29
|
*/
|
|
23
30
|
protected async handle(
|
|
24
|
-
|
|
31
|
+
name: string,
|
|
25
32
|
{ file }: { file: boolean }
|
|
26
33
|
): Promise<void> {
|
|
27
34
|
const path = file
|
|
28
|
-
? this.projectService.resolvePath(
|
|
29
|
-
: this.ensureProfileProject(
|
|
35
|
+
? this.projectService.resolvePath(name)
|
|
36
|
+
: this.ensureProfileProject(name).path;
|
|
30
37
|
const projectInstance = await this.projectService.initProject(path);
|
|
31
38
|
try {
|
|
32
39
|
await projectInstance.validate();
|
|
33
|
-
this.outputService.success(`Project "%s" is valid`,
|
|
40
|
+
this.outputService.success(`Project "%s" is valid`, name);
|
|
34
41
|
} catch (error) {
|
|
35
|
-
|
|
42
|
+
const errorMessages = getErrorMessages(
|
|
43
|
+
(error as ValidationError).payload.errors,
|
|
44
|
+
{
|
|
45
|
+
filter: error => error.original?.keyword !== 'oneOf',
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
36
49
|
this.outputService.error(
|
|
37
|
-
`Project "%s" validation failed`,
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
`Project "%s" validation failed:\n\n%s`,
|
|
51
|
+
name,
|
|
52
|
+
errorMessages.join('\n')
|
|
40
53
|
);
|
|
41
54
|
}
|
|
42
55
|
}
|
|
@@ -11,6 +11,19 @@ import { ProjectValidateCommand } from './project-validate.js';
|
|
|
11
11
|
* Project group command
|
|
12
12
|
*/
|
|
13
13
|
export class ProjectCommand extends RuniumCommand {
|
|
14
|
+
/**
|
|
15
|
+
* Subcommands
|
|
16
|
+
*/
|
|
17
|
+
subcommands = [
|
|
18
|
+
ProjectListCommand,
|
|
19
|
+
ProjectAddCommand,
|
|
20
|
+
ProjectRemoveCommand,
|
|
21
|
+
ProjectStartCommand,
|
|
22
|
+
ProjectStopCommand,
|
|
23
|
+
ProjectStatusCommand,
|
|
24
|
+
ProjectValidateCommand,
|
|
25
|
+
];
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* Config command
|
|
16
29
|
*/
|
|
@@ -24,22 +37,4 @@ export class ProjectCommand extends RuniumCommand {
|
|
|
24
37
|
protected async handle(): Promise<void> {
|
|
25
38
|
this.command.help();
|
|
26
39
|
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Add subcommands
|
|
30
|
-
*/
|
|
31
|
-
protected addSubcommands(): void {
|
|
32
|
-
const constructors = [
|
|
33
|
-
ProjectListCommand,
|
|
34
|
-
ProjectAddCommand,
|
|
35
|
-
ProjectRemoveCommand,
|
|
36
|
-
ProjectStartCommand,
|
|
37
|
-
ProjectStopCommand,
|
|
38
|
-
ProjectStatusCommand,
|
|
39
|
-
ProjectValidateCommand,
|
|
40
|
-
];
|
|
41
|
-
for (const CommandConstructor of constructors) {
|
|
42
|
-
this.subcommands.push(new CommandConstructor(this.command));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
40
|
}
|
|
@@ -1,42 +1,44 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { Container } from 'typedi';
|
|
3
|
-
import { OutputService } from '@services';
|
|
3
|
+
import { CommandService, OutputService } from '@services';
|
|
4
|
+
|
|
5
|
+
export type RuniumCommandConstructor = new (program: Command) => RuniumCommand;
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Base runium command
|
|
7
9
|
*/
|
|
8
10
|
export abstract class RuniumCommand {
|
|
9
|
-
protected outputService: OutputService;
|
|
10
|
-
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Output service
|
|
13
13
|
*/
|
|
14
|
-
protected
|
|
14
|
+
protected outputService: OutputService;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Current command
|
|
18
18
|
*/
|
|
19
|
-
|
|
19
|
+
command: Command;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Subcommands
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
subcommands: RuniumCommandConstructor[] = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run command
|
|
28
|
+
*/
|
|
29
|
+
run: (...args: unknown[]) => Promise<void>;
|
|
25
30
|
|
|
26
31
|
constructor(parent: Command) {
|
|
32
|
+
const commandService = Container.get(CommandService);
|
|
33
|
+
this.run = commandService.createRunCommand(this.handle, this);
|
|
34
|
+
|
|
27
35
|
this.outputService = Container.get(OutputService);
|
|
28
|
-
this.parent = parent;
|
|
29
36
|
this.command = new Command();
|
|
30
37
|
this.config();
|
|
31
|
-
this.command.action(this.
|
|
32
|
-
this.addSubcommands();
|
|
33
|
-
this.parent.addCommand(this.command);
|
|
34
|
-
}
|
|
38
|
+
this.command.action(this.run.bind(this));
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*/
|
|
39
|
-
protected addSubcommands(): void {}
|
|
40
|
+
parent.addCommand(this.command);
|
|
41
|
+
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Config command
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
export enum ErrorCode {
|
|
2
|
+
FILE_READ_JSON_ERROR = 'file-read-json-error',
|
|
3
|
+
FILE_WRITE_JSON_ERROR = 'file-write-json-error',
|
|
4
|
+
FILE_READ_ERROR = 'file-read-error',
|
|
5
|
+
FILE_WRITE_ERROR = 'file-write-error',
|
|
6
|
+
FILE_CREATE_DIR_ERROR = 'file-create-dir-error',
|
|
2
7
|
PLUGIN_NOT_FOUND = 'plugin-not-found',
|
|
3
8
|
PLUGIN_FILE_NOT_FOUND = 'plugin-file-not-found',
|
|
4
9
|
PLUGIN_INCORRECT_MODULE = 'plugin-incorrect-module',
|
|
5
|
-
|
|
10
|
+
PLUGIN_INVALID = 'plugin-invalid',
|
|
6
11
|
PLUGIN_PATH_RESOLVE_ERROR = 'plugin-path-resolve-error',
|
|
12
|
+
PLUGIN_LOAD_ERROR = 'plugin-load-error',
|
|
13
|
+
PLUGIN_HOOK_ERROR = 'plugin-hook-error',
|
|
7
14
|
PROJECT_ALREADY_STARTED = 'project-already-started',
|
|
8
15
|
PROJECT_NOT_STARTED = 'project-not-started',
|
|
9
16
|
PROJECT_STOP_ERROR = 'project-stop-error',
|
|
@@ -11,5 +18,11 @@ export enum ErrorCode {
|
|
|
11
18
|
PROJECT_FILE_NOT_FOUND = 'project-file-not-found',
|
|
12
19
|
PROJECT_FILE_CAN_NOT_READ = 'project-file-can-not-read',
|
|
13
20
|
PROJECT_JSON_PARSE_ERROR = 'project-json-parse-error',
|
|
14
|
-
|
|
21
|
+
INVALID_ARGUMENT = 'invalid-argument',
|
|
22
|
+
INVALID_PATH = 'invalid-path',
|
|
23
|
+
CONFIG_INVALID_DATA = 'config-invalid-data',
|
|
24
|
+
COMMAND_REGISTRATION_ERROR = 'command-registration-error',
|
|
25
|
+
COMMAND_INCORRECT = 'command-incorrect',
|
|
26
|
+
COMMAND_NOT_FOUND = 'command-not-found',
|
|
27
|
+
COMMAND_RUN_ERROR = 'command-run-error',
|
|
15
28
|
}
|
package/src/global.d.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import 'reflect-metadata';
|
|
4
4
|
import { Container } from 'typedi';
|
|
5
5
|
import { RuniumCliApp } from './app.js';
|
|
6
|
-
import { OutputService } from '@services';
|
|
6
|
+
import { OutputService, ShutdownService } from '@services';
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
9
|
const app = new RuniumCliApp();
|
|
@@ -17,5 +17,8 @@ main().catch(error => {
|
|
|
17
17
|
code: error.code,
|
|
18
18
|
payload: error.payload,
|
|
19
19
|
});
|
|
20
|
-
|
|
20
|
+
const shutdownService = Container.get(ShutdownService);
|
|
21
|
+
shutdownService.shutdown('error').catch(() => {
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
21
24
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { formatTimestamp } from '@utils/format-timestamp.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Date
|
|
5
|
+
*/
|
|
6
|
+
export function dateMacro(): string {
|
|
7
|
+
return formatTimestamp(Date.now());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Timestamp
|
|
12
|
+
*/
|
|
13
|
+
export function timestampMacro(): string {
|
|
14
|
+
return Date.now().toString();
|
|
15
|
+
}
|
package/src/macros/index.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { eqMacro, neMacro } from './conditional.js';
|
|
2
|
+
import { dateMacro, timestampMacro } from './date.js';
|
|
2
3
|
import { emptyMacro } from './empty.js';
|
|
3
4
|
import { envMacro } from './env.js';
|
|
4
|
-
import { pathMacro } from './path.js';
|
|
5
|
+
import { homeDirMacro, pathMacro, tmpDirMacro } from './path.js';
|
|
5
6
|
|
|
6
7
|
export const macros = {
|
|
8
|
+
date: dateMacro,
|
|
9
|
+
homedir: homeDirMacro,
|
|
7
10
|
env: envMacro,
|
|
8
11
|
empty: emptyMacro,
|
|
9
12
|
eq: eqMacro,
|
|
10
13
|
ne: neMacro,
|
|
11
14
|
path: pathMacro,
|
|
15
|
+
tmpdir: tmpDirMacro,
|
|
16
|
+
timestamp: timestampMacro,
|
|
12
17
|
};
|
package/src/macros/path.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
+
import { homedir, tmpdir } from 'node:os';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Resolve path
|
|
5
6
|
* @param path
|
|
6
7
|
*/
|
|
7
|
-
export function pathMacro(path: string): string {
|
|
8
|
-
return resolve(path);
|
|
8
|
+
export function pathMacro(...path: string[]): string {
|
|
9
|
+
return resolve(...path);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tmpdir path
|
|
14
|
+
*/
|
|
15
|
+
export function tmpDirMacro(): string {
|
|
16
|
+
return tmpdir();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Home directory path
|
|
21
|
+
*/
|
|
22
|
+
export function homeDirMacro(): string {
|
|
23
|
+
return homedir();
|
|
9
24
|
}
|