@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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { Command, Option } from 'commander';
|
|
3
|
+
import { Container } from 'typedi';
|
|
4
|
+
import {
|
|
5
|
+
Project,
|
|
6
|
+
ProjectEvent,
|
|
7
|
+
ProjectState,
|
|
8
|
+
RuniumError,
|
|
9
|
+
TaskState,
|
|
10
|
+
} from '@runium/core';
|
|
11
|
+
import { ErrorCode } from '@constants';
|
|
12
|
+
import { AtomicWriter, ShutdownService } from '@services';
|
|
13
|
+
import { ProjectData, ProjectStateCommand } from './project-state-command.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Project start command
|
|
17
|
+
*/
|
|
18
|
+
export class ProjectStartCommand extends ProjectStateCommand {
|
|
19
|
+
protected shutdownService: ShutdownService;
|
|
20
|
+
protected fileWriter: AtomicWriter | null = null;
|
|
21
|
+
|
|
22
|
+
constructor(parent: Command) {
|
|
23
|
+
super(parent);
|
|
24
|
+
this.shutdownService = Container.get(ShutdownService);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Config command
|
|
29
|
+
*/
|
|
30
|
+
protected config(): void {
|
|
31
|
+
this.command
|
|
32
|
+
.name('start')
|
|
33
|
+
.description('start project')
|
|
34
|
+
.option('-f, --file', 'use file path instead of project name')
|
|
35
|
+
.option('-o, --output', 'output project state changes')
|
|
36
|
+
.addOption(
|
|
37
|
+
new Option('-w, --working-dir <choice>', 'set working directory')
|
|
38
|
+
.choices(['cwd', 'project'])
|
|
39
|
+
.default('cwd')
|
|
40
|
+
)
|
|
41
|
+
.argument('<name>', 'project name');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handle command
|
|
46
|
+
* @param name
|
|
47
|
+
* @param file
|
|
48
|
+
* @param workingDir
|
|
49
|
+
* @param output
|
|
50
|
+
*/
|
|
51
|
+
protected async handle(
|
|
52
|
+
name: string,
|
|
53
|
+
{
|
|
54
|
+
file,
|
|
55
|
+
workingDir,
|
|
56
|
+
output,
|
|
57
|
+
}: { file: boolean; workingDir?: string; output?: boolean }
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const path = file
|
|
60
|
+
? this.projectService.resolvePath(name)
|
|
61
|
+
: this.ensureProfileProject(name).path;
|
|
62
|
+
|
|
63
|
+
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
64
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
65
|
+
'projects',
|
|
66
|
+
projectDataFileName
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// check if project is started
|
|
70
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
71
|
+
if (projectData && this.isProjectProcessStarted(projectData.pid)) {
|
|
72
|
+
throw new RuniumError(
|
|
73
|
+
`Project "${name}" is already started`,
|
|
74
|
+
ErrorCode.PROJECT_ALREADY_STARTED,
|
|
75
|
+
{ name }
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (workingDir === 'project') {
|
|
80
|
+
const projectDir = dirname(path);
|
|
81
|
+
if (projectDir !== process.cwd()) {
|
|
82
|
+
process.chdir(projectDir);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const project = await this.projectService.initProject(path);
|
|
87
|
+
this.shutdownService.addBlocker((reason?: string) => project.stop(reason));
|
|
88
|
+
|
|
89
|
+
await this.fileService.ensureDirExists(dirname(projectDataFilePath));
|
|
90
|
+
this.fileWriter = this.fileService.createAtomicWriter(projectDataFilePath);
|
|
91
|
+
|
|
92
|
+
this.addProjectListeners(project, {
|
|
93
|
+
projectPath: path,
|
|
94
|
+
output,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await this.projectService.runHook('project.beforeStart', project);
|
|
98
|
+
|
|
99
|
+
await project.start();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Add project listeners
|
|
104
|
+
* @param project
|
|
105
|
+
* @param options
|
|
106
|
+
*/
|
|
107
|
+
protected addProjectListeners(
|
|
108
|
+
project: Project,
|
|
109
|
+
options: { projectPath: string; output?: boolean }
|
|
110
|
+
): void {
|
|
111
|
+
const { projectPath, output } = options;
|
|
112
|
+
|
|
113
|
+
const projectData: ProjectData = {
|
|
114
|
+
id: project.getConfig().id,
|
|
115
|
+
pid: process.pid,
|
|
116
|
+
cwd: process.cwd(),
|
|
117
|
+
path: projectPath,
|
|
118
|
+
state: {
|
|
119
|
+
project: [],
|
|
120
|
+
tasks: {},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const writeProjectData = () => {
|
|
125
|
+
this.fileWriter!.writeJson(projectData).then();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// write initial data
|
|
129
|
+
writeProjectData();
|
|
130
|
+
|
|
131
|
+
project.on(ProjectEvent.STATE_CHANGE, async (state: ProjectState) => {
|
|
132
|
+
projectData.state.project.push(state);
|
|
133
|
+
writeProjectData();
|
|
134
|
+
if (output) {
|
|
135
|
+
this.outputService.info('Project %s', state.status);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
project.on(
|
|
140
|
+
ProjectEvent.TASK_STATE_CHANGE,
|
|
141
|
+
(taskId: string, state: TaskState) => {
|
|
142
|
+
if (!projectData.state.tasks[taskId]) {
|
|
143
|
+
projectData.state.tasks[taskId] = [];
|
|
144
|
+
}
|
|
145
|
+
projectData.state.tasks[taskId].push(state);
|
|
146
|
+
writeProjectData();
|
|
147
|
+
if (output) {
|
|
148
|
+
this.outputService.info(
|
|
149
|
+
'Task %s %s %s',
|
|
150
|
+
taskId,
|
|
151
|
+
state.status,
|
|
152
|
+
state.exitCode || state.error || ''
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { convertPathToValidFileName } from '@utils';
|
|
2
|
+
import { ProjectCommand } from './project-command.js';
|
|
3
|
+
import { ProjectState, TaskState } from '@runium/core';
|
|
4
|
+
|
|
5
|
+
export interface ProjectData {
|
|
6
|
+
id: string;
|
|
7
|
+
pid: number;
|
|
8
|
+
cwd: string;
|
|
9
|
+
path: string;
|
|
10
|
+
state: {
|
|
11
|
+
project: ProjectState[];
|
|
12
|
+
tasks: Record<string, TaskState[]>;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base project state command
|
|
18
|
+
*/
|
|
19
|
+
export abstract class ProjectStateCommand extends ProjectCommand {
|
|
20
|
+
/**
|
|
21
|
+
* Get project data file name
|
|
22
|
+
* @param name
|
|
23
|
+
*/
|
|
24
|
+
protected getProjectDataFileName(name: string): string {
|
|
25
|
+
let fileName = convertPathToValidFileName(name);
|
|
26
|
+
if (!fileName.endsWith('.json')) {
|
|
27
|
+
fileName = fileName + '.json';
|
|
28
|
+
}
|
|
29
|
+
return fileName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read project data
|
|
34
|
+
* @param path
|
|
35
|
+
*/
|
|
36
|
+
protected async readProjectData(path: string): Promise<ProjectData | null> {
|
|
37
|
+
return this.fileService
|
|
38
|
+
.readJson(path)
|
|
39
|
+
.catch(() => null) as Promise<ProjectData | null>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a project process is started
|
|
44
|
+
* @param pid
|
|
45
|
+
*/
|
|
46
|
+
protected isProjectProcessStarted(pid: number): boolean {
|
|
47
|
+
try {
|
|
48
|
+
return process.kill(Number(pid), 0);
|
|
49
|
+
} catch (ex) {
|
|
50
|
+
return (ex as { code?: string }).code === 'EPERM';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { formatTimestamp } from '@utils';
|
|
2
|
+
import { ProjectStateCommand } from './project-state-command.js';
|
|
3
|
+
|
|
4
|
+
interface StateRecord {
|
|
5
|
+
name: string;
|
|
6
|
+
status: string;
|
|
7
|
+
time: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
reason?: string;
|
|
10
|
+
exitCode?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Project status command
|
|
15
|
+
*/
|
|
16
|
+
export class ProjectStatusCommand extends ProjectStateCommand {
|
|
17
|
+
/**
|
|
18
|
+
* Config command
|
|
19
|
+
*/
|
|
20
|
+
protected config(): void {
|
|
21
|
+
this.command
|
|
22
|
+
.name('status')
|
|
23
|
+
.description('get project status')
|
|
24
|
+
.option('-f, --file', 'use file path instead of project name')
|
|
25
|
+
.option('-t, --tasks', 'show tasks status')
|
|
26
|
+
.option('-a, --all', 'show status change history')
|
|
27
|
+
.argument('<name>', 'project name');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handle command
|
|
32
|
+
* @param name
|
|
33
|
+
* @param file
|
|
34
|
+
* @param tasks
|
|
35
|
+
* @param all
|
|
36
|
+
*/
|
|
37
|
+
protected async handle(
|
|
38
|
+
name: string,
|
|
39
|
+
{ file, tasks, all }: { file: boolean; tasks: boolean; all: boolean }
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
const path = file
|
|
42
|
+
? this.projectService.resolvePath(name)
|
|
43
|
+
: this.ensureProfileProject(name).path;
|
|
44
|
+
|
|
45
|
+
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
46
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
47
|
+
'projects',
|
|
48
|
+
projectDataFileName
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
52
|
+
if (projectData) {
|
|
53
|
+
let { project: projectState = [] } = projectData.state;
|
|
54
|
+
if (!all) {
|
|
55
|
+
projectState =
|
|
56
|
+
projectState.length > 0
|
|
57
|
+
? [projectState[projectState.length - 1]]
|
|
58
|
+
: [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (tasks) {
|
|
62
|
+
const projectMappedState = projectState.map(state => {
|
|
63
|
+
return {
|
|
64
|
+
name: 'Project',
|
|
65
|
+
status: state.status,
|
|
66
|
+
time: formatTimestamp(state.timestamp),
|
|
67
|
+
timestamp: state.timestamp,
|
|
68
|
+
reason: state.reason || '',
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const { tasks: tasksState = [] } = projectData.state;
|
|
73
|
+
|
|
74
|
+
const tasksMappedState: StateRecord[] = [];
|
|
75
|
+
Object.entries(tasksState).forEach(([key, value]) => {
|
|
76
|
+
if (!all) {
|
|
77
|
+
value = value.length > 0 ? [value[value.length - 1]] : [];
|
|
78
|
+
}
|
|
79
|
+
value.forEach(task => {
|
|
80
|
+
tasksMappedState.push({
|
|
81
|
+
name: key,
|
|
82
|
+
status: task.status,
|
|
83
|
+
time: formatTimestamp(task.timestamp),
|
|
84
|
+
timestamp: task.timestamp,
|
|
85
|
+
reason: [task.reason, task.exitCode].filter(Boolean).join(' '),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const mappedState: StateRecord[] = [
|
|
91
|
+
...projectMappedState,
|
|
92
|
+
...tasksMappedState,
|
|
93
|
+
];
|
|
94
|
+
mappedState.sort((a, b) => a.timestamp - b.timestamp);
|
|
95
|
+
|
|
96
|
+
this.outputService.table(mappedState, [
|
|
97
|
+
'time',
|
|
98
|
+
'name',
|
|
99
|
+
'status',
|
|
100
|
+
'reason',
|
|
101
|
+
]);
|
|
102
|
+
} else {
|
|
103
|
+
const mappedState = projectState.map(state => {
|
|
104
|
+
return {
|
|
105
|
+
status: state.status,
|
|
106
|
+
time: formatTimestamp(state.timestamp),
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.outputService.table(mappedState, ['time', 'status']);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
this.outputService.info(`No project status for "${name}"`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { RuniumError } from '@runium/core';
|
|
2
|
+
import { ErrorCode } from '@constants';
|
|
3
|
+
import { ProjectStateCommand } from './project-state-command.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Project stop command
|
|
7
|
+
*/
|
|
8
|
+
export class ProjectStopCommand extends ProjectStateCommand {
|
|
9
|
+
/**
|
|
10
|
+
* Config command
|
|
11
|
+
*/
|
|
12
|
+
protected config(): void {
|
|
13
|
+
this.command
|
|
14
|
+
.name('stop')
|
|
15
|
+
.description('stop project')
|
|
16
|
+
.option('-f, --file', 'use file path instead of project name')
|
|
17
|
+
.argument('<name>', 'project name');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Handle command
|
|
22
|
+
* @param name
|
|
23
|
+
* @param file
|
|
24
|
+
*/
|
|
25
|
+
protected async handle(
|
|
26
|
+
name: string,
|
|
27
|
+
{ file }: { file: boolean }
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
const path = file
|
|
30
|
+
? this.projectService.resolvePath(name)
|
|
31
|
+
: this.ensureProfileProject(name).path;
|
|
32
|
+
|
|
33
|
+
const projectDataFileName = this.getProjectDataFileName(file ? path : name);
|
|
34
|
+
const projectDataFilePath = this.profileService.getPath(
|
|
35
|
+
'projects',
|
|
36
|
+
projectDataFileName
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// check if project is started
|
|
40
|
+
const projectData = await this.readProjectData(projectDataFilePath);
|
|
41
|
+
if (!projectData || !this.isProjectProcessStarted(projectData.pid)) {
|
|
42
|
+
throw new RuniumError(
|
|
43
|
+
`Project "${name}" is not started`,
|
|
44
|
+
ErrorCode.PROJECT_NOT_STARTED,
|
|
45
|
+
{ name }
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
process.kill(projectData.pid, 'SIGTERM');
|
|
51
|
+
} catch (ex) {
|
|
52
|
+
throw new RuniumError(
|
|
53
|
+
`Failed to stop project "${name}"`,
|
|
54
|
+
ErrorCode.PROJECT_STOP_ERROR,
|
|
55
|
+
{ name, original: ex }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as ajv from 'ajv';
|
|
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
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Project validate command
|
|
12
|
+
*/
|
|
13
|
+
export class ProjectValidateCommand extends ProjectCommand {
|
|
14
|
+
/**
|
|
15
|
+
* Config command
|
|
16
|
+
*/
|
|
17
|
+
protected config(): void {
|
|
18
|
+
this.command
|
|
19
|
+
.name('validate')
|
|
20
|
+
.description('validate project')
|
|
21
|
+
.option('-f, --file', 'use file path instead of project name')
|
|
22
|
+
.argument('<name>', 'project name');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handle command
|
|
27
|
+
* @param name
|
|
28
|
+
* @param file
|
|
29
|
+
*/
|
|
30
|
+
protected async handle(
|
|
31
|
+
name: string,
|
|
32
|
+
{ file }: { file: boolean }
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const path = file
|
|
35
|
+
? this.projectService.resolvePath(name)
|
|
36
|
+
: this.ensureProfileProject(name).path;
|
|
37
|
+
const projectInstance = await this.projectService.initProject(path);
|
|
38
|
+
try {
|
|
39
|
+
await projectInstance.validate();
|
|
40
|
+
this.outputService.success(`Project "%s" is valid`, name);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const errorMessages = getErrorMessages(
|
|
43
|
+
(error as ValidationError).payload.errors,
|
|
44
|
+
{
|
|
45
|
+
filter: error => error.original?.keyword !== 'oneOf',
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
this.outputService.error(
|
|
50
|
+
`Project "%s" validation failed:\n\n%s`,
|
|
51
|
+
name,
|
|
52
|
+
errorMessages.join('\n')
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { RuniumCommand } from '@commands/runium-command.js';
|
|
2
|
+
import { ProjectAddCommand } from './project-add.js';
|
|
3
|
+
import { ProjectListCommand } from './project-list.js';
|
|
4
|
+
import { ProjectRemoveCommand } from './project-remove.js';
|
|
5
|
+
import { ProjectStartCommand } from './project-start.js';
|
|
6
|
+
import { ProjectStopCommand } from './project-stop.js';
|
|
7
|
+
import { ProjectStatusCommand } from './project-status.js';
|
|
8
|
+
import { ProjectValidateCommand } from './project-validate.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Project group command
|
|
12
|
+
*/
|
|
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
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Config command
|
|
29
|
+
*/
|
|
30
|
+
protected config(): void {
|
|
31
|
+
this.command.name('project').description('manage projects');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Handle command
|
|
36
|
+
*/
|
|
37
|
+
protected async handle(): Promise<void> {
|
|
38
|
+
this.command.help();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { Container } from 'typedi';
|
|
3
|
+
import { CommandService, OutputService } from '@services';
|
|
4
|
+
|
|
5
|
+
export type RuniumCommandConstructor = new (program: Command) => RuniumCommand;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base runium command
|
|
9
|
+
*/
|
|
10
|
+
export abstract class RuniumCommand {
|
|
11
|
+
/**
|
|
12
|
+
* Output service
|
|
13
|
+
*/
|
|
14
|
+
protected outputService: OutputService;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Current command
|
|
18
|
+
*/
|
|
19
|
+
command: Command;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Subcommands
|
|
23
|
+
*/
|
|
24
|
+
subcommands: RuniumCommandConstructor[] = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run command
|
|
28
|
+
*/
|
|
29
|
+
run: (...args: unknown[]) => Promise<void>;
|
|
30
|
+
|
|
31
|
+
constructor(parent: Command) {
|
|
32
|
+
const commandService = Container.get(CommandService);
|
|
33
|
+
this.run = commandService.createRunCommand(this.handle, this);
|
|
34
|
+
|
|
35
|
+
this.outputService = Container.get(OutputService);
|
|
36
|
+
this.command = new Command();
|
|
37
|
+
this.config();
|
|
38
|
+
this.command.action(this.run.bind(this));
|
|
39
|
+
|
|
40
|
+
parent.addCommand(this.command);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Config command
|
|
45
|
+
*/
|
|
46
|
+
protected abstract config(): void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handle command
|
|
50
|
+
*/
|
|
51
|
+
protected abstract handle(...args: unknown[]): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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',
|
|
7
|
+
PLUGIN_NOT_FOUND = 'plugin-not-found',
|
|
8
|
+
PLUGIN_FILE_NOT_FOUND = 'plugin-file-not-found',
|
|
9
|
+
PLUGIN_INCORRECT_MODULE = 'plugin-incorrect-module',
|
|
10
|
+
PLUGIN_INVALID = 'plugin-invalid',
|
|
11
|
+
PLUGIN_PATH_RESOLVE_ERROR = 'plugin-path-resolve-error',
|
|
12
|
+
PLUGIN_LOAD_ERROR = 'plugin-load-error',
|
|
13
|
+
PLUGIN_HOOK_ERROR = 'plugin-hook-error',
|
|
14
|
+
PROJECT_ALREADY_STARTED = 'project-already-started',
|
|
15
|
+
PROJECT_NOT_STARTED = 'project-not-started',
|
|
16
|
+
PROJECT_STOP_ERROR = 'project-stop-error',
|
|
17
|
+
PROJECT_NOT_FOUND = 'project-not-found',
|
|
18
|
+
PROJECT_FILE_NOT_FOUND = 'project-file-not-found',
|
|
19
|
+
PROJECT_FILE_CAN_NOT_READ = 'project-file-can-not-read',
|
|
20
|
+
PROJECT_JSON_PARSE_ERROR = 'project-json-parse-error',
|
|
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',
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './error-code.js';
|
package/src/global.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'reflect-metadata';
|
|
4
|
+
import { Container } from 'typedi';
|
|
5
|
+
import { RuniumCliApp } from './app.js';
|
|
6
|
+
import { OutputService, ShutdownService } from '@services';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const app = new RuniumCliApp();
|
|
10
|
+
await app.start();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main().catch(error => {
|
|
14
|
+
const outputService = Container.get(OutputService);
|
|
15
|
+
outputService.error('Error: %s', error.message);
|
|
16
|
+
outputService.debug('Error details:', {
|
|
17
|
+
code: error.code,
|
|
18
|
+
payload: error.payload,
|
|
19
|
+
});
|
|
20
|
+
const shutdownService = Container.get(ShutdownService);
|
|
21
|
+
shutdownService.shutdown('error').catch(() => {
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equal
|
|
3
|
+
* @param value1
|
|
4
|
+
* @param value2
|
|
5
|
+
* @param trueValue
|
|
6
|
+
* @param falseValue
|
|
7
|
+
*/
|
|
8
|
+
export function eqMacro(
|
|
9
|
+
value1: string,
|
|
10
|
+
value2: string,
|
|
11
|
+
trueValue?: string,
|
|
12
|
+
falseValue?: string
|
|
13
|
+
): string {
|
|
14
|
+
return value1 === value2 ? (trueValue ?? '') : (falseValue ?? '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Not equal
|
|
19
|
+
* @param value1
|
|
20
|
+
* @param value2
|
|
21
|
+
* @param trueValue
|
|
22
|
+
* @param falseValue
|
|
23
|
+
*/
|
|
24
|
+
export function neMacro(
|
|
25
|
+
value1: string,
|
|
26
|
+
value2: string,
|
|
27
|
+
trueValue?: string,
|
|
28
|
+
falseValue?: string
|
|
29
|
+
): string {
|
|
30
|
+
return value1 !== value2 ? (trueValue ?? '') : (falseValue ?? '');
|
|
31
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { eqMacro, neMacro } from './conditional.js';
|
|
2
|
+
import { dateMacro, timestampMacro } from './date.js';
|
|
3
|
+
import { emptyMacro } from './empty.js';
|
|
4
|
+
import { envMacro } from './env.js';
|
|
5
|
+
import { homeDirMacro, pathMacro, tmpDirMacro } from './path.js';
|
|
6
|
+
|
|
7
|
+
export const macros = {
|
|
8
|
+
date: dateMacro,
|
|
9
|
+
homedir: homeDirMacro,
|
|
10
|
+
env: envMacro,
|
|
11
|
+
empty: emptyMacro,
|
|
12
|
+
eq: eqMacro,
|
|
13
|
+
ne: neMacro,
|
|
14
|
+
path: pathMacro,
|
|
15
|
+
tmpdir: tmpDirMacro,
|
|
16
|
+
timestamp: timestampMacro,
|
|
17
|
+
};
|