@eventvisor/core 0.0.2

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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/jest.config.js +4 -0
  4. package/lib/builder/buildProject.d.ts +21 -0
  5. package/lib/builder/buildProject.js +153 -0
  6. package/lib/builder/buildProject.js.map +1 -0
  7. package/lib/builder/hashes.d.ts +2 -0
  8. package/lib/builder/hashes.js +59 -0
  9. package/lib/builder/hashes.js.map +1 -0
  10. package/lib/builder/index.d.ts +1 -0
  11. package/lib/builder/index.js +18 -0
  12. package/lib/builder/index.js.map +1 -0
  13. package/lib/cli/cli.d.ts +26 -0
  14. package/lib/cli/cli.js +69 -0
  15. package/lib/cli/cli.js.map +1 -0
  16. package/lib/cli/index.d.ts +1 -0
  17. package/lib/cli/index.js +18 -0
  18. package/lib/cli/index.js.map +1 -0
  19. package/lib/cli/plugins.d.ts +4 -0
  20. package/lib/cli/plugins.js +12 -0
  21. package/lib/cli/plugins.js.map +1 -0
  22. package/lib/config/index.d.ts +2 -0
  23. package/lib/config/index.js +19 -0
  24. package/lib/config/index.js.map +1 -0
  25. package/lib/config/parsers.d.ts +15 -0
  26. package/lib/config/parsers.js +65 -0
  27. package/lib/config/parsers.js.map +1 -0
  28. package/lib/config/projectConfig.d.ts +42 -0
  29. package/lib/config/projectConfig.js +137 -0
  30. package/lib/config/projectConfig.js.map +1 -0
  31. package/lib/datasource/adapter.d.ts +16 -0
  32. package/lib/datasource/adapter.js +7 -0
  33. package/lib/datasource/adapter.js.map +1 -0
  34. package/lib/datasource/datasource.d.ts +48 -0
  35. package/lib/datasource/datasource.js +117 -0
  36. package/lib/datasource/datasource.js.map +1 -0
  37. package/lib/datasource/filesystemAdapter.d.ts +29 -0
  38. package/lib/datasource/filesystemAdapter.js +192 -0
  39. package/lib/datasource/filesystemAdapter.js.map +1 -0
  40. package/lib/datasource/index.d.ts +3 -0
  41. package/lib/datasource/index.js +20 -0
  42. package/lib/datasource/index.js.map +1 -0
  43. package/lib/dependencies.d.ts +11 -0
  44. package/lib/dependencies.js +3 -0
  45. package/lib/dependencies.js.map +1 -0
  46. package/lib/index.d.ts +4 -0
  47. package/lib/index.js +21 -0
  48. package/lib/index.js.map +1 -0
  49. package/lib/index.spec.d.ts +0 -0
  50. package/lib/index.spec.js +6 -0
  51. package/lib/index.spec.js.map +1 -0
  52. package/lib/init/index.d.ts +8 -0
  53. package/lib/init/index.js +90 -0
  54. package/lib/init/index.js.map +1 -0
  55. package/lib/linter/attributeSchema.d.ts +5 -0
  56. package/lib/linter/attributeSchema.js +55 -0
  57. package/lib/linter/attributeSchema.js.map +1 -0
  58. package/lib/linter/conditionsSchema.d.ts +303 -0
  59. package/lib/linter/conditionsSchema.js +106 -0
  60. package/lib/linter/conditionsSchema.js.map +1 -0
  61. package/lib/linter/destinationSchema.d.ts +5 -0
  62. package/lib/linter/destinationSchema.js +57 -0
  63. package/lib/linter/destinationSchema.js.map +1 -0
  64. package/lib/linter/effectSchema.d.ts +1257 -0
  65. package/lib/linter/effectSchema.js +77 -0
  66. package/lib/linter/effectSchema.js.map +1 -0
  67. package/lib/linter/eventSchema.d.ts +5 -0
  68. package/lib/linter/eventSchema.js +70 -0
  69. package/lib/linter/eventSchema.js.map +1 -0
  70. package/lib/linter/index.d.ts +1 -0
  71. package/lib/linter/index.js +18 -0
  72. package/lib/linter/index.js.map +1 -0
  73. package/lib/linter/jsonSchema.d.ts +25 -0
  74. package/lib/linter/jsonSchema.js +487 -0
  75. package/lib/linter/jsonSchema.js.map +1 -0
  76. package/lib/linter/jsonSchema.spec.d.ts +1 -0
  77. package/lib/linter/jsonSchema.spec.js +875 -0
  78. package/lib/linter/jsonSchema.spec.js.map +1 -0
  79. package/lib/linter/lintProject.d.ts +2 -0
  80. package/lib/linter/lintProject.js +141 -0
  81. package/lib/linter/lintProject.js.map +1 -0
  82. package/lib/linter/persistSchema.d.ts +609 -0
  83. package/lib/linter/persistSchema.js +52 -0
  84. package/lib/linter/persistSchema.js.map +1 -0
  85. package/lib/linter/printError.d.ts +9 -0
  86. package/lib/linter/printError.js +75 -0
  87. package/lib/linter/printError.js.map +1 -0
  88. package/lib/linter/sampleSchema.d.ts +331 -0
  89. package/lib/linter/sampleSchema.js +70 -0
  90. package/lib/linter/sampleSchema.js.map +1 -0
  91. package/lib/linter/sourceSchema.d.ts +11 -0
  92. package/lib/linter/sourceSchema.js +73 -0
  93. package/lib/linter/sourceSchema.js.map +1 -0
  94. package/lib/linter/tagsSchema.d.ts +3 -0
  95. package/lib/linter/tagsSchema.js +44 -0
  96. package/lib/linter/tagsSchema.js.map +1 -0
  97. package/lib/linter/testSchema.d.ts +5 -0
  98. package/lib/linter/testSchema.js +44 -0
  99. package/lib/linter/testSchema.js.map +1 -0
  100. package/lib/linter/transformsSchema.d.ts +29 -0
  101. package/lib/linter/transformsSchema.js +66 -0
  102. package/lib/linter/transformsSchema.js.map +1 -0
  103. package/lib/tester/createTestInstance.d.ts +16 -0
  104. package/lib/tester/createTestInstance.js +158 -0
  105. package/lib/tester/createTestInstance.js.map +1 -0
  106. package/lib/tester/executeTest.d.ts +24 -0
  107. package/lib/tester/executeTest.js +305 -0
  108. package/lib/tester/executeTest.js.map +1 -0
  109. package/lib/tester/index.d.ts +1 -0
  110. package/lib/tester/index.js +18 -0
  111. package/lib/tester/index.js.map +1 -0
  112. package/lib/tester/printTestResult.d.ts +10 -0
  113. package/lib/tester/printTestResult.js +80 -0
  114. package/lib/tester/printTestResult.js.map +1 -0
  115. package/lib/tester/testProject.d.ts +12 -0
  116. package/lib/tester/testProject.js +93 -0
  117. package/lib/tester/testProject.js.map +1 -0
  118. package/lib/utils/index.d.ts +1 -0
  119. package/lib/utils/index.js +18 -0
  120. package/lib/utils/index.js.map +1 -0
  121. package/lib/utils/prettyDuration.d.ts +1 -0
  122. package/lib/utils/prettyDuration.js +27 -0
  123. package/lib/utils/prettyDuration.js.map +1 -0
  124. package/package.json +42 -0
  125. package/src/builder/buildProject.ts +222 -0
  126. package/src/builder/hashes.ts +30 -0
  127. package/src/builder/index.ts +1 -0
  128. package/src/cli/cli.ts +110 -0
  129. package/src/cli/index.ts +1 -0
  130. package/src/cli/plugins.ts +13 -0
  131. package/src/config/index.ts +2 -0
  132. package/src/config/parsers.ts +40 -0
  133. package/src/config/projectConfig.ts +158 -0
  134. package/src/datasource/adapter.ts +23 -0
  135. package/src/datasource/datasource.ts +164 -0
  136. package/src/datasource/filesystemAdapter.ts +206 -0
  137. package/src/datasource/index.ts +3 -0
  138. package/src/dependencies.ts +13 -0
  139. package/src/index.spec.ts +5 -0
  140. package/src/index.ts +4 -0
  141. package/src/init/index.ts +65 -0
  142. package/src/linter/attributeSchema.ts +23 -0
  143. package/src/linter/conditionsSchema.ts +89 -0
  144. package/src/linter/destinationSchema.ts +25 -0
  145. package/src/linter/effectSchema.ts +49 -0
  146. package/src/linter/eventSchema.ts +40 -0
  147. package/src/linter/index.ts +1 -0
  148. package/src/linter/jsonSchema.spec.ts +934 -0
  149. package/src/linter/jsonSchema.ts +533 -0
  150. package/src/linter/lintProject.ts +182 -0
  151. package/src/linter/persistSchema.ts +21 -0
  152. package/src/linter/printError.ts +50 -0
  153. package/src/linter/sampleSchema.ts +45 -0
  154. package/src/linter/sourceSchema.ts +42 -0
  155. package/src/linter/tagsSchema.ts +12 -0
  156. package/src/linter/testSchema.ts +9 -0
  157. package/src/linter/transformsSchema.ts +35 -0
  158. package/src/tester/createTestInstance.ts +209 -0
  159. package/src/tester/executeTest.ts +436 -0
  160. package/src/tester/index.ts +1 -0
  161. package/src/tester/printTestResult.ts +60 -0
  162. package/src/tester/testProject.ts +129 -0
  163. package/src/utils/index.ts +1 -0
  164. package/src/utils/prettyDuration.ts +27 -0
  165. package/tsconfig.cjs.json +11 -0
