ts-runtime-validation 1.7.0 → 1.8.1

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 (44) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +93 -30
  3. package/dist/SchemaGenerator.deterministic-extended.test.js +420 -0
  4. package/dist/SchemaGenerator.deterministic-extended.test.js.map +1 -0
  5. package/dist/SchemaGenerator.deterministic.test.js +251 -0
  6. package/dist/SchemaGenerator.deterministic.test.js.map +1 -0
  7. package/dist/SchemaGenerator.integration.test.js +3 -3
  8. package/dist/SchemaGenerator.integration.test.js.map +1 -1
  9. package/dist/SchemaGenerator.test.js +94 -0
  10. package/dist/SchemaGenerator.test.js.map +1 -1
  11. package/dist/cli.test.js +155 -0
  12. package/dist/cli.test.js.map +1 -0
  13. package/dist/index.js +1 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/services/CodeGenerator.js +29 -13
  16. package/dist/services/CodeGenerator.js.map +1 -1
  17. package/dist/services/FileDiscovery.js +4 -2
  18. package/dist/services/FileDiscovery.js.map +1 -1
  19. package/dist/services/FileDiscovery.test.js +1 -1
  20. package/dist/services/FileDiscovery.test.js.map +1 -1
  21. package/dist/services/SchemaProcessor.js +27 -11
  22. package/dist/services/SchemaProcessor.js.map +1 -1
  23. package/dist/services/SchemaProcessor.test.js +61 -1
  24. package/dist/services/SchemaProcessor.test.js.map +1 -1
  25. package/dist/services/SchemaWriter.test.js +1 -1
  26. package/dist/services/SchemaWriter.test.js.map +1 -1
  27. package/package.json +10 -9
  28. package/src/SchemaGenerator.deterministic-extended.test.ts +429 -0
  29. package/src/SchemaGenerator.deterministic.test.ts +276 -0
  30. package/src/SchemaGenerator.integration.test.ts +3 -3
  31. package/src/SchemaGenerator.test.ts +111 -0
  32. package/src/cli.test.ts +130 -0
  33. package/src/index.ts +1 -1
  34. package/src/services/CodeGenerator.ts +30 -12
  35. package/src/services/FileDiscovery.test.ts +1 -1
  36. package/src/services/FileDiscovery.ts +5 -2
  37. package/src/services/SchemaProcessor.test.ts +73 -1
  38. package/src/services/SchemaProcessor.ts +30 -9
  39. package/src/services/SchemaWriter.test.ts +1 -1
  40. package/.claude/settings.local.json +0 -9
  41. package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js +0 -3
  42. package/dist/test/output/duplicate-symbols-identitcal-implementation/ValidationType.js.map +0 -1
  43. package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js +0 -49
  44. package/dist/test/output/duplicate-symbols-identitcal-implementation/isValidSchema.js.map +0 -1
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { SchemaGenerator } from "./SchemaGenerator";
4
4
  import { ICommandOptions } from "./ICommandOptions";
5
5
 
6
- const testDir = path.resolve(__dirname, "./test-tmp/integration");
6
+ const testDir = path.resolve(__dirname, "../.test-tmp/integration");
7
7
 
