ts-runtime-validation 1.6.16 → 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,357 @@
1
+ import { ProgressReporter } from "./ProgressReporter";
2
+
3
+ // Mock process.stdout for testing
4
+ const mockStdout = {
5
+ isTTY: true,
6
+ clearLine: jest.fn(),
7
+ cursorTo: jest.fn(),
8
+ write: jest.fn()
9
+ };
10
+
11
+ const originalStdout = process.stdout;
12
+
13
+ beforeEach(() => {
14
+ // Reset mock functions
15
+ mockStdout.clearLine = jest.fn();
16
+ mockStdout.cursorTo = jest.fn();
17
+ mockStdout.write = jest.fn();
18
+ mockStdout.isTTY = true;
19
+
20
+ // Replace process.stdout with mock
21
+ Object.defineProperty(process, 'stdout', {
22
+ value: mockStdout,
23
+ configurable: true
24
+ });
25
+ });
26
+
27
+ afterAll(() => {
28
+ // Restore original stdout
29
+ Object.defineProperty(process, 'stdout', {
30
+ value: originalStdout,
31
+ configurable: true
32
+ });
33
+ });
34
+
35
+ describe("ProgressReporter", () => {
36
+ describe("disabled reporter", () => {
37
+ it("should not output anything when disabled", () => {
38
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
39
+
40
+ const reporter = new ProgressReporter({
41
+ enabled: false,
42
+ total: 100,
43
+ showBar: true
44
+ });
45
+
46
+ reporter.start("Starting task");
47
+ reporter.update(50, "Half done");
48
+ reporter.increment("Almost there");
49
+ reporter.complete("Task completed");
50
+
51
+ expect(consoleSpy).not.toHaveBeenCalled();
52
+ expect(mockStdout.write).not.toHaveBeenCalled();
53
+
54
+ consoleSpy.mockRestore();
55
+ });
56
+ });
57
+
58
+ describe("enabled reporter without progress bar", () => {
59
+ it("should show start and complete messages", () => {
60
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
61
+
62
+ const reporter = new ProgressReporter({
63
+ enabled: true,
64
+ showBar: false
65
+ });
66
+
67
+ reporter.start("Starting task");
68
+ reporter.complete("Task completed");
69
+
70
+ expect(consoleSpy).toHaveBeenCalledWith("Starting task");
71
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Task completed"));
72
+
73
+ consoleSpy.mockRestore();
74
+ });
75
+
76
+ it("should show custom update messages", () => {
77
+ const reporter = new ProgressReporter({
78
+ enabled: true,
79
+ showBar: false
80
+ });
81
+
82
+ reporter.start("Starting");
83
+ reporter.update(25, "Processing file 1");
84
+ reporter.update(50, "Processing file 2");
85
+
86
+ expect(mockStdout.write).toHaveBeenCalledWith("Processing file 1");
87
+ expect(mockStdout.write).toHaveBeenCalledWith("Processing file 2");
88
+ });
89
+
90
+ it("should handle increment without message", () => {
91
+ const reporter = new ProgressReporter({
92
+ enabled: true,
93
+ showBar: false
94
+ });
95
+
96
+ reporter.start("Starting");
97
+ reporter.increment();
98
+
99
+ // Should not crash when incrementing without message
100
+ expect(() => reporter.increment()).not.toThrow();
101
+ });
102
+ });
103
+
104
+ describe("enabled reporter with progress bar", () => {
105
+ it("should draw progress bar", () => {
106
+ const reporter = new ProgressReporter({
107
+ enabled: true,
108
+ total: 100,
109
+ showBar: true
110
+ });
111
+
112
+ reporter.start("Processing files");
113
+ reporter.update(25);
114
+
115
+ expect(mockStdout.write).toHaveBeenCalledWith(
116
+ expect.stringContaining("Progress: [")
117
+ );
118
+ expect(mockStdout.write).toHaveBeenCalledWith(
119
+ expect.stringContaining("25%")
120
+ );
121
+ expect(mockStdout.write).toHaveBeenCalledWith(
122
+ expect.stringContaining("(25/100)")
123
+ );
124
+ });
125
+
126
+ it("should show 100% when complete", () => {
127
+ const reporter = new ProgressReporter({
128
+ enabled: true,
129
+ total: 50,
130
+ showBar: true
131
+ });
132
+
133
+ reporter.start("Processing");
134
+ reporter.update(50);
135
+
136
+ expect(mockStdout.write).toHaveBeenCalledWith(
137
+ expect.stringContaining("100%")
138
+ );
139
+ expect(mockStdout.write).toHaveBeenCalledWith(
140
+ expect.stringContaining("(50/50)")
141
+ );
142
+ });
143
+
144
+ it("should handle progress beyond total", () => {
145
+ const reporter = new ProgressReporter({
146
+ enabled: true,
147
+ total: 10,
148
+ showBar: true
149
+ });
150
+
151
+ reporter.start("Processing");
152
+ reporter.update(15); // More than total
153
+
154
+ // Should cap at 100% but show actual numbers
155
+ expect(mockStdout.write).toHaveBeenCalledWith(
156
+ expect.stringContaining("(15/10)")
157
+ );
158
+ });
159
+
160
+ it("should use increment correctly", () => {
161
+ const reporter = new ProgressReporter({
162
+ enabled: true,
163
+ total: 3,
164
+ showBar: true
165
+ });
166
+
167
+ reporter.start("Processing");
168
+ reporter.increment(); // 1
169
+ reporter.increment(); // 2
170
+ reporter.increment(); // 3
171
+
172
+ expect(mockStdout.write).toHaveBeenCalledWith(
173
+ expect.stringContaining("(3/3)")
174
+ );
175
+ });
176
+ });
177
+
178
+ describe("time formatting", () => {
179
+ it("should format completion time in different units", () => {
180
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
181
+
182
+ // Mock Date.now to control timing
183
+ const originalNow = Date.now;
184
+ let currentTime = 1000;
185
+ Date.now = jest.fn(() => currentTime);
186
+
187
+ const reporter = new ProgressReporter({
188
+ enabled: true
189
+ });
190
+
191
+ reporter.start("Processing");
192
+
193
+ // Simulate 500ms elapsed
194
+ currentTime = 1500;
195
+ reporter.complete("Done");
196
+
197
+ expect(consoleSpy).toHaveBeenCalledWith(
198
+ expect.stringContaining("500ms")
199
+ );
200
+
201
+ consoleSpy.mockClear();
202
+
203
+ // Test seconds
204
+ currentTime = 1000; // Reset time
205
+ reporter.start("Processing");
206
+ currentTime = 3500; // 2.5 seconds elapsed
207
+ reporter.complete("Done");
208
+
209
+ expect(consoleSpy).toHaveBeenCalledWith(
210
+ expect.stringContaining("2.5s")
211
+ );
212
+
213
+ consoleSpy.mockClear();
214
+
215
+ // Test minutes
216
+ currentTime = 1000; // Reset time
217
+ reporter.start("Processing");
218
+ currentTime = 126000; // 125 seconds = 2m 5s elapsed
219
+ reporter.complete("Done");
220
+
221
+ expect(consoleSpy).toHaveBeenCalledWith(
222
+ expect.stringContaining("2m 5s")
223
+ );
224
+
225
+ // Restore Date.now
226
+ Date.now = originalNow;
227
+ consoleSpy.mockRestore();
228
+ });
229
+ });
230
+
231
+ describe("TTY handling", () => {
232
+ it("should handle non-TTY environments", () => {
233
+ mockStdout.isTTY = false;
234
+ (mockStdout as any).clearLine = undefined;
235
+
236
+ const reporter = new ProgressReporter({
237
+ enabled: true,
238
+ total: 10,
239
+ showBar: true
240
+ });
241
+
242
+ reporter.start("Processing");
243
+ reporter.update(5);
244
+
245
+ // Should not crash when clearLine is not available
246
+ expect(mockStdout.write).toHaveBeenCalled();
247
+ });
248
+
249
+ it("should handle missing cursorTo", () => {
250
+ mockStdout.isTTY = true;
251
+ mockStdout.clearLine = jest.fn();
252
+ (mockStdout as any).cursorTo = undefined;
253
+
254
+ const reporter = new ProgressReporter({
255
+ enabled: true,
256
+ total: 10,
257
+ showBar: true
258
+ });
259
+
260
+ reporter.start("Processing");
261
+ reporter.update(5);
262
+
263
+ // Should not crash when cursorTo is not available
264
+ expect(mockStdout.write).toHaveBeenCalled();
265
+ });
266
+ });
267
+
268
+ describe("edge cases", () => {
269
+ it("should handle zero total", () => {
270
+ const reporter = new ProgressReporter({
271
+ enabled: true,
272
+ total: 0,
273
+ showBar: true
274
+ });
275
+
276
+ // Should not crash with zero total
277
+ expect(() => {
278
+ reporter.start("Processing");
279
+ reporter.update(1);
280
+ }).not.toThrow();
281
+ });
282
+
283
+ it("should handle negative progress", () => {
284
+ const reporter = new ProgressReporter({
285
+ enabled: true,
286
+ total: 10,
287
+ showBar: true
288
+ });
289
+
290
+ reporter.start("Processing");
291
+ reporter.update(-5);
292
+
293
+ // Should handle gracefully
294
+ expect(mockStdout.write).toHaveBeenCalled();
295
+ });
296
+
297
+ it("should handle undefined total with progress bar", () => {
298
+ const reporter = new ProgressReporter({
299
+ enabled: true,
300
+ total: undefined,
301
+ showBar: true
302
+ });
303
+
304
+ // Should not crash with undefined total
305
+ expect(() => {
306
+ reporter.start("Processing");
307
+ reporter.update(5);
308
+ }).not.toThrow();
309
+ });
310
+
311
+ it("should handle very large numbers", () => {
312
+ const reporter = new ProgressReporter({
313
+ enabled: true,
314
+ total: 1000000,
315
+ showBar: true
316
+ });
317
+
318
+ reporter.start("Processing");
319
+ reporter.update(500000);
320
+
321
+ expect(mockStdout.write).toHaveBeenCalledWith(
322
+ expect.stringContaining("50%")
323
+ );
324
+ expect(mockStdout.write).toHaveBeenCalledWith(
325
+ expect.stringContaining("(500000/1000000)")
326
+ );
327
+ });
328
+ });
329
+
330
+ describe("visual progress bar", () => {
331
+ it("should show correct bar fill at different percentages", () => {
332
+ const reporter = new ProgressReporter({
333
+ enabled: true,
334
+ total: 100,
335
+ showBar: true
336
+ });
337
+
338
+ reporter.start("Test");
339
+
340
+ // 0%
341
+ reporter.update(0);
342
+ let lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
343
+ expect(lastCall).toContain("░".repeat(30)); // All empty
344
+
345
+ // 50%
346
+ reporter.update(50);
347
+ lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
348
+ expect(lastCall).toContain("█".repeat(15)); // Half filled
349
+ expect(lastCall).toContain("░".repeat(15)); // Half empty
350
+
351
+ // 100%
352
+ reporter.update(100);
353
+ lastCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1][0];
354
+ expect(lastCall).toContain("█".repeat(30)); // All filled
355
+ });
356
+ });
357
+ });
@@ -0,0 +1,76 @@
1
+ export interface ProgressReporterOptions {
2
+ enabled: boolean;
3
+ total?: number;
4
+ showBar?: boolean;
5
+ }
6
+
7
+ export class ProgressReporter {
8
+ private current: number = 0;
9
+ private startTime: number = Date.now();
10
+
11
+ constructor(public options: ProgressReporterOptions) {}
12
+
13
+ public start(message: string): void {
14
+ if (!this.options.enabled) return;
15
+
16
+ this.startTime = Date.now();
17
+ this.current = 0;
18
+ console.log(message);
19
+
20
+ if (this.options.showBar && this.options.total) {
21
+ this.drawProgressBar();
22
+ }
23
+ }
24
+
25
+ public update(current: number, message?: string): void {
26
+ if (!this.options.enabled) return;
27
+
28
+ this.current = current;
29
+
30
+ if (message) {
31
+ this.clearLine();
32
+ process.stdout.write(message);
33
+ } else if (this.options.showBar && this.options.total) {
34
+ this.drawProgressBar();
35
+ }
36
+ }
37
+
38
+ public increment(message?: string): void {
39
+ this.update(this.current + 1, message);
40
+ }
41
+
42
+ public complete(message: string): void {
43
+ if (!this.options.enabled) return;
44
+
45
+ const elapsed = Date.now() - this.startTime;
46
+ this.clearLine();
47
+ console.log(`${message} (${this.formatTime(elapsed)})`);
48
+ }
49
+
50
+ private drawProgressBar(): void {
51
+ if (!this.options.total) return;
52
+
53
+ const percentage = Math.min(100, Math.floor((this.current / this.options.total) * 100));
54
+ const barLength = 30;
55
+ const filled = Math.max(0, Math.min(barLength, Math.floor((this.current / this.options.total) * barLength)));
56
+ const bar = '█'.repeat(filled) + '░'.repeat(barLength - filled);
57
+
58
+ this.clearLine();
59
+ process.stdout.write(
60
+ `Progress: [${bar}] ${percentage}% (${this.current}/${this.options.total})`
61
+ );
62
+ }
63
+
64
+ private clearLine(): void {
65
+ if (process.stdout.isTTY && process.stdout.clearLine && process.stdout.cursorTo) {
66
+ process.stdout.clearLine(0);
67
+ process.stdout.cursorTo(0);
68
+ }
69
+ }
70
+
71
+ private formatTime(ms: number): string {
72
+ if (ms < 1000) return `${ms}ms`;
73
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
74
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
75
+ }
76
+ }