apify-schema-tools 3.1.0 → 3.2.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 (130) hide show
  1. package/.node-version +1 -1
  2. package/CHANGELOG.md +7 -1
  3. package/biome.json +8 -2
  4. package/dist/apify-schema-tools.js +12 -9
  5. package/dist/apify-schema-tools.js.map +1 -1
  6. package/dist/apify.d.ts +1 -1
  7. package/dist/apify.d.ts.map +1 -1
  8. package/dist/apify.js +19 -5
  9. package/dist/apify.js.map +1 -1
  10. package/dist/cli/check.d.ts +5 -0
  11. package/dist/cli/check.d.ts.map +1 -0
  12. package/dist/cli/check.js +86 -0
  13. package/dist/cli/check.js.map +1 -0
  14. package/dist/cli/init.d.ts +5 -0
  15. package/dist/cli/init.d.ts.map +1 -0
  16. package/dist/cli/init.js +92 -0
  17. package/dist/cli/init.js.map +1 -0
  18. package/dist/cli/sync.d.ts +5 -0
  19. package/dist/cli/sync.d.ts.map +1 -0
  20. package/dist/cli/sync.js +112 -0
  21. package/dist/cli/sync.js.map +1 -0
  22. package/dist/configuration.d.ts +14 -5
  23. package/dist/configuration.d.ts.map +1 -1
  24. package/dist/configuration.js +9 -5
  25. package/dist/configuration.js.map +1 -1
  26. package/dist/main.d.ts +4 -0
  27. package/dist/main.d.ts.map +1 -0
  28. package/dist/main.js +19 -0
  29. package/dist/main.js.map +1 -0
  30. package/dist/middle-schema/compare-schemas.d.ts +3 -0
  31. package/dist/middle-schema/compare-schemas.d.ts.map +1 -0
  32. package/dist/middle-schema/compare-schemas.js +90 -0
  33. package/dist/middle-schema/compare-schemas.js.map +1 -0
  34. package/dist/middle-schema/generate-typescript.d.ts +7 -0
  35. package/dist/middle-schema/generate-typescript.d.ts.map +1 -0
  36. package/dist/middle-schema/generate-typescript.js +70 -0
  37. package/dist/middle-schema/generate-typescript.js.map +1 -0
  38. package/dist/middle-schema/parse-json-schema.d.ts +4 -0
  39. package/dist/middle-schema/parse-json-schema.d.ts.map +1 -0
  40. package/dist/middle-schema/parse-json-schema.js +65 -0
  41. package/dist/middle-schema/parse-json-schema.js.map +1 -0
  42. package/dist/middle-schema/parse-typescript.d.ts +4 -0
  43. package/dist/middle-schema/parse-typescript.d.ts.map +1 -0
  44. package/dist/middle-schema/parse-typescript.js +199 -0
  45. package/dist/middle-schema/parse-typescript.js.map +1 -0
  46. package/dist/middle-schema/schema-types.d.ts +24 -0
  47. package/dist/middle-schema/schema-types.d.ts.map +1 -0
  48. package/dist/middle-schema/schema-types.js +14 -0
  49. package/dist/middle-schema/schema-types.js.map +1 -0
  50. package/dist/middle-schema/schema.d.ts +24 -0
  51. package/dist/middle-schema/schema.d.ts.map +1 -0
  52. package/dist/middle-schema/schema.js +14 -0
  53. package/dist/middle-schema/schema.js.map +1 -0
  54. package/dist/schema/entities/abstract-entity.d.ts +5 -0
  55. package/dist/schema/entities/abstract-entity.d.ts.map +1 -0
  56. package/dist/schema/entities/abstract-entity.js +3 -0
  57. package/dist/schema/entities/abstract-entity.js.map +1 -0
  58. package/dist/schema/entities/primitive-union.d.ts +12 -0
  59. package/dist/schema/entities/primitive-union.d.ts.map +1 -0
  60. package/dist/schema/entities/primitive-union.js +74 -0
  61. package/dist/schema/entities/primitive-union.js.map +1 -0
  62. package/dist/schema/entities/primitive.d.ts +15 -0
  63. package/dist/schema/entities/primitive.d.ts.map +1 -0
  64. package/dist/schema/entities/primitive.js +54 -0
  65. package/dist/schema/entities/primitive.js.map +1 -0
  66. package/dist/schema/parsers/json-schema.d.ts +4 -0
  67. package/dist/schema/parsers/json-schema.d.ts.map +1 -0
  68. package/dist/schema/parsers/json-schema.js +12 -0
  69. package/dist/schema/parsers/json-schema.js.map +1 -0
  70. package/dist/schema/parsers/typescript.d.ts +3 -0
  71. package/dist/schema/parsers/typescript.d.ts.map +1 -0
  72. package/dist/schema/parsers/typescript.js +24 -0
  73. package/dist/schema/parsers/typescript.js.map +1 -0
  74. package/dist/schemas/input.d.ts +840 -0
  75. package/dist/schemas/input.d.ts.map +1 -0
  76. package/dist/schemas/input.js +349 -0
  77. package/dist/schemas/input.js.map +1 -0
  78. package/dist/utils/filesystem.d.ts +8 -0
  79. package/dist/utils/filesystem.d.ts.map +1 -0
  80. package/dist/utils/filesystem.js +16 -0
  81. package/dist/utils/filesystem.js.map +1 -0
  82. package/dist/utils/json-schemas-interactive-conflict.d.ts +16 -0
  83. package/dist/utils/json-schemas-interactive-conflict.d.ts.map +1 -0
  84. package/dist/utils/json-schemas-interactive-conflict.js +165 -0
  85. package/dist/utils/json-schemas-interactive-conflict.js.map +1 -0
  86. package/dist/utils/json-schemas.d.ts +42 -0
  87. package/dist/utils/json-schemas.d.ts.map +1 -0
  88. package/dist/utils/json-schemas.js +162 -0
  89. package/dist/utils/json-schemas.js.map +1 -0
  90. package/dist/zod/schemas/input.d.ts +840 -0
  91. package/dist/zod/schemas/input.d.ts.map +1 -0
  92. package/dist/zod/schemas/input.js +393 -0
  93. package/dist/zod/schemas/input.js.map +1 -0
  94. package/package.json +12 -12
  95. package/samples/all-defaults/.actor/input_schema.json +32 -3
  96. package/samples/all-defaults/src-schemas/input.json +2 -1
  97. package/samples/deep-merged-schemas/.actor/input_schema.json +36 -3
  98. package/samples/merged-schemas/.actor/input_schema.json +27 -3
  99. package/samples/package-json-config/.actor/input_schema.json +32 -3
  100. package/samples/package-json-config-merged/.actor/input_schema.json +36 -3
  101. package/src/apify.ts +21 -6
  102. package/src/cli/check.ts +114 -0
  103. package/src/cli/init.ts +125 -0
  104. package/src/cli/sync.ts +164 -0
  105. package/src/configuration.ts +17 -7
  106. package/src/main.ts +25 -0
  107. package/src/middle-schema/compare-schemas.ts +113 -0
  108. package/src/middle-schema/generate-typescript.ts +88 -0
  109. package/src/middle-schema/parse-json-schema.ts +104 -0
  110. package/src/middle-schema/parse-typescript.ts +239 -0
  111. package/src/middle-schema/schema-types.ts +40 -0
  112. package/test/apify.test.ts +410 -2
  113. package/test/cli/check.test.ts +1571 -0
  114. package/test/cli/init.test.ts +459 -0
  115. package/test/cli/sync.test.ts +341 -0
  116. package/test/common.ts +68 -0
  117. package/test/configuration.test.ts +8 -8
  118. package/test/middle-schema/compare-schemas.test.ts +585 -0
  119. package/test/middle-schema/generate-typescript.test.ts +191 -0
  120. package/test/middle-schema/parse-json-schema.test.ts +178 -0
  121. package/test/middle-schema/parse-typescript.test.ts +143 -0
  122. package/test/{json-schema-conflicts.test.ts → utils/json-schemas-interactive-conflict.test.ts} +2 -2
  123. package/test/{json-schemas.test.ts → utils/json-schemas.test.ts} +3 -3
  124. package/src/apify-schema-tools.ts +0 -420
  125. package/src/typescript.ts +0 -563
  126. package/test/apify-schema-tools.test.ts +0 -2216
  127. package/test/typescript.test.ts +0 -1079
  128. /package/src/{filesystem.ts → utils/filesystem.ts} +0 -0
  129. /package/src/{json-schema-conflicts.ts → utils/json-schemas-interactive-conflict.ts} +0 -0
  130. /package/src/{json-schemas.ts → utils/json-schemas.ts} +0 -0