8
8
  const createTestFile = async (filePath: string, content: string) => {
9
9
  const fullPath = path.resolve(testDir, filePath);
@@ -396,8 +396,8 @@ describe("SchemaGenerator Integration Tests", () => {
396
396
  const endTime = Date.now();
397
397
  const duration = endTime - startTime;
398
398
 
399
- // Should complete reasonably quickly (less than 10 seconds)
400
- expect(duration).toBeLessThan(10000);
399
+ // Should complete reasonably quickly (less than 20 seconds)
400
+ expect(duration).toBeLessThan(20000);
401
401
 
402
402
  // Verify all files were processed
403
403
  const schemaFile = path.join(testDir, "output", "validation.schema.json");
@@ -122,4 +122,115 @@ describe("SchemaGenerator", () => {
122
122
  };
123
123
  expect(result).toStrictEqual(expected);
124
124
  });
125
+
126
+ test("it should sort definitions alphabetically in the generated schema", async () => {
127
+ // Create test directory and files
128
+ const testDir = path.resolve(__dirname, "./test/alphabetical-sorting");
129
+
130
+ // Ensure test directory exists
131
+ fs.mkdirSync(testDir, { recursive: true });
132
+
133
+ // Create test file with unsorted type definitions
134
+ const testFilePath = path.join(testDir, "types.jsonschema.ts");
135
+ fs.writeFileSync(testFilePath, `
136
+ export interface ZebraType {
137
+ id: string;
138
+ }
139
+
140
+ export interface AppleType {
141
+ name: string;
142
+ }
143
+
144
+ export interface MiddleType {
145
+ value: number;
146
+ }
147
+
148
+ export interface BananaType {
149
+ flag: boolean;
150
+ }
151
+ `);
152
+
153
+ try {
154
+ const options = getGeneratorConfig("alphabetical-sorting");
155
+ const generator = new SchemaGenerator(options);
156
+ await generator.Generate();
157
+
158
+ const rawFile = fs.readFileSync(getOutputSchemaPath("alphabetical-sorting")).toString();
159
+ const result = JSON.parse(rawFile);
160
+
161
+ // Check that definitions are sorted alphabetically
162
+ const definitionKeys = Object.keys(result.definitions);
163
+ const sortedKeys = [...definitionKeys].sort();
164
+
165
+ expect(definitionKeys).toEqual(sortedKeys);
166
+ expect(definitionKeys).toEqual(['AppleType', 'BananaType', 'MiddleType', 'ZebraType']);
167
+ } finally {
168
+ // Clean up test directory
169
+ if (fs.existsSync(testDir)) {
170
+ fs.rmSync(testDir, { recursive: true });
171
+ }
172
+ }
173
+ });
174
+
175
+ test("it should sort properties alphabetically in generated TypeScript helpers", async () => {
176
+ // Create test directory and files
177
+ const testDir = path.resolve(__dirname, "./test/alphabetical-helpers");
178
+ const outputDir = path.resolve(__dirname, "./test/output/alphabetical-helpers");
179
+
180
+ // Ensure test directory exists
181
+ fs.mkdirSync(testDir, { recursive: true });
182
+
183
+ // Create test file with unsorted type definitions
184
+ const testFilePath = path.join(testDir, "types.jsonschema.ts");
185
+ fs.writeFileSync(testFilePath, `
186
+ export type ZebraType = string;
187
+ export type AppleType = number;
188
+ export type MiddleType = boolean;
189
+ export type BananaType = object;
190
+ `);
191
+
192
+ try {
193
+ const options = {
194
+ ...getGeneratorConfig("alphabetical-helpers"),
195
+ helpers: true
196
+ };
197
+ const generator = new SchemaGenerator(options);
198
+ await generator.Generate();
199
+
200
+ // Check SchemaDefinition.ts for sorted imports and properties
201
+ const schemaDefPath = path.resolve(outputDir, "SchemaDefinition.ts");
202
+ if (fs.existsSync(schemaDefPath)) {
203
+ const schemaDefContent = fs.readFileSync(schemaDefPath, 'utf-8');
204
+
205
+ // Extract type names from the schemas object (looking only at the schemas constant)
206
+ const schemasMatch = schemaDefContent.match(/export const schemas[^{]*{([\s\S]*?)}/);
207
+ let schemaKeys: string[] = [];
208
+ if (schemasMatch) {
209
+ const schemaLines = schemasMatch[1].split('\n').filter(line => line.includes('#/definitions/'));
210
+ schemaKeys = schemaLines.map(line => {
211
+ const match = line.match(/\["#\/definitions\/([^"]+)"\]/);
212
+ return match ? match[1] : null;
213
+ }).filter(Boolean) as string[];
214
+ }
215
+
216
+ const sortedSchemaKeys = [...schemaKeys].sort();
217
+
218
+ expect(schemaKeys).toEqual(sortedSchemaKeys);
219
+ expect(schemaKeys).toEqual(['AppleType', 'BananaType', 'MiddleType', 'ZebraType']);
220
+
221
+ // Check that imports are sorted
222
+ const importMatch = schemaDefContent.match(/import\s+{\s*([^}]+)\s*}/);
223
+ if (importMatch) {
224
+ const imports = importMatch[1].split(',').map(s => s.trim());
225
+ const sortedImports = [...imports].sort();
226
+ expect(imports).toEqual(sortedImports);
227
+ }
228
+ }
229
+ } finally {
230
+ // Clean up test directory
231
+ if (fs.existsSync(testDir)) {
232
+ fs.rmSync(testDir, { recursive: true });
233
+ }
234
+ }
235
+ });
125
236
  });
