ts-runtime-validation 1.6.16 → 1.8.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 (83) hide show
  1. package/CONTRIBUTING.md +430 -0
  2. package/README.md +505 -62
  3. package/dist/ICommandOptions.js +3 -0
  4. package/dist/ICommandOptions.js.map +1 -0
  5. package/dist/SchemaGenerator.deterministic-extended.test.js +420 -0
  6. package/dist/SchemaGenerator.deterministic-extended.test.js.map +1 -0
  7. package/dist/SchemaGenerator.deterministic.test.js +251 -0
  8. package/dist/SchemaGenerator.deterministic.test.js.map +1 -0
  9. package/dist/SchemaGenerator.integration.test.js +323 -0
  10. package/dist/SchemaGenerator.integration.test.js.map +1 -0
  11. package/dist/SchemaGenerator.js +120 -0
  12. package/dist/SchemaGenerator.js.map +1 -0
  13. package/dist/SchemaGenerator.test.js +226 -0
  14. package/dist/SchemaGenerator.test.js.map +1 -0
  15. package/dist/cli.test.js +155 -0
  16. package/dist/cli.test.js.map +1 -0
  17. package/dist/errors/index.js +95 -0
  18. package/dist/errors/index.js.map +1 -0
  19. package/dist/errors/index.test.js +232 -0
  20. package/dist/errors/index.test.js.map +1 -0
  21. package/dist/getPosixPath.js +13 -0
  22. package/dist/getPosixPath.js.map +1 -0
  23. package/dist/index.js +27 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lib.js.map +1 -0
  26. package/dist/services/CodeGenerator.js +321 -0
  27. package/dist/services/CodeGenerator.js.map +1 -0
  28. package/dist/services/FileDiscovery.js +123 -0
  29. package/dist/services/FileDiscovery.js.map +1 -0
  30. package/dist/services/FileDiscovery.test.js +184 -0
  31. package/dist/services/FileDiscovery.test.js.map +1 -0
  32. package/dist/services/SchemaProcessor.js +198 -0
  33. package/dist/services/SchemaProcessor.js.map +1 -0
  34. package/dist/services/SchemaProcessor.test.js +455 -0
  35. package/dist/services/SchemaProcessor.test.js.map +1 -0
  36. package/dist/services/SchemaWriter.js +76 -0
  37. package/dist/services/SchemaWriter.js.map +1 -0
  38. package/dist/services/SchemaWriter.test.js +255 -0
  39. package/dist/services/SchemaWriter.test.js.map +1 -0
  40. package/dist/test/basic-scenario/types.jsonschema.js +3 -0
  41. package/dist/test/basic-scenario/types.jsonschema.js.map +1 -0
  42. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js +3 -0
  43. package/dist/test/duplicate-symbols-different-implementation/IBaseType.js.map +1 -0
  44. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  45. package/dist/test/duplicate-symbols-different-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  46. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js +3 -0
  47. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  48. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js +3 -0
  49. package/dist/test/duplicate-symbols-different-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  50. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js +3 -0
  51. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseType.js.map +1 -0
  52. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js +3 -0
  53. package/dist/test/duplicate-symbols-identitcal-implementation/IBaseTypeDefinitionReplicated.js.map +1 -0
  54. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js +3 -0
  55. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesA.jsonschema.js.map +1 -0
  56. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js +3 -0
  57. package/dist/test/duplicate-symbols-identitcal-implementation/IBasicTypesB.jsonschema.js.map +1 -0
  58. package/dist/utils/ProgressReporter.js +67 -0
  59. package/dist/utils/ProgressReporter.js.map +1 -0
  60. package/dist/utils/ProgressReporter.test.js +267 -0
  61. package/dist/utils/ProgressReporter.test.js.map +1 -0
  62. package/dist/writeLine.js +12 -0
  63. package/dist/writeLine.js.map +1 -0
  64. package/package.json +2 -2
  65. package/src/ICommandOptions.ts +7 -0
  66. package/src/SchemaGenerator.deterministic-extended.test.ts +429 -0
  67. package/src/SchemaGenerator.deterministic.test.ts +276 -0
  68. package/src/SchemaGenerator.integration.test.ts +411 -0
  69. package/src/SchemaGenerator.test.ts +118 -0
  70. package/src/SchemaGenerator.ts +112 -298
  71. package/src/cli.test.ts +130 -0
  72. package/src/errors/index.test.ts +319 -0
  73. package/src/errors/index.ts +92 -0
  74. package/src/index.ts +8 -1
  75. package/src/services/CodeGenerator.ts +370 -0
  76. package/src/services/FileDiscovery.test.ts +216 -0
  77. package/src/services/FileDiscovery.ts +140 -0
  78. package/src/services/SchemaProcessor.test.ts +536 -0
  79. package/src/services/SchemaProcessor.ts +194 -0
  80. package/src/services/SchemaWriter.test.ts +304 -0
  81. package/src/services/SchemaWriter.ts +75 -0
  82. package/src/utils/ProgressReporter.test.ts +357 -0
  83. package/src/utils/ProgressReporter.ts +76 -0
