ts-runtime-validation 1.6.15 → 1.7.0

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/.claude/settings.local.json +9 -0
  2. package/CONTRIBUTING.md +430 -0
  3. package/README.md +444 -64
  4. package/dist/ICommandOptions.js +3 -0
  5. package/dist/ICommandOptions.js.map +1 -0
  6. package/dist/SchemaGenerator.integration.test.js +323 -0
  7. package/dist/SchemaGenerator.integration.test.js.map +1 -0
  8. package/dist/SchemaGenerator.js +120 -0
  9. package/dist/SchemaGenerator.js.map +1 -0
  10. package/dist/SchemaGenerator.test.js +132 -0
  11. package/dist/SchemaGenerator.test.js.map +1 -0
  12. package/dist/errors/index.js +95 -0
  13. package/dist/errors/index.js.map +1 -0
  14. package/dist/errors/index.test.js +232 -0
  15. package/dist/errors/index.test.js.map +1 -0
  16. package/dist/getPosixPath.js +13 -0
  17. package/dist/getPosixPath.js.map +1 -0
  18. package/dist/index.js +27 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/lib.js.map +1 -0
  21. package/dist/services/CodeGenerator.js +305 -0
  22. package/dist/services/CodeGenerator.js.map +1 -0
  23. package/dist/services/FileDiscovery.js +121 -0
  24. package/dist/services/FileDiscovery.js.map +1 -0
  25. package/dist/services/FileDiscovery.test.js +184 -0
  26. package/dist/services/FileDiscovery.test.js.map +1 -0
  27. package/dist/services/SchemaProcessor.js +182 -0
  28. package/dist/services/SchemaProcessor.js.map +1 -0
  29. package/dist/services/SchemaProcessor.test.js +395 -0
  30. package/dist/services/SchemaProcessor.test.js.map +1 -0
  31. package/dist/services/SchemaWriter.js +76 -0
  32. package/dist/services/SchemaWriter.js.map +1 -0
  33. package/dist/services/SchemaWriter.test.js +255 -0
  34. package/dist/services/SchemaWriter.test.js.map +1 -0
  35. package/dist/test/basic-scenario/types.jsonschema.js +3 -0
  36. package/dist/test/basic-scenario/types.jsonschema.js.map +1 -0
  37. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js +3 -0
  38. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js.map +1 -0
  39. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  40. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  41. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js +3 -0
  42. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  43. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js +3 -0
  44. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  45. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js +3 -0
  46. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js.map +1 -0
  47. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  48. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  49. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js +3 -0
  50. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  51. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js +3 -0
  52. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  53. package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js +3 -0
  54. package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js.map +1 -0
  55. package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js +49 -0
  56. package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js.map +1 -0
  57. package/dist/utils/ProgressReporter.js +67 -0
  58. package/dist/utils/ProgressReporter.js.map +1 -0
  59. package/dist/utils/ProgressReporter.test.js +267 -0
  60. package/dist/utils/ProgressReporter.test.js.map +1 -0
  61. package/dist/writeLine.js +12 -0
  62. package/dist/writeLine.js.map +1 -0
  63. package/package.json +2 -2
  64. package/src/ICommandOptions.ts +7 -0
  65. package/src/SchemaGenerator.integration.test.ts +411 -0
  66. package/src/SchemaGenerator.test.ts +7 -0
  67. package/src/SchemaGenerator.ts +112 -298
  68. package/src/errors/index.test.ts +319 -0
  69. package/src/errors/index.ts +92 -0
  70. package/src/index.ts +7 -0
  71. package/src/services/CodeGenerator.ts +352 -0
  72. package/src/services/FileDiscovery.test.ts +216 -0
  73. package/src/services/FileDiscovery.ts +137 -0
  74. package/src/services/SchemaProcessor.test.ts +464 -0
  75. package/src/services/SchemaProcessor.ts +173 -0
  76. package/src/services/SchemaWriter.test.ts +304 -0
  77. package/src/services/SchemaWriter.ts +75 -0
  78. package/src/utils/ProgressReporter.test.ts +357 -0
  79. package/src/utils/ProgressReporter.ts +76 -0