@@ -0,0 +1,130 @@
1
+ import { spawn } from "child_process";
2
+ import * as path from "path";
3
+ import * as fs from "fs";
4
+
5
+ describe("CLI Arguments", () => {
6
+ const cliPath = path.join(__dirname, "..", "dist", "index.js");
7
+ const testOutputPath = path.join(__dirname, "../.test-tmp/cli-output");
8
+
9
+ beforeEach(() => {
10
+ if (fs.existsSync(testOutputPath)) {
11
+ fs.rmSync(testOutputPath, { recursive: true, force: true });
12
+ }
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (fs.existsSync(testOutputPath)) {
17
+ fs.rmSync(testOutputPath, { recursive: true, force: true });
18
+ }
19
+ });
20
+
21
+ const runCLI = (args: string[]): Promise<{ code: number; stdout: string; stderr: string }> => {
22
+ return new Promise((resolve) => {
23
+ const proc = spawn("node", [cliPath, ...args]);
24
+ let stdout = "";
25
+ let stderr = "";
26
+
27
+ proc.stdout.on("data", (data) => {
28
+ stdout += data.toString();
29
+ });
30
+
31
+ proc.stderr.on("data", (data) => {
32
+ stderr += data.toString();
33
+ });
34
+
35
+ proc.on("close", (code) => {
36
+ resolve({ code: code || 0, stdout, stderr });
37
+ });
38
+ });
39
+ };
40
+
41
+ it("should accept glob pattern with --glob parameter", async () => {
42
+ const result = await runCLI([
43
+ "--glob", "test/basic-scenario/*.jsonschema.ts",
44
+ "--rootPath", path.join(__dirname),
45
+ "--output", "../.test-tmp/cli-output"
46
+ ]);
47
+
48
+ expect(result.code).toBe(0);
49
+ expect(fs.existsSync(testOutputPath)).toBe(true);
50
+
51
+ // Check that files were generated
52
+ const generatedFiles = fs.readdirSync(testOutputPath);
53
+ expect(generatedFiles).toContain("validation.schema.json");
54
+ expect(generatedFiles).toContain("SchemaDefinition.ts");
55
+ expect(generatedFiles).toContain("ValidationType.ts");
56
+ expect(generatedFiles).toContain("isValidSchema.ts");
57
+ });
58
+
59
+ it("should use default glob pattern when --glob is not provided", async () => {
60
+ // Create test files matching the default pattern
61
+ const testDir = path.join(__dirname, "test-default-glob");
62
+ const testFile1 = path.join(testDir, "test.jsonschema.ts");
63
+ const testFile2 = path.join(testDir, "test2.jsonschema.tsx");
64
+
65
+ if (!fs.existsSync(testDir)) {
66
+ fs.mkdirSync(testDir, { recursive: true });
67
+ }
68
+
69
+ fs.writeFileSync(testFile1, "export interface TestInterface { test: string; }");
70
+ fs.writeFileSync(testFile2, "export interface TestInterface2 { test2: number; }");
71
+
72
+ try {
73
+ const result = await runCLI([
74
+ "--rootPath", testDir,
75
+ "--output", path.join("..", "../.test-tmp/cli-output")
76
+ ]);
77
+
78
+ expect(result.code).toBe(0);
79
+ expect(fs.existsSync(testOutputPath)).toBe(true);
80
+ } finally {
81
+ // Clean up test files and directory
82
+ if (fs.existsSync(testDir)) {
83
+ fs.rmSync(testDir, { recursive: true, force: true });
84
+ }
85
+ }
86
+ });
87
+
88
+ it("should accept all supported CLI options", async () => {
89
+ const result = await runCLI([
90
+ "--glob", "test/basic-scenario/*.jsonschema.ts",
91
+ "--rootPath", path.join(__dirname),
92
+ "--output", "../.test-tmp/cli-output",
93
+ "--additionalProperties",
94
+ "--verbose",
95
+ "--progress",
96
+ "--minify",
97
+ "--cache",
98
+ "--tree-shaking",
99
+ "--lazy-load"
100
+ ]);
101
+
102
+ expect(result.code).toBe(0);
103
+ // With verbose flag, we should see output
104
+ expect(result.stdout).toContain("Found");
105
+ });
106
+
107
+ it("should handle parallel processing flags correctly", async () => {
108
+ // Test with parallel disabled
109
+ const result = await runCLI([
110
+ "--glob", "test/basic-scenario/*.jsonschema.ts",
111
+ "--rootPath", path.join(__dirname),
112
+ "--output", "../.test-tmp/cli-output",
113
+ "--no-parallel"
114
+ ]);
115
+
116
+ expect(result.code).toBe(0);
117
+ expect(fs.existsSync(testOutputPath)).toBe(true);
118
+ });
119
+
120
+ it("should error gracefully when no matching files are found", async () => {
121
+ const result = await runCLI([
122
+ "--glob", "nonexistent/*.jsonschema.ts",
123
+ "--rootPath", path.join(__dirname),
124
+ "--output", "../.test-tmp/cli-output"
125
+ ]);
126
+
127
+ expect(result.code).not.toBe(0);
128
+ expect(result.stderr).toContain("No files found");
129
+ });
130
+ });
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ const defaultOutputFolder = "./.ts-runtime-validation";
11
11
  const defaultTsconfig = "";
12
12
 
13
13
  program.option(
14
- "--glob",
14
+ "--glob <glob>",
15
15
  `Glob file path of typescript files to generate ts-interface -> json-schema validations - default: ${defaultGlobPattern}`,
16
16
  defaultGlobPattern
17
17
  );
@@ -98,7 +98,10 @@ export class CodeGenerator {
98
98
  const readerProject = new Project(defaultTsMorphProjectSettings);
99
99
  const typeInfos: TypeInfo[] = [];
100
100
 
101
- schemaMap.forEach((schema, filePath) => {
101
+ // Sort schema map entries by file path for consistent processing order
102
+ const sortedEntries = [...schemaMap.entries()].sort(([a], [b]) => a.localeCompare(b));
103
+
104
+ for (const [filePath, schema] of sortedEntries) {
102
105
  const dir = path.dirname(filePath);
103
106
  const fileWithoutExtension = path.parse(filePath).name;
104
107
  const relativeFilePath = path.relative(this.options.outputPath, dir);
@@ -107,7 +110,8 @@ export class CodeGenerator {
107
110
 
108
111
  const readerSourceFile = readerProject.addSourceFileAtPath(filePath);
109
112
 
110
- Object.keys(defs).forEach((symbol) => {
113
+ // Sort definition keys alphabetically
114
+ Object.keys(defs).sort().forEach((symbol) => {
111
115
  const typeAlias = readerSourceFile.getTypeAlias(symbol);
112
116
  const typeInterface = readerSourceFile.getInterface(symbol);
113
117
 
@@ -119,9 +123,10 @@ export class CodeGenerator {
119
123
  });
120
124
  }
121
125
  });
122
- });
126
+ }
123
127
 
124
- return typeInfos;
128
+ // Sort typeInfos alphabetically by symbol
129
+ return typeInfos.sort((a, b) => a.symbol.localeCompare(b.symbol));
125
130
  }
126
131
 
127
132
  private async writeSchemaDefinitionFile(
@@ -145,7 +150,8 @@ export class CodeGenerator {
145
150
  type: "Record<keyof ISchema, string>",
146
151
  initializer: (writer: CodeBlockWriter) => {
147
152
  writer.writeLine(`{`);
148
- typeInfos.forEach(({ symbol }) => {
153
+ // Sort by symbol for consistent output
154
+ [...typeInfos].sort((a, b) => a.symbol.localeCompare(b.symbol)).forEach(({ symbol }) => {
149
155
  writer.writeLine(`["#/definitions/${symbol}"] : "${symbol}",`);
150
156
  });
151
157
  writer.writeLine(`}`);
@@ -158,7 +164,7 @@ export class CodeGenerator {
158
164
  kind: StructureKind.Interface,
159
165
  name: "ISchema",
160
166
  isExported: true,
161
- properties: typeInfos.map(({ symbol }) => ({
167
+ properties: [...typeInfos].sort((a, b) => a.symbol.localeCompare(b.symbol)).map(({ symbol }) => ({
162
168
  name: `readonly ["#/definitions/${symbol}"]`,
163
169
  type: symbol
164
170
  })),
@@ -176,7 +182,10 @@ export class CodeGenerator {
176
182
  importMap.set(importPath, existing);
177
183
  });
178
184
 
179
- importMap.forEach((symbols, importPath) => {
185
+ // Sort import paths and symbols alphabetically
186
+ const sortedImportPaths = Array.from(importMap.keys()).sort();
187
+ sortedImportPaths.forEach((importPath) => {
188
+ const symbols = importMap.get(importPath)!.sort();
180
189
  sourceFile.addImportDeclaration({
181
190
  namedImports: symbols,
182
191
  moduleSpecifier: importPath
@@ -193,7 +202,10 @@ export class CodeGenerator {
193
202
  importMap.set(importPath, existing);
194
203
  });
195
204
 
196
- importMap.forEach((symbols, importPath) => {
205
+ // Sort import paths and symbols alphabetically
206
+ const sortedImportPaths = Array.from(importMap.keys()).sort();
207
+ sortedImportPaths.forEach((importPath) => {
208
+ const symbols = importMap.get(importPath)!.sort();
197
209
  sourceFile.addImportDeclaration({
198
210
  namedImports: symbols,
199
211
  moduleSpecifier: importPath
@@ -306,13 +318,19 @@ export class CodeGenerator {
306
318
  const sourceFile = this.project.createSourceFile(outputFile, {}, defaultCreateFileOptions);
307
319
 
308
320
  const importMap = new Map<string, string[]>();
309
- typeInfos.forEach(({ symbol, importPath }) => {
321
+ // Sort typeInfos for consistent processing
322
+ const sortedTypeInfos = [...typeInfos].sort((a, b) => a.symbol.localeCompare(b.symbol));
323
+
324
+ sortedTypeInfos.forEach(({ symbol, importPath }) => {
310
325
  const existing = importMap.get(importPath) || [];
311
326
  existing.push(symbol);
312
327
  importMap.set(importPath, existing);
313
328
  });
314
329
 
315
- importMap.forEach((symbols, importPath) => {
330
+ // Sort import paths and symbols alphabetically
331
+ const sortedImportPaths = Array.from(importMap.keys()).sort();
332
+ sortedImportPaths.forEach((importPath) => {
333
+ const symbols = importMap.get(importPath)!.sort();
316
334
  const declaration = sourceFile.addImportDeclaration({
317
335
  moduleSpecifier: importPath
318
336
  });
@@ -325,7 +343,7 @@ export class CodeGenerator {
325
343
  });
326
344
 
327
345
  if (this.options.treeShaking) {
328
- typeInfos.forEach(({ symbol }) => {
346
+ sortedTypeInfos.forEach(({ symbol }) => {
329
347
  sourceFile.addTypeAlias({
330
348
  name: symbol,
331
349
  type: `_${symbol}`,
@@ -338,7 +356,7 @@ export class CodeGenerator {
338
356
  isExported: true,
339
357
  });
340
358
 
341
- typeInfos.forEach(({ symbol }) => {
359
+ sortedTypeInfos.forEach(({ symbol }) => {
342
360
  namespace.addTypeAlias({
343
361
  name: symbol,
344
362
  type: `_${symbol}`,
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { FileDiscovery } from "./FileDiscovery";
4
4
  import { FileDiscoveryError } from "../errors";
5
5
 
6
- const testDir = path.resolve(__dirname, "../test-tmp/file-discovery");
6
+ const testDir = path.resolve(__dirname, "../../.test-tmp/file-discovery");
7
7
  const cacheDir = path.resolve(testDir, ".cache");
8
8
 
9
9
  const createTestFile = async (filePath: string, content: string = "test content") => {
@@ -54,9 +54,12 @@ export class FileDiscovery {
54
54
  );
55
55
  }
56
56
 
57
+ // Sort files alphabetically to ensure consistent ordering
58
+ const sortedFiles = [...files].sort();
59
+
57
60
  return this.options.cacheEnabled
58
- ? await this.enrichWithCacheInfo(files)
59
- : files.map(path => ({ path }));
61
+ ? await this.enrichWithCacheInfo(sortedFiles)
62
+ : sortedFiles.map(path => ({ path }));
60
63
  } catch (error) {
61
64
  if (error instanceof FileDiscoveryError) {
62
65
  throw error;
@@ -4,7 +4,7 @@ import { SchemaProcessor } from "./SchemaProcessor";
4
4
  import { FileInfo } from "./FileDiscovery";
5
5
  import { DuplicateSymbolError } from "../errors";
6
6
 
7
- const testDir = path.resolve(__dirname, "../test-tmp/schema-processor");
7
+ const testDir = path.resolve(__dirname, "../../.test-tmp/schema-processor");
8
8
 
9
9
  const createTestFile = async (filePath: string, content: string) => {
10
10
  const fullPath = path.resolve(testDir, filePath);
@@ -340,6 +340,78 @@ describe("SchemaProcessor", () => {
340
340
  expect(mergedSchema.$schema).toBe("http://json-schema.org/draft-07/schema#");
341
341
  expect(mergedSchema.definitions).toEqual({});
342
342
  });
343
+
344
+ it("should sort definitions alphabetically in merged schema", async () => {
345
+ const file1 = await createTestFile("types1.jsonschema.ts", `
346
+ export interface ZebraType {
347
+ id: string;
348
+ }
349
+ export interface AppleType {
350
+ name: string;
351
+ }
352
+ `);
353
+
354
+ const file2 = await createTestFile("types2.jsonschema.ts", `
355
+ export interface MiddleType {
356
+ value: number;
357
+ }
358
+ export interface BananaType {
359
+ flag: boolean;
360
+ }
361
+ `);
362
+
363
+ const processor = new SchemaProcessor({
364
+ additionalProperties: false,
365
+ parallel: false
366
+ });
367
+
368
+ const files: FileInfo[] = [
369
+ { path: file1 },
370
+ { path: file2 }
371
+ ];
372
+
373
+ const schemaMap = await processor.processFiles(files);
374
+ const mergedSchema = processor.mergeSchemas(schemaMap);
375
+
376
+ const definitionKeys = Object.keys(mergedSchema.definitions || {});
377
+ const sortedKeys = [...definitionKeys].sort();
378
+
379
+ expect(definitionKeys).toEqual(sortedKeys);
380
+ expect(definitionKeys).toEqual(['AppleType', 'BananaType', 'MiddleType', 'ZebraType']);
381
+ });
382
+
383
+ it("should maintain alphabetical order for definitions with numbers and special characters", async () => {
384
+ const file1 = await createTestFile("special.jsonschema.ts", `
385
+ export interface Type1 {
386
+ id: string;
387
+ }
388
+ export interface TypeA {
389
+ name: string;
390
+ }
391
+ export interface Type10 {
392
+ value: number;
393
+ }
394
+ export interface Type2 {
395
+ flag: boolean;
396
+ }
397
+ `);
398
+
399
+ const processor = new SchemaProcessor({
400
+ additionalProperties: false,
401
+ parallel: false
402
+ });
403
+
404
+ const files: FileInfo[] = [{ path: file1 }];
405
+ const schemaMap = await processor.processFiles(files);
406
+ const mergedSchema = processor.mergeSchemas(schemaMap);
407
+
408
+ const definitionKeys = Object.keys(mergedSchema.definitions || {});
409
+ const sortedKeys = [...definitionKeys].sort();
410
+
411
+ expect(definitionKeys).toEqual(sortedKeys);
412
+ // Natural alphabetical order: numbers before letters
413
+ expect(definitionKeys).toEqual(['Type1', 'Type10', 'Type2', 'TypeA']);
414
+ });
343
415
  });
344
416
 
345
417
  describe("error handling", () => {
@@ -27,9 +27,12 @@ export class SchemaProcessor {
27
27
  console.log(`Processing ${files.length} files...`);
28
28
  }
29
29
 
30
+ // Sort files by path to ensure consistent processing order
31
+ const sortedFiles = [...files].sort((a, b) => a.path.localeCompare(b.path));
32
+
30
33
  const results = parallel
31
- ? await this.processInParallel(files)
32
- : await this.processSequentially(files);
34
+ ? await this.processInParallel(sortedFiles)
35
+ : await this.processSequentially(sortedFiles);
33
36
 
34
37
  return this.consolidateSchemas(results);
35
38
  }
@@ -38,6 +41,7 @@ export class SchemaProcessor {
38
41
  const promises = files.map(file => this.processFile(file));
39
42
  const results = await Promise.allSettled(promises);
40
43
 
44
+ // Map results back to original file order to maintain consistency
41
45
  return results.map((result, index) => {
42
46
  if (result.status === 'fulfilled') {
43
47
  return result.value;
@@ -106,7 +110,10 @@ export class SchemaProcessor {
106
110
  const schemaMap = new Map<string, Schema>();
107
111
  const errors: Error[] = [];
108
112
 
109
- for (const result of results) {
113
+ // Sort results by file path to ensure consistent order
114
+ const sortedResults = [...results].sort((a, b) => a.file.localeCompare(b.file));
115
+
116
+ for (const result of sortedResults) {
110
117
  if (result.error) {
111
118
  errors.push(result.error);
112
119
  continue;
@@ -128,10 +135,14 @@ export class SchemaProcessor {
128
135
  public validateSchemaCompatibility(schemaMap: Map<string, Schema>): void {
129
136
  const definitions: { [id: string]: any } = {};
130
137
 
131
- schemaMap.forEach((fileSchema, filePath) => {
138
+ // Sort by file path for consistent processing order
139
+ const sortedEntries = [...schemaMap.entries()].sort(([a], [b]) => a.localeCompare(b));
140
+
141
+ for (const [filePath, fileSchema] of sortedEntries) {
132
142
  const defs = fileSchema.definitions ?? {};
133
143
 
134
- Object.keys(defs).forEach((key) => {
144
+ // Sort definition keys for consistent processing
145
+ Object.keys(defs).sort().forEach((key) => {
135
146
  if (definitions[key] !== undefined) {
136
147
  try {
137
148
  assert.deepEqual(definitions[key], defs[key]);
@@ -147,27 +158,37 @@ export class SchemaProcessor {
147
158
  }
148
159
  definitions[key] = defs[key];
149
160
  });
150
- });
161
+ }
151
162
  }
152
163
 
153
164
  public mergeSchemas(schemaMap: Map<string, Schema>): Schema {
154
165
  const definitions: { [id: string]: Schema } = {};
155
166
  let schemaVersion = "";
156
167
 
157
- schemaMap.forEach((fileSchema) => {
168
+ // Sort by file path for consistent processing order
169
+ const sortedEntries = [...schemaMap.entries()].sort(([a], [b]) => a.localeCompare(b));
170
+
171
+ for (const [, fileSchema] of sortedEntries) {
158
172
  if (!schemaVersion && fileSchema["$schema"]) {
159
173
  schemaVersion = fileSchema["$schema"];
160
174
  }
161
175
 
162
176
  const defs = fileSchema.definitions ?? {};
163
- Object.keys(defs).forEach((key) => {
177
+ // Sort definition keys for consistent processing
178
+ Object.keys(defs).sort().forEach((key) => {
164
179
  definitions[key] = defs[key] as Schema;
165
180
  });
181
+ }
182
+
183
+ // Sort definitions alphabetically
184
+ const sortedDefinitions: { [id: string]: Schema } = {};
185
+ Object.keys(definitions).sort().forEach(key => {
186
+ sortedDefinitions[key] = definitions[key];
166
187
  });
167
188
 
168
189
  return {
169
190
  $schema: schemaVersion || "http://json-schema.org/draft-07/schema#",
170
- definitions,
191
+ definitions: sortedDefinitions,
171
192
  };
172
193
  }
173
194
  }
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { SchemaWriter } from "./SchemaWriter";
4
4
  import { CodeGenerationError } from "../errors";
5
5
 
6
- const testDir = path.resolve(__dirname, "../test-tmp/schema-writer");
6
+ const testDir = path.resolve(__dirname, "../../.test-tmp/schema-writer");
7
7
 
8
8
  const cleanup = async () => {
9
9
  if (fs.existsSync(testDir)) {
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(yarn test)"
5
- ],
6
- "deny": [],
7
- "ask": []
8
- }
9
- }
@@ -1,3 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- //# sourceMappingURL=ValidationType.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ValidationType.js","sourceRoot":"","sources":["../../../../src/test/output/duplicate-symbols-identitcal-implementation/ValidationType.ts"],"names":[],"mappings":""}