@@ -0,0 +1,533 @@
1
+ import * as z from "zod";
2
+
3
+ export const JSONZodSchema = getJSONSchema();
4
+
5
+ /**
6
+ * Creates a Zod schema for a JSON Schema object based on the JSON Schema specification
7
+ */
8
+ export function getJSONSchema(): z.ZodObject<any> {
9
+ return z
10
+ .object({
11
+ // Basic metadata
12
+ description: z.string().optional(),
13
+
14
+ // General validation keywords
15
+ type: z
16
+ .enum(["object", "array", "string", "number", "integer", "boolean", "null"])
17
+ .optional(),
18
+ enum: z.array(createValueSchema()).optional(),
19
+ const: createValueSchema().optional(),
20
+
21
+ // Numeric validation keywords
22
+ maximum: z.number().optional(),
23
+ minimum: z.number().optional(),
24
+
25
+ // String validation keywords
26
+ maxLength: z.number().int().min(0).optional(),
27
+ minLength: z.number().int().min(0).optional(),
28
+ pattern: z.string().optional(),
29
+
30
+ // Array validation keywords
31
+ items: z.union([createJSONSchema(), z.array(createJSONSchema())]).optional(),
32
+ maxItems: z.number().int().min(0).optional(),
33
+ minItems: z.number().int().min(0).optional(),
34
+ uniqueItems: z.boolean().optional(),
35
+
36
+ // Object validation keywords
37
+ required: z.array(z.string()).optional(),
38
+ properties: z.record(z.string(), createJSONSchema()).optional(),
39
+
40
+ // Annotations
41
+ default: createValueSchema().optional(),
42
+ examples: z.array(createValueSchema()).optional(),
43
+
44
+ // project specific additional properties
45
+ defaultSource: z.string().optional(),
46
+ defaultSources: z.array(z.string()).optional(),
47
+ })
48
+ .refine((schema) => validateSchemaConstraints(schema), {
49
+ message: "Schema validation failed: schema does not conform to JSON Schema specification",
50
+ path: ["schema"],
51
+ })
52
+ .strict();
53
+ }
54
+
55
+ /**
56
+ * Creates a Zod schema for JSON Schema values
57
+ */
58
+ function createValueSchema(): z.ZodType<any> {
59
+ return z.union([
60
+ z.string(),
61
+ z.number(),
62
+ z.boolean(),
63
+ z.null(),
64
+ z.undefined(),
65
+ z.lazy(() => z.record(z.string(), createValueSchema())),
66
+ z.lazy(() => z.array(createValueSchema())),
67
+ ]);
68
+ }
69
+
70
+ /**
71
+ * Creates a Zod schema for JSON Schema objects
72
+ */
73
+ function createJSONSchema(): z.ZodType<any> {
74
+ return z.lazy(() => getJSONSchema());
75
+ }
76
+
77
+ /**
78
+ * Main validation function that validates a JSON Schema definition
79
+ */
80
+ export function validateJSONSchema(schema: any): JSONSchemaValidationResult {
81
+ if (schema === null || typeof schema !== "object") {
82
+ return {
83
+ valid: false,
84
+ errors: [
85
+ {
86
+ code: "INVALID_SCHEMA_TYPE",
87
+ message: "Schema must be an object",
88
+ path: [],
89
+ },
90
+ ],
91
+ };
92
+ }
93
+
94
+ const errors: JSONSchemaValidationError[] = [];
95
+
96
+ // Validate type constraints
97
+ validateTypeConstraints(schema, errors);
98
+
99
+ // Validate numeric constraints
100
+ validateNumericConstraints(schema, errors);
101
+
102
+ // Validate string constraints
103
+ validateStringConstraints(schema, errors);
104
+
105
+ // Validate array constraints
106
+ validateArrayConstraints(schema, errors);
107
+
108
+ // Validate object constraints
109
+ validateObjectConstraints(schema, errors);
110
+
111
+ return {
112
+ valid: errors.length === 0,
113
+ errors,
114
+ };
115
+ }
116
+
117
+ function validateTypeConstraints(
118
+ schema: any,
119
+ errors: JSONSchemaValidationError[],
120
+ path: string[] = [],
121
+ ) {
122
+ if (
123
+ schema.type &&
124
+ !["object", "array", "string", "number", "integer", "boolean", "null"].includes(schema.type)
125
+ ) {
126
+ errors.push({
127
+ path: [...path, "type"],
128
+ message: `Invalid type: ${schema.type}. Must be one of: object, array, string, number, integer, boolean, null`,
129
+ code: "INVALID_TYPE",
130
+ });
131
+ }
132
+
133
+ // Validate enum values match declared type
134
+ if (schema.enum && Array.isArray(schema.enum)) {
135
+ if (schema.type === "null") {
136
+ if (!schema.enum.includes(null)) {
137
+ errors.push({
138
+ path: [...path, "enum"],
139
+ message: "Enum for null type must include null value",
140
+ code: "INVALID_ENUM_FOR_NULL_TYPE",
141
+ });
142
+ }
143
+ } else if (schema.type === "boolean") {
144
+ if (!schema.enum.every((value: any) => value === true || value === false)) {
145
+ errors.push({
146
+ path: [...path, "enum"],
147
+ message: "Enum for boolean type can only contain true or false",
148
+ code: "INVALID_ENUM_FOR_BOOLEAN_TYPE",
149
+ });
150
+ }
151
+ } else if (schema.type) {
152
+ // Check each enum value matches the declared type
153
+ schema.enum.forEach((value: any, index: number) => {
154
+ if (!isValueOfType(value, schema.type)) {
155
+ errors.push({
156
+ path: [...path, "enum", index.toString()],
157
+ message: `Enum value ${JSON.stringify(value)} does not match type ${schema.type}`,
158
+ code: "TYPE_MISMATCH_IN_ENUM",
159
+ });
160
+ }
161
+ });
162
+ }
163
+ }
164
+
165
+ // Validate const value matches declared type
166
+ if (schema.const !== undefined && schema.type && !isValueOfType(schema.const, schema.type)) {
167
+ errors.push({
168
+ path: [...path, "const"],
169
+ message: `Const value ${JSON.stringify(schema.const)} does not match type ${schema.type}`,
170
+ code: "TYPE_MISMATCH_IN_CONST",
171
+ });
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Helper function to check if a value matches a declared type
177
+ */
178
+ function isValueOfType(value: any, declaredType: string): boolean {
179
+ switch (declaredType) {
180
+ case "string":
181
+ return typeof value === "string";
182
+ case "number":
183
+ return typeof value === "number" && !isNaN(value);
184
+ case "integer":
185
+ return typeof value === "number" && Number.isInteger(value) && !isNaN(value);
186
+ case "boolean":
187
+ return typeof value === "boolean";
188
+ case "null":
189
+ return value === null;
190
+ case "array":
191
+ return Array.isArray(value);
192
+ case "object":
193
+ return typeof value === "object" && value !== null && !Array.isArray(value);
194
+ default:
195
+ return true;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Validates numeric constraints in the schema definition
201
+ */
202
+ function validateNumericConstraints(
203
+ schema: any,
204
+ errors: JSONSchemaValidationError[],
205
+ path: string[] = [],
206
+ ): void {
207
+ // Check that numeric constraints are only used with numeric types
208
+ const numericKeywords = ["maximum", "minimum"];
209
+ const hasNumericConstraints = numericKeywords.some((key) => schema[key] !== undefined);
210
+
211
+ if (hasNumericConstraints && schema.type && !["number", "integer"].includes(schema.type)) {
212
+ errors.push({
213
+ path: [...path, "type"],
214
+ message: "Numeric constraints can only be used with number or integer types",
215
+ code: "NUMERIC_CONSTRAINTS_ON_NON_NUMERIC_TYPE",
216
+ });
217
+ }
218
+
219
+ // Validate maximum
220
+ if (schema.maximum !== undefined) {
221
+ if (typeof schema.maximum !== "number" || isNaN(schema.maximum)) {
222
+ errors.push({
223
+ path: [...path, "maximum"],
224
+ message: "maximum must be a valid number",
225
+ code: "INVALID_MAXIMUM",
226
+ });
227
+ }
228
+ }
229
+
230
+ // Validate minimum
231
+ if (schema.minimum !== undefined) {
232
+ if (typeof schema.minimum !== "number" || isNaN(schema.minimum)) {
233
+ errors.push({
234
+ path: [...path, "minimum"],
235
+ message: "minimum must be a valid number",
236
+ code: "INVALID_MINIMUM",
237
+ });
238
+ }
239
+ }
240
+
241
+ // Check for logical inconsistencies
242
+ if (schema.maximum !== undefined && schema.minimum !== undefined) {
243
+ if (schema.maximum < schema.minimum) {
244
+ errors.push({
245
+ path: [...path, "maximum"],
246
+ message: "maximum cannot be less than minimum",
247
+ code: "MAXIMUM_LESS_THAN_MINIMUM",
248
+ });
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Validates string constraints in the schema definition
255
+ */
256
+ function validateStringConstraints(
257
+ schema: any,
258
+ errors: JSONSchemaValidationError[],
259
+ path: string[] = [],
260
+ ): void {
261
+ // Check that string constraints are only used with string types
262
+ const stringKeywords = ["maxLength", "minLength", "pattern", "format"];
263
+ const hasStringConstraints = stringKeywords.some((key) => schema[key] !== undefined);
264
+
265
+ if (hasStringConstraints && schema.type && schema.type !== "string") {
266
+ errors.push({
267
+ path: [...path, "type"],
268
+ message: "String constraints can only be used with string type",
269
+ code: "STRING_CONSTRAINTS_ON_NON_STRING_TYPE",
270
+ });
271
+ }
272
+
273
+ // Validate maxLength/minLength
274
+ if (schema.maxLength !== undefined) {
275
+ if (
276
+ typeof schema.maxLength !== "number" ||
277
+ !Number.isInteger(schema.maxLength) ||
278
+ schema.maxLength < 0
279
+ ) {
280
+ errors.push({
281
+ path: [...path, "maxLength"],
282
+ message: "maxLength must be a non-negative integer",
283
+ code: "INVALID_MAX_LENGTH",
284
+ });
285
+ }
286
+ }
287
+
288
+ if (schema.minLength !== undefined) {
289
+ if (
290
+ typeof schema.minLength !== "number" ||
291
+ !Number.isInteger(schema.minLength) ||
292
+ schema.minLength < 0
293
+ ) {
294
+ errors.push({
295
+ path: [...path, "minLength"],
296
+ message: "minLength must be a non-negative integer",
297
+ code: "INVALID_MIN_LENGTH",
298
+ });
299
+ }
300
+ }
301
+
302
+ // Check for logical inconsistencies
303
+ if (schema.maxLength !== undefined && schema.minLength !== undefined) {
304
+ if (schema.maxLength < schema.minLength) {
305
+ errors.push({
306
+ path: [...path, "maxLength"],
307
+ message: "maxLength cannot be less than minLength",
308
+ code: "MAX_LENGTH_LESS_THAN_MIN_LENGTH",
309
+ });
310
+ }
311
+ }
312
+
313
+ // Validate pattern
314
+ if (schema.pattern !== undefined) {
315
+ if (typeof schema.pattern !== "string") {
316
+ errors.push({
317
+ path: [...path, "pattern"],
318
+ message: "pattern must be a string",
319
+ code: "INVALID_PATTERN_TYPE",
320
+ });
321
+ } else {
322
+ try {
323
+ new RegExp(schema.pattern);
324
+ // eslint-disable-next-line
325
+ } catch (e) {
326
+ errors.push({
327
+ path: [...path, "pattern"],
328
+ message: `Invalid regex pattern: ${schema.pattern}`,
329
+ code: "INVALID_REGEX_PATTERN",
330
+ });
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Validates array constraints in the schema definition
338
+ */
339
+ function validateArrayConstraints(
340
+ schema: any,
341
+ errors: JSONSchemaValidationError[],
342
+ path: string[] = [],
343
+ ): void {
344
+ // Check that array constraints are only used with array types
345
+ const arrayKeywords = ["items", "maxItems", "minItems", "uniqueItems"];
346
+ const hasArrayConstraints = arrayKeywords.some((key) => schema[key] !== undefined);
347
+
348
+ if (hasArrayConstraints && schema.type && schema.type !== "array") {
349
+ errors.push({
350
+ path: [...path, "type"],
351
+ message: "Array constraints can only be used with array type",
352
+ code: "ARRAY_CONSTRAINTS_ON_NON_ARRAY_TYPE",
353
+ });
354
+ }
355
+
356
+ // Validate maxItems/minItems
357
+ if (schema.maxItems !== undefined) {
358
+ if (!Number.isInteger(schema.maxItems) || schema.maxItems < 0) {
359
+ errors.push({
360
+ path: [...path, "maxItems"],
361
+ message: "maxItems must be a non-negative integer",
362
+ code: "INVALID_MAX_ITEMS",
363
+ });
364
+ }
365
+ }
366
+
367
+ if (schema.minItems !== undefined) {
368
+ if (!Number.isInteger(schema.minItems) || schema.minItems < 0) {
369
+ errors.push({
370
+ path: [...path, "minItems"],
371
+ message: "minItems must be a non-negative integer",
372
+ code: "INVALID_MIN_ITEMS",
373
+ });
374
+ }
375
+ }
376
+
377
+ // Check for logical inconsistencies
378
+ if (schema.maxItems !== undefined && schema.minItems !== undefined) {
379
+ if (schema.maxItems < schema.minItems) {
380
+ errors.push({
381
+ path: [...path, "maxItems"],
382
+ message: "maxItems cannot be less than minItems",
383
+ code: "MAX_ITEMS_LESS_THAN_MIN_ITEMS",
384
+ });
385
+ }
386
+ }
387
+
388
+ // Validate uniqueItems
389
+ if (schema.uniqueItems !== undefined && typeof schema.uniqueItems !== "boolean") {
390
+ errors.push({
391
+ path: [...path, "uniqueItems"],
392
+ message: "uniqueItems must be a boolean",
393
+ code: "INVALID_UNIQUE_ITEMS",
394
+ });
395
+ }
396
+
397
+ // Validate items
398
+ if (schema.items !== undefined) {
399
+ if (Array.isArray(schema.items)) {
400
+ // Array of schemas - validate each item
401
+ schema.items.forEach((item: any, index: number) => {
402
+ if (typeof item !== "object" || item === null) {
403
+ errors.push({
404
+ path: [...path, "items", index.toString()],
405
+ message: "Array items must be schema objects",
406
+ code: "INVALID_ITEMS_SCHEMA",
407
+ });
408
+ } else {
409
+ // Recursively validate the item schema
410
+ const itemResult = validateJSONSchema(item);
411
+ errors.push(
412
+ ...itemResult.errors.map((error) => ({
413
+ ...error,
414
+ path: [...path, "items", index.toString(), ...error.path],
415
+ })),
416
+ );
417
+ }
418
+ });
419
+ } else if (typeof schema.items !== "object" || schema.items === null) {
420
+ errors.push({
421
+ path: [...path, "items"],
422
+ message: "items must be a schema object or array of schemas",
423
+ code: "INVALID_ITEMS_TYPE",
424
+ });
425
+ } else {
426
+ // Single schema - recursively validate
427
+ const itemsResult = validateJSONSchema(schema.items);
428
+ errors.push(
429
+ ...itemsResult.errors.map((error) => ({
430
+ ...error,
431
+ path: [...path, "items", ...error.path],
432
+ })),
433
+ );
434
+ }
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Validates object constraints in the schema definition
440
+ */
441
+ function validateObjectConstraints(
442
+ schema: any,
443
+ errors: JSONSchemaValidationError[],
444
+ path: string[] = [],
445
+ ): void {
446
+ // Check that object constraints are only used with object types
447
+ const objectKeywords = ["required", "properties"];
448
+ const hasObjectConstraints = objectKeywords.some((key) => schema[key] !== undefined);
449
+
450
+ if (hasObjectConstraints && schema.type && schema.type !== "object") {
451
+ errors.push({
452
+ path: [...path, "type"],
453
+ message: "Object constraints can only be used with object type",
454
+ code: "OBJECT_CONSTRAINTS_ON_NON_OBJECT_TYPE",
455
+ });
456
+ }
457
+
458
+ // Validate required
459
+ if (schema.required !== undefined) {
460
+ if (!Array.isArray(schema.required)) {
461
+ errors.push({
462
+ path: [...path, "required"],
463
+ message: "required must be an array",
464
+ code: "INVALID_REQUIRED_TYPE",
465
+ });
466
+ } else {
467
+ schema.required.forEach((item: any, index: number) => {
468
+ if (typeof item !== "string") {
469
+ errors.push({
470
+ path: [...path, "required", index.toString()],
471
+ message: "Required items must be strings",
472
+ code: "INVALID_REQUIRED_ITEM",
473
+ });
474
+ }
475
+ });
476
+ }
477
+ }
478
+
479
+ // Validate properties
480
+ if (schema.properties !== undefined) {
481
+ if (typeof schema.properties !== "object" || schema.properties === null) {
482
+ errors.push({
483
+ path: [...path, "properties"],
484
+ message: "properties must be an object",
485
+ code: "INVALID_PROPERTIES_TYPE",
486
+ });
487
+ } else {
488
+ Object.entries(schema.properties).forEach(([key, value]) => {
489
+ if (typeof value !== "object" || value === null) {
490
+ errors.push({
491
+ path: [...path, "properties", key],
492
+ message: "Property values must be schema objects",
493
+ code: "INVALID_PROPERTY_SCHEMA",
494
+ });
495
+ } else {
496
+ // Recursively validate the property schema
497
+ const propertyResult = validateJSONSchema(value);
498
+ errors.push(
499
+ ...propertyResult.errors.map((error) => ({
500
+ ...error,
501
+ path: [...path, "properties", key, ...error.path],
502
+ })),
503
+ );
504
+ }
505
+ });
506
+ }
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Validates all schema constraints
512
+ */
513
+ function validateSchemaConstraints(schema: any): boolean {
514
+ const result = validateJSONSchema(schema);
515
+ return result.valid;
516
+ }
517
+
518
+ /**
519
+ * Validation result interface
520
+ */
521
+ export interface JSONSchemaValidationResult {
522
+ valid: boolean;
523
+ errors: JSONSchemaValidationError[];
524
+ }
525
+
526
+ /**
527
+ * Validation error interface
528
+ */
529
+ export interface JSONSchemaValidationError {
530
+ path: (string | number)[];
531
+ message: string;
532
+ code: string;
533
+ }
@@ -0,0 +1,182 @@
1
+ import { Plugin } from "../cli";
2
+ import { Dependencies } from "../dependencies";
3
+
4
+ import { getAttributeSchema } from "./attributeSchema";
5
+ import { getDestinationSchema } from "./destinationSchema";
6
+ import { getEffectSchema } from "./effectSchema";
7
+ import { getEventSchema } from "./eventSchema";
8
+ import { printError } from "./printError";
9
+
10
+ async function lintProject(
11
+ options: Dependencies,
12
+ filterOptions: {
13
+ keyPattern?: string;
14
+ entityType?: string;
15
+ } = {},
16
+ ): Promise<boolean> {
17
+ const { projectConfig, datasource } = options;
18
+ const { keyPattern, entityType } = filterOptions;
19
+
20
+ let hasErrors = false;
21
+
22
+ // attributes
23
+ console.log("\nLinting attributes...");
24
+
25
+ const attributes = await datasource.listAttributes();
26
+ const attributeSchema = getAttributeSchema(options);
27
+
28
+ for (const attributeKey of attributes) {
29
+ if (entityType && entityType !== "attribute") {
30
+ continue;
31
+ }
32
+
33
+ if (keyPattern && !attributeKey.includes(keyPattern)) {
34
+ continue;
35
+ }
36
+
37
+ const attributeContent = await datasource.readAttribute(attributeKey);
38
+ const result = attributeSchema.safeParse(attributeContent);
39
+
40
+ if (!result.success) {
41
+ printError({
42
+ entityType: "attribute",
43
+ entityKey: attributeKey,
44
+ error: result.error,
45
+ projectConfig,
46
+ });
47
+ hasErrors = true;
48
+ }
49
+ }
50
+
51
+ // events
52
+ console.log("\nLinting events...");
53
+
54
+ const events = await datasource.listEvents();
55
+ const eventSchema = getEventSchema(options);
56
+
57
+ for (const eventKey of events) {
58
+ if (entityType && entityType !== "event") {
59
+ continue;
60
+ }
61
+
62
+ if (keyPattern && !eventKey.includes(keyPattern)) {
63
+ continue;
64
+ }
65
+
66
+ const eventContent = await datasource.readEvent(eventKey);
67
+ const result = eventSchema.safeParse(eventContent);
68
+
69
+ if (!result.success) {
70
+ printError({
71
+ entityType: "event",
72
+ entityKey: eventKey,
73
+ error: result.error,
74
+ projectConfig,
75
+ });
76
+
77
+ hasErrors = true;
78
+ }
79
+ }
80
+
81
+ // destinations
82
+ console.log("\nLinting destinations...");
83
+
84
+ const destinations = await datasource.listDestinations();
85
+ const destinationSchema = getDestinationSchema(options);
86
+
87
+ for (const destinationKey of destinations) {
88
+ if (entityType && entityType !== "destination") {
89
+ continue;
90
+ }
91
+
92
+ if (keyPattern && !destinationKey.includes(keyPattern)) {
93
+ continue;
94
+ }
95
+
96
+ const destinationContent = await datasource.readDestination(destinationKey);
97
+ const result = destinationSchema.safeParse(destinationContent);
98
+
99
+ if (!result.success) {
100
+ printError({
101
+ entityType: "destination",
102
+ entityKey: destinationKey,
103
+ error: result.error,
104
+ projectConfig,
105
+ });
106
+ hasErrors = true;
107
+ }
108
+ }
109
+
110
+ // effects
111
+ console.log("\nLinting effects...");
112
+
113
+ const effects = await datasource.listEffects();
114
+ const effectSchema = getEffectSchema(options);
115
+
116
+ for (const effectKey of effects) {
117
+ if (entityType && entityType !== "effect") {
118
+ continue;
119
+ }
120
+
121
+ if (keyPattern && !effectKey.includes(keyPattern)) {
122
+ continue;
123
+ }
124
+
125
+ const effectContent = await datasource.readEffect(effectKey);
126
+ const result = effectSchema.safeParse(effectContent);
127
+
128
+ if (!result.success) {
129
+ printError({
130
+ entityType: "effect",
131
+ entityKey: effectKey,
132
+ error: result.error,
133
+ projectConfig,
134
+ });
135
+ hasErrors = true;
136
+ }
137
+ }
138
+
139
+ // @TODO: tests
140
+
141
+ if (hasErrors) {
142
+ return false;
143
+ }
144
+
145
+ return true;
146
+ }
147
+
148
+ export const lintPlugin: Plugin = {
149
+ command: "lint",
150
+ handler: async function (options) {
151
+ const { rootDirectoryPath, projectConfig, datasource, parsed } = options;
152
+
153
+ const successfullyLinted = await lintProject(
154
+ {
155
+ rootDirectoryPath,
156
+ projectConfig,
157
+ datasource,
158
+ options: parsed,
159
+ },
160
+ {
161
+ keyPattern: parsed.keyPattern,
162
+ entityType: parsed.entityType,
163
+ },
164
+ );
165
+
166
+ return successfullyLinted;
167
+ },
168
+ examples: [
169
+ {
170
+ command: "lint",
171
+ description: "lint all entities",
172
+ },
173
+ // {
174
+ // command: "lint --entityType=event",
175
+ // description: "lint only events",
176
+ // },
177
+ // {
178
+ // command: 'lint --keyPattern="abc"',
179
+ // description: `lint only entities with keys containing "abc"`,
180
+ // },
181
+ ],
182
+ };