@@ -0,0 +1,411 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { SchemaGenerator } from "./SchemaGenerator";
4
+ import { ICommandOptions } from "./ICommandOptions";
5
+
6
+ const testDir = path.resolve(__dirname, "../.test-tmp/integration");
7
+
8
+ const createTestFile = async (filePath: string, content: string) => {
9
+ const fullPath = path.resolve(testDir, filePath);
10
+ await fs.promises.mkdir(path.dirname(fullPath), { recursive: true });
11
+ await fs.promises.writeFile(fullPath, content);
12
+ return fullPath;
13
+ };
14
+
15
+ const cleanup = async () => {
16
+ if (fs.existsSync(testDir)) {
17
+ await fs.promises.rm(testDir, { recursive: true, force: true });
18
+ }
19
+ };
20
+
21
+ const getGeneratorConfig = (overrides: Partial<ICommandOptions> = {}): ICommandOptions => ({
22
+ glob: "*.jsonschema.ts",
23
+ rootPath: testDir,
24
+ output: "./output",
25
+ helpers: true,
26
+ additionalProperties: false,
27
+ tsconfigPath: "",
28
+ verbose: false,
29
+ progress: false,
30
+ minify: false,
31
+ cache: false,
32
+ parallel: false,
33
+ treeShaking: false,
34
+ lazyLoad: false,
35
+ ...overrides
36
+ });
37
+
38
+ beforeEach(cleanup);
39
+ afterAll(cleanup);
40
+
41
+ describe("SchemaGenerator Integration Tests", () => {
42
+ describe("new CLI options", () => {
43
+ it("should work with verbose mode enabled", async () => {
44
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
45
+
46
+ await createTestFile("user.jsonschema.ts", `
47
+ export interface IUser {
48
+ id: string;
49
+ name: string;
50
+ }
51
+ `);
52
+
53
+ const generator = new SchemaGenerator(getGeneratorConfig({
54
+ verbose: true
55
+ }));
56
+
57
+ await generator.Generate();
58
+
59
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Found 1 schema file(s)"));
60
+
61
+ consoleSpy.mockRestore();
62
+ });
63
+
64
+ it("should work with progress reporting enabled", async () => {
65
+ await createTestFile("user.jsonschema.ts", `
66
+ export interface IUser {
67
+ id: string;
68
+ name: string;
69
+ }
70
+ `);
71
+
72
+ const generator = new SchemaGenerator(getGeneratorConfig({
73
+ progress: true
74
+ }));
75
+
76
+ await generator.Generate();
77
+
78
+ // Check that output files were generated
79
+ const outputDir = path.join(testDir, "output");
80
+ expect(fs.existsSync(path.join(outputDir, "validation.schema.json"))).toBe(true);
81
+ expect(fs.existsSync(path.join(outputDir, "SchemaDefinition.ts"))).toBe(true);
82
+ });
83
+
84
+ it("should work with caching enabled", async () => {
85
+ await createTestFile("user.jsonschema.ts", `
86
+ export interface IUser {
87
+ id: string;
88
+ name: string;
89
+ }
90
+ `);
91
+
92
+ const generator = new SchemaGenerator(getGeneratorConfig({
93
+ cache: true
94
+ }));
95
+
96
+ await generator.Generate();
97
+
98
+ // Check that cache was created
99
+ const cacheDir = path.join(testDir, ".ts-runtime-validation-cache");
100
+ expect(fs.existsSync(path.join(cacheDir, "file-hashes.json"))).toBe(true);
101
+
102
+ // Run again to test cache usage
103
+ await generator.Generate();
104
+
105
+ const outputDir = path.join(testDir, "output");
106
+ expect(fs.existsSync(path.join(outputDir, "validation.schema.json"))).toBe(true);
107
+ });
108
+
109
+ it("should work with parallel processing enabled", async () => {
110
+ await createTestFile("user.jsonschema.ts", `
111
+ export interface IUser {
112
+ id: string;
113
+ name: string;
114
+ }
115
+ `);
116
+
117
+ await createTestFile("product.jsonschema.ts", `
118
+ export interface IProduct {
119
+ id: string;
120
+ title: string;
121
+ price: number;
122
+ }
123
+ `);
124
+
125
+ const generator = new SchemaGenerator(getGeneratorConfig({
126
+ parallel: true
127
+ }));
128
+
129
+ await generator.Generate();
130
+
131
+ // Check that both interfaces were processed
132
+ const schemaFile = path.join(testDir, "output", "validation.schema.json");
133
+ const schemaContent = JSON.parse(fs.readFileSync(schemaFile, 'utf-8'));
134
+
135
+ expect(schemaContent.definitions.IUser).toBeDefined();
136
+ expect(schemaContent.definitions.IProduct).toBeDefined();
137
+ });
138
+
139
+ it("should work with minified output", async () => {
140
+ await createTestFile("user.jsonschema.ts", `
141
+ export interface IUser {
142
+ id: string;
143
+ name: string;
144
+ }
145
+ `);
146
+
147
+ const generator = new SchemaGenerator(getGeneratorConfig({
148
+ minify: true
149
+ }));
150
+
151
+ await generator.Generate();
152
+
153
+ // Check that JSON schema is minified (no pretty formatting)
154
+ const schemaFile = path.join(testDir, "output", "validation.schema.json");
155
+ const schemaContent = fs.readFileSync(schemaFile, 'utf-8');
156
+
157
+ // Minified JSON shouldn't have indentation
158
+ expect(schemaContent).not.toMatch(/\n /);
159
+
160
+ // But should still be valid JSON
161
+ expect(() => JSON.parse(schemaContent)).not.toThrow();
162
+ });
163
+
164
+ it("should work with tree-shaking enabled", async () => {
165
+ await createTestFile("user.jsonschema.ts", `
166
+ export interface IUser {
167
+ id: string;
168
+ name: string;
169
+ }
170
+ `);
171
+
172
+ const generator = new SchemaGenerator(getGeneratorConfig({
173
+ treeShaking: true
174
+ }));
175
+
176
+ await generator.Generate();
177
+
178
+ // Check that ValidationType.ts has individual exports instead of namespace
179
+ const validationTypesFile = path.join(testDir, "output", "ValidationType.ts");
180
+ const content = fs.readFileSync(validationTypesFile, 'utf-8');
181
+
182
+ expect(content).toContain("export type IUser");
183
+ expect(content).not.toContain("namespace ValidationType");
184
+ });
185
+
186
+ it("should work with lazy loading enabled", async () => {
187
+ await createTestFile("user.jsonschema.ts", `
188
+ export interface IUser {
189
+ id: string;
190
+ name: string;
191
+ }
192
+ `);
193
+
194
+ const generator = new SchemaGenerator(getGeneratorConfig({
195
+ lazyLoad: true
196
+ }));
197
+
198
+ await generator.Generate();
199
+
200
+ // Check that isValidSchema.ts uses lazy loading
201
+ const validatorFile = path.join(testDir, "output", "isValidSchema.ts");
202
+ const content = fs.readFileSync(validatorFile, 'utf-8');
203
+
204
+ expect(content).toContain("let validator");
205
+ expect(content).toContain("getValidator");
206
+ expect(content).toContain("require(\"ajv\")");
207
+ });
208
+ });
209
+
210
+ describe("combined options", () => {
211
+ it("should work with multiple options enabled", async () => {
212
+ await createTestFile("user.jsonschema.ts", `
213
+ export interface IUser {
214
+ id: string;
215
+ name: string;
216
+ }
217
+ `);
218
+
219
+ await createTestFile("product.jsonschema.ts", `
220
+ export interface IProduct {
221
+ id: string;
222
+ title: string;
223
+ }
224
+ `);
225
+
226
+ const generator = new SchemaGenerator(getGeneratorConfig({
227
+ verbose: true,
228
+ progress: true,
229
+ cache: true,
230
+ parallel: true,
231
+ minify: true,
232
+ treeShaking: true
233
+ }));
234
+
235
+ await generator.Generate();
236
+
237
+ // Verify all features work together
238
+ const outputDir = path.join(testDir, "output");
239
+ const cacheDir = path.join(testDir, ".ts-runtime-validation-cache");
240
+
241
+ // Check files exist
242
+ expect(fs.existsSync(path.join(outputDir, "validation.schema.json"))).toBe(true);
243
+ expect(fs.existsSync(path.join(outputDir, "SchemaDefinition.ts"))).toBe(true);
244
+ expect(fs.existsSync(path.join(outputDir, "ValidationType.ts"))).toBe(true);
245
+ expect(fs.existsSync(path.join(cacheDir, "file-hashes.json"))).toBe(true);
246
+
247
+ // Check minification
248
+ const schemaContent = fs.readFileSync(path.join(outputDir, "validation.schema.json"), 'utf-8');
249
+ expect(schemaContent).not.toMatch(/\n /);
250
+
251
+ // Check tree-shaking
252
+ const validationTypesContent = fs.readFileSync(path.join(outputDir, "ValidationType.ts"), 'utf-8');
253
+ expect(validationTypesContent).toContain("export type IUser");
254
+ expect(validationTypesContent).not.toContain("namespace ValidationType");
255
+ });
256
+ });
257
+
258
+ describe("error handling with new options", () => {
259
+ it("should handle errors gracefully with verbose mode", async () => {
260
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
261
+
262
+ // Use non-existent glob pattern to trigger error
263
+ const generator = new SchemaGenerator(getGeneratorConfig({
264
+ glob: "*.nonexistent.ts",
265
+ verbose: true
266
+ }));
267
+
268
+ await expect(generator.Generate()).rejects.toThrow();
269
+
270
+ consoleErrorSpy.mockRestore();
271
+ });
272
+
273
+ it("should provide detailed error context", async () => {
274
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
275
+
276
+ const generator = new SchemaGenerator(getGeneratorConfig({
277
+ glob: "*.nonexistent.ts", // Pattern that won't match any files
278
+ verbose: true
279
+ }));
280
+
281
+ await expect(generator.Generate()).rejects.toThrow();
282
+
283
+ consoleErrorSpy.mockRestore();
284
+ });
285
+ });
286
+
287
+ describe("cache functionality", () => {
288
+ it("should invalidate cache when file changes", async () => {
289
+ const filePath = await createTestFile("user.jsonschema.ts", `
290
+ export interface IUser {
291
+ id: string;
292
+ name: string;
293
+ }
294
+ `);
295
+
296
+ const generator = new SchemaGenerator(getGeneratorConfig({
297
+ cache: true
298
+ }));
299
+
300
+ // First run
301
+ await generator.Generate();
302
+
303
+ const cacheFile = path.join(testDir, ".ts-runtime-validation-cache", "file-hashes.json");
304
+ expect(fs.existsSync(cacheFile)).toBe(true);
305
+
306
+ const originalCache = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
307
+
308
+ // Modify file
309
+ await fs.promises.writeFile(filePath, `
310
+ export interface IUser {
311
+ id: string;
312
+ name: string;
313
+ email: string; // Added field
314
+ }
315
+ `);
316
+
317
+ // Second run
318
+ await generator.Generate();
319
+
320
+ const updatedCache = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
321
+
322
+ // Hash should have changed
323
+ expect(originalCache[filePath]).not.toBe(updatedCache[filePath]);
324
+ });
325
+
326
+ it("should clear cache correctly", async () => {
327
+ await createTestFile("user.jsonschema.ts", `
328
+ export interface IUser {
329
+ id: string;
330
+ name: string;
331
+ }
332
+ `);
333
+
334
+ const generator = new SchemaGenerator(getGeneratorConfig({
335
+ cache: true
336
+ }));
337
+
338
+ await generator.Generate();
339
+
340
+ const cacheFile = path.join(testDir, ".ts-runtime-validation-cache", "file-hashes.json");
341
+ expect(fs.existsSync(cacheFile)).toBe(true);
342
+
343
+ generator.clearCache();
344
+ expect(fs.existsSync(cacheFile)).toBe(false);
345
+ });
346
+ });
347
+
348
+ describe("output management", () => {
349
+ it("should clean output directory", async () => {
350
+ await createTestFile("user.jsonschema.ts", `
351
+ export interface IUser {
352
+ id: string;
353
+ name: string;
354
+ }
355
+ `);
356
+
357
+ const generator = new SchemaGenerator(getGeneratorConfig());
358
+
359
+ // Generate first time
360
+ await generator.Generate();
361
+
362
+ const outputDir = path.join(testDir, "output");
363
+ expect(fs.existsSync(path.join(outputDir, "validation.schema.json"))).toBe(true);
364
+
365
+ // Clean output
366
+ await generator.cleanOutput();
367
+
368
+ // TypeScript and JSON files should be removed
369
+ expect(fs.existsSync(path.join(outputDir, "validation.schema.json"))).toBe(false);
370
+ expect(fs.existsSync(path.join(outputDir, "SchemaDefinition.ts"))).toBe(false);
371
+ });
372
+ });
373
+
374
+ describe("performance with different options", () => {
375
+ it("should process multiple files efficiently with parallel mode", async () => {
376
+ // Create multiple test files
377
+ const numFiles = 5;
378
+ for (let i = 0; i < numFiles; i++) {
379
+ await createTestFile(`interface${i}.jsonschema.ts`, `
380
+ export interface IInterface${i} {
381
+ id: string;
382
+ name: string;
383
+ value${i}: number;
384
+ }
385
+ `);
386
+ }
387
+
388
+ const startTime = Date.now();
389
+
390
+ const generator = new SchemaGenerator(getGeneratorConfig({
391
+ parallel: true
392
+ }));
393
+
394
+ await generator.Generate();
395
+
396
+ const endTime = Date.now();
397
+ const duration = endTime - startTime;
398
+
399
+ // Should complete reasonably quickly (less than 20 seconds)
400
+ expect(duration).toBeLessThan(20000);
401
+
402
+ // Verify all files were processed
403
+ const schemaFile = path.join(testDir, "output", "validation.schema.json");
404
+ const schemaContent = JSON.parse(fs.readFileSync(schemaFile, 'utf-8'));
405
+
406
+ for (let i = 0; i < numFiles; i++) {
407
+ expect(schemaContent.definitions[`IInterface${i}`]).toBeDefined();
408
+ }
409
+ });
410
+ });
411
+ });
@@ -19,6 +19,13 @@ const getGeneratorConfig = (scenarioPath: string) => {
19
19
  helpers: true,
20
20
  additionalProperties: false,
21
21
  tsconfigPath: "",
22
+ verbose: false,
23
+ progress: false,
24
+ minify: false,
25
+ cache: false,
26
+ parallel: false,
27
+ treeShaking: false,
28
+ lazyLoad: false
22
29
  };
23
30
  return options;
24
31
  };
@@ -115,4 +122,115 @@ describe("SchemaGenerator", () => {
115
122
  };
116
123
  expect(result).toStrictEqual(expected);
117
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
+ });
118
236
  });