@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
package/src/services/plugin.ts
CHANGED
|
@@ -1,49 +1,100 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { Service } from 'typedi';
|
|
3
|
+
import { Inject, Service } from 'typedi';
|
|
4
4
|
import {
|
|
5
5
|
isRuniumError,
|
|
6
|
-
|
|
6
|
+
MacrosCollection,
|
|
7
|
+
Project,
|
|
8
|
+
ProjectConfig,
|
|
7
9
|
ProjectSchemaExtension,
|
|
8
10
|
RuniumError,
|
|
9
11
|
RuniumTaskConstructor,
|
|
10
12
|
RuniumTriggerConstructor,
|
|
11
13
|
RuniumTriggerOptions,
|
|
12
14
|
} from '@runium/core';
|
|
15
|
+
import { RuniumCommandConstructor } from '@commands/runium-command.js';
|
|
13
16
|
import { ErrorCode } from '@constants';
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
import { OutputService } from '@services';
|
|
18
|
+
import {
|
|
19
|
+
createValidator,
|
|
20
|
+
getErrorMessages,
|
|
21
|
+
getPluginSchema,
|
|
22
|
+
} from '@validation';
|
|
16
23
|
|
|
17
24
|
type PluginModule = { default: (options?: PluginOptions) => Plugin };
|
|
18
25
|
|
|
26
|
+
type PluginHookErrorHandler = (error: RuniumError) => void;
|
|
27
|
+
|
|
28
|
+
type PluginHookName =
|
|
29
|
+
| `app.${keyof PluginAppHooksDefinition}`
|
|
30
|
+
| `project.${keyof PluginProjectHooksDefinition}`;
|
|
31
|
+
|
|
32
|
+
interface PluginHookOptions<M extends boolean> {
|
|
33
|
+
mutable?: M;
|
|
34
|
+
onError?: PluginHookErrorHandler;
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
export interface PluginProjectDefinition {
|
|
20
|
-
macros?:
|
|
38
|
+
macros?: MacrosCollection;
|
|
21
39
|
tasks?: Record<string, RuniumTaskConstructor>;
|
|
22
40
|
actions?: Record<string, (payload: unknown) => void>;
|
|
23
41
|
triggers?: Record<string, RuniumTriggerConstructor<RuniumTriggerOptions>>;
|
|
24
42
|
validationSchema?: ProjectSchemaExtension;
|
|
25
43
|
}
|
|
26
44
|
|
|
27
|
-
export
|
|
28
|
-
value?: PluginOptions;
|
|
29
|
-
validate?(options: PluginOptions): boolean;
|
|
30
|
-
}
|
|
45
|
+
export type PluginOptions = Record<string, unknown>;
|
|
31
46
|
|
|
32
47
|
export interface PluginAppDefinition {
|
|
33
|
-
commands?:
|
|
48
|
+
commands?: RuniumCommandConstructor[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PluginProjectHooksDefinition {
|
|
52
|
+
beforeConfigRead?(path: string): Promise<void>;
|
|
53
|
+
afterConfigRead?(content: string): Promise<string>;
|
|
54
|
+
afterConfigMacrosApply?(content: string): Promise<string>;
|
|
55
|
+
afterConfigParse?<T extends ProjectConfig>(config: T): Promise<T>;
|
|
56
|
+
beforeStart?(project: Project): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface PluginAppHooksDefinition {
|
|
60
|
+
afterInit?(params: { profilePath: string }): Promise<void>;
|
|
61
|
+
beforeExit?(reason?: string): Promise<void>;
|
|
62
|
+
beforeCommandRun?(params: {
|
|
63
|
+
command: string;
|
|
64
|
+
args: unknown[];
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
afterCommandRun?(params: { command: string; args: unknown[] }): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PluginHooksDefinition {
|
|
70
|
+
app?: PluginAppHooksDefinition;
|
|
71
|
+
project?: PluginProjectHooksDefinition;
|
|
34
72
|
}
|
|
35
73
|
|
|
36
74
|
export interface Plugin {
|
|
37
75
|
name: string;
|
|
38
76
|
project?: PluginProjectDefinition;
|
|
39
|
-
options?:
|
|
77
|
+
options?: PluginOptions;
|
|
40
78
|
app?: PluginAppDefinition;
|
|
79
|
+
hooks?: PluginHooksDefinition;
|
|
41
80
|
}
|
|
42
81
|
|
|
43
82
|
@Service()
|
|
44
83
|
export class PluginService {
|
|
84
|
+
/**
|
|
85
|
+
* Loaded plugins
|
|
86
|
+
*/
|
|
45
87
|
private plugins: Map<string, Plugin> = new Map();
|
|
46
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Validate plugin schema
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
private validator: ReturnType<typeof createValidator> =
|
|
94
|
+
createValidator(getPluginSchema());
|
|
95
|
+
|
|
96
|
+
constructor(@Inject() private outputService: OutputService) {}
|
|
97
|
+
|
|
47
98
|
/**
|
|
48
99
|
* Get all plugins
|
|
49
100
|
*/
|
|
@@ -62,8 +113,9 @@ export class PluginService {
|
|
|
62
113
|
/**
|
|
63
114
|
* Load plugin
|
|
64
115
|
* @param path
|
|
116
|
+
* @param options
|
|
65
117
|
*/
|
|
66
|
-
async loadPlugin(path: string): Promise<string> {
|
|
118
|
+
async loadPlugin(path: string, options?: PluginOptions): Promise<string> {
|
|
67
119
|
if (!path || !existsSync(path)) {
|
|
68
120
|
throw new RuniumError(
|
|
69
121
|
`Plugin file "${path}" does not exist`,
|
|
@@ -82,7 +134,7 @@ export class PluginService {
|
|
|
82
134
|
{ path }
|
|
83
135
|
);
|
|
84
136
|
}
|
|
85
|
-
const plugin = getPlugin();
|
|
137
|
+
const plugin = getPlugin(options);
|
|
86
138
|
|
|
87
139
|
this.validate(plugin);
|
|
88
140
|
|
|
@@ -95,7 +147,7 @@ export class PluginService {
|
|
|
95
147
|
}
|
|
96
148
|
throw new RuniumError(
|
|
97
149
|
`Failed to load plugin "${path}"`,
|
|
98
|
-
ErrorCode.
|
|
150
|
+
ErrorCode.PLUGIN_LOAD_ERROR,
|
|
99
151
|
{ path, original: error }
|
|
100
152
|
);
|
|
101
153
|
}
|
|
@@ -127,17 +179,69 @@ export class PluginService {
|
|
|
127
179
|
}
|
|
128
180
|
}
|
|
129
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Run plugin hook
|
|
184
|
+
* @param name
|
|
185
|
+
* @param params
|
|
186
|
+
* @param options
|
|
187
|
+
*/
|
|
188
|
+
async runHook<T, M extends boolean>(
|
|
189
|
+
name: PluginHookName,
|
|
190
|
+
params: T,
|
|
191
|
+
{ mutable, onError }: PluginHookOptions<M> = {}
|
|
192
|
+
): Promise<M extends true ? T : void> {
|
|
193
|
+
const plugins = this.getAllPlugins();
|
|
194
|
+
|
|
195
|
+
const [group, hookName] = name.split('.') as [
|
|
196
|
+
keyof PluginHooksDefinition,
|
|
197
|
+
keyof PluginHooksDefinition[keyof PluginHooksDefinition],
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const plugin of plugins) {
|
|
201
|
+
const hook = plugin.hooks?.[group]?.[hookName] as
|
|
202
|
+
| ((params: T) => Promise<T>)
|
|
203
|
+
| undefined;
|
|
204
|
+
if (hook) {
|
|
205
|
+
try {
|
|
206
|
+
if (!mutable) {
|
|
207
|
+
await hook(params);
|
|
208
|
+
} else {
|
|
209
|
+
params = (await hook(params)) ?? params;
|
|
210
|
+
}
|
|
211
|
+
} catch (ex) {
|
|
212
|
+
const error = new RuniumError(
|
|
213
|
+
`Failed to run "${plugin.name}.${name}" hook`,
|
|
214
|
+
ErrorCode.PLUGIN_HOOK_ERROR,
|
|
215
|
+
{ plugin: plugin.name, hook: name, original: ex }
|
|
216
|
+
);
|
|
217
|
+
if (onError) {
|
|
218
|
+
onError(error);
|
|
219
|
+
} else {
|
|
220
|
+
this.outputService.error('Error: %s', error.message);
|
|
221
|
+
this.outputService.debug('Error details:', {
|
|
222
|
+
code: error.code,
|
|
223
|
+
payload: error.payload,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (mutable ? params : undefined) as M extends true ? T : void;
|
|
231
|
+
}
|
|
232
|
+
|
|
130
233
|
/**
|
|
131
234
|
* Validate plugin
|
|
132
235
|
* @param plugin
|
|
133
236
|
*/
|
|
134
237
|
private validate(plugin: Plugin): void {
|
|
135
|
-
|
|
136
|
-
if (!
|
|
238
|
+
const result = this.validator(plugin || {});
|
|
239
|
+
if (!result && this.validator.errors) {
|
|
240
|
+
const errorMessages = getErrorMessages(this.validator.errors);
|
|
137
241
|
throw new RuniumError(
|
|
138
242
|
'Incorrect plugin format',
|
|
139
|
-
ErrorCode.
|
|
140
|
-
{
|
|
243
|
+
ErrorCode.PLUGIN_INVALID,
|
|
244
|
+
{ errors: errorMessages }
|
|
141
245
|
);
|
|
142
246
|
}
|
|
143
247
|
}
|
package/src/services/profile.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { join } from 'node:path';
|
|
4
4
|
import { Inject, Service } from 'typedi';
|
|
5
|
-
import {
|
|
6
|
-
JSONObject,
|
|
7
|
-
readJsonFile,
|
|
8
|
-
RuniumError,
|
|
9
|
-
writeJsonFile,
|
|
10
|
-
} from '@runium/core';
|
|
11
|
-
import { ConfigService } from '@services';
|
|
12
|
-
import { ErrorCode } from '@constants';
|
|
5
|
+
import { ConfigService, FileService } from '@services';
|
|
13
6
|
|
|
14
7
|
export interface ProfilePlugin {
|
|
15
8
|
name: string;
|
|
@@ -35,7 +28,10 @@ export class ProfileService {
|
|
|
35
28
|
|
|
36
29
|
private projects: ProfileProject[] = [];
|
|
37
30
|
|
|
38
|
-
constructor(
|
|
31
|
+
constructor(
|
|
32
|
+
@Inject() private configService: ConfigService,
|
|
33
|
+
@Inject() private fileService: FileService
|
|
34
|
+
) {}
|
|
39
35
|
|
|
40
36
|
/**
|
|
41
37
|
* Initialize the profile service
|
|
@@ -47,6 +43,7 @@ export class ProfileService {
|
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
await this.readPlugins();
|
|
46
|
+
await this.patchPlugins();
|
|
50
47
|
await this.readProjects();
|
|
51
48
|
}
|
|
52
49
|
|
|
@@ -140,39 +137,10 @@ export class ProfileService {
|
|
|
140
137
|
}
|
|
141
138
|
|
|
142
139
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @param pathParts
|
|
145
|
-
*/
|
|
146
|
-
async readJsonFile(...pathParts: string[]): Promise<JSONObject> {
|
|
147
|
-
return readJsonFile(this.getPath(...pathParts));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Write JSON file
|
|
152
|
-
* @param data
|
|
153
|
-
* @param parts
|
|
154
|
-
*/
|
|
155
|
-
async writeJsonFile(data: unknown, ...parts: string[]): Promise<void> {
|
|
156
|
-
const path = this.getPath(...parts);
|
|
157
|
-
try {
|
|
158
|
-
const dirName = dirname(path);
|
|
159
|
-
if (!existsSync(dirName)) {
|
|
160
|
-
await mkdir(dirName, { recursive: true });
|
|
161
|
-
}
|
|
162
|
-
await writeJsonFile(path, data);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
throw new RuniumError(
|
|
165
|
-
`Failed to write JSON file`,
|
|
166
|
-
ErrorCode.PROFILE_JSON_WRITE_ERROR,
|
|
167
|
-
{ path, data, original: error }
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Read JSON file
|
|
140
|
+
* Get path for a file
|
|
173
141
|
* @param parts
|
|
174
142
|
*/
|
|
175
|
-
|
|
143
|
+
getPath(...parts: string[]): string {
|
|
176
144
|
return join(this.path, ...parts);
|
|
177
145
|
}
|
|
178
146
|
|
|
@@ -181,31 +149,51 @@ export class ProfileService {
|
|
|
181
149
|
*/
|
|
182
150
|
private async readPlugins(): Promise<void> {
|
|
183
151
|
this.plugins =
|
|
184
|
-
((await
|
|
185
|
-
()
|
|
186
|
-
|
|
152
|
+
((await this.fileService
|
|
153
|
+
.readJson(this.getPath(PLUGINS_FILE_NAME))
|
|
154
|
+
.catch(() => [])) as ProfilePlugin[]) || this.plugins;
|
|
187
155
|
}
|
|
188
156
|
|
|
189
157
|
/**
|
|
190
158
|
* Write plugins to file
|
|
191
159
|
*/
|
|
192
160
|
private async writePlugins(): Promise<void> {
|
|
193
|
-
await
|
|
161
|
+
await this.fileService.writeJson(
|
|
162
|
+
this.getPath(PLUGINS_FILE_NAME),
|
|
163
|
+
this.plugins
|
|
164
|
+
);
|
|
194
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Patch plugins
|
|
169
|
+
*/
|
|
170
|
+
private async patchPlugins(): Promise<void> {
|
|
171
|
+
const pluginsConfig = this.configService.get('plugins');
|
|
172
|
+
for (const plugin of this.plugins) {
|
|
173
|
+
const config = pluginsConfig[plugin.name];
|
|
174
|
+
if (config) {
|
|
175
|
+
Object.assign(plugin, config);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
195
180
|
/**
|
|
196
181
|
* Read projects from file
|
|
197
182
|
*/
|
|
198
183
|
private async readProjects(): Promise<void> {
|
|
199
184
|
this.projects =
|
|
200
|
-
((await
|
|
201
|
-
()
|
|
202
|
-
|
|
185
|
+
((await this.fileService
|
|
186
|
+
.readJson(this.getPath(PROJECTS_FILE_NAME))
|
|
187
|
+
.catch(() => [])) as ProfileProject[]) || this.projects;
|
|
203
188
|
}
|
|
204
189
|
|
|
205
190
|
/**
|
|
206
191
|
* Write projects to file
|
|
207
192
|
*/
|
|
208
193
|
private async writeProjects(): Promise<void> {
|
|
209
|
-
await
|
|
194
|
+
await this.fileService.writeJson(
|
|
195
|
+
this.getPath(PROJECTS_FILE_NAME),
|
|
196
|
+
this.projects
|
|
197
|
+
);
|
|
210
198
|
}
|
|
211
199
|
}
|
package/src/services/project.ts
CHANGED
|
@@ -16,9 +16,27 @@ export class ProjectService {
|
|
|
16
16
|
* @param path
|
|
17
17
|
*/
|
|
18
18
|
async initProject(path: string): Promise<Project> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
await this.pluginService.runHook('project.beforeConfigRead', path);
|
|
20
|
+
|
|
21
|
+
let content = await this.readFile(path);
|
|
22
|
+
content = await this.pluginService.runHook(
|
|
23
|
+
'project.afterConfigRead',
|
|
24
|
+
content,
|
|
25
|
+
{ mutable: true }
|
|
26
|
+
);
|
|
27
|
+
content = this.applyMacros(content);
|
|
28
|
+
content = await this.pluginService.runHook(
|
|
29
|
+
'project.afterConfigMacrosApply',
|
|
30
|
+
content,
|
|
31
|
+
{ mutable: true }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let projectData = this.parseProjectContent(content, path);
|
|
35
|
+
projectData = await this.pluginService.runHook(
|
|
36
|
+
'project.afterConfigParse',
|
|
37
|
+
projectData,
|
|
38
|
+
{ mutable: true }
|
|
39
|
+
);
|
|
22
40
|
|
|
23
41
|
const project = new Project(projectData);
|
|
24
42
|
return this.extendProjectWithPlugins(project);
|
|
@@ -32,6 +50,16 @@ export class ProjectService {
|
|
|
32
50
|
return resolve(path);
|
|
33
51
|
}
|
|
34
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Run hook
|
|
55
|
+
* @param args
|
|
56
|
+
*/
|
|
57
|
+
runHook(
|
|
58
|
+
...args: Parameters<PluginService['runHook']>
|
|
59
|
+
): ReturnType<PluginService['runHook']> {
|
|
60
|
+
return this.pluginService.runHook(...args);
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
/**
|
|
36
64
|
* Read project file
|
|
37
65
|
* @param path
|
package/src/services/shutdown.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Inject, Service } from 'typedi';
|
|
2
|
-
import { OutputService } from '@services';
|
|
2
|
+
import { OutputService, PluginService } from '@services';
|
|
3
3
|
|
|
4
|
-
type ShutdownBlocker = () => Promise<void> | void;
|
|
4
|
+
type ShutdownBlocker = (reason?: string) => Promise<void> | void;
|
|
5
5
|
|
|
6
6
|
const TIMEOUT = 30000;
|
|
7
7
|
const EXIT_DELAY = 250;
|
|
@@ -12,7 +12,10 @@ export class ShutdownService {
|
|
|
12
12
|
private blockers: Set<ShutdownBlocker> = new Set();
|
|
13
13
|
private isShuttingDown = false;
|
|
14
14
|
|
|
15
|
-
constructor(
|
|
15
|
+
constructor(
|
|
16
|
+
@Inject() private outputService: OutputService,
|
|
17
|
+
@Inject() private pluginService: PluginService
|
|
18
|
+
) {}
|
|
16
19
|
/**
|
|
17
20
|
* Initialize shutdown handlers
|
|
18
21
|
*/
|
|
@@ -82,6 +85,15 @@ export class ShutdownService {
|
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
// add plugin beforeExit hooks as shutdown blockers
|
|
89
|
+
const plugins = this.pluginService.getAllPlugins();
|
|
90
|
+
for (const plugin of plugins) {
|
|
91
|
+
const hook = plugin.hooks?.app?.beforeExit;
|
|
92
|
+
if (hook) {
|
|
93
|
+
this.addBlocker(hook.bind(plugin, reason));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
85
97
|
this.isShuttingDown = true;
|
|
86
98
|
|
|
87
99
|
const exitProcess = (code: number): void => {
|
|
@@ -96,7 +108,7 @@ export class ShutdownService {
|
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
try {
|
|
99
|
-
await this.executeBlockersWithTimeout();
|
|
111
|
+
await this.executeBlockersWithTimeout(reason);
|
|
100
112
|
exitProcess(0);
|
|
101
113
|
} catch (error) {
|
|
102
114
|
exitProcess(1);
|
|
@@ -105,10 +117,11 @@ export class ShutdownService {
|
|
|
105
117
|
|
|
106
118
|
/**
|
|
107
119
|
* Execute all blockers with timeout
|
|
120
|
+
* @param reason
|
|
108
121
|
*/
|
|
109
|
-
private async executeBlockersWithTimeout(): Promise<void> {
|
|
122
|
+
private async executeBlockersWithTimeout(reason: string): Promise<void> {
|
|
110
123
|
const blockerPromises = Array.from(this.blockers).map(blocker =>
|
|
111
|
-
this.executeBlocker(blocker)
|
|
124
|
+
this.executeBlocker(blocker, reason)
|
|
112
125
|
);
|
|
113
126
|
|
|
114
127
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
@@ -123,8 +136,12 @@ export class ShutdownService {
|
|
|
123
136
|
/**
|
|
124
137
|
* Execute a single blocker with error handling
|
|
125
138
|
* @param blocker
|
|
139
|
+
* @param reason
|
|
126
140
|
*/
|
|
127
|
-
private async executeBlocker(
|
|
128
|
-
|
|
141
|
+
private async executeBlocker(
|
|
142
|
+
blocker: ShutdownBlocker,
|
|
143
|
+
reason: string
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
await blocker(reason);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
let version: string | null = null;
|
|
5
|
+
|
|
6
|
+
export function getVersion(): string {
|
|
7
|
+
if (!version) {
|
|
8
|
+
const packageJsonPath = resolve(import.meta.dirname, '..', 'package.json');
|
|
9
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
10
|
+
version = packageJson.version;
|
|
11
|
+
}
|
|
12
|
+
return version as string;
|
|
13
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Ajv, { ValidateFunction } from 'ajv/dist/2020.js';
|
|
2
|
+
|
|
3
|
+
const classes = { Function: Function };
|
|
4
|
+
|
|
5
|
+
export type Validator<T = unknown> = ValidateFunction<T>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create validator
|
|
9
|
+
* @param schema
|
|
10
|
+
*/
|
|
11
|
+
export function createValidator<T = unknown>(schema: object): Validator<T> {
|
|
12
|
+
const ajv = new Ajv({
|
|
13
|
+
allowUnionTypes: true,
|
|
14
|
+
allErrors: true,
|
|
15
|
+
verbose: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
ajv.addKeyword({
|
|
19
|
+
keyword: 'instanceof',
|
|
20
|
+
schemaType: 'string',
|
|
21
|
+
validate: (schema: string, data: unknown) => {
|
|
22
|
+
return data instanceof classes[schema as keyof typeof classes];
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return ajv.compile<T>(schema);
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get config schema
|
|
3
|
+
*/
|
|
4
|
+
export function getConfigSchema(): object {
|
|
5
|
+
return {
|
|
6
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
7
|
+
$id: 'https://runium.dev/schemas/config.json',
|
|
8
|
+
title: 'Runium Config',
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
env: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
path: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
output: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
debug: {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
},
|
|
32
|
+
profile: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
path: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
plugins: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
additionalProperties: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
disabled: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
},
|
|
49
|
+
options: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ErrorObject } from 'ajv';
|
|
2
|
+
import { AggregateAjvError, Options } from '@segment/ajv-human-errors';
|
|
3
|
+
import { AjvError } from '@segment/ajv-human-errors/dist/cjs/aggregate-ajv-error';
|
|
4
|
+
|
|
5
|
+
interface GetErrorMessagesOptions extends Options {
|
|
6
|
+
filter?: (error: AjvError) => boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get error messages
|
|
11
|
+
* @param errors
|
|
12
|
+
* @param options
|
|
13
|
+
*/
|
|
14
|
+
export function getErrorMessages(
|
|
15
|
+
errors: ErrorObject[],
|
|
16
|
+
options: GetErrorMessagesOptions = {
|
|
17
|
+
filter: () => true,
|
|
18
|
+
}
|
|
19
|
+
): string[] {
|
|
20
|
+
const humanErrors = new AggregateAjvError(errors, {
|
|
21
|
+
fieldLabels: 'jsonPath',
|
|
22
|
+
includeData: true,
|
|
23
|
+
includeOriginalError: true,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const filter = options?.filter ?? (_ => true);
|
|
27
|
+
const messages = [];
|
|
28
|
+
for (const error of humanErrors) {
|
|
29
|
+
if (filter(error)) {
|
|
30
|
+
messages.push(error.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return messages;
|
|
35
|
+
}
|