@@ -0,0 +1,1571 @@
1
+ import { execSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeAll, describe, expect, it } from "vitest";
4
+ import { DATASET_SCHEMA_FIELD } from "../../src/apify.js";
5
+ import { writeFile } from "../../src/utils/filesystem.js";
6
+ import { type ObjectSchema, writeJsonSchema, writeSchemaToField } from "../../src/utils/json-schemas.js";
7
+ import {
8
+ cleanupTestDirectory,
9
+ getDatasetSchemaPath,
10
+ getInputSchemaPath,
11
+ getTestDir,
12
+ setupTestDirectory,
13
+ setupTestDirectoryFiles,
14
+ } from "../common.js";
15
+
16
+ const EXEC_CMD = `node ${import.meta.dirname}/../../dist/apify-schema-tools.js`;
17
+
18
+ const TEST_DIR = getTestDir("check-command");
19
+ const INPUT_SCHEMA_PATH = getInputSchemaPath(TEST_DIR);
20
+ const DATASET_SCHEMA_PATH = getDatasetSchemaPath(TEST_DIR);
21
+
22
+ const ADDITIONAL_PROPERTIES_ERROR_PATTERN =
23
+ /If you generated an interface from a JSON schema, pay attention that "additionalProperties" is set to an empty schema by default/;
24
+
25
+ describe("check command", () => {
26
+ beforeAll(() => {
27
+ execSync("npm run build", { stdio: "inherit" });
28
+ });
29
+ afterEach(() => {
30
+ cleanupTestDirectory(TEST_DIR);
31
+ });
32
+
33
+ it("should pass if the schemas match the source schemas", () => {
34
+ const inputSchema: ObjectSchema = {
35
+ title: "Input Schema",
36
+ type: "object",
37
+ properties: {
38
+ name: { type: "string" },
39
+ age: { type: "integer" },
40
+ },
41
+ required: ["name"],
42
+ };
43
+ const datasetSchema: ObjectSchema = {
44
+ title: "Dataset Item Schema",
45
+ type: "object",
46
+ properties: {
47
+ id: { type: "string" },
48
+ value: { type: "number" },
49
+ },
50
+ required: ["id"],
51
+ };
52
+ setupTestDirectory(TEST_DIR);
53
+ const srcDir = join(TEST_DIR, "src-schemas");
54
+ const srcInputPath = join(srcDir, "input.json");
55
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
56
+ setupTestDirectoryFiles({
57
+ testDirPath: TEST_DIR,
58
+ srcInputPath,
59
+ srcInput: inputSchema,
60
+ srcDatasetPath,
61
+ srcDataset: datasetSchema,
62
+ });
63
+ writeJsonSchema(INPUT_SCHEMA_PATH, inputSchema);
64
+ writeSchemaToField(DATASET_SCHEMA_PATH, datasetSchema, DATASET_SCHEMA_FIELD);
65
+ expect(() =>
66
+ execSync(
67
+ [
68
+ `${EXEC_CMD} check`,
69
+ "-i input dataset",
70
+ "-o json-schemas",
71
+ `--src-input ${srcInputPath}`,
72
+ `--src-dataset ${srcDatasetPath}`,
73
+ ].join(" "),
74
+ {
75
+ cwd: TEST_DIR,
76
+ stdio: "inherit",
77
+ },
78
+ ),
79
+ ).not.toThrow();
80
+ });
81
+
82
+ it("should consider additional schemas", () => {
83
+ const inputSchema: ObjectSchema = {
84
+ title: "Input Schema",
85
+ type: "object",
86
+ properties: {
87
+ name: { type: "string" },
88
+ },
89
+ required: ["name"],
90
+ };
91
+ const additionalSchema: ObjectSchema = {
92
+ type: "object",
93
+ properties: {
94
+ age: { type: "integer" },
95
+ },
96
+ };
97
+ const fullSchema: ObjectSchema = {
98
+ title: "Input Schema",
99
+ type: "object",
100
+ properties: {
101
+ name: { type: "string" },
102
+ age: { type: "integer" },
103
+ },
104
+ required: ["name"],
105
+ };
106
+ setupTestDirectory(TEST_DIR);
107
+ const srcDir = join(TEST_DIR, "src-schemas");
108
+ const srcInputPath = join(srcDir, "input.json");
109
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
110
+ const addDir = join(TEST_DIR, "add-schemas");
111
+ const addInputPath = join(addDir, "add-input.json");
112
+ setupTestDirectoryFiles({
113
+ testDirPath: TEST_DIR,
114
+ srcInputPath,
115
+ srcInput: inputSchema,
116
+ srcDatasetPath,
117
+ srcDataset: {} as ObjectSchema,
118
+ addDir,
119
+ addInputPath,
120
+ addInput: additionalSchema,
121
+ });
122
+ writeJsonSchema(INPUT_SCHEMA_PATH, fullSchema);
123
+ expect(() =>
124
+ execSync(
125
+ [
126
+ `${EXEC_CMD} check`,
127
+ "-i input",
128
+ "-o json-schemas",
129
+ `--src-input ${srcInputPath}`,
130
+ `--src-dataset ${srcDatasetPath}`,
131
+ `--add-input ${addInputPath}`,
132
+ ].join(" "),
133
+ {
134
+ cwd: TEST_DIR,
135
+ stdio: "inherit",
136
+ },
137
+ ),
138
+ ).not.toThrow();
139
+ });
140
+
141
+ it("should pass when source schema has position fields that are removed in output", () => {
142
+ const inputSchemaWithPositions: ObjectSchema = {
143
+ title: "Input Schema",
144
+ type: "object",
145
+ properties: {
146
+ name: { type: "string", position: 1 },
147
+ age: { type: "integer", position: 2 },
148
+ email: { type: "string", position: 0 },
149
+ },
150
+ required: ["name"],
151
+ };
152
+ // Expected output schema (positions are removed by the filtering process)
153
+ const expectedOutputSchema: ObjectSchema = {
154
+ title: "Input Schema",
155
+ type: "object",
156
+ properties: {
157
+ email: { type: "string" }, // position 0 comes first
158
+ name: { type: "string" }, // position 1 comes second
159
+ age: { type: "integer" }, // position 2 comes last
160
+ },
161
+ required: ["name"],
162
+ };
163
+ setupTestDirectory(TEST_DIR);
164
+ const srcDir = join(TEST_DIR, "src-schemas");
165
+ const srcInputPath = join(srcDir, "input.json");
166
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
167
+ setupTestDirectoryFiles({
168
+ testDirPath: TEST_DIR,
169
+ srcInputPath,
170
+ srcInput: inputSchemaWithPositions,
171
+ srcDatasetPath,
172
+ srcDataset: {} as ObjectSchema,
173
+ });
174
+ writeJsonSchema(INPUT_SCHEMA_PATH, expectedOutputSchema);
175
+ expect(() =>
176
+ execSync(
177
+ [
178
+ `${EXEC_CMD} check`,
179
+ "-i input",
180
+ "-o json-schemas",
181
+ `--src-input ${srcInputPath}`,
182
+ `--src-dataset ${srcDatasetPath}`,
183
+ ].join(" "),
184
+ {
185
+ cwd: TEST_DIR,
186
+ stdio: "inherit",
187
+ },
188
+ ),
189
+ ).not.toThrow();
190
+ });
191
+
192
+ it("should pass when source schema has invalid input schema fields that are filtered out", () => {
193
+ const inputSchemaWithInvalidFields: ObjectSchema = {
194
+ title: "Input Schema",
195
+ type: "object",
196
+ properties: {
197
+ name: {
198
+ type: "string",
199
+ format: "email", // Invalid field for Apify input schemas - will be removed
200
+ custom: "field", // Invalid field - will be removed
201
+ },
202
+ age: {
203
+ type: "integer",
204
+ invalidField: "value", // Invalid field - will be removed
205
+ },
206
+ description: {
207
+ type: "string",
208
+ pattern: "^[a-z]+$", // Valid field - will be kept
209
+ },
210
+ },
211
+ required: ["name"],
212
+ invalidRootField: "should be removed", // Invalid root field - will be removed
213
+ };
214
+ // Expected output schema (invalid fields are removed by the filtering process)
215
+ const expectedOutputSchema: ObjectSchema = {
216
+ title: "Input Schema",
217
+ type: "object",
218
+ properties: {
219
+ name: { type: "string" },
220
+ age: { type: "integer" },
221
+ description: {
222
+ type: "string",
223
+ pattern: "^[a-z]+$",
224
+ },
225
+ },
226
+ required: ["name"],
227
+ };
228
+ setupTestDirectory(TEST_DIR);
229
+ const srcDir = join(TEST_DIR, "src-schemas");
230
+ const srcInputPath = join(srcDir, "input.json");
231
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
232
+ setupTestDirectoryFiles({
233
+ testDirPath: TEST_DIR,
234
+ srcInputPath,
235
+ srcInput: inputSchemaWithInvalidFields,
236
+ srcDatasetPath,
237
+ srcDataset: {} as ObjectSchema,
238
+ });
239
+ writeJsonSchema(INPUT_SCHEMA_PATH, expectedOutputSchema);
240
+ expect(() =>
241
+ execSync(
242
+ [
243
+ `${EXEC_CMD} check`,
244
+ "-i input",
245
+ "-o json-schemas",
246
+ `--src-input ${srcInputPath}`,
247
+ `--src-dataset ${srcDatasetPath}`,
248
+ ].join(" "),
249
+ {
250
+ cwd: TEST_DIR,
251
+ stdio: "inherit",
252
+ },
253
+ ),
254
+ ).not.toThrow();
255
+ });
256
+
257
+ it("should pass when source schema has both position and invalid fields", () => {
258
+ const complexSourceSchema: ObjectSchema = {
259
+ title: "Complex Input Schema",
260
+ type: "object",
261
+ properties: {
262
+ priority: {
263
+ type: "string",
264
+ position: 0,
265
+ format: "email", // Invalid - will be removed
266
+ },
267
+ name: {
268
+ type: "string",
269
+ position: 2,
270
+ customField: "invalid", // Invalid - will be removed
271
+ },
272
+ age: {
273
+ type: "integer",
274
+ position: 1,
275
+ maximum: 100, // Valid - will be kept
276
+ },
277
+ settings: {
278
+ type: "object",
279
+ position: 3,
280
+ patternKey: "^[a-z]+$", // Valid for object type - will be kept
281
+ invalidObjectField: "remove", // Invalid - will be removed
282
+ },
283
+ },
284
+ required: ["priority", "name"],
285
+ customRootField: "invalid", // Invalid root field - will be removed
286
+ };
287
+ // Expected output schema (positions removed, invalid fields removed, properties ordered by position)
288
+ const expectedOutputSchema: ObjectSchema = {
289
+ title: "Complex Input Schema",
290
+ type: "object",
291
+ properties: {
292
+ priority: { type: "string" }, // position 0
293
+ age: { type: "integer", maximum: 100 }, // position 1
294
+ name: { type: "string" }, // position 2
295
+ settings: {
296
+ type: "object",
297
+ patternKey: "^[a-z]+$",
298
+ }, // position 3
299
+ },
300
+ required: ["priority", "name"],
301
+ };
302
+ setupTestDirectory(TEST_DIR);
303
+ const srcDir = join(TEST_DIR, "src-schemas");
304
+ const srcInputPath = join(srcDir, "input.json");
305
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
306
+ setupTestDirectoryFiles({
307
+ testDirPath: TEST_DIR,
308
+ srcInputPath,
309
+ srcInput: complexSourceSchema,
310
+ srcDatasetPath,
311
+ srcDataset: {} as ObjectSchema,
312
+ });
313
+ writeJsonSchema(INPUT_SCHEMA_PATH, expectedOutputSchema);
314
+ expect(() =>
315
+ execSync(
316
+ [
317
+ `${EXEC_CMD} check`,
318
+ "-i input",
319
+ "-o json-schemas",
320
+ `--src-input ${srcInputPath}`,
321
+ `--src-dataset ${srcDatasetPath}`,
322
+ ].join(" "),
323
+ {
324
+ cwd: TEST_DIR,
325
+ stdio: "inherit",
326
+ },
327
+ ),
328
+ ).not.toThrow();
329
+ });
330
+
331
+ it("should pass when additional schemas also have fields that get filtered out", () => {
332
+ const baseInputSchema: ObjectSchema = {
333
+ title: "Base Input Schema",
334
+ type: "object",
335
+ properties: {
336
+ name: { type: "string", position: 1 },
337
+ },
338
+ required: ["name"],
339
+ };
340
+ const additionalSchemaWithInvalidFields: ObjectSchema = {
341
+ type: "object",
342
+ properties: {
343
+ email: {
344
+ type: "string",
345
+ position: 0,
346
+ format: "email", // Invalid field - will be removed
347
+ },
348
+ age: {
349
+ type: "integer",
350
+ position: 2,
351
+ customField: "invalid", // Invalid field - will be removed
352
+ },
353
+ },
354
+ required: ["email"],
355
+ invalidAdditionalField: "remove", // Invalid root field - will be removed
356
+ };
357
+ // Expected merged output schema (positions and invalid fields removed, properties ordered by position)
358
+ const expectedMergedSchema: ObjectSchema = {
359
+ title: "Base Input Schema",
360
+ type: "object",
361
+ properties: {
362
+ email: { type: "string" }, // position 0
363
+ name: { type: "string" }, // position 1
364
+ age: { type: "integer" }, // position 2
365
+ },
366
+ required: ["name", "email"],
367
+ };
368
+ setupTestDirectory(TEST_DIR);
369
+ const srcDir = join(TEST_DIR, "src-schemas");
370
+ const srcInputPath = join(srcDir, "input.json");
371
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
372
+ const addDir = join(TEST_DIR, "add-schemas");
373
+ const addInputPath = join(addDir, "add-input.json");
374
+ setupTestDirectoryFiles({
375
+ testDirPath: TEST_DIR,
376
+ srcInputPath,
377
+ srcInput: baseInputSchema,
378
+ srcDatasetPath,
379
+ srcDataset: {} as ObjectSchema,
380
+ addDir,
381
+ addInputPath,
382
+ addInput: additionalSchemaWithInvalidFields,
383
+ });
384
+ writeJsonSchema(INPUT_SCHEMA_PATH, expectedMergedSchema);
385
+ expect(() =>
386
+ execSync(
387
+ [
388
+ `${EXEC_CMD} check`,
389
+ "-i input",
390
+ "-o json-schemas",
391
+ `--src-input ${srcInputPath}`,
392
+ `--src-dataset ${srcDatasetPath}`,
393
+ `--add-input ${addInputPath}`,
394
+ ].join(" "),
395
+ {
396
+ cwd: TEST_DIR,
397
+ stdio: "inherit",
398
+ },
399
+ ),
400
+ ).not.toThrow();
401
+ });
402
+
403
+ it("should fail when output schema doesn't match after filtering", () => {
404
+ const sourceSchemaWithInvalidFields: ObjectSchema = {
405
+ title: "Input Schema",
406
+ type: "object",
407
+ properties: {
408
+ name: {
409
+ type: "string",
410
+ format: "email", // This will be filtered out
411
+ },
412
+ },
413
+ required: ["name"],
414
+ };
415
+ // Incorrect output schema (includes the field that should have been filtered)
416
+ const incorrectOutputSchema: ObjectSchema = {
417
+ title: "Input Schema",
418
+ type: "object",
419
+ properties: {
420
+ name: {
421
+ type: "string",
422
+ format: "email", // This field should not be in the output
423
+ },
424
+ },
425
+ required: ["name"],
426
+ };
427
+ setupTestDirectory(TEST_DIR);
428
+ const srcDir = join(TEST_DIR, "src-schemas");
429
+ const srcInputPath = join(srcDir, "input.json");
430
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
431
+ setupTestDirectoryFiles({
432
+ testDirPath: TEST_DIR,
433
+ srcInputPath,
434
+ srcInput: sourceSchemaWithInvalidFields,
435
+ srcDatasetPath,
436
+ srcDataset: {} as ObjectSchema,
437
+ });
438
+ writeJsonSchema(INPUT_SCHEMA_PATH, incorrectOutputSchema);
439
+ expect(() =>
440
+ execSync(
441
+ [
442
+ `${EXEC_CMD} check`,
443
+ "-i input",
444
+ "-o json-schemas",
445
+ `--src-input ${srcInputPath}`,
446
+ `--src-dataset ${srcDatasetPath}`,
447
+ ].join(" "),
448
+ {
449
+ cwd: TEST_DIR,
450
+ stdio: "inherit",
451
+ },
452
+ ),
453
+ ).toThrow();
454
+ });
455
+
456
+ it("should pass when descriptions differ but --ignore-descriptions is set", () => {
457
+ const sourceInputSchema: ObjectSchema = {
458
+ title: "Source Input Schema",
459
+ description: "Original description for input schema",
460
+ type: "object",
461
+ properties: {
462
+ name: {
463
+ type: "string",
464
+ description: "Name of the user",
465
+ },
466
+ age: {
467
+ type: "integer",
468
+ description: "Age in years",
469
+ },
470
+ },
471
+ required: ["name"],
472
+ };
473
+ // Output schema with different descriptions but same structure
474
+ const outputInputSchema: ObjectSchema = {
475
+ title: "Generated Input Schema", // Different title
476
+ description: "Auto-generated description for input schema", // Different description
477
+ type: "object",
478
+ properties: {
479
+ name: {
480
+ type: "string",
481
+ description: "User's full name", // Different description
482
+ },
483
+ age: {
484
+ type: "integer",
485
+ description: "User's age", // Different description
486
+ },
487
+ },
488
+ required: ["name"],
489
+ };
490
+ setupTestDirectory(TEST_DIR);
491
+ const srcDir = join(TEST_DIR, "src-schemas");
492
+ const srcInputPath = join(srcDir, "input.json");
493
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
494
+ setupTestDirectoryFiles({
495
+ testDirPath: TEST_DIR,
496
+ srcInputPath,
497
+ srcInput: sourceInputSchema,
498
+ srcDatasetPath,
499
+ srcDataset: {} as ObjectSchema,
500
+ });
501
+ writeJsonSchema(INPUT_SCHEMA_PATH, outputInputSchema);
502
+ expect(() =>
503
+ execSync(
504
+ [
505
+ `${EXEC_CMD} check`,
506
+ "-i input",
507
+ "-o json-schemas",
508
+ `--src-input ${srcInputPath}`,
509
+ `--src-dataset ${srcDatasetPath}`,
510
+ "--ignore-descriptions",
511
+ ].join(" "),
512
+ {
513
+ cwd: TEST_DIR,
514
+ stdio: "inherit",
515
+ },
516
+ ),
517
+ ).not.toThrow();
518
+ });
519
+
520
+ it("should fail when descriptions differ and --ignore-descriptions is not set", () => {
521
+ const sourceInputSchema: ObjectSchema = {
522
+ title: "Source Input Schema",
523
+ description: "Original description",
524
+ type: "object",
525
+ properties: {
526
+ name: { type: "string" },
527
+ },
528
+ required: ["name"],
529
+ };
530
+ // Output schema with different title (same structure otherwise)
531
+ const outputInputSchema: ObjectSchema = {
532
+ title: "Different Title", // Different title should cause failure
533
+ description: "Original description",
534
+ type: "object",
535
+ properties: {
536
+ name: { type: "string" },
537
+ },
538
+ required: ["name"],
539
+ };
540
+ setupTestDirectory(TEST_DIR);
541
+ const srcDir = join(TEST_DIR, "src-schemas");
542
+ const srcInputPath = join(srcDir, "input.json");
543
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
544
+ setupTestDirectoryFiles({
545
+ testDirPath: TEST_DIR,
546
+ srcInputPath,
547
+ srcInput: sourceInputSchema,
548
+ srcDatasetPath,
549
+ srcDataset: {} as ObjectSchema,
550
+ });
551
+ writeJsonSchema(INPUT_SCHEMA_PATH, outputInputSchema);
552
+ expect(() =>
553
+ execSync(
554
+ [
555
+ `${EXEC_CMD} check`,
556
+ "-i input",
557
+ "-o json-schemas",
558
+ `--src-input ${srcInputPath}`,
559
+ `--src-dataset ${srcDatasetPath}`,
560
+ // Note: --ignore-descriptions is NOT set
561
+ ].join(" "),
562
+ {
563
+ cwd: TEST_DIR,
564
+ stdio: "inherit",
565
+ },
566
+ ),
567
+ ).toThrow();
568
+ });
569
+
570
+ it("should pass with --ignore-descriptions when additional schema has different descriptions", () => {
571
+ const baseInputSchema: ObjectSchema = {
572
+ title: "Base Input Schema",
573
+ description: "Base description",
574
+ type: "object",
575
+ properties: {
576
+ name: {
577
+ type: "string",
578
+ description: "Base name description",
579
+ },
580
+ },
581
+ required: ["name"],
582
+ };
583
+ const additionalSchema: ObjectSchema = {
584
+ title: "Additional Schema", // Different title
585
+ description: "Additional description", // Different description
586
+ type: "object",
587
+ properties: {
588
+ age: {
589
+ type: "integer",
590
+ description: "Additional age description",
591
+ },
592
+ },
593
+ required: ["age"],
594
+ };
595
+ // Expected merged output with different descriptions
596
+ const expectedOutputSchema: ObjectSchema = {
597
+ title: "Generated Combined Schema", // Different from both source schemas
598
+ description: "Auto-generated combined description", // Different from both
599
+ type: "object",
600
+ properties: {
601
+ name: {
602
+ type: "string",
603
+ description: "Generated name description", // Different description
604
+ },
605
+ age: {
606
+ type: "integer",
607
+ description: "Generated age description", // Different description
608
+ },
609
+ },
610
+ required: ["name", "age"],
611
+ };
612
+ setupTestDirectory(TEST_DIR);
613
+ const srcDir = join(TEST_DIR, "src-schemas");
614
+ const srcInputPath = join(srcDir, "input.json");
615
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
616
+ const addDir = join(TEST_DIR, "add-schemas");
617
+ const addInputPath = join(addDir, "add-input.json");
618
+ setupTestDirectoryFiles({
619
+ testDirPath: TEST_DIR,
620
+ srcInputPath,
621
+ srcInput: baseInputSchema,
622
+ srcDatasetPath,
623
+ srcDataset: {} as ObjectSchema,
624
+ addDir,
625
+ addInputPath,
626
+ addInput: additionalSchema,
627
+ });
628
+ writeJsonSchema(INPUT_SCHEMA_PATH, expectedOutputSchema);
629
+ expect(() =>
630
+ execSync(
631
+ [
632
+ `${EXEC_CMD} check`,
633
+ "-i input",
634
+ "-o json-schemas",
635
+ `--src-input ${srcInputPath}`,
636
+ `--src-dataset ${srcDatasetPath}`,
637
+ `--add-input ${addInputPath}`,
638
+ "--ignore-descriptions",
639
+ ].join(" "),
640
+ {
641
+ cwd: TEST_DIR,
642
+ stdio: "inherit",
643
+ },
644
+ ),
645
+ ).not.toThrow();
646
+ });
647
+
648
+ it("should pass with --ignore-descriptions when dataset schema has different descriptions", () => {
649
+ const sourceDatasetSchema: ObjectSchema = {
650
+ title: "Source Dataset Item Schema",
651
+ description: "Original dataset description",
652
+ type: "object",
653
+ properties: {
654
+ id: {
655
+ type: "string",
656
+ description: "Unique identifier",
657
+ },
658
+ value: {
659
+ type: "number",
660
+ description: "Numeric value",
661
+ },
662
+ },
663
+ required: ["id"],
664
+ };
665
+ // Output dataset schema with different descriptions
666
+ const outputDatasetSchema: ObjectSchema = {
667
+ title: "Generated Dataset Item Schema", // Different title
668
+ description: "Auto-generated dataset description", // Different description
669
+ type: "object",
670
+ properties: {
671
+ id: {
672
+ type: "string",
673
+ description: "Item identifier", // Different description
674
+ },
675
+ value: {
676
+ type: "number",
677
+ description: "Item value", // Different description
678
+ },
679
+ },
680
+ required: ["id"],
681
+ };
682
+ setupTestDirectory(TEST_DIR);
683
+ const srcDir = join(TEST_DIR, "src-schemas");
684
+ const srcInputPath = join(srcDir, "input.json");
685
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
686
+ setupTestDirectoryFiles({
687
+ testDirPath: TEST_DIR,
688
+ srcInputPath,
689
+ srcInput: {} as ObjectSchema,
690
+ srcDatasetPath,
691
+ srcDataset: sourceDatasetSchema,
692
+ });
693
+ // Write a simple input schema to avoid validation issues
694
+ writeJsonSchema(INPUT_SCHEMA_PATH, {
695
+ title: "Simple Input",
696
+ type: "object",
697
+ properties: {},
698
+ });
699
+ writeSchemaToField(DATASET_SCHEMA_PATH, outputDatasetSchema, DATASET_SCHEMA_FIELD);
700
+ expect(() =>
701
+ execSync(
702
+ [
703
+ `${EXEC_CMD} check`,
704
+ "-i dataset",
705
+ "-o json-schemas",
706
+ `--src-input ${srcInputPath}`,
707
+ `--src-dataset ${srcDatasetPath}`,
708
+ "--ignore-descriptions",
709
+ ].join(" "),
710
+ {
711
+ cwd: TEST_DIR,
712
+ stdio: "inherit",
713
+ },
714
+ ),
715
+ ).not.toThrow();
716
+ });
717
+
718
+ it("should fail with --ignore-descriptions when there are structural differences beyond descriptions", () => {
719
+ const sourceInputSchema: ObjectSchema = {
720
+ title: "Source Input Schema",
721
+ description: "Original description",
722
+ type: "object",
723
+ properties: {
724
+ name: {
725
+ type: "string",
726
+ description: "Name of the user",
727
+ },
728
+ age: {
729
+ type: "integer",
730
+ description: "Age in years",
731
+ },
732
+ },
733
+ required: ["name"],
734
+ };
735
+ // Output schema with different structure (missing age property)
736
+ const outputInputSchema: ObjectSchema = {
737
+ title: "Different Title", // Different title (should be ignored)
738
+ description: "Different description", // Different description (should be ignored)
739
+ type: "object",
740
+ properties: {
741
+ name: {
742
+ type: "string",
743
+ description: "Different name description", // Different description (should be ignored)
744
+ },
745
+ // Missing age property - this is a structural difference that should cause failure
746
+ },
747
+ required: ["name"],
748
+ };
749
+ setupTestDirectory(TEST_DIR);
750
+ const srcDir = join(TEST_DIR, "src-schemas");
751
+ const srcInputPath = join(srcDir, "input.json");
752
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
753
+ setupTestDirectoryFiles({
754
+ testDirPath: TEST_DIR,
755
+ srcInputPath,
756
+ srcInput: sourceInputSchema,
757
+ srcDatasetPath,
758
+ srcDataset: {} as ObjectSchema,
759
+ });
760
+ writeJsonSchema(INPUT_SCHEMA_PATH, outputInputSchema);
761
+ expect(() =>
762
+ execSync(
763
+ [
764
+ `${EXEC_CMD} check`,
765
+ "-i input",
766
+ "-o json-schemas",
767
+ `--src-input ${srcInputPath}`,
768
+ `--src-dataset ${srcDatasetPath}`,
769
+ "--ignore-descriptions", // Should still fail despite this flag
770
+ ].join(" "),
771
+ {
772
+ cwd: TEST_DIR,
773
+ stdio: "inherit",
774
+ },
775
+ ),
776
+ ).toThrow();
777
+ });
778
+
779
+ it("should pass when TypeScript files match the source schemas", () => {
780
+ const sourceInputSchema: ObjectSchema = {
781
+ title: "Input Schema",
782
+ description: "Schema for actor input",
783
+ type: "object",
784
+ properties: {
785
+ name: {
786
+ type: "string",
787
+ description: "User's name",
788
+ },
789
+ age: {
790
+ type: "integer",
791
+ description: "User's age",
792
+ },
793
+ isActive: {
794
+ type: "boolean",
795
+ description: "Whether user is active",
796
+ },
797
+ },
798
+ required: ["name"],
799
+ additionalProperties: false,
800
+ };
801
+ const sourceDatasetSchema: ObjectSchema = {
802
+ title: "Dataset Item Schema",
803
+ description: "Schema for dataset items",
804
+ type: "object",
805
+ properties: {
806
+ id: {
807
+ type: "string",
808
+ description: "Unique identifier",
809
+ },
810
+ value: {
811
+ type: "number",
812
+ description: "Numeric value",
813
+ },
814
+ },
815
+ required: ["id"],
816
+ };
817
+ setupTestDirectory(TEST_DIR);
818
+ const srcDir = join(TEST_DIR, "src-schemas");
819
+ const srcInputPath = join(srcDir, "input.json");
820
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
821
+ const outputTsDir = join(TEST_DIR, "src", "generated");
822
+ setupTestDirectoryFiles({
823
+ testDirPath: TEST_DIR,
824
+ srcInputPath,
825
+ srcInput: sourceInputSchema,
826
+ srcDatasetPath,
827
+ srcDataset: sourceDatasetSchema,
828
+ });
829
+
830
+ // Generate the TypeScript files that should match the schemas
831
+ const inputTsContent = `/**
832
+ * This file was automatically generated by apify-schema-tools.
833
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
834
+ * and run apify-schema-tools' "sync" command to regenerate this file.
835
+ */
836
+
837
+ /**
838
+ * Schema for actor input
839
+ */
840
+ export interface Input {
841
+ /**
842
+ * User's name
843
+ */
844
+ name: string;
845
+ /**
846
+ * User's age
847
+ */
848
+ age?: number;
849
+ /**
850
+ * Whether user is active
851
+ */
852
+ isActive?: boolean;
853
+ }
854
+ `;
855
+ const datasetTsContent = `/**
856
+ * This file was automatically generated by apify-schema-tools.
857
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
858
+ * and run apify-schema-tools' "sync" command to regenerate this file.
859
+ */
860
+
861
+ /**
862
+ * Schema for dataset items
863
+ */
864
+ export interface DatasetItem {
865
+ /**
866
+ * Unique identifier
867
+ */
868
+ id: string;
869
+ /**
870
+ * Numeric value
871
+ */
872
+ value?: number;
873
+ [key: string]: unknown;
874
+ }
875
+ `;
876
+ writeFile(join(outputTsDir, "input.ts"), inputTsContent);
877
+ writeFile(join(outputTsDir, "dataset.ts"), datasetTsContent);
878
+
879
+ expect(() =>
880
+ execSync(
881
+ [
882
+ `${EXEC_CMD} check`,
883
+ "-i input dataset",
884
+ "-o ts-types",
885
+ `--src-input ${srcInputPath}`,
886
+ `--src-dataset ${srcDatasetPath}`,
887
+ ].join(" "),
888
+ {
889
+ cwd: TEST_DIR,
890
+ stdio: "inherit",
891
+ },
892
+ ),
893
+ ).not.toThrow();
894
+ });
895
+
896
+ it("should fail when TypeScript interface has different property types", () => {
897
+ const sourceInputSchema: ObjectSchema = {
898
+ title: "Input Schema",
899
+ type: "object",
900
+ properties: {
901
+ name: { type: "string" },
902
+ age: { type: "integer" },
903
+ },
904
+ required: ["name"],
905
+ };
906
+ setupTestDirectory(TEST_DIR);
907
+ const srcDir = join(TEST_DIR, "src-schemas");
908
+ const srcInputPath = join(srcDir, "input.json");
909
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
910
+ const outputTsDir = join(TEST_DIR, "src", "generated");
911
+ setupTestDirectoryFiles({
912
+ testDirPath: TEST_DIR,
913
+ srcInputPath,
914
+ srcInput: sourceInputSchema,
915
+ srcDatasetPath,
916
+ srcDataset: {} as ObjectSchema,
917
+ });
918
+
919
+ // Generate TypeScript file with wrong type (age should be number, not string)
920
+ const incorrectInputTsContent = `/**
921
+ * This file was automatically generated by apify-schema-tools.
922
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
923
+ * and run apify-schema-tools' "sync" command to regenerate this file.
924
+ */
925
+
926
+ export interface Input {
927
+ name: string;
928
+ age?: string;
929
+ }
930
+ `;
931
+ writeFile(join(outputTsDir, "input.ts"), incorrectInputTsContent);
932
+
933
+ expect(() =>
934
+ execSync(
935
+ [
936
+ `${EXEC_CMD} check`,
937
+ "-i input",
938
+ "-o ts-types",
939
+ `--src-input ${srcInputPath}`,
940
+ `--src-dataset ${srcDatasetPath}`,
941
+ ].join(" "),
942
+ {
943
+ cwd: TEST_DIR,
944
+ stdio: "inherit",
945
+ },
946
+ ),
947
+ ).toThrow();
948
+ });
949
+
950
+ it("should fail when TypeScript interface has different required properties", () => {
951
+ const sourceInputSchema: ObjectSchema = {
952
+ title: "Input Schema",
953
+ type: "object",
954
+ properties: {
955
+ name: { type: "string" },
956
+ age: { type: "integer" },
957
+ },
958
+ required: ["name", "age"], // Both required
959
+ };
960
+ setupTestDirectory(TEST_DIR);
961
+ const srcDir = join(TEST_DIR, "src-schemas");
962
+ const srcInputPath = join(srcDir, "input.json");
963
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
964
+ const outputTsDir = join(TEST_DIR, "src", "generated");
965
+ setupTestDirectoryFiles({
966
+ testDirPath: TEST_DIR,
967
+ srcInputPath,
968
+ srcInput: sourceInputSchema,
969
+ srcDatasetPath,
970
+ srcDataset: {} as ObjectSchema,
971
+ });
972
+
973
+ // Generate TypeScript file where age is optional but should be required
974
+ const incorrectInputTsContent = `/**
975
+ * This file was automatically generated by apify-schema-tools.
976
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
977
+ * and run apify-schema-tools' "sync" command to regenerate this file.
978
+ */
979
+
980
+ export interface Input {
981
+ name: string;
982
+ age?: number;
983
+ }
984
+ `;
985
+ writeFile(join(outputTsDir, "input.ts"), incorrectInputTsContent);
986
+
987
+ expect(() =>
988
+ execSync(
989
+ [
990
+ `${EXEC_CMD} check`,
991
+ "-i input",
992
+ "-o ts-types",
993
+ `--src-input ${srcInputPath}`,
994
+ `--src-dataset ${srcDatasetPath}`,
995
+ ].join(" "),
996
+ {
997
+ cwd: TEST_DIR,
998
+ stdio: "inherit",
999
+ },
1000
+ ),
1001
+ ).toThrow();
1002
+ });
1003
+
1004
+ it("should fail when TypeScript interface has missing properties", () => {
1005
+ const sourceInputSchema: ObjectSchema = {
1006
+ title: "Input Schema",
1007
+ type: "object",
1008
+ properties: {
1009
+ name: { type: "string" },
1010
+ age: { type: "integer" },
1011
+ email: { type: "string" },
1012
+ },
1013
+ required: ["name"],
1014
+ };
1015
+ setupTestDirectory(TEST_DIR);
1016
+ const srcDir = join(TEST_DIR, "src-schemas");
1017
+ const srcInputPath = join(srcDir, "input.json");
1018
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1019
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1020
+ setupTestDirectoryFiles({
1021
+ testDirPath: TEST_DIR,
1022
+ srcInputPath,
1023
+ srcInput: sourceInputSchema,
1024
+ srcDatasetPath,
1025
+ srcDataset: {} as ObjectSchema,
1026
+ });
1027
+
1028
+ // Generate TypeScript file missing the email property
1029
+ const incompleteInputTsContent = `/**
1030
+ * This file was automatically generated by apify-schema-tools.
1031
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1032
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1033
+ */
1034
+
1035
+ export interface Input {
1036
+ name: string;
1037
+ age?: number;
1038
+ }
1039
+ `;
1040
+ writeFile(join(outputTsDir, "input.ts"), incompleteInputTsContent);
1041
+
1042
+ expect(() =>
1043
+ execSync(
1044
+ [
1045
+ `${EXEC_CMD} check`,
1046
+ "-i input",
1047
+ "-o ts-types",
1048
+ `--src-input ${srcInputPath}`,
1049
+ `--src-dataset ${srcDatasetPath}`,
1050
+ ].join(" "),
1051
+ {
1052
+ cwd: TEST_DIR,
1053
+ stdio: "inherit",
1054
+ },
1055
+ ),
1056
+ ).toThrow();
1057
+ });
1058
+
1059
+ it("should fail when additionalProperties differ", () => {
1060
+ const sourceInputSchema: ObjectSchema = {
1061
+ title: "Input Schema",
1062
+ type: "object",
1063
+ properties: {
1064
+ name: { type: "string" },
1065
+ age: { type: "integer" },
1066
+ },
1067
+ required: ["name"],
1068
+ additionalProperties: false, // Original schema does not allow additional properties
1069
+ };
1070
+ setupTestDirectory(TEST_DIR);
1071
+ const srcDir = join(TEST_DIR, "src-schemas");
1072
+ const srcInputPath = join(srcDir, "input.json");
1073
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1074
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1075
+ setupTestDirectoryFiles({
1076
+ testDirPath: TEST_DIR,
1077
+ srcInputPath,
1078
+ srcInput: sourceInputSchema,
1079
+ srcDatasetPath,
1080
+ srcDataset: {} as ObjectSchema,
1081
+ });
1082
+
1083
+ // Generate TypeScript file that allows additional properties
1084
+ const inputTsContentWithAdditionalProperties = `/**
1085
+ * This file was automatically generated by apify-schema-tools.
1086
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1087
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1088
+ */
1089
+
1090
+ export interface Input {
1091
+ name: string;
1092
+ age?: number;
1093
+ [key: string]: unknown;
1094
+ }
1095
+ `;
1096
+ writeFile(join(outputTsDir, "input.ts"), inputTsContentWithAdditionalProperties);
1097
+ let error: Error | null = null;
1098
+ let stderr = "";
1099
+ try {
1100
+ execSync(
1101
+ [
1102
+ `${EXEC_CMD} check`,
1103
+ "-i input",
1104
+ "-o ts-types",
1105
+ `--src-input ${srcInputPath}`,
1106
+ `--src-dataset ${srcDatasetPath}`,
1107
+ ].join(" "),
1108
+ {
1109
+ cwd: TEST_DIR,
1110
+ stdio: ["ignore", "pipe", "pipe"],
1111
+ encoding: "utf8",
1112
+ },
1113
+ );
1114
+ // biome-ignore lint/suspicious/noExplicitAny: for testing purposes
1115
+ } catch (e: any) {
1116
+ error = e;
1117
+ stderr = e.stderr ?? "";
1118
+ }
1119
+ expect(error).toBeTruthy();
1120
+ expect(stderr).toMatch(ADDITIONAL_PROPERTIES_ERROR_PATTERN);
1121
+ });
1122
+
1123
+ it("should pass when TypeScript descriptions differ but --ignore-descriptions is set", () => {
1124
+ const sourceInputSchema: ObjectSchema = {
1125
+ title: "Input Schema",
1126
+ description: "Original input description",
1127
+ type: "object",
1128
+ properties: {
1129
+ name: {
1130
+ type: "string",
1131
+ description: "Original name description",
1132
+ },
1133
+ age: {
1134
+ type: "integer",
1135
+ description: "Original age description",
1136
+ },
1137
+ },
1138
+ required: ["name"],
1139
+ additionalProperties: false,
1140
+ };
1141
+ setupTestDirectory(TEST_DIR);
1142
+ const srcDir = join(TEST_DIR, "src-schemas");
1143
+ const srcInputPath = join(srcDir, "input.json");
1144
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1145
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1146
+ setupTestDirectoryFiles({
1147
+ testDirPath: TEST_DIR,
1148
+ srcInputPath,
1149
+ srcInput: sourceInputSchema,
1150
+ srcDatasetPath,
1151
+ srcDataset: {} as ObjectSchema,
1152
+ });
1153
+
1154
+ // Generate TypeScript file with different descriptions
1155
+ const inputTsContentWithDifferentDescriptions = `/**
1156
+ * This file was automatically generated by apify-schema-tools.
1157
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1158
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1159
+ */
1160
+
1161
+ /**
1162
+ * Different input description
1163
+ */
1164
+ export interface Input {
1165
+ /**
1166
+ * Different name description
1167
+ */
1168
+ name: string;
1169
+ /**
1170
+ * Different age description
1171
+ */
1172
+ age?: number;
1173
+ }
1174
+ `;
1175
+ writeFile(join(outputTsDir, "input.ts"), inputTsContentWithDifferentDescriptions);
1176
+
1177
+ expect(() =>
1178
+ execSync(
1179
+ [
1180
+ `${EXEC_CMD} check`,
1181
+ "-i input",
1182
+ "-o ts-types",
1183
+ `--src-input ${srcInputPath}`,
1184
+ `--src-dataset ${srcDatasetPath}`,
1185
+ "--ignore-descriptions",
1186
+ ].join(" "),
1187
+ {
1188
+ cwd: TEST_DIR,
1189
+ stdio: "inherit",
1190
+ },
1191
+ ),
1192
+ ).not.toThrow();
1193
+ });
1194
+
1195
+ it("should fail when TypeScript descriptions differ and --ignore-descriptions is not set", () => {
1196
+ const sourceInputSchema: ObjectSchema = {
1197
+ title: "Input Schema",
1198
+ description: "Original input description",
1199
+ type: "object",
1200
+ properties: {
1201
+ name: {
1202
+ type: "string",
1203
+ description: "Original name description",
1204
+ },
1205
+ },
1206
+ required: ["name"],
1207
+ };
1208
+ setupTestDirectory(TEST_DIR);
1209
+ const srcDir = join(TEST_DIR, "src-schemas");
1210
+ const srcInputPath = join(srcDir, "input.json");
1211
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1212
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1213
+ setupTestDirectoryFiles({
1214
+ testDirPath: TEST_DIR,
1215
+ srcInputPath,
1216
+ srcInput: sourceInputSchema,
1217
+ srcDatasetPath,
1218
+ srcDataset: {} as ObjectSchema,
1219
+ });
1220
+
1221
+ // Generate TypeScript file with different descriptions
1222
+ const inputTsContentWithDifferentDescriptions = `/**
1223
+ * This file was automatically generated by apify-schema-tools.
1224
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1225
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1226
+ */
1227
+
1228
+ /**
1229
+ * Different input description
1230
+ */
1231
+ export interface Input {
1232
+ /**
1233
+ * Different name description
1234
+ */
1235
+ name: string;
1236
+ }
1237
+ `;
1238
+ writeFile(join(outputTsDir, "input.ts"), inputTsContentWithDifferentDescriptions);
1239
+
1240
+ expect(() =>
1241
+ execSync(
1242
+ [
1243
+ `${EXEC_CMD} check`,
1244
+ "-i input",
1245
+ "-o ts-types",
1246
+ `--src-input ${srcInputPath}`,
1247
+ `--src-dataset ${srcDatasetPath}`,
1248
+ // Note: --ignore-descriptions is NOT set
1249
+ ].join(" "),
1250
+ {
1251
+ cwd: TEST_DIR,
1252
+ stdio: "inherit",
1253
+ },
1254
+ ),
1255
+ ).toThrow();
1256
+ });
1257
+
1258
+ it("should pass when TypeScript files match schemas with additional merged schemas", () => {
1259
+ const baseInputSchema: ObjectSchema = {
1260
+ title: "Base Input Schema",
1261
+ type: "object",
1262
+ properties: {
1263
+ name: { type: "string" },
1264
+ },
1265
+ required: ["name"],
1266
+ additionalProperties: false,
1267
+ };
1268
+ const additionalSchema: ObjectSchema = {
1269
+ type: "object",
1270
+ properties: {
1271
+ age: { type: "integer" },
1272
+ email: { type: "string" },
1273
+ },
1274
+ required: ["email"],
1275
+ };
1276
+ setupTestDirectory(TEST_DIR);
1277
+ const srcDir = join(TEST_DIR, "src-schemas");
1278
+ const srcInputPath = join(srcDir, "input.json");
1279
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1280
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1281
+ const addDir = join(TEST_DIR, "add-schemas");
1282
+ const addInputPath = join(addDir, "add-input.json");
1283
+ setupTestDirectoryFiles({
1284
+ testDirPath: TEST_DIR,
1285
+ srcInputPath,
1286
+ srcInput: baseInputSchema,
1287
+ srcDatasetPath,
1288
+ srcDataset: {} as ObjectSchema,
1289
+ addDir,
1290
+ addInputPath,
1291
+ addInput: additionalSchema,
1292
+ });
1293
+
1294
+ // Generate TypeScript file that matches the merged schema
1295
+ const mergedInputTsContent = `/**
1296
+ * This file was automatically generated by apify-schema-tools.
1297
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1298
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1299
+ */
1300
+
1301
+ /**
1302
+ * Base Input Schema
1303
+ */
1304
+ export interface Input {
1305
+ name: string;
1306
+ age?: number;
1307
+ email: string;
1308
+ }
1309
+ `;
1310
+ writeFile(join(outputTsDir, "input.ts"), mergedInputTsContent);
1311
+
1312
+ expect(() =>
1313
+ execSync(
1314
+ [
1315
+ `${EXEC_CMD} check`,
1316
+ "-i input",
1317
+ "-o ts-types",
1318
+ `--src-input ${srcInputPath}`,
1319
+ `--src-dataset ${srcDatasetPath}`,
1320
+ `--add-input ${addInputPath}`,
1321
+ ].join(" "),
1322
+ {
1323
+ cwd: TEST_DIR,
1324
+ stdio: "inherit",
1325
+ },
1326
+ ),
1327
+ ).not.toThrow();
1328
+ });
1329
+
1330
+ it("should handle complex nested object structures in TypeScript files", () => {
1331
+ const sourceInputSchema: ObjectSchema = {
1332
+ title: "Complex Input Schema",
1333
+ type: "object",
1334
+ properties: {
1335
+ user: {
1336
+ type: "object",
1337
+ properties: {
1338
+ name: { type: "string" },
1339
+ contacts: {
1340
+ type: "object",
1341
+ properties: {
1342
+ email: { type: "string" },
1343
+ phone: { type: "string" },
1344
+ },
1345
+ required: ["email"],
1346
+ additionalProperties: { type: "boolean" },
1347
+ },
1348
+ },
1349
+ required: ["name"],
1350
+ },
1351
+ settings: {
1352
+ type: "array",
1353
+ items: {
1354
+ type: "object",
1355
+ properties: {
1356
+ key: { type: "string" },
1357
+ value: { type: "string" },
1358
+ },
1359
+ required: ["key"],
1360
+ additionalProperties: true,
1361
+ },
1362
+ },
1363
+ },
1364
+ required: ["user"],
1365
+ additionalProperties: false,
1366
+ };
1367
+ setupTestDirectory(TEST_DIR);
1368
+ const srcDir = join(TEST_DIR, "src-schemas");
1369
+ const srcInputPath = join(srcDir, "input.json");
1370
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1371
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1372
+ setupTestDirectoryFiles({
1373
+ testDirPath: TEST_DIR,
1374
+ srcInputPath,
1375
+ srcInput: sourceInputSchema,
1376
+ srcDatasetPath,
1377
+ srcDataset: {} as ObjectSchema,
1378
+ });
1379
+
1380
+ // Generate TypeScript file with correct nested structure
1381
+ const complexInputTsContent = `/**
1382
+ * This file was automatically generated by apify-schema-tools.
1383
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1384
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1385
+ */
1386
+
1387
+ /**
1388
+ * Complex Input Schema
1389
+ */
1390
+ export interface Input {
1391
+ user: {
1392
+ name: string;
1393
+ contacts?: {
1394
+ email: string;
1395
+ phone?: string;
1396
+ [key: string]: boolean;
1397
+ };
1398
+ [key: string]: unknown;
1399
+ };
1400
+ settings?: {
1401
+ key: string;
1402
+ value?: string;
1403
+ [key: string]: unknown;
1404
+ }[];
1405
+ }
1406
+ `;
1407
+ writeFile(join(outputTsDir, "input.ts"), complexInputTsContent);
1408
+
1409
+ expect(() =>
1410
+ execSync(
1411
+ [
1412
+ `${EXEC_CMD} check`,
1413
+ "-i input",
1414
+ "-o ts-types",
1415
+ `--src-input ${srcInputPath}`,
1416
+ `--src-dataset ${srcDatasetPath}`,
1417
+ ].join(" "),
1418
+ {
1419
+ cwd: TEST_DIR,
1420
+ stdio: "inherit",
1421
+ },
1422
+ ),
1423
+ ).not.toThrow();
1424
+ });
1425
+
1426
+ it("should handle enum types in TypeScript files", () => {
1427
+ const sourceInputSchema: ObjectSchema = {
1428
+ title: "Input Schema with Enums",
1429
+ type: "object",
1430
+ properties: {
1431
+ status: {
1432
+ type: "string",
1433
+ enum: ["active", "inactive", "pending"],
1434
+ },
1435
+ priorities: {
1436
+ type: "array",
1437
+ items: {
1438
+ type: "string",
1439
+ enum: ["high", "medium", "low"],
1440
+ },
1441
+ },
1442
+ },
1443
+ required: ["status"],
1444
+ additionalProperties: false,
1445
+ };
1446
+ setupTestDirectory(TEST_DIR);
1447
+ const srcDir = join(TEST_DIR, "src-schemas");
1448
+ const srcInputPath = join(srcDir, "input.json");
1449
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1450
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1451
+ setupTestDirectoryFiles({
1452
+ testDirPath: TEST_DIR,
1453
+ srcInputPath,
1454
+ srcInput: sourceInputSchema,
1455
+ srcDatasetPath,
1456
+ srcDataset: {} as ObjectSchema,
1457
+ });
1458
+
1459
+ // Generate TypeScript file with correct enum types
1460
+ const enumInputTsContent = `/**
1461
+ * This file was automatically generated by apify-schema-tools.
1462
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1463
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1464
+ */
1465
+
1466
+ /**
1467
+ * Input Schema with Enums
1468
+ */
1469
+ export interface Input {
1470
+ status: "active" | "inactive" | "pending";
1471
+ priorities?: ("high" | "medium" | "low")[];
1472
+ }
1473
+ `;
1474
+ writeFile(join(outputTsDir, "input.ts"), enumInputTsContent);
1475
+
1476
+ expect(() =>
1477
+ execSync(
1478
+ [
1479
+ `${EXEC_CMD} check`,
1480
+ "-i input",
1481
+ "-o ts-types",
1482
+ `--src-input ${srcInputPath}`,
1483
+ `--src-dataset ${srcDatasetPath}`,
1484
+ ].join(" "),
1485
+ {
1486
+ cwd: TEST_DIR,
1487
+ stdio: "inherit",
1488
+ },
1489
+ ),
1490
+ ).not.toThrow();
1491
+ });
1492
+
1493
+ it("should check both input and dataset TypeScript files when both are specified", () => {
1494
+ const sourceInputSchema: ObjectSchema = {
1495
+ title: "Input Schema",
1496
+ type: "object",
1497
+ properties: {
1498
+ inputField: { type: "string" },
1499
+ },
1500
+ required: ["inputField"],
1501
+ additionalProperties: false,
1502
+ };
1503
+ const sourceDatasetSchema: ObjectSchema = {
1504
+ title: "Dataset Item Schema",
1505
+ type: "object",
1506
+ properties: {
1507
+ dataField: { type: "number" },
1508
+ },
1509
+ required: ["dataField"],
1510
+ };
1511
+ setupTestDirectory(TEST_DIR);
1512
+ const srcDir = join(TEST_DIR, "src-schemas");
1513
+ const srcInputPath = join(srcDir, "input.json");
1514
+ const srcDatasetPath = join(srcDir, "dataset-item.json");
1515
+ const outputTsDir = join(TEST_DIR, "src", "generated");
1516
+ setupTestDirectoryFiles({
1517
+ testDirPath: TEST_DIR,
1518
+ srcInputPath,
1519
+ srcInput: sourceInputSchema,
1520
+ srcDatasetPath,
1521
+ srcDataset: sourceDatasetSchema,
1522
+ });
1523
+
1524
+ // Generate both TypeScript files correctly
1525
+ const inputTsContent = `/**
1526
+ * This file was automatically generated by apify-schema-tools.
1527
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1528
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1529
+ */
1530
+
1531
+ /**
1532
+ * Input Schema
1533
+ */
1534
+ export interface Input {
1535
+ inputField: string;
1536
+ }
1537
+ `;
1538
+ const datasetTsContent = `/**
1539
+ * This file was automatically generated by apify-schema-tools.
1540
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
1541
+ * and run apify-schema-tools' "sync" command to regenerate this file.
1542
+ */
1543
+
1544
+ /**
1545
+ * Dataset Item Schema
1546
+ */
1547
+ export interface DatasetItem {
1548
+ dataField: number;
1549
+ [key: string]: unknown;
1550
+ }
1551
+ `;
1552
+ writeFile(join(outputTsDir, "input.ts"), inputTsContent);
1553
+ writeFile(join(outputTsDir, "dataset.ts"), datasetTsContent);
1554
+
1555
+ expect(() =>
1556
+ execSync(
1557
+ [
1558
+ `${EXEC_CMD} check`,
1559
+ "-i input dataset",
1560
+ "-o ts-types",
1561
+ `--src-input ${srcInputPath}`,
1562
+ `--src-dataset ${srcDatasetPath}`,
1563
+ ].join(" "),
1564
+ {
1565
+ cwd: TEST_DIR,
1566
+ stdio: "inherit",
1567
+ },
1568
+ ),
1569
+ ).not.toThrow();
1570
+ });
1571
+ });