@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.
Files changed (79) hide show
  1. package/build.js +21 -0
  2. package/lib/app.js +2 -2
  3. package/lib/commands/plugin/plugin-add.js +1 -1
  4. package/lib/commands/plugin/plugin-disable.js +1 -1
  5. package/lib/commands/plugin/plugin-enable.js +1 -1
  6. package/lib/commands/plugin/plugin-remove.js +1 -1
  7. package/lib/commands/plugin/plugin.js +1 -1
  8. package/lib/commands/project/project-add.js +1 -1
  9. package/lib/commands/project/project-command.js +1 -1
  10. package/lib/commands/project/project-start.js +1 -1
  11. package/lib/commands/project/project-state-command.js +1 -1
  12. package/lib/commands/project/project-status.js +1 -1
  13. package/lib/commands/project/project-stop.js +1 -1
  14. package/lib/commands/project/project-validate.js +4 -1
  15. package/lib/commands/project/project.js +1 -1
  16. package/lib/commands/runium-command.js +1 -1
  17. package/lib/constants/error-code.js +1 -1
  18. package/lib/index.js +1 -1
  19. package/lib/macros/date.js +1 -0
  20. package/lib/macros/index.js +1 -1
  21. package/lib/macros/path.js +1 -1
  22. package/lib/package.json +3 -2
  23. package/lib/services/command.js +1 -0
  24. package/lib/services/config.js +1 -1
  25. package/lib/services/file.js +1 -0
  26. package/lib/services/index.js +1 -1
  27. package/lib/services/output.js +3 -3
  28. package/lib/services/plugin-context.js +1 -1
  29. package/lib/services/plugin.js +1 -1
  30. package/lib/services/profile.js +1 -1
  31. package/lib/services/project.js +1 -1
  32. package/lib/services/shutdown.js +1 -1
  33. package/lib/utils/get-version.js +1 -0
  34. package/lib/utils/index.js +1 -1
  35. package/lib/validation/create-validator.js +1 -0
  36. package/lib/validation/get-config-schema.js +1 -0
  37. package/lib/validation/get-error-messages.js +1 -0
  38. package/lib/validation/get-plugin-schema.js +1 -0
  39. package/lib/validation/index.js +1 -0
  40. package/package.json +5 -2
  41. package/src/app.ts +35 -20
  42. package/src/commands/plugin/plugin-add.ts +2 -2
  43. package/src/commands/plugin/plugin-disable.ts +1 -1
  44. package/src/commands/plugin/plugin-enable.ts +2 -2
  45. package/src/commands/plugin/plugin-remove.ts +1 -1
  46. package/src/commands/plugin/plugin.ts +11 -16
  47. package/src/commands/project/project-add.ts +23 -5
  48. package/src/commands/project/project-command.ts +8 -1
  49. package/src/commands/project/project-start.ts +18 -12
  50. package/src/commands/project/project-state-command.ts +4 -19
  51. package/src/commands/project/project-status.ts +16 -3
  52. package/src/commands/project/project-stop.ts +5 -1
  53. package/src/commands/project/project-validate.ts +23 -10
  54. package/src/commands/project/project.ts +13 -18
  55. package/src/commands/runium-command.ts +18 -16
  56. package/src/constants/error-code.ts +15 -2
  57. package/src/global.d.ts +6 -0
  58. package/src/index.ts +5 -2
  59. package/src/macros/date.ts +15 -0
  60. package/src/macros/index.ts +6 -1
  61. package/src/macros/path.ts +17 -2
  62. package/src/services/command.ts +171 -0
  63. package/src/services/config.ts +47 -4
  64. package/src/services/file.ts +272 -0
  65. package/src/services/index.ts +2 -0
  66. package/src/services/output.ts +5 -1
  67. package/src/services/plugin-context.ts +68 -9
  68. package/src/services/plugin.ts +122 -18
  69. package/src/services/profile.ts +37 -49
  70. package/src/services/project.ts +31 -3
  71. package/src/services/shutdown.ts +25 -8
  72. package/src/utils/get-version.ts +13 -0
  73. package/src/utils/index.ts +1 -0
  74. package/src/validation/create-validator.ts +27 -0
  75. package/src/validation/get-config-schema.ts +59 -0
  76. package/src/validation/get-error-messages.ts +35 -0
  77. package/src/validation/get-plugin-schema.ts +137 -0
  78. package/src/validation/index.ts +4 -0
  79. package/tsconfig.json +8 -10
@@ -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
- Macro,
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
- type PluginOptions = Record<string, unknown>;
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?: Record<string, Macro>;
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 interface PluginOptionsDefinition {
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?: Record<string, unknown>;
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?: PluginOptionsDefinition;
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.PLUGIN_INCORRECT_PLUGIN,
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
- // TODO add plugin validation
136
- if (!plugin || !plugin?.name) {
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.PLUGIN_INCORRECT_PLUGIN,
140
- { name: plugin.name }
243
+ ErrorCode.PLUGIN_INVALID,
244
+ { errors: errorMessages }
141
245
  );
142
246
  }
143
247
  }
@@ -1,15 +1,8 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { mkdir } from 'node:fs/promises';
3
- import { dirname, join } from 'node:path';
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(@Inject() private configService: ConfigService) {}
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
- * Read JSON file
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
- private getPath(...parts: string[]): string {
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 readJsonFile(this.getPath(PLUGINS_FILE_NAME)).catch(
185
- () => []
186
- )) as ProfilePlugin[]) || this.plugins;
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 writeJsonFile(this.getPath(PLUGINS_FILE_NAME), this.plugins);
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 readJsonFile(this.getPath(PROJECTS_FILE_NAME)).catch(
201
- () => []
202
- )) as ProfileProject[]) || this.projects;
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 writeJsonFile(this.getPath(PROJECTS_FILE_NAME), this.projects);
194
+ await this.fileService.writeJson(
195
+ this.getPath(PROJECTS_FILE_NAME),
196
+ this.projects
197
+ );
210
198
  }
211
199
  }
@@ -16,9 +16,27 @@ export class ProjectService {
16
16
  * @param path
17
17
  */
18
18
  async initProject(path: string): Promise<Project> {
19
- const content = await this.readFile(path);
20
- const projectContent = this.applyMacros(content);
21
- const projectData = this.parseProjectContent(projectContent, path);
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
@@ -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(@Inject() private outputService: OutputService) {}
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(blocker: ShutdownBlocker): Promise<void> {
128
- await blocker();
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
+ }
@@ -1,3 +1,4 @@
1
1
  export { convertPathToValidFileName } from './convert-path-to-valid-file-name.js';
2
2
  export { debounce } from './debounce.js';
3
3
  export { formatTimestamp } from './format-timestamp.js';
4
+ export { getVersion } from './get-version.js';
@@ -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
+ }