@@ -0,0 +1,464 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { SchemaProcessor } from "./SchemaProcessor";
4
+ import { FileInfo } from "./FileDiscovery";
5
+ import { DuplicateSymbolError } from "../errors";
6
+
7
+ const testDir = path.resolve(__dirname, "../test-tmp/schema-processor");
8
+
9
+ const createTestFile = async (filePath: string, content: string) => {
10
+ const fullPath = path.resolve(testDir, filePath);
11
+ await fs.promises.mkdir(path.dirname(fullPath), { recursive: true });
12
+ await fs.promises.writeFile(fullPath, content);
13
+ return fullPath;
14
+ };
15
+
16
+ const cleanup = async () => {
17
+ if (fs.existsSync(testDir)) {
18
+ await fs.promises.rm(testDir, { recursive: true, force: true });
19
+ }
20
+ };
21
+
22
+ beforeEach(cleanup);
23
+ afterAll(cleanup);
24
+
25
+ describe("SchemaProcessor", () => {
26
+ describe("processFiles", () => {
27
+ it("should process valid TypeScript files", async () => {
28
+ const filePath = await createTestFile("user.jsonschema.ts", `
29
+ export interface IUser {
30
+ id: string;
31
+ name: string;
32
+ email?: string;
33
+ }
34
+ `);
35
+
36
+ const processor = new SchemaProcessor({
37
+ additionalProperties: false,
38
+ parallel: false,
39
+ verbose: false
40
+ });
41
+
42
+ const files: FileInfo[] = [{ path: filePath }];
43
+ const schemaMap = await processor.processFiles(files);
44
+
45
+ expect(schemaMap.size).toBe(1);
46
+ expect(schemaMap.has(filePath)).toBe(true);
47
+
48
+ const schema = schemaMap.get(filePath)!;
49
+ expect(schema.definitions).toBeDefined();
50
+ expect(schema.definitions!.IUser).toBeDefined();
51
+ expect(schema.definitions!.IUser).toMatchObject({
52
+ type: "object",
53
+ properties: {
54
+ id: { type: "string" },
55
+ name: { type: "string" },
56
+ email: { type: "string" }
57
+ },
58
+ required: ["id", "name"],
59
+ additionalProperties: false
60
+ });
61
+ });
62
+
63
+ it("should handle parallel processing", async () => {
64
+ const file1 = await createTestFile("user.jsonschema.ts", `
65
+ export interface IUser {
66
+ id: string;
67
+ name: string;
68
+ }
69
+ `);
70
+
71
+ const file2 = await createTestFile("product.jsonschema.ts", `
72
+ export interface IProduct {
73
+ id: string;
74
+ title: string;
75
+ price: number;
76
+ }
77
+ `);
78
+
79
+ const processor = new SchemaProcessor({
80
+ additionalProperties: false,
81
+ parallel: true,
82
+ verbose: false
83
+ });
84
+
85
+ const files: FileInfo[] = [
86
+ { path: file1 },
87
+ { path: file2 }
88
+ ];
89
+
90
+ const schemaMap = await processor.processFiles(files);
91
+
92
+ expect(schemaMap.size).toBe(2);
93
+ expect(schemaMap.has(file1)).toBe(true);
94
+ expect(schemaMap.has(file2)).toBe(true);
95
+ });
96
+
97
+ it("should handle sequential processing", async () => {
98
+ const file1 = await createTestFile("user.jsonschema.ts", `
99
+ export interface IUser {
100
+ id: string;
101
+ name: string;
102
+ }
103
+ `);
104
+
105
+ const processor = new SchemaProcessor({
106
+ additionalProperties: false,
107
+ parallel: false,
108
+ verbose: false
109
+ });
110
+
111
+ const files: FileInfo[] = [{ path: file1 }];
112
+ const schemaMap = await processor.processFiles(files);
113
+
114
+ expect(schemaMap.size).toBe(1);
115
+ });
116
+
117
+ it("should handle files with syntax errors gracefully", async () => {
118
+ const validFile = await createTestFile("valid.jsonschema.ts", `
119
+ export interface IValid {
120
+ id: string;
121
+ }
122
+ `);
123
+
124
+ const invalidFile = await createTestFile("invalid.jsonschema.ts", `
125
+ export interface IInvalid {
126
+ id: string
127
+ // missing semicolon and other syntax errors
128
+ name string;
129
+ }
130
+ `);
131
+
132
+ const processor = new SchemaProcessor({
133
+ additionalProperties: false,
134
+ parallel: false,
135
+ verbose: false
136
+ });
137
+
138
+ const files: FileInfo[] = [
139
+ { path: validFile },
140
+ { path: invalidFile }
141
+ ];
142
+
143
+ // Should not throw but should process valid files
144
+ const schemaMap = await processor.processFiles(files);
145
+
146
+ // At least valid file should be processed
147
+ expect(schemaMap.size).toBeGreaterThan(0);
148
+ expect(schemaMap.has(validFile)).toBe(true);
149
+ });
150
+
151
+ it("should respect additionalProperties setting", async () => {
152
+ const filePath = await createTestFile("user.jsonschema.ts", `
153
+ export interface IUser {
154
+ id: string;
155
+ name: string;
156
+ }
157
+ `);
158
+
159
+ const processorStrict = new SchemaProcessor({
160
+ additionalProperties: false,
161
+ parallel: false
162
+ });
163
+
164
+ const processorLoose = new SchemaProcessor({
165
+ additionalProperties: true,
166
+ parallel: false
167
+ });
168
+
169
+ const files: FileInfo[] = [{ path: filePath }];
170
+
171
+ const strictSchema = await processorStrict.processFiles(files);
172
+ const looseSchema = await processorLoose.processFiles(files);
173
+
174
+ const strictUser = strictSchema.get(filePath)!.definitions!.IUser as any;
175
+ const looseUser = looseSchema.get(filePath)!.definitions!.IUser as any;
176
+
177
+ expect(strictUser.additionalProperties).toBe(false);
178
+ // Note: ts-json-schema-generator may not set additionalProperties: true explicitly
179
+ expect(looseUser.additionalProperties === true || looseUser.additionalProperties === undefined).toBe(true);
180
+ });
181
+ });
182
+
183
+ describe("validateSchemaCompatibility", () => {
184
+ it("should pass with identical schemas", async () => {
185
+ const file1 = await createTestFile("user1.jsonschema.ts", `
186
+ export interface IUser {
187
+ id: string;
188
+ name: string;
189
+ }
190
+ `);
191
+
192
+ const file2 = await createTestFile("user2.jsonschema.ts", `
193
+ export interface IUser {
194
+ id: string;
195
+ name: string;
196
+ }
197
+ `);
198
+
199
+ const processor = new SchemaProcessor({
200
+ additionalProperties: false,
201
+ parallel: false
202
+ });
203
+
204
+ const files: FileInfo[] = [
205
+ { path: file1 },
206
+ { path: file2 }
207
+ ];
208
+
209
+ const schemaMap = await processor.processFiles(files);
210
+
211
+ expect(() => {
212
+ processor.validateSchemaCompatibility(schemaMap);
213
+ }).not.toThrow();
214
+ });
215
+
216
+ it("should throw DuplicateSymbolError for conflicting schemas", async () => {
217
+ const file1 = await createTestFile("user1.jsonschema.ts", `
218
+ export interface IUser {
219
+ id: string;
220
+ name: string;
221
+ }
222
+ `);
223
+
224
+ const file2 = await createTestFile("user2.jsonschema.ts", `
225
+ export interface IUser {
226
+ id: string;
227
+ email: string;
228
+ }
229
+ `);
230
+
231
+ const processor = new SchemaProcessor({
232
+ additionalProperties: false,
233
+ parallel: false
234
+ });
235
+
236
+ const files: FileInfo[] = [
237
+ { path: file1 },
238
+ { path: file2 }
239
+ ];
240
+
241
+ const schemaMap = await processor.processFiles(files);
242
+
243
+ expect(() => {
244
+ processor.validateSchemaCompatibility(schemaMap);
245
+ }).toThrow(DuplicateSymbolError);
246
+ });
247
+
248
+ it("should allow different symbols with same name in different contexts", async () => {
249
+ const file1 = await createTestFile("api/user.jsonschema.ts", `
250
+ export interface IUser {
251
+ id: string;
252
+ name: string;
253
+ }
254
+
255
+ export interface IProduct {
256
+ id: string;
257
+ title: string;
258
+ }
259
+ `);
260
+
261
+ const processor = new SchemaProcessor({
262
+ additionalProperties: false,
263
+ parallel: false
264
+ });
265
+
266
+ const files: FileInfo[] = [{ path: file1 }];
267
+ const schemaMap = await processor.processFiles(files);
268
+
269
+ expect(() => {
270
+ processor.validateSchemaCompatibility(schemaMap);
271
+ }).not.toThrow();
272
+ });
273
+ });
274
+
275
+ describe("mergeSchemas", () => {
276
+ it("should merge multiple schemas correctly", async () => {
277
+ const file1 = await createTestFile("user.jsonschema.ts", `
278
+ export interface IUser {
279
+ id: string;
280
+ name: string;
281
+ }
282
+ `);
283
+
284
+ const file2 = await createTestFile("product.jsonschema.ts", `
285
+ export interface IProduct {
286
+ id: string;
287
+ title: string;
288
+ price: number;
289
+ }
290
+ `);
291
+
292
+ const processor = new SchemaProcessor({
293
+ additionalProperties: false,
294
+ parallel: false
295
+ });
296
+
297
+ const files: FileInfo[] = [
298
+ { path: file1 },
299
+ { path: file2 }
300
+ ];
301
+
302
+ const schemaMap = await processor.processFiles(files);
303
+ const mergedSchema = processor.mergeSchemas(schemaMap);
304
+
305
+ expect(mergedSchema.$schema).toBe("http://json-schema.org/draft-07/schema#");
306
+ expect(mergedSchema.definitions).toBeDefined();
307
+ expect(mergedSchema.definitions!.IUser).toBeDefined();
308
+ expect(mergedSchema.definitions!.IProduct).toBeDefined();
309
+ });
310
+
311
+ it("should preserve schema version from first file", async () => {
312
+ const file1 = await createTestFile("user.jsonschema.ts", `
313
+ export interface IUser {
314
+ id: string;
315
+ name: string;
316
+ }
317
+ `);
318
+
319
+ const processor = new SchemaProcessor({
320
+ additionalProperties: false,
321
+ parallel: false
322
+ });
323
+
324
+ const files: FileInfo[] = [{ path: file1 }];
325
+ const schemaMap = await processor.processFiles(files);
326
+ const mergedSchema = processor.mergeSchemas(schemaMap);
327
+
328
+ expect(mergedSchema.$schema).toMatch(/json-schema\.org/);
329
+ });
330
+
331
+ it("should handle empty schema map", () => {
332
+ const processor = new SchemaProcessor({
333
+ additionalProperties: false,
334
+ parallel: false
335
+ });
336
+
337
+ const schemaMap = new Map();
338
+ const mergedSchema = processor.mergeSchemas(schemaMap);
339
+
340
+ expect(mergedSchema.$schema).toBe("http://json-schema.org/draft-07/schema#");
341
+ expect(mergedSchema.definitions).toEqual({});
342
+ });
343
+ });
344
+
345
+ describe("error handling", () => {
346
+ it("should handle missing files gracefully", async () => {
347
+ const processor = new SchemaProcessor({
348
+ additionalProperties: false,
349
+ parallel: false,
350
+ verbose: false
351
+ });
352
+
353
+ const files: FileInfo[] = [
354
+ { path: "/nonexistent/file.ts" }
355
+ ];
356
+
357
+ // Should not throw but return empty map
358
+ const schemaMap = await processor.processFiles(files);
359
+ expect(schemaMap.size).toBe(0);
360
+ });
361
+
362
+ it("should provide verbose error information", async () => {
363
+ const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
364
+
365
+ const processor = new SchemaProcessor({
366
+ additionalProperties: false,
367
+ parallel: false,
368
+ verbose: true
369
+ });
370
+
371
+ const files: FileInfo[] = [
372
+ { path: "/nonexistent/file.ts" }
373
+ ];
374
+
375
+ await processor.processFiles(files);
376
+
377
+ expect(consoleWarnSpy).toHaveBeenCalled();
378
+ consoleWarnSpy.mockRestore();
379
+ });
380
+ });
381
+
382
+ describe("TypeScript features", () => {
383
+ it("should handle union types", async () => {
384
+ const filePath = await createTestFile("types.jsonschema.ts", `
385
+ export type Status = "active" | "inactive" | "pending";
386
+
387
+ export interface IUser {
388
+ id: string;
389
+ status: Status;
390
+ }
391
+ `);
392
+
393
+ const processor = new SchemaProcessor({
394
+ additionalProperties: false,
395
+ parallel: false
396
+ });
397
+
398
+ const files: FileInfo[] = [{ path: filePath }];
399
+ const schemaMap = await processor.processFiles(files);
400
+
401
+ expect(schemaMap.size).toBe(1);
402
+ const schema = schemaMap.get(filePath)!;
403
+ expect(schema.definitions!.Status).toBeDefined();
404
+ expect(schema.definitions!.IUser).toBeDefined();
405
+ });
406
+
407
+ it("should handle optional properties", async () => {
408
+ const filePath = await createTestFile("user.jsonschema.ts", `
409
+ export interface IUser {
410
+ id: string;
411
+ name?: string;
412
+ email?: string;
413
+ age: number;
414
+ }
415
+ `);
416
+
417
+ const processor = new SchemaProcessor({
418
+ additionalProperties: false,
419
+ parallel: false
420
+ });
421
+
422
+ const files: FileInfo[] = [{ path: filePath }];
423
+ const schemaMap = await processor.processFiles(files);
424
+
425
+ const schema = schemaMap.get(filePath)!;
426
+ const userSchema = schema.definitions!.IUser as any;
427
+
428
+ expect(userSchema.required).toEqual(expect.arrayContaining(["id", "age"]));
429
+ expect(userSchema.required).not.toContain("name");
430
+ expect(userSchema.required).not.toContain("email");
431
+ });
432
+
433
+ it("should handle nested interfaces", async () => {
434
+ const filePath = await createTestFile("nested.jsonschema.ts", `
435
+ export interface IAddress {
436
+ street: string;
437
+ city: string;
438
+ country: string;
439
+ }
440
+
441
+ export interface IUser {
442
+ id: string;
443
+ name: string;
444
+ address: IAddress;
445
+ }
446
+ `);
447
+
448
+ const processor = new SchemaProcessor({
449
+ additionalProperties: false,
450
+ parallel: false
451
+ });
452
+
453
+ const files: FileInfo[] = [{ path: filePath }];
454
+ const schemaMap = await processor.processFiles(files);
455
+
456
+ const schema = schemaMap.get(filePath)!;
457
+ expect(schema.definitions!.IAddress).toBeDefined();
458
+ expect(schema.definitions!.IUser).toBeDefined();
459
+
460
+ const userSchema = schema.definitions!.IUser as any;
461
+ expect(userSchema.properties.address.$ref).toBe("#/definitions/IAddress");
462
+ });
463
+ });
464
+ });
@@ -0,0 +1,173 @@
1
+ import * as tsj from "ts-json-schema-generator";
2
+ import { Config, Schema } from "ts-json-schema-generator";
3
+ import assert from "assert";
4
+ import { SchemaGenerationError, DuplicateSymbolError } from "../errors";
5
+ import { FileInfo } from "./FileDiscovery";
6
+
7
+ export interface SchemaProcessorOptions {
8
+ additionalProperties: boolean;
9
+ tsconfigPath?: string;
10
+ parallel?: boolean;
11
+ verbose?: boolean;
12
+ }
13
+
14
+ export interface ProcessingResult {
15
+ file: string;
16
+ schema: Schema | null;
17
+ error?: Error;
18
+ }
19
+
20
+ export class SchemaProcessor {
21
+ constructor(private options: SchemaProcessorOptions) {}
22
+
23
+ public async processFiles(files: FileInfo[]): Promise<Map<string, Schema>> {
24
+ const { parallel = true, verbose = false } = this.options;
25
+
26
+ if (verbose) {
27
+ console.log(`Processing ${files.length} files...`);
28
+ }
29
+
30
+ const results = parallel
31
+ ? await this.processInParallel(files)
32
+ : await this.processSequentially(files);
33
+
34
+ return this.consolidateSchemas(results);
35
+ }
36
+
37
+ private async processInParallel(files: FileInfo[]): Promise<ProcessingResult[]> {
38
+ const promises = files.map(file => this.processFile(file));
39
+ const results = await Promise.allSettled(promises);
40
+
41
+ return results.map((result, index) => {
42
+ if (result.status === 'fulfilled') {
43
+ return result.value;
44
+ } else {
45
+ return {
46
+ file: files[index].path,
47
+ schema: null,
48
+ error: result.reason
49
+ };
50
+ }
51
+ });
52
+ }
53
+
54
+ private async processSequentially(files: FileInfo[]): Promise<ProcessingResult[]> {
55
+ const results: ProcessingResult[] = [];
56
+
57
+ for (const file of files) {
58
+ try {
59
+ const result = await this.processFile(file);
60
+ results.push(result);
61
+ } catch (error) {
62
+ results.push({
63
+ file: file.path,
64
+ schema: null,
65
+ error: error instanceof Error ? error : new Error(String(error))
66
+ });
67
+ }
68
+ }
69
+
70
+ return results;
71
+ }
72
+
73
+ private async processFile(file: FileInfo): Promise<ProcessingResult> {
74
+ const { additionalProperties, tsconfigPath, verbose } = this.options;
75
+
76
+ try {
77
+ if (verbose) {
78
+ console.log(`Processing: ${file.path}`);
79
+ }
80
+
81
+ const config: Config = {
82
+ path: file.path,
83
+ type: "*",
84
+ additionalProperties,
85
+ encodeRefs: false,
86
+ sortProps: true,
87
+ ...(tsconfigPath ? { tsconfig: tsconfigPath } : {}),
88
+ };
89
+
90
+ const schemaGenerator = tsj.createGenerator(config);
91
+ const schema = schemaGenerator.createSchema(config.type);
92
+
93
+ return {
94
+ file: file.path,
95
+ schema,
96
+ error: undefined
97
+ };
98
+ } catch (error) {
99
+ throw new SchemaGenerationError(
100
+ `Failed to process ${file.path}: ${error instanceof Error ? error.message : String(error)}`
101
+ );
102
+ }
103
+ }
104
+
105
+ private consolidateSchemas(results: ProcessingResult[]): Map<string, Schema> {
106
+ const schemaMap = new Map<string, Schema>();
107
+ const errors: Error[] = [];
108
+
109
+ for (const result of results) {
110
+ if (result.error) {
111
+ errors.push(result.error);
112
+ continue;
113
+ }
114
+
115
+ if (result.schema) {
116
+ schemaMap.set(result.file, result.schema);
117
+ }
118
+ }
119
+
120
+ if (errors.length > 0 && this.options.verbose) {
121
+ console.warn(`Encountered ${errors.length} errors during processing:`);
122
+ errors.forEach(error => console.warn(` - ${error.message}`));
123
+ }
124
+
125
+ return schemaMap;
126
+ }
127
+
128
+ public validateSchemaCompatibility(schemaMap: Map<string, Schema>): void {
129
+ const definitions: { [id: string]: any } = {};
130
+
131
+ schemaMap.forEach((fileSchema, filePath) => {
132
+ const defs = fileSchema.definitions ?? {};
133
+
134
+ Object.keys(defs).forEach((key) => {
135
+ if (definitions[key] !== undefined) {
136
+ try {
137
+ assert.deepEqual(definitions[key], defs[key]);
138
+ } catch (e) {
139
+ throw new DuplicateSymbolError(
140
+ `Duplicate symbol '${key}' found with different implementations`,
141
+ key,
142
+ filePath,
143
+ definitions[key],
144
+ defs[key]
145
+ );
146
+ }
147
+ }
148
+ definitions[key] = defs[key];
149
+ });
150
+ });
151
+ }
152
+
153
+ public mergeSchemas(schemaMap: Map<string, Schema>): Schema {
154
+ const definitions: { [id: string]: Schema } = {};
155
+ let schemaVersion = "";
156
+
157
+ schemaMap.forEach((fileSchema) => {
158
+ if (!schemaVersion && fileSchema["$schema"]) {
159
+ schemaVersion = fileSchema["$schema"];
160
+ }
161
+
162
+ const defs = fileSchema.definitions ?? {};
163
+ Object.keys(defs).forEach((key) => {
164
+ definitions[key] = defs[key] as Schema;
165
+ });
166
+ });
167
+
168
+ return {
169
+ $schema: schemaVersion || "http://json-schema.org/draft-07/schema#",
170
+ definitions,
171
+ };
172
+ }
173
+ }