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,304 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { SchemaWriter } from "./SchemaWriter";
4
+ import { CodeGenerationError } from "../errors";
5
+
6
+ const testDir = path.resolve(__dirname, "../test-tmp/schema-writer");
7
+
8
+ const cleanup = async () => {
9
+ if (fs.existsSync(testDir)) {
10
+ await fs.promises.rm(testDir, { recursive: true, force: true });
11
+ }
12
+ };
13
+
14
+ beforeEach(cleanup);
15
+ afterAll(cleanup);
16
+
17
+ describe("SchemaWriter", () => {
18
+ describe("writeJsonSchema", () => {
19
+ it("should write JSON schema with pretty formatting", async () => {
20
+ const writer = new SchemaWriter({
21
+ outputPath: testDir,
22
+ minify: false
23
+ });
24
+
25
+ const schema: any = {
26
+ $schema: "http://json-schema.org/draft-07/schema#",
27
+ definitions: {
28
+ IUser: {
29
+ type: "object",
30
+ properties: {
31
+ id: { type: "string" },
32
+ name: { type: "string" }
33
+ },
34
+ required: ["id", "name"],
35
+ additionalProperties: false
36
+ }
37
+ }
38
+ };
39
+
40
+ const outputFile = path.join(testDir, "schema.json");
41
+ await writer.writeJsonSchema(schema, outputFile);
42
+
43
+ expect(fs.existsSync(outputFile)).toBe(true);
44
+
45
+ const content = await fs.promises.readFile(outputFile, 'utf-8');
46
+ const parsed = JSON.parse(content);
47
+
48
+ expect(parsed).toEqual(schema);
49
+ // Pretty formatted should have indentation
50
+ expect(content).toMatch(/\n /);
51
+ });
52
+
53
+ it("should write minified JSON schema", async () => {
54
+ const writer = new SchemaWriter({
55
+ outputPath: testDir,
56
+ minify: true
57
+ });
58
+
59
+ const schema: any = {
60
+ $schema: "http://json-schema.org/draft-07/schema#",
61
+ definitions: {
62
+ IUser: {
63
+ type: "object",
64
+ properties: {
65
+ id: { type: "string" }
66
+ }
67
+ }
68
+ }
69
+ };
70
+
71
+ const outputFile = path.join(testDir, "schema.json");
72
+ await writer.writeJsonSchema(schema, outputFile);
73
+
74
+ expect(fs.existsSync(outputFile)).toBe(true);
75
+
76
+ const content = await fs.promises.readFile(outputFile, 'utf-8');
77
+ const parsed = JSON.parse(content);
78
+
79
+ expect(parsed).toEqual(schema);
80
+ // Minified should not have extra whitespace
81
+ expect(content).not.toMatch(/\n /);
82
+ });
83
+
84
+ it("should create output directory if it doesn't exist", async () => {
85
+ const nestedPath = path.join(testDir, "nested", "deep", "path");
86
+ const writer = new SchemaWriter({
87
+ outputPath: nestedPath,
88
+ minify: false
89
+ });
90
+
91
+ const schema = { $schema: "test" };
92
+ const outputFile = path.join(nestedPath, "schema.json");
93
+
94
+ await writer.writeJsonSchema(schema, outputFile);
95
+
96
+ expect(fs.existsSync(outputFile)).toBe(true);
97
+ });
98
+
99
+ it("should throw CodeGenerationError on write failure", async () => {
100
+ const writer = new SchemaWriter({
101
+ outputPath: testDir,
102
+ minify: false
103
+ });
104
+
105
+ const schema = { $schema: "test" };
106
+ // Try to write to an invalid path (file as directory)
107
+ const invalidPath = path.join(testDir, "file.json", "invalid");
108
+
109
+ // Create file first to make path invalid
110
+ await fs.promises.mkdir(testDir, { recursive: true });
111
+ await fs.promises.writeFile(path.join(testDir, "file.json"), "test");
112
+
113
+ await expect(writer.writeJsonSchema(schema, invalidPath))
114
+ .rejects.toThrow(CodeGenerationError);
115
+ });
116
+ });
117
+
118
+ describe("writeTypeScriptFile", () => {
119
+ it("should write TypeScript file correctly", async () => {
120
+ const writer = new SchemaWriter({
121
+ outputPath: testDir,
122
+ minify: false
123
+ });
124
+
125
+ const content = `export interface IUser {
126
+ id: string;
127
+ name: string;
128
+ }`;
129
+
130
+ const outputFile = path.join(testDir, "types.ts");
131
+ await writer.writeTypeScriptFile(content, outputFile);
132
+
133
+ expect(fs.existsSync(outputFile)).toBe(true);
134
+
135
+ const fileContent = await fs.promises.readFile(outputFile, 'utf-8');
136
+ expect(fileContent).toBe(content);
137
+ });
138
+
139
+ it("should create directories for TypeScript files", async () => {
140
+ const nestedPath = path.join(testDir, "src", "types");
141
+ const writer = new SchemaWriter({
142
+ outputPath: nestedPath,
143
+ minify: false
144
+ });
145
+
146
+ const content = "export const test = 'value';";
147
+ const outputFile = path.join(nestedPath, "test.ts");
148
+
149
+ await writer.writeTypeScriptFile(content, outputFile);
150
+
151
+ expect(fs.existsSync(outputFile)).toBe(true);
152
+ });
153
+
154
+ it("should handle empty content", async () => {
155
+ const writer = new SchemaWriter({
156
+ outputPath: testDir,
157
+ minify: false
158
+ });
159
+
160
+ const outputFile = path.join(testDir, "empty.ts");
161
+ await writer.writeTypeScriptFile("", outputFile);
162
+
163
+ expect(fs.existsSync(outputFile)).toBe(true);
164
+
165
+ const content = await fs.promises.readFile(outputFile, 'utf-8');
166
+ expect(content).toBe("");
167
+ });
168
+ });
169
+
170
+ describe("cleanOutputDirectory", () => {
171
+ it("should remove TypeScript and JSON files", async () => {
172
+ await fs.promises.mkdir(testDir, { recursive: true });
173
+
174
+ // Create various files
175
+ await fs.promises.writeFile(path.join(testDir, "schema.json"), "{}");
176
+ await fs.promises.writeFile(path.join(testDir, "types.ts"), "export interface Test {}");
177
+ await fs.promises.writeFile(path.join(testDir, "readme.md"), "# Test");
178
+ await fs.promises.writeFile(path.join(testDir, "config.yaml"), "test: value");
179
+
180
+ const writer = new SchemaWriter({
181
+ outputPath: testDir,
182
+ minify: false
183
+ });
184
+
185
+ await writer.cleanOutputDirectory();
186
+
187
+ // Should remove .ts and .json files
188
+ expect(fs.existsSync(path.join(testDir, "schema.json"))).toBe(false);
189
+ expect(fs.existsSync(path.join(testDir, "types.ts"))).toBe(false);
190
+
191
+ // Should keep other files
192
+ expect(fs.existsSync(path.join(testDir, "readme.md"))).toBe(true);
193
+ expect(fs.existsSync(path.join(testDir, "config.yaml"))).toBe(true);
194
+ });
195
+
196
+ it("should handle non-existent directory", async () => {
197
+ const writer = new SchemaWriter({
198
+ outputPath: path.join(testDir, "nonexistent"),
199
+ minify: false
200
+ });
201
+
202
+ // Should not throw
203
+ await expect(writer.cleanOutputDirectory()).resolves.not.toThrow();
204
+ });
205
+
206
+ it("should not remove subdirectories", async () => {
207
+ await fs.promises.mkdir(path.join(testDir, "subdir"), { recursive: true });
208
+ await fs.promises.writeFile(path.join(testDir, "subdir", "file.ts"), "test");
209
+ await fs.promises.writeFile(path.join(testDir, "main.ts"), "test");
210
+
211
+ const writer = new SchemaWriter({
212
+ outputPath: testDir,
213
+ minify: false
214
+ });
215
+
216
+ await writer.cleanOutputDirectory();
217
+
218
+ // Should remove file in root
219
+ expect(fs.existsSync(path.join(testDir, "main.ts"))).toBe(false);
220
+
221
+ // Should keep subdirectory and its contents
222
+ expect(fs.existsSync(path.join(testDir, "subdir"))).toBe(true);
223
+ expect(fs.existsSync(path.join(testDir, "subdir", "file.ts"))).toBe(true);
224
+ });
225
+
226
+ it("should handle permission errors gracefully", async () => {
227
+ // Skip this test as it's environment dependent
228
+ // Real permission errors would be caught by the try-catch in SchemaWriter
229
+ });
230
+ });
231
+
232
+ describe("edge cases", () => {
233
+ it("should handle large files", async () => {
234
+ const writer = new SchemaWriter({
235
+ outputPath: testDir,
236
+ minify: false
237
+ });
238
+
239
+ // Create a large schema
240
+ const largeSchema: any = {
241
+ $schema: "http://json-schema.org/draft-07/schema#",
242
+ definitions: {}
243
+ };
244
+
245
+ // Add many definitions
246
+ for (let i = 0; i < 1000; i++) {
247
+ largeSchema.definitions[`Interface${i}`] = {
248
+ type: "object",
249
+ properties: {
250
+ id: { type: "string" },
251
+ name: { type: "string" },
252
+ value: { type: "number" }
253
+ }
254
+ };
255
+ }
256
+
257
+ const outputFile = path.join(testDir, "large-schema.json");
258
+ await writer.writeJsonSchema(largeSchema, outputFile);
259
+
260
+ expect(fs.existsSync(outputFile)).toBe(true);
261
+
262
+ const stats = await fs.promises.stat(outputFile);
263
+ expect(stats.size).toBeGreaterThan(1000); // Should be reasonably large
264
+ });
265
+
266
+ it("should handle unicode content", async () => {
267
+ const writer = new SchemaWriter({
268
+ outputPath: testDir,
269
+ minify: false
270
+ });
271
+
272
+ const unicodeContent = `export interface IUser {
273
+ // Unicode: café, naïve, résumé
274
+ name: string; // 中文注释
275
+ emoji: "👨‍💻" | "🚀" | "💡";
276
+ }`;
277
+
278
+ const outputFile = path.join(testDir, "unicode.ts");
279
+ await writer.writeTypeScriptFile(unicodeContent, outputFile);
280
+
281
+ const content = await fs.promises.readFile(outputFile, 'utf-8');
282
+ expect(content).toBe(unicodeContent);
283
+ });
284
+
285
+ it("should overwrite existing files", async () => {
286
+ const writer = new SchemaWriter({
287
+ outputPath: testDir,
288
+ minify: false
289
+ });
290
+
291
+ const outputFile = path.join(testDir, "overwrite.ts");
292
+
293
+ // Write first content
294
+ await writer.writeTypeScriptFile("const first = 1;", outputFile);
295
+ const firstContent = await fs.promises.readFile(outputFile, 'utf-8');
296
+ expect(firstContent).toBe("const first = 1;");
297
+
298
+ // Overwrite with second content
299
+ await writer.writeTypeScriptFile("const second = 2;", outputFile);
300
+ const secondContent = await fs.promises.readFile(outputFile, 'utf-8');
301
+ expect(secondContent).toBe("const second = 2;");
302
+ });
303
+ });
304
+ });
@@ -0,0 +1,75 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { Schema } from "ts-json-schema-generator";
4
+ import { CodeGenerationError } from "../errors";
5
+
6
+ export interface SchemaWriterOptions {
7
+ outputPath: string;
8
+ minify?: boolean;
9
+ }
10
+
11
+ export class SchemaWriter {
12
+ constructor(private options: SchemaWriterOptions) {}
13
+
14
+ public async writeJsonSchema(
15
+ schema: Schema,
16
+ outputFile: string
17
+ ): Promise<void> {
18
+ try {
19
+ await this.ensureOutputPath();
20
+
21
+ const content = this.options.minify
22
+ ? JSON.stringify(schema)
23
+ : JSON.stringify(schema, null, 4);
24
+
25
+ await fs.promises.writeFile(outputFile, content, 'utf-8');
26
+ } catch (error) {
27
+ throw new CodeGenerationError(
28
+ `Failed to write JSON schema: ${error instanceof Error ? error.message : String(error)}`,
29
+ outputFile
30
+ );
31
+ }
32
+ }
33
+
34
+ public async writeTypeScriptFile(
35
+ content: string,
36
+ outputFile: string
37
+ ): Promise<void> {
38
+ try {
39
+ await this.ensureOutputPath();
40
+ await fs.promises.writeFile(outputFile, content, 'utf-8');
41
+ } catch (error) {
42
+ throw new CodeGenerationError(
43
+ `Failed to write TypeScript file: ${error instanceof Error ? error.message : String(error)}`,
44
+ outputFile
45
+ );
46
+ }
47
+ }
48
+
49
+ private async ensureOutputPath(): Promise<void> {
50
+ if (!fs.existsSync(this.options.outputPath)) {
51
+ await fs.promises.mkdir(this.options.outputPath, { recursive: true });
52
+ }
53
+ }
54
+
55
+ public async cleanOutputDirectory(): Promise<void> {
56
+ try {
57
+ if (fs.existsSync(this.options.outputPath)) {
58
+ const files = await fs.promises.readdir(this.options.outputPath);
59
+
60
+ for (const file of files) {
61
+ const filePath = path.join(this.options.outputPath, file);
62
+ const stat = await fs.promises.stat(filePath);
63
+
64
+ if (stat.isFile() && (file.endsWith('.ts') || file.endsWith('.json'))) {
65
+ await fs.promises.unlink(filePath);
66
+ }
67
+ }
68
+ }
69
+ } catch (error) {
70
+ throw new CodeGenerationError(
71
+ `Failed to clean output directory: ${error instanceof Error ? error.message : String(error)}`
72
+ );
73
+ }
74
+ }
75
+ }