@eventcatalog/linter 1.0.1 → 1.0.3
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.
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1343 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +1326 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.d.mts +8339 -0
- package/dist/index.d.ts +8339 -0
- package/dist/index.js +1408 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1326 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/cli/index.ts
|
|
27
|
+
var import_commander = require("commander");
|
|
28
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
29
|
+
var import_ora = __toESM(require("ora"));
|
|
30
|
+
var import_path3 = __toESM(require("path"));
|
|
31
|
+
|
|
32
|
+
// src/scanner/index.ts
|
|
33
|
+
var import_fast_glob = __toESM(require("fast-glob"));
|
|
34
|
+
var import_path = __toESM(require("path"));
|
|
35
|
+
var RESOURCE_PATTERNS = {
|
|
36
|
+
domain: [
|
|
37
|
+
"domains/*/index.{md,mdx}",
|
|
38
|
+
"domains/*/versioned/*/index.{md,mdx}",
|
|
39
|
+
"domains/*/subdomains/*/index.{md,mdx}",
|
|
40
|
+
"domains/*/subdomains/*/versioned/*/index.{md,mdx}"
|
|
41
|
+
],
|
|
42
|
+
service: [
|
|
43
|
+
"domains/*/services/*/index.{md,mdx}",
|
|
44
|
+
"domains/*/services/*/versioned/*/index.{md,mdx}",
|
|
45
|
+
"domains/*/subdomains/*/services/*/index.{md,mdx}",
|
|
46
|
+
"domains/*/subdomains/*/services/*/versioned/*/index.{md,mdx}",
|
|
47
|
+
"services/*/index.{md,mdx}",
|
|
48
|
+
"services/*/versioned/*/index.{md,mdx}"
|
|
49
|
+
],
|
|
50
|
+
event: ["**/events/*/index.{md,mdx}", "**/events/*/versioned/*/index.{md,mdx}"],
|
|
51
|
+
command: ["**/commands/*/index.{md,mdx}", "**/commands/*/versioned/*/index.{md,mdx}"],
|
|
52
|
+
query: ["**/queries/*/index.{md,mdx}", "**/queries/*/versioned/*/index.{md,mdx}"],
|
|
53
|
+
channel: ["**/channels/*/index.{md,mdx}", "**/channels/*/versioned/*/index.{md,mdx}"],
|
|
54
|
+
flow: ["**/flows/*/index.{md,mdx}", "**/flows/*/versioned/*/index.{md,mdx}"],
|
|
55
|
+
entity: ["**/entities/*/index.{md,mdx}", "**/entities/*/versioned/*/index.{md,mdx}"],
|
|
56
|
+
user: ["users/*.{md,mdx}"],
|
|
57
|
+
team: ["teams/*.{md,mdx}"],
|
|
58
|
+
dataStore: ["**/containers/*/index.{md,mdx}", "**/containers/*/versioned/*/index.{md,mdx}"]
|
|
59
|
+
};
|
|
60
|
+
var extractResourceInfo = (filePath, resourceType) => {
|
|
61
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
62
|
+
const relativePath = normalizedPath.split("/");
|
|
63
|
+
if (resourceType === "user" || resourceType === "team") {
|
|
64
|
+
const filename = import_path.default.basename(filePath, import_path.default.extname(filePath));
|
|
65
|
+
return { id: filename };
|
|
66
|
+
}
|
|
67
|
+
const resourceTypePattern = `${resourceType}s`;
|
|
68
|
+
const resourceTypeIndex = relativePath.findIndex((part) => part === resourceTypePattern);
|
|
69
|
+
if (resourceTypeIndex === -1) {
|
|
70
|
+
const parts2 = relativePath.slice(1, -1);
|
|
71
|
+
return { id: parts2[parts2.length - 1] };
|
|
72
|
+
}
|
|
73
|
+
const parts = relativePath.slice(resourceTypeIndex + 1, -1);
|
|
74
|
+
if (parts.length === 0) {
|
|
75
|
+
return { id: "unknown" };
|
|
76
|
+
}
|
|
77
|
+
if (parts.length >= 3 && parts[parts.length - 2] === "versioned") {
|
|
78
|
+
const version = parts[parts.length - 1];
|
|
79
|
+
const resourceId = parts.slice(0, -2).join("/");
|
|
80
|
+
return { id: resourceId, version };
|
|
81
|
+
}
|
|
82
|
+
if (resourceType === "domain" && parts.length >= 2) {
|
|
83
|
+
const versionedIndex = parts.findIndex((part) => part === "versioned");
|
|
84
|
+
if (versionedIndex !== -1 && versionedIndex < parts.length - 1) {
|
|
85
|
+
const version = parts[versionedIndex + 1];
|
|
86
|
+
const resourceId = parts.slice(0, versionedIndex).join("/");
|
|
87
|
+
return { id: resourceId, version };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (parts.length === 1) {
|
|
91
|
+
return { id: parts[0] };
|
|
92
|
+
}
|
|
93
|
+
return { id: parts.join("/") };
|
|
94
|
+
};
|
|
95
|
+
var scanCatalogFiles = async (rootDir) => {
|
|
96
|
+
const files = [];
|
|
97
|
+
for (const [resourceType, patterns] of Object.entries(RESOURCE_PATTERNS)) {
|
|
98
|
+
const foundFiles = await (0, import_fast_glob.default)(patterns, {
|
|
99
|
+
cwd: rootDir,
|
|
100
|
+
absolute: true,
|
|
101
|
+
onlyFiles: true,
|
|
102
|
+
followSymbolicLinks: false
|
|
103
|
+
});
|
|
104
|
+
for (const filePath of foundFiles) {
|
|
105
|
+
const relativePath = import_path.default.relative(rootDir, filePath);
|
|
106
|
+
const { id, version } = extractResourceInfo(relativePath, resourceType);
|
|
107
|
+
files.push({
|
|
108
|
+
path: filePath,
|
|
109
|
+
relativePath,
|
|
110
|
+
resourceType,
|
|
111
|
+
resourceId: id,
|
|
112
|
+
version
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return files;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/parser/index.ts
|
|
120
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
121
|
+
var import_gray_matter = __toESM(require("gray-matter"));
|
|
122
|
+
var parseFrontmatter = async (file) => {
|
|
123
|
+
try {
|
|
124
|
+
const fileContent = await import_promises.default.readFile(file.path, "utf-8");
|
|
125
|
+
const { data, content } = (0, import_gray_matter.default)(fileContent);
|
|
126
|
+
return {
|
|
127
|
+
file,
|
|
128
|
+
frontmatter: data,
|
|
129
|
+
content,
|
|
130
|
+
raw: fileContent
|
|
131
|
+
};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return {
|
|
134
|
+
file,
|
|
135
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var parseAllFiles = async (files) => {
|
|
140
|
+
const results = await Promise.all(files.map(parseFrontmatter));
|
|
141
|
+
const parsed = [];
|
|
142
|
+
const errors = [];
|
|
143
|
+
for (const result of results) {
|
|
144
|
+
if ("error" in result) {
|
|
145
|
+
errors.push(result);
|
|
146
|
+
} else {
|
|
147
|
+
parsed.push(result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { parsed, errors };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/validators/schema-validator.ts
|
|
154
|
+
var import_zod11 = require("zod");
|
|
155
|
+
|
|
156
|
+
// src/schemas/common.ts
|
|
157
|
+
var import_zod = require("zod");
|
|
158
|
+
var badgeSchema = import_zod.z.object({
|
|
159
|
+
content: import_zod.z.string(),
|
|
160
|
+
backgroundColor: import_zod.z.string(),
|
|
161
|
+
textColor: import_zod.z.string(),
|
|
162
|
+
icon: import_zod.z.string().optional()
|
|
163
|
+
});
|
|
164
|
+
var ownerReferenceSchema = import_zod.z.union([
|
|
165
|
+
// The ID of the user or team
|
|
166
|
+
import_zod.z.string(),
|
|
167
|
+
// The full object with the ID and collection (keep compatibility with `reference`)
|
|
168
|
+
import_zod.z.object({
|
|
169
|
+
id: import_zod.z.string(),
|
|
170
|
+
collection: import_zod.z.enum(["users", "teams"])
|
|
171
|
+
})
|
|
172
|
+
]).transform(
|
|
173
|
+
// This transformation is needed to keep compatibility with `reference`.
|
|
174
|
+
// The utilities `getTeams` and `getUsers` rely on this transformation.
|
|
175
|
+
(lookup) => ({ id: typeof lookup === "string" ? lookup : lookup.id })
|
|
176
|
+
);
|
|
177
|
+
var specificationSchema = import_zod.z.union([
|
|
178
|
+
import_zod.z.object({
|
|
179
|
+
openapiPath: import_zod.z.string().optional(),
|
|
180
|
+
asyncapiPath: import_zod.z.string().optional()
|
|
181
|
+
}),
|
|
182
|
+
import_zod.z.array(
|
|
183
|
+
import_zod.z.object({
|
|
184
|
+
type: import_zod.z.enum(["openapi", "asyncapi"]),
|
|
185
|
+
path: import_zod.z.string(),
|
|
186
|
+
name: import_zod.z.string().optional()
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
]);
|
|
190
|
+
var repositorySchema = import_zod.z.object({
|
|
191
|
+
language: import_zod.z.string().optional(),
|
|
192
|
+
url: import_zod.z.string().optional()
|
|
193
|
+
});
|
|
194
|
+
var draftSchema = import_zod.z.union([
|
|
195
|
+
import_zod.z.boolean(),
|
|
196
|
+
import_zod.z.object({
|
|
197
|
+
title: import_zod.z.string().optional(),
|
|
198
|
+
message: import_zod.z.string()
|
|
199
|
+
})
|
|
200
|
+
]);
|
|
201
|
+
var deprecatedSchema = import_zod.z.union([
|
|
202
|
+
import_zod.z.object({
|
|
203
|
+
message: import_zod.z.string().optional(),
|
|
204
|
+
date: import_zod.z.union([import_zod.z.string(), import_zod.z.date()]).optional()
|
|
205
|
+
}),
|
|
206
|
+
import_zod.z.boolean().optional()
|
|
207
|
+
]);
|
|
208
|
+
var pointerSchema = import_zod.z.object({
|
|
209
|
+
id: import_zod.z.string(),
|
|
210
|
+
version: import_zod.z.string().optional().default("latest")
|
|
211
|
+
});
|
|
212
|
+
var resourcePointerSchema = import_zod.z.object({
|
|
213
|
+
id: import_zod.z.string(),
|
|
214
|
+
version: import_zod.z.string().optional().default("latest"),
|
|
215
|
+
type: import_zod.z.enum(["service", "event", "command", "query", "flow", "channel", "domain", "user", "team"])
|
|
216
|
+
});
|
|
217
|
+
var channelPointerSchema = import_zod.z.object({
|
|
218
|
+
parameters: import_zod.z.record(import_zod.z.string()).optional()
|
|
219
|
+
}).merge(pointerSchema);
|
|
220
|
+
var semverSchema = import_zod.z.string().refine((version) => {
|
|
221
|
+
if (version === "latest") return true;
|
|
222
|
+
if (version.includes(".x")) {
|
|
223
|
+
const xPattern = /^\d+(\.\d+)*\.x$/;
|
|
224
|
+
return xPattern.test(version);
|
|
225
|
+
}
|
|
226
|
+
if (version.startsWith("^") || version.startsWith("~")) {
|
|
227
|
+
const rangeVersion = version.substring(1);
|
|
228
|
+
const semverRegex2 = /^\d+\.\d+\.\d+(-[\w\d-.]+)?(\+[\w\d-.]+)?$/;
|
|
229
|
+
return semverRegex2.test(rangeVersion);
|
|
230
|
+
}
|
|
231
|
+
const semverRegex = /^\d+\.\d+\.\d+(-[\w\d-.]+)?(\+[\w\d-.]+)?$/;
|
|
232
|
+
return semverRegex.test(version);
|
|
233
|
+
}, "Invalid semantic version format");
|
|
234
|
+
var sidebarSchema = import_zod.z.object({
|
|
235
|
+
label: import_zod.z.string().optional(),
|
|
236
|
+
badge: import_zod.z.string().optional()
|
|
237
|
+
}).optional();
|
|
238
|
+
var stylesSchema = import_zod.z.object({
|
|
239
|
+
icon: import_zod.z.string().optional(),
|
|
240
|
+
node: import_zod.z.object({
|
|
241
|
+
color: import_zod.z.string().optional(),
|
|
242
|
+
label: import_zod.z.string().optional()
|
|
243
|
+
}).optional()
|
|
244
|
+
}).optional();
|
|
245
|
+
var resourceGroupSchema = import_zod.z.array(
|
|
246
|
+
import_zod.z.object({
|
|
247
|
+
id: import_zod.z.string().optional(),
|
|
248
|
+
title: import_zod.z.string().optional(),
|
|
249
|
+
items: import_zod.z.array(resourcePointerSchema),
|
|
250
|
+
limit: import_zod.z.number().optional().default(10),
|
|
251
|
+
sidebar: import_zod.z.boolean().optional().default(true)
|
|
252
|
+
})
|
|
253
|
+
).optional();
|
|
254
|
+
var catalogMetadataSchema = import_zod.z.object({
|
|
255
|
+
path: import_zod.z.string(),
|
|
256
|
+
filePath: import_zod.z.string(),
|
|
257
|
+
astroContentFilePath: import_zod.z.string(),
|
|
258
|
+
publicPath: import_zod.z.string(),
|
|
259
|
+
type: import_zod.z.string()
|
|
260
|
+
}).optional();
|
|
261
|
+
var baseSchema = import_zod.z.object({
|
|
262
|
+
id: import_zod.z.string(),
|
|
263
|
+
name: import_zod.z.string(),
|
|
264
|
+
summary: import_zod.z.string().optional(),
|
|
265
|
+
version: semverSchema,
|
|
266
|
+
draft: import_zod.z.union([import_zod.z.boolean(), import_zod.z.object({ title: import_zod.z.string().optional(), message: import_zod.z.string() })]).optional(),
|
|
267
|
+
badges: import_zod.z.array(badgeSchema).optional(),
|
|
268
|
+
owners: import_zod.z.array(ownerReferenceSchema).optional(),
|
|
269
|
+
schemaPath: import_zod.z.string().optional(),
|
|
270
|
+
sidebar: sidebarSchema,
|
|
271
|
+
repository: repositorySchema.optional(),
|
|
272
|
+
specifications: specificationSchema.optional(),
|
|
273
|
+
hidden: import_zod.z.boolean().optional(),
|
|
274
|
+
resourceGroups: resourceGroupSchema,
|
|
275
|
+
styles: stylesSchema,
|
|
276
|
+
deprecated: deprecatedSchema.optional(),
|
|
277
|
+
visualiser: import_zod.z.boolean().optional(),
|
|
278
|
+
versions: import_zod.z.array(import_zod.z.string()).optional(),
|
|
279
|
+
latestVersion: import_zod.z.string().optional(),
|
|
280
|
+
catalog: catalogMetadataSchema
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// src/schemas/domain.ts
|
|
284
|
+
var import_zod2 = require("zod");
|
|
285
|
+
var domainSchema = import_zod2.z.object({
|
|
286
|
+
services: import_zod2.z.array(pointerSchema).optional(),
|
|
287
|
+
domains: import_zod2.z.array(pointerSchema).optional(),
|
|
288
|
+
entities: import_zod2.z.array(pointerSchema).optional()
|
|
289
|
+
}).merge(baseSchema);
|
|
290
|
+
|
|
291
|
+
// src/schemas/service.ts
|
|
292
|
+
var import_zod3 = require("zod");
|
|
293
|
+
var serviceSchema = import_zod3.z.object({
|
|
294
|
+
sends: import_zod3.z.array(pointerSchema).optional(),
|
|
295
|
+
receives: import_zod3.z.array(pointerSchema).optional(),
|
|
296
|
+
entities: import_zod3.z.array(pointerSchema).optional(),
|
|
297
|
+
writesTo: import_zod3.z.array(pointerSchema).optional(),
|
|
298
|
+
readsFrom: import_zod3.z.array(pointerSchema).optional()
|
|
299
|
+
}).merge(baseSchema);
|
|
300
|
+
|
|
301
|
+
// src/schemas/message.ts
|
|
302
|
+
var import_zod4 = require("zod");
|
|
303
|
+
var baseMessageSchema = import_zod4.z.object({
|
|
304
|
+
producers: import_zod4.z.array(import_zod4.z.any()).optional(),
|
|
305
|
+
// reference('services')
|
|
306
|
+
consumers: import_zod4.z.array(import_zod4.z.any()).optional(),
|
|
307
|
+
// reference('services')
|
|
308
|
+
channels: import_zod4.z.array(channelPointerSchema).optional(),
|
|
309
|
+
messageChannels: import_zod4.z.array(import_zod4.z.any()).optional()
|
|
310
|
+
// reference('channels')
|
|
311
|
+
}).merge(baseSchema);
|
|
312
|
+
var eventSchema = baseMessageSchema;
|
|
313
|
+
var commandSchema = baseMessageSchema;
|
|
314
|
+
var querySchema = baseMessageSchema;
|
|
315
|
+
|
|
316
|
+
// src/schemas/channel.ts
|
|
317
|
+
var import_zod5 = require("zod");
|
|
318
|
+
var parameterSchema = import_zod5.z.object({
|
|
319
|
+
enum: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
320
|
+
default: import_zod5.z.string().optional(),
|
|
321
|
+
examples: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
322
|
+
description: import_zod5.z.string().optional()
|
|
323
|
+
});
|
|
324
|
+
var channelSchema = import_zod5.z.object({
|
|
325
|
+
address: import_zod5.z.string().optional(),
|
|
326
|
+
protocols: import_zod5.z.array(import_zod5.z.string()).optional(),
|
|
327
|
+
parameters: import_zod5.z.record(parameterSchema).optional(),
|
|
328
|
+
messages: import_zod5.z.array(import_zod5.z.object({ collection: import_zod5.z.string(), name: import_zod5.z.string(), ...pointerSchema.shape })).optional()
|
|
329
|
+
}).merge(baseSchema);
|
|
330
|
+
|
|
331
|
+
// src/schemas/flow.ts
|
|
332
|
+
var import_zod6 = require("zod");
|
|
333
|
+
var flowStep = import_zod6.z.union([
|
|
334
|
+
import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number()]),
|
|
335
|
+
import_zod6.z.object({
|
|
336
|
+
id: import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number()]),
|
|
337
|
+
label: import_zod6.z.string().optional()
|
|
338
|
+
}).optional()
|
|
339
|
+
]).optional();
|
|
340
|
+
var flowStepSchema = import_zod6.z.object({
|
|
341
|
+
id: import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number()]),
|
|
342
|
+
type: import_zod6.z.enum(["node", "message", "user", "actor"]).optional(),
|
|
343
|
+
title: import_zod6.z.string(),
|
|
344
|
+
summary: import_zod6.z.string().optional(),
|
|
345
|
+
message: pointerSchema.optional(),
|
|
346
|
+
service: pointerSchema.optional(),
|
|
347
|
+
flow: pointerSchema.optional(),
|
|
348
|
+
actor: import_zod6.z.object({
|
|
349
|
+
name: import_zod6.z.string()
|
|
350
|
+
}).optional(),
|
|
351
|
+
custom: import_zod6.z.object({
|
|
352
|
+
title: import_zod6.z.string(),
|
|
353
|
+
icon: import_zod6.z.string().optional(),
|
|
354
|
+
type: import_zod6.z.string().optional(),
|
|
355
|
+
summary: import_zod6.z.string().optional(),
|
|
356
|
+
url: import_zod6.z.string().url().optional(),
|
|
357
|
+
color: import_zod6.z.string().optional(),
|
|
358
|
+
properties: import_zod6.z.record(import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number()])).optional(),
|
|
359
|
+
height: import_zod6.z.number().optional(),
|
|
360
|
+
menu: import_zod6.z.array(
|
|
361
|
+
import_zod6.z.object({
|
|
362
|
+
label: import_zod6.z.string(),
|
|
363
|
+
url: import_zod6.z.string().url().optional()
|
|
364
|
+
})
|
|
365
|
+
).optional()
|
|
366
|
+
}).optional(),
|
|
367
|
+
externalSystem: import_zod6.z.object({
|
|
368
|
+
name: import_zod6.z.string(),
|
|
369
|
+
summary: import_zod6.z.string().optional(),
|
|
370
|
+
url: import_zod6.z.string().url().optional()
|
|
371
|
+
}).optional(),
|
|
372
|
+
next_step: flowStep,
|
|
373
|
+
next_steps: import_zod6.z.array(flowStep).optional()
|
|
374
|
+
}).refine((data) => {
|
|
375
|
+
if (data.next_step && data.next_steps) return false;
|
|
376
|
+
const typesUsed = [data.message, data.service, data.flow, data.actor, data.custom].filter((v) => v).length;
|
|
377
|
+
return typesUsed === 0 || typesUsed === 1;
|
|
378
|
+
});
|
|
379
|
+
var flowSchema = import_zod6.z.object({
|
|
380
|
+
steps: import_zod6.z.array(flowStepSchema)
|
|
381
|
+
}).merge(baseSchema);
|
|
382
|
+
|
|
383
|
+
// src/schemas/entity.ts
|
|
384
|
+
var import_zod7 = require("zod");
|
|
385
|
+
var propertySchema = import_zod7.z.object({
|
|
386
|
+
name: import_zod7.z.string(),
|
|
387
|
+
type: import_zod7.z.string(),
|
|
388
|
+
required: import_zod7.z.boolean().optional(),
|
|
389
|
+
description: import_zod7.z.string().optional(),
|
|
390
|
+
references: import_zod7.z.string().optional(),
|
|
391
|
+
referencesIdentifier: import_zod7.z.string().optional(),
|
|
392
|
+
relationType: import_zod7.z.string().optional()
|
|
393
|
+
});
|
|
394
|
+
var entitySchema = import_zod7.z.object({
|
|
395
|
+
aggregateRoot: import_zod7.z.boolean().optional(),
|
|
396
|
+
identifier: import_zod7.z.string().optional(),
|
|
397
|
+
properties: import_zod7.z.array(propertySchema).optional(),
|
|
398
|
+
services: import_zod7.z.array(import_zod7.z.any()).optional(),
|
|
399
|
+
// reference('services')
|
|
400
|
+
domains: import_zod7.z.array(import_zod7.z.any()).optional()
|
|
401
|
+
// reference('domains')
|
|
402
|
+
}).merge(baseSchema);
|
|
403
|
+
|
|
404
|
+
// src/schemas/user.ts
|
|
405
|
+
var import_zod8 = require("zod");
|
|
406
|
+
var userSchema = import_zod8.z.object({
|
|
407
|
+
id: import_zod8.z.string(),
|
|
408
|
+
name: import_zod8.z.string(),
|
|
409
|
+
avatarUrl: import_zod8.z.string().optional(),
|
|
410
|
+
role: import_zod8.z.string().optional(),
|
|
411
|
+
hidden: import_zod8.z.boolean().optional(),
|
|
412
|
+
email: import_zod8.z.string().email().optional(),
|
|
413
|
+
slackDirectMessageUrl: import_zod8.z.string().optional(),
|
|
414
|
+
msTeamsDirectMessageUrl: import_zod8.z.string().optional(),
|
|
415
|
+
ownedDomains: import_zod8.z.array(import_zod8.z.any()).optional(),
|
|
416
|
+
// reference('domains')
|
|
417
|
+
ownedServices: import_zod8.z.array(import_zod8.z.any()).optional(),
|
|
418
|
+
// reference('services')
|
|
419
|
+
ownedEvents: import_zod8.z.array(import_zod8.z.any()).optional(),
|
|
420
|
+
// reference('events')
|
|
421
|
+
ownedCommands: import_zod8.z.array(import_zod8.z.any()).optional(),
|
|
422
|
+
// reference('commands')
|
|
423
|
+
ownedQueries: import_zod8.z.array(import_zod8.z.any()).optional(),
|
|
424
|
+
// reference('queries')
|
|
425
|
+
associatedTeams: import_zod8.z.array(import_zod8.z.any()).optional()
|
|
426
|
+
// reference('teams')
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// src/schemas/team.ts
|
|
430
|
+
var import_zod9 = require("zod");
|
|
431
|
+
var teamSchema = import_zod9.z.object({
|
|
432
|
+
id: import_zod9.z.string(),
|
|
433
|
+
name: import_zod9.z.string(),
|
|
434
|
+
summary: import_zod9.z.string().optional(),
|
|
435
|
+
email: import_zod9.z.string().email().optional(),
|
|
436
|
+
hidden: import_zod9.z.boolean().optional(),
|
|
437
|
+
slackDirectMessageUrl: import_zod9.z.string().optional(),
|
|
438
|
+
msTeamsDirectMessageUrl: import_zod9.z.string().optional(),
|
|
439
|
+
members: import_zod9.z.array(import_zod9.z.any()).optional(),
|
|
440
|
+
// reference('users')
|
|
441
|
+
ownedCommands: import_zod9.z.array(import_zod9.z.any()).optional(),
|
|
442
|
+
// reference('commands')
|
|
443
|
+
ownedQueries: import_zod9.z.array(import_zod9.z.any()).optional(),
|
|
444
|
+
// reference('queries')
|
|
445
|
+
ownedDomains: import_zod9.z.array(import_zod9.z.any()).optional(),
|
|
446
|
+
// reference('domains')
|
|
447
|
+
ownedServices: import_zod9.z.array(import_zod9.z.any()).optional(),
|
|
448
|
+
// reference('services')
|
|
449
|
+
ownedEvents: import_zod9.z.array(import_zod9.z.any()).optional()
|
|
450
|
+
// reference('events')
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// src/schemas/data-store.ts
|
|
454
|
+
var import_zod10 = require("zod");
|
|
455
|
+
var dataStoreSchema = import_zod10.z.object({
|
|
456
|
+
container_type: import_zod10.z.enum(["database", "cache", "objectStore", "searchIndex", "dataWarehouse", "dataLake", "externalSaaS"]),
|
|
457
|
+
technology: import_zod10.z.string().optional(),
|
|
458
|
+
authoritative: import_zod10.z.boolean().optional(),
|
|
459
|
+
access_mode: import_zod10.z.enum(["read", "write", "readWrite", "appendOnly"]),
|
|
460
|
+
classification: import_zod10.z.enum(["public", "internal", "confidential", "regulated"]),
|
|
461
|
+
residency: import_zod10.z.string().optional(),
|
|
462
|
+
retention: import_zod10.z.string().optional()
|
|
463
|
+
}).merge(baseSchema);
|
|
464
|
+
|
|
465
|
+
// src/schemas/index.ts
|
|
466
|
+
var schemas = {
|
|
467
|
+
domain: domainSchema,
|
|
468
|
+
service: serviceSchema,
|
|
469
|
+
event: eventSchema,
|
|
470
|
+
command: commandSchema,
|
|
471
|
+
query: querySchema,
|
|
472
|
+
channel: channelSchema,
|
|
473
|
+
flow: flowSchema,
|
|
474
|
+
entity: entitySchema,
|
|
475
|
+
user: userSchema,
|
|
476
|
+
team: teamSchema,
|
|
477
|
+
dataStore: dataStoreSchema
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// src/validators/schema-validator.ts
|
|
481
|
+
var validateSchema = (parsedFile) => {
|
|
482
|
+
const { file, frontmatter } = parsedFile;
|
|
483
|
+
const schema = schemas[file.resourceType];
|
|
484
|
+
const errors = [];
|
|
485
|
+
try {
|
|
486
|
+
schema.parse(frontmatter);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (error instanceof import_zod11.z.ZodError) {
|
|
489
|
+
for (const issue of error.issues) {
|
|
490
|
+
const field = issue.path.join(".");
|
|
491
|
+
let message = issue.message;
|
|
492
|
+
if (issue.code === "invalid_type") {
|
|
493
|
+
message = `Expected ${issue.expected}, but received ${issue.received}`;
|
|
494
|
+
}
|
|
495
|
+
let rule = "schema/required-fields";
|
|
496
|
+
if (issue.code === "invalid_type") {
|
|
497
|
+
rule = "schema/valid-type";
|
|
498
|
+
} else if (issue.code === "invalid_string" && issue.validation === "email") {
|
|
499
|
+
rule = "schema/valid-email";
|
|
500
|
+
} else if (field && field.includes("version")) {
|
|
501
|
+
rule = "schema/valid-semver";
|
|
502
|
+
}
|
|
503
|
+
errors.push({
|
|
504
|
+
type: "schema",
|
|
505
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
506
|
+
field: field || void 0,
|
|
507
|
+
message: field ? `${field}: ${message}` : message,
|
|
508
|
+
file: file.relativePath,
|
|
509
|
+
rule
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
} else {
|
|
513
|
+
errors.push({
|
|
514
|
+
type: "schema",
|
|
515
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
516
|
+
message: error instanceof Error ? error.message : String(error),
|
|
517
|
+
file: file.relativePath,
|
|
518
|
+
rule: "schema/validation-error"
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return errors;
|
|
523
|
+
};
|
|
524
|
+
var validateAllSchemas = (parsedFiles) => {
|
|
525
|
+
const errors = [];
|
|
526
|
+
for (const parsedFile of parsedFiles) {
|
|
527
|
+
errors.push(...validateSchema(parsedFile));
|
|
528
|
+
}
|
|
529
|
+
return errors;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// src/validators/reference-validator.ts
|
|
533
|
+
var import_semver = __toESM(require("semver"));
|
|
534
|
+
var buildResourceIndex = (parsedFiles, dependencies) => {
|
|
535
|
+
const index = {};
|
|
536
|
+
for (const parsedFile of parsedFiles) {
|
|
537
|
+
const { file, frontmatter } = parsedFile;
|
|
538
|
+
const { resourceType } = file;
|
|
539
|
+
let resourceId = file.resourceId;
|
|
540
|
+
if (frontmatter.id && typeof frontmatter.id === "string") {
|
|
541
|
+
resourceId = frontmatter.id;
|
|
542
|
+
}
|
|
543
|
+
if (!index[resourceType]) {
|
|
544
|
+
index[resourceType] = {};
|
|
545
|
+
}
|
|
546
|
+
if (!index[resourceType][resourceId]) {
|
|
547
|
+
index[resourceType][resourceId] = /* @__PURE__ */ new Set();
|
|
548
|
+
}
|
|
549
|
+
if (frontmatter.version && typeof frontmatter.version === "string") {
|
|
550
|
+
index[resourceType][resourceId].add(frontmatter.version);
|
|
551
|
+
} else {
|
|
552
|
+
index[resourceType][resourceId].add("latest");
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (dependencies) {
|
|
556
|
+
for (const [resourceType, entries] of Object.entries(dependencies)) {
|
|
557
|
+
if (!index[resourceType]) {
|
|
558
|
+
index[resourceType] = {};
|
|
559
|
+
}
|
|
560
|
+
for (const entry of entries) {
|
|
561
|
+
if (!index[resourceType][entry.id]) {
|
|
562
|
+
index[resourceType][entry.id] = /* @__PURE__ */ new Set();
|
|
563
|
+
}
|
|
564
|
+
index[resourceType][entry.id].add(entry.version || "latest");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return index;
|
|
569
|
+
};
|
|
570
|
+
var checkResourceExists = (ref, resourceType, index) => {
|
|
571
|
+
const resourceVersions = index[resourceType]?.[ref.id];
|
|
572
|
+
if (!resourceVersions || resourceVersions.size === 0) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
if (!ref.version) {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
const refVersion = ref.version === "latest" ? ref.version : ref.version;
|
|
579
|
+
const availableVersions = Array.from(resourceVersions);
|
|
580
|
+
if (refVersion === "latest") {
|
|
581
|
+
return availableVersions.includes("latest") || availableVersions.length > 0;
|
|
582
|
+
}
|
|
583
|
+
if (availableVersions.includes(refVersion)) {
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const semverVersions = availableVersions.filter((v) => v !== "latest" && import_semver.default.valid(v));
|
|
588
|
+
for (const availableVersion of semverVersions) {
|
|
589
|
+
if (import_semver.default.satisfies(availableVersion, refVersion)) {
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (refVersion.includes(".x")) {
|
|
594
|
+
const pattern = refVersion.replace(/\.x/g, "");
|
|
595
|
+
for (const availableVersion of semverVersions) {
|
|
596
|
+
if (availableVersion.startsWith(pattern)) {
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
} catch (error) {
|
|
603
|
+
return availableVersions.includes(refVersion);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
var extractReferences = (parsedFile) => {
|
|
607
|
+
const { file, frontmatter } = parsedFile;
|
|
608
|
+
const references = [];
|
|
609
|
+
if (file.resourceType === "domain") {
|
|
610
|
+
if (frontmatter.services && Array.isArray(frontmatter.services)) {
|
|
611
|
+
frontmatter.services.forEach((ref) => {
|
|
612
|
+
references.push({ ref, possibleTypes: ["service"], field: "services" });
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
if (frontmatter.domains && Array.isArray(frontmatter.domains)) {
|
|
616
|
+
frontmatter.domains.forEach((ref) => {
|
|
617
|
+
references.push({ ref, possibleTypes: ["domain"], field: "domains" });
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (frontmatter.entities && Array.isArray(frontmatter.entities)) {
|
|
621
|
+
frontmatter.entities.forEach((ref) => {
|
|
622
|
+
references.push({ ref, possibleTypes: ["entity"], field: "entities" });
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (file.resourceType === "service") {
|
|
627
|
+
if (frontmatter.sends && Array.isArray(frontmatter.sends)) {
|
|
628
|
+
frontmatter.sends.forEach((ref) => {
|
|
629
|
+
references.push({ ref, possibleTypes: ["event", "command", "query"], field: "sends" });
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (frontmatter.receives && Array.isArray(frontmatter.receives)) {
|
|
633
|
+
frontmatter.receives.forEach((ref) => {
|
|
634
|
+
references.push({ ref, possibleTypes: ["event", "command", "query"], field: "receives" });
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
if (frontmatter.entities && Array.isArray(frontmatter.entities)) {
|
|
638
|
+
frontmatter.entities.forEach((ref) => {
|
|
639
|
+
references.push({ ref, possibleTypes: ["entity"], field: "entities" });
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
if (frontmatter.writesTo && Array.isArray(frontmatter.writesTo)) {
|
|
643
|
+
frontmatter.writesTo.forEach((ref) => {
|
|
644
|
+
references.push({ ref, possibleTypes: ["dataStore"], field: "writesTo" });
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
if (frontmatter.readsFrom && Array.isArray(frontmatter.readsFrom)) {
|
|
648
|
+
frontmatter.readsFrom.forEach((ref) => {
|
|
649
|
+
references.push({ ref, possibleTypes: ["dataStore"], field: "readsFrom" });
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (file.resourceType === "flow" && frontmatter.steps && Array.isArray(frontmatter.steps)) {
|
|
654
|
+
frontmatter.steps.forEach((step, index) => {
|
|
655
|
+
if (step.message) {
|
|
656
|
+
references.push({
|
|
657
|
+
ref: step.message,
|
|
658
|
+
possibleTypes: ["event", "command", "query"],
|
|
659
|
+
field: `steps[${index}].message`
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
if (step.service) {
|
|
663
|
+
references.push({ ref: step.service, possibleTypes: ["service"], field: `steps[${index}].service` });
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
if (file.resourceType === "entity" && frontmatter.properties && Array.isArray(frontmatter.properties)) {
|
|
668
|
+
frontmatter.properties.forEach((prop, index) => {
|
|
669
|
+
if (prop.references) {
|
|
670
|
+
references.push({
|
|
671
|
+
ref: { id: prop.references },
|
|
672
|
+
possibleTypes: ["entity"],
|
|
673
|
+
field: `properties[${index}].references`
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (frontmatter.owners && Array.isArray(frontmatter.owners)) {
|
|
679
|
+
frontmatter.owners.forEach((owner) => {
|
|
680
|
+
references.push({ ref: { id: owner }, possibleTypes: ["user", "team"], field: "owners" });
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
if (file.resourceType === "team" && frontmatter.members && Array.isArray(frontmatter.members)) {
|
|
684
|
+
frontmatter.members.forEach((member) => {
|
|
685
|
+
references.push({ ref: { id: member }, possibleTypes: ["user"], field: "members" });
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return references;
|
|
689
|
+
};
|
|
690
|
+
var extractChannelReferences = (parsedFile) => {
|
|
691
|
+
const { file, frontmatter } = parsedFile;
|
|
692
|
+
const references = [];
|
|
693
|
+
if (file.resourceType !== "service" && file.resourceType !== "domain") {
|
|
694
|
+
return references;
|
|
695
|
+
}
|
|
696
|
+
const extractFromPointers = (pointers, parentField) => {
|
|
697
|
+
if (!Array.isArray(pointers)) return;
|
|
698
|
+
pointers.forEach((pointer, idx) => {
|
|
699
|
+
if (pointer.to && Array.isArray(pointer.to)) {
|
|
700
|
+
pointer.to.forEach((channelRef, cIdx) => {
|
|
701
|
+
if (channelRef && channelRef.id) {
|
|
702
|
+
references.push({
|
|
703
|
+
ref: { id: channelRef.id, version: channelRef.version },
|
|
704
|
+
possibleTypes: ["channel"],
|
|
705
|
+
field: `${parentField}[${idx}].to[${cIdx}]`
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
if (pointer.from && Array.isArray(pointer.from)) {
|
|
711
|
+
pointer.from.forEach((channelRef, cIdx) => {
|
|
712
|
+
if (channelRef && channelRef.id) {
|
|
713
|
+
references.push({
|
|
714
|
+
ref: { id: channelRef.id, version: channelRef.version },
|
|
715
|
+
possibleTypes: ["channel"],
|
|
716
|
+
field: `${parentField}[${idx}].from[${cIdx}]`
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
if (frontmatter.sends && Array.isArray(frontmatter.sends)) {
|
|
724
|
+
extractFromPointers(frontmatter.sends, "sends");
|
|
725
|
+
}
|
|
726
|
+
if (frontmatter.receives && Array.isArray(frontmatter.receives)) {
|
|
727
|
+
extractFromPointers(frontmatter.receives, "receives");
|
|
728
|
+
}
|
|
729
|
+
return references;
|
|
730
|
+
};
|
|
731
|
+
var validateReferences = (parsedFiles, dependencies) => {
|
|
732
|
+
const index = buildResourceIndex(parsedFiles, dependencies);
|
|
733
|
+
const errors = [];
|
|
734
|
+
for (const parsedFile of parsedFiles) {
|
|
735
|
+
const references = extractReferences(parsedFile);
|
|
736
|
+
for (const { ref, possibleTypes, field } of references) {
|
|
737
|
+
const found = possibleTypes.some((type) => checkResourceExists(ref, type, index));
|
|
738
|
+
if (!found) {
|
|
739
|
+
const versionStr = ref.version ? ` (version: ${ref.version})` : "";
|
|
740
|
+
const typeStr = possibleTypes.length === 1 ? possibleTypes[0] : possibleTypes.join("/");
|
|
741
|
+
let rule = "refs/resource-exists";
|
|
742
|
+
if (field === "owners") {
|
|
743
|
+
rule = "refs/owner-exists";
|
|
744
|
+
} else if (field === "writesTo" || field === "readsFrom") {
|
|
745
|
+
rule = "refs/container-exists";
|
|
746
|
+
} else if (ref.version) {
|
|
747
|
+
rule = "refs/valid-version-range";
|
|
748
|
+
}
|
|
749
|
+
errors.push({
|
|
750
|
+
type: "reference",
|
|
751
|
+
resource: `${parsedFile.file.resourceType}/${parsedFile.file.resourceId}`,
|
|
752
|
+
field,
|
|
753
|
+
message: `Referenced ${typeStr} "${ref.id}"${versionStr} does not exist`,
|
|
754
|
+
file: parsedFile.file.relativePath,
|
|
755
|
+
rule
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const channelRefs = extractChannelReferences(parsedFile);
|
|
760
|
+
for (const { ref, possibleTypes, field } of channelRefs) {
|
|
761
|
+
const found = possibleTypes.some((type) => checkResourceExists(ref, type, index));
|
|
762
|
+
if (!found) {
|
|
763
|
+
const versionStr = ref.version ? ` (version: ${ref.version})` : "";
|
|
764
|
+
errors.push({
|
|
765
|
+
type: "reference",
|
|
766
|
+
resource: `${parsedFile.file.resourceType}/${parsedFile.file.resourceId}`,
|
|
767
|
+
field,
|
|
768
|
+
message: `Referenced channel "${ref.id}"${versionStr} does not exist`,
|
|
769
|
+
file: parsedFile.file.relativePath,
|
|
770
|
+
rule: "refs/channel-exists"
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return errors;
|
|
776
|
+
};
|
|
777
|
+
var validateOrphanMessages = (parsedFiles, dependencies) => {
|
|
778
|
+
const errors = [];
|
|
779
|
+
const messageTypes = ["event", "command", "query"];
|
|
780
|
+
const messageFiles = parsedFiles.filter((pf) => messageTypes.includes(pf.file.resourceType));
|
|
781
|
+
if (messageFiles.length === 0) return errors;
|
|
782
|
+
const producedMessages = /* @__PURE__ */ new Set();
|
|
783
|
+
const consumedMessages = /* @__PURE__ */ new Set();
|
|
784
|
+
for (const parsedFile of parsedFiles) {
|
|
785
|
+
const { file, frontmatter } = parsedFile;
|
|
786
|
+
if (file.resourceType === "service" || file.resourceType === "domain") {
|
|
787
|
+
if (frontmatter.sends && Array.isArray(frontmatter.sends)) {
|
|
788
|
+
frontmatter.sends.forEach((ref) => {
|
|
789
|
+
if (ref && ref.id) producedMessages.add(ref.id);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (frontmatter.receives && Array.isArray(frontmatter.receives)) {
|
|
793
|
+
frontmatter.receives.forEach((ref) => {
|
|
794
|
+
if (ref && ref.id) consumedMessages.add(ref.id);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (messageTypes.includes(file.resourceType)) {
|
|
799
|
+
if (frontmatter.producers && Array.isArray(frontmatter.producers) && frontmatter.producers.length > 0) {
|
|
800
|
+
const msgId = frontmatter.id || file.resourceId;
|
|
801
|
+
producedMessages.add(msgId);
|
|
802
|
+
}
|
|
803
|
+
if (frontmatter.consumers && Array.isArray(frontmatter.consumers) && frontmatter.consumers.length > 0) {
|
|
804
|
+
const msgId = frontmatter.id || file.resourceId;
|
|
805
|
+
consumedMessages.add(msgId);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (dependencies) {
|
|
810
|
+
for (const [type, entries] of Object.entries(dependencies)) {
|
|
811
|
+
if (messageTypes.includes(type)) {
|
|
812
|
+
entries.forEach((entry) => {
|
|
813
|
+
producedMessages.add(entry.id);
|
|
814
|
+
consumedMessages.add(entry.id);
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
for (const parsedFile of messageFiles) {
|
|
820
|
+
const msgId = parsedFile.frontmatter.id || parsedFile.file.resourceId;
|
|
821
|
+
const isProduced = producedMessages.has(msgId);
|
|
822
|
+
const isConsumed = consumedMessages.has(msgId);
|
|
823
|
+
if (!isProduced && !isConsumed) {
|
|
824
|
+
errors.push({
|
|
825
|
+
type: "reference",
|
|
826
|
+
resource: `${parsedFile.file.resourceType}/${parsedFile.file.resourceId}`,
|
|
827
|
+
field: "id",
|
|
828
|
+
message: `${parsedFile.file.resourceType} "${msgId}" has no producer and no consumer`,
|
|
829
|
+
file: parsedFile.file.relativePath,
|
|
830
|
+
rule: "refs/orphan-messages"
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return errors;
|
|
835
|
+
};
|
|
836
|
+
var validateDeprecatedReferences = (parsedFiles) => {
|
|
837
|
+
const errors = [];
|
|
838
|
+
const deprecatedIndex = {};
|
|
839
|
+
for (const parsedFile of parsedFiles) {
|
|
840
|
+
const { file, frontmatter } = parsedFile;
|
|
841
|
+
if (frontmatter.deprecated && frontmatter.deprecated !== false) {
|
|
842
|
+
const resourceId = frontmatter.id || file.resourceId;
|
|
843
|
+
const key = `${file.resourceType}:${resourceId}`;
|
|
844
|
+
if (!deprecatedIndex[key]) {
|
|
845
|
+
deprecatedIndex[key] = /* @__PURE__ */ new Set();
|
|
846
|
+
}
|
|
847
|
+
if (frontmatter.version && typeof frontmatter.version === "string") {
|
|
848
|
+
deprecatedIndex[key].add(frontmatter.version);
|
|
849
|
+
} else {
|
|
850
|
+
deprecatedIndex[key].add("*");
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (Object.keys(deprecatedIndex).length === 0) return errors;
|
|
855
|
+
const isDeprecated = (type, id, version) => {
|
|
856
|
+
const key = `${type}:${id}`;
|
|
857
|
+
const versions = deprecatedIndex[key];
|
|
858
|
+
if (!versions) return false;
|
|
859
|
+
if (versions.has("*")) return true;
|
|
860
|
+
if (version && versions.has(version)) return true;
|
|
861
|
+
if (!version || version === "latest") return versions.size > 0;
|
|
862
|
+
return false;
|
|
863
|
+
};
|
|
864
|
+
for (const parsedFile of parsedFiles) {
|
|
865
|
+
const references = extractReferences(parsedFile);
|
|
866
|
+
for (const { ref, possibleTypes, field } of references) {
|
|
867
|
+
if (field === "owners" || field === "members") continue;
|
|
868
|
+
for (const type of possibleTypes) {
|
|
869
|
+
if (isDeprecated(type, ref.id, ref.version)) {
|
|
870
|
+
const versionStr = ref.version ? ` (version: ${ref.version})` : "";
|
|
871
|
+
errors.push({
|
|
872
|
+
type: "reference",
|
|
873
|
+
resource: `${parsedFile.file.resourceType}/${parsedFile.file.resourceId}`,
|
|
874
|
+
field,
|
|
875
|
+
message: `Referenced ${type} "${ref.id}"${versionStr} is deprecated`,
|
|
876
|
+
file: parsedFile.file.relativePath,
|
|
877
|
+
rule: "versions/no-deprecated-references"
|
|
878
|
+
});
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return errors;
|
|
885
|
+
};
|
|
886
|
+
var validateDuplicateResourceIds = (parsedFiles) => {
|
|
887
|
+
const errors = [];
|
|
888
|
+
const seen = {};
|
|
889
|
+
for (const parsedFile of parsedFiles) {
|
|
890
|
+
const { file, frontmatter } = parsedFile;
|
|
891
|
+
const resourceId = frontmatter.id || file.resourceId;
|
|
892
|
+
const version = frontmatter.version || "latest";
|
|
893
|
+
const key = `${file.resourceType}:${resourceId}:${version}`;
|
|
894
|
+
if (seen[key]) {
|
|
895
|
+
errors.push({
|
|
896
|
+
type: "reference",
|
|
897
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
898
|
+
field: "id",
|
|
899
|
+
message: `Duplicate ${file.resourceType} "${resourceId}" (version: ${version}) \u2014 also defined in ${seen[key]}`,
|
|
900
|
+
file: file.relativePath,
|
|
901
|
+
rule: "structure/duplicate-resource-ids"
|
|
902
|
+
});
|
|
903
|
+
} else {
|
|
904
|
+
seen[key] = file.relativePath;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return errors;
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
// src/validators/best-practices-validator.ts
|
|
911
|
+
var validateBestPractices = (parsedFiles) => {
|
|
912
|
+
const errors = [];
|
|
913
|
+
for (const parsedFile of parsedFiles) {
|
|
914
|
+
const { file, frontmatter, content } = parsedFile;
|
|
915
|
+
if (!frontmatter.summary || typeof frontmatter.summary === "string" && frontmatter.summary.trim() === "") {
|
|
916
|
+
errors.push({
|
|
917
|
+
type: "schema",
|
|
918
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
919
|
+
field: "summary",
|
|
920
|
+
message: "Summary is required for better documentation",
|
|
921
|
+
file: file.relativePath,
|
|
922
|
+
severity: "error",
|
|
923
|
+
rule: "best-practices/summary-required"
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
if (file.resourceType !== "user" && file.resourceType !== "team" && (!frontmatter.owners || !Array.isArray(frontmatter.owners) || frontmatter.owners.length === 0)) {
|
|
927
|
+
errors.push({
|
|
928
|
+
type: "schema",
|
|
929
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
930
|
+
field: "owners",
|
|
931
|
+
message: "At least one owner is required",
|
|
932
|
+
file: file.relativePath,
|
|
933
|
+
severity: "error",
|
|
934
|
+
rule: "best-practices/owner-required"
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
if (!content || content.trim() === "") {
|
|
938
|
+
errors.push({
|
|
939
|
+
type: "schema",
|
|
940
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
941
|
+
field: "description",
|
|
942
|
+
message: "Resource should have a markdown description (body content) beyond just frontmatter",
|
|
943
|
+
file: file.relativePath,
|
|
944
|
+
severity: "warning",
|
|
945
|
+
rule: "best-practices/description-required"
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
if ((file.resourceType === "event" || file.resourceType === "command" || file.resourceType === "query") && !frontmatter.schemaPath) {
|
|
949
|
+
errors.push({
|
|
950
|
+
type: "schema",
|
|
951
|
+
resource: `${file.resourceType}/${file.resourceId}`,
|
|
952
|
+
field: "schemaPath",
|
|
953
|
+
message: `${file.resourceType} should have a schemaPath defined for consumers to understand the contract`,
|
|
954
|
+
file: file.relativePath,
|
|
955
|
+
severity: "warning",
|
|
956
|
+
rule: "best-practices/schema-required"
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return errors;
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// src/validators/index.ts
|
|
964
|
+
var validateCatalog = (parsedFiles, dependencies) => {
|
|
965
|
+
const schemaErrors = validateAllSchemas(parsedFiles);
|
|
966
|
+
const referenceErrors = validateReferences(parsedFiles, dependencies);
|
|
967
|
+
const orphanErrors = validateOrphanMessages(parsedFiles, dependencies);
|
|
968
|
+
const deprecatedRefErrors = validateDeprecatedReferences(parsedFiles);
|
|
969
|
+
const duplicateErrors = validateDuplicateResourceIds(parsedFiles);
|
|
970
|
+
const bestPracticeErrors = validateBestPractices(parsedFiles);
|
|
971
|
+
return [
|
|
972
|
+
...schemaErrors,
|
|
973
|
+
...referenceErrors,
|
|
974
|
+
...orphanErrors,
|
|
975
|
+
...deprecatedRefErrors,
|
|
976
|
+
...duplicateErrors,
|
|
977
|
+
...bestPracticeErrors
|
|
978
|
+
];
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// src/reporters/index.ts
|
|
982
|
+
var import_chalk = __toESM(require("chalk"));
|
|
983
|
+
var formatError = (error, showFilename = true) => {
|
|
984
|
+
const lineInfo = error.line ? import_chalk.default.dim(`:${error.line}:1`) : "";
|
|
985
|
+
const filename = showFilename ? `${import_chalk.default.dim(error.file)}${lineInfo}` : "";
|
|
986
|
+
const isWarning = error.severity === "warning";
|
|
987
|
+
const severity = isWarning ? import_chalk.default.yellow("warning") : import_chalk.default.red("error");
|
|
988
|
+
const icon = isWarning ? import_chalk.default.yellow("\u26A0") : import_chalk.default.red("\u2716");
|
|
989
|
+
const errorCode = getErrorCode(error);
|
|
990
|
+
const field = error.field ? import_chalk.default.dim(`[${error.field}]`) : "";
|
|
991
|
+
const parts = [];
|
|
992
|
+
if (filename) parts.push(filename);
|
|
993
|
+
parts.push(icon, severity, error.message, field, import_chalk.default.dim(errorCode));
|
|
994
|
+
return parts.filter(Boolean).join(" ");
|
|
995
|
+
};
|
|
996
|
+
var getErrorCode = (error) => {
|
|
997
|
+
if (error.rule) {
|
|
998
|
+
return `(${error.rule})`;
|
|
999
|
+
}
|
|
1000
|
+
if (error.type === "schema") {
|
|
1001
|
+
if (error.field) {
|
|
1002
|
+
if (error.message.includes("Required")) return "(@eventcatalog/required-field)";
|
|
1003
|
+
if (error.message.includes("Expected")) return "(@eventcatalog/invalid-type)";
|
|
1004
|
+
return "(@eventcatalog/schema-validation)";
|
|
1005
|
+
}
|
|
1006
|
+
return "(@eventcatalog/schema)";
|
|
1007
|
+
}
|
|
1008
|
+
if (error.type === "reference") {
|
|
1009
|
+
return "(@eventcatalog/invalid-reference)";
|
|
1010
|
+
}
|
|
1011
|
+
return "(@eventcatalog/unknown)";
|
|
1012
|
+
};
|
|
1013
|
+
var formatParseError = (error, showFilename = true) => {
|
|
1014
|
+
const filename = showFilename ? import_chalk.default.dim(error.file.relativePath) : "";
|
|
1015
|
+
const severity = import_chalk.default.red("error");
|
|
1016
|
+
const message = `Parse error: ${error.error.message}`;
|
|
1017
|
+
const errorCode = import_chalk.default.dim("(@eventcatalog/parse-error)");
|
|
1018
|
+
const parts = [];
|
|
1019
|
+
if (filename) parts.push(filename);
|
|
1020
|
+
parts.push(import_chalk.default.red("\u2716"), severity, message, errorCode);
|
|
1021
|
+
return parts.filter(Boolean).join(" ");
|
|
1022
|
+
};
|
|
1023
|
+
var groupErrorsByFile = (errors) => {
|
|
1024
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1025
|
+
for (const error of errors) {
|
|
1026
|
+
if (!grouped.has(error.file)) {
|
|
1027
|
+
grouped.set(error.file, []);
|
|
1028
|
+
}
|
|
1029
|
+
grouped.get(error.file).push(error);
|
|
1030
|
+
}
|
|
1031
|
+
return grouped;
|
|
1032
|
+
};
|
|
1033
|
+
var reportErrors = (validationErrors, parseErrors, verbose = false) => {
|
|
1034
|
+
const allErrors = [
|
|
1035
|
+
...validationErrors,
|
|
1036
|
+
...parseErrors.map((pe) => ({
|
|
1037
|
+
type: "parse",
|
|
1038
|
+
resource: pe.file.resourceType || "unknown",
|
|
1039
|
+
message: pe.error.message,
|
|
1040
|
+
file: pe.file.relativePath,
|
|
1041
|
+
line: void 0
|
|
1042
|
+
}))
|
|
1043
|
+
];
|
|
1044
|
+
const schemaErrors = validationErrors.filter((e) => e.type === "schema");
|
|
1045
|
+
const referenceErrors = validationErrors.filter((e) => e.type === "reference");
|
|
1046
|
+
const warnings = validationErrors.filter((e) => e.severity === "warning");
|
|
1047
|
+
const errors = [...validationErrors.filter((e) => e.severity !== "warning"), ...parseErrors];
|
|
1048
|
+
const totalErrors = errors.length;
|
|
1049
|
+
const totalWarnings = warnings.length;
|
|
1050
|
+
if (totalErrors === 0 && totalWarnings === 0) {
|
|
1051
|
+
console.log(import_chalk.default.green("\u2714 No problems found!"));
|
|
1052
|
+
return {
|
|
1053
|
+
totalErrors: 0,
|
|
1054
|
+
totalWarnings: 0,
|
|
1055
|
+
schemaErrors: 0,
|
|
1056
|
+
referenceErrors: 0,
|
|
1057
|
+
parseErrors: 0,
|
|
1058
|
+
filesChecked: 0,
|
|
1059
|
+
filesWithErrors: 0
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
const grouped = groupErrorsByFile(validationErrors);
|
|
1063
|
+
const parseErrorsGrouped = groupParseErrorsByFile(parseErrors);
|
|
1064
|
+
const allFiles = /* @__PURE__ */ new Set([...grouped.keys(), ...parseErrorsGrouped.keys()]);
|
|
1065
|
+
console.log();
|
|
1066
|
+
for (const file of Array.from(allFiles).sort()) {
|
|
1067
|
+
const fileErrors = grouped.get(file) || [];
|
|
1068
|
+
const fileParseErrors = parseErrorsGrouped.get(file) || [];
|
|
1069
|
+
const fileErrorCount = fileErrors.length + fileParseErrors.length;
|
|
1070
|
+
if (fileErrorCount === 0) continue;
|
|
1071
|
+
console.log(import_chalk.default.underline(file));
|
|
1072
|
+
for (const error of fileParseErrors) {
|
|
1073
|
+
console.log(` ${formatParseError(error, false)}`);
|
|
1074
|
+
}
|
|
1075
|
+
for (const error of fileErrors) {
|
|
1076
|
+
console.log(` ${formatError(error, false)}`);
|
|
1077
|
+
}
|
|
1078
|
+
const fileWarnings = fileErrors.filter((e) => e.severity === "warning").length;
|
|
1079
|
+
const fileActualErrors = fileErrorCount - fileWarnings;
|
|
1080
|
+
const problemText2 = fileErrorCount === 1 ? "problem" : "problems";
|
|
1081
|
+
const summaryColor2 = fileActualErrors > 0 ? import_chalk.default.red : import_chalk.default.yellow;
|
|
1082
|
+
const summaryIcon2 = fileActualErrors > 0 ? "\u2716" : "\u26A0";
|
|
1083
|
+
console.log(summaryColor2(`
|
|
1084
|
+
${summaryIcon2} ${fileErrorCount} ${problemText2}
|
|
1085
|
+
`));
|
|
1086
|
+
}
|
|
1087
|
+
const filesWithErrors = allFiles.size;
|
|
1088
|
+
const totalProblems = totalErrors + totalWarnings;
|
|
1089
|
+
const problemText = totalProblems === 1 ? "problem" : "problems";
|
|
1090
|
+
const fileText = filesWithErrors === 1 ? "file" : "files";
|
|
1091
|
+
const summaryColor = totalErrors > 0 ? import_chalk.default.red.bold : import_chalk.default.yellow.bold;
|
|
1092
|
+
const summaryIcon = totalErrors > 0 ? "\u2716" : "\u26A0";
|
|
1093
|
+
console.log(summaryColor(`${summaryIcon} ${totalProblems} ${problemText} (${totalErrors} errors, ${totalWarnings} warnings)`));
|
|
1094
|
+
console.log(import_chalk.default.dim(` ${filesWithErrors} ${fileText} checked`));
|
|
1095
|
+
return {
|
|
1096
|
+
totalErrors,
|
|
1097
|
+
totalWarnings,
|
|
1098
|
+
schemaErrors: schemaErrors.length,
|
|
1099
|
+
referenceErrors: referenceErrors.length,
|
|
1100
|
+
parseErrors: parseErrors.length,
|
|
1101
|
+
filesChecked: allFiles.size,
|
|
1102
|
+
filesWithErrors
|
|
1103
|
+
};
|
|
1104
|
+
};
|
|
1105
|
+
var groupParseErrorsByFile = (errors) => {
|
|
1106
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1107
|
+
for (const error of errors) {
|
|
1108
|
+
const file = error.file.relativePath;
|
|
1109
|
+
if (!grouped.has(file)) {
|
|
1110
|
+
grouped.set(file, []);
|
|
1111
|
+
}
|
|
1112
|
+
grouped.get(file).push(error);
|
|
1113
|
+
}
|
|
1114
|
+
return grouped;
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
// src/config/index.ts
|
|
1118
|
+
var import_fs = __toESM(require("fs"));
|
|
1119
|
+
var import_path2 = __toESM(require("path"));
|
|
1120
|
+
var DEFAULT_IGNORE_PATTERNS = ["dependencies/**"];
|
|
1121
|
+
var DEFAULT_RULES = {
|
|
1122
|
+
"schema/required-fields": "error",
|
|
1123
|
+
"schema/valid-semver": "error",
|
|
1124
|
+
"schema/valid-email": "error",
|
|
1125
|
+
"refs/owner-exists": "error",
|
|
1126
|
+
"refs/valid-version-range": "error",
|
|
1127
|
+
"refs/resource-exists": "error",
|
|
1128
|
+
"refs/channel-exists": "error",
|
|
1129
|
+
"refs/container-exists": "error",
|
|
1130
|
+
"refs/orphan-messages": "warn",
|
|
1131
|
+
"best-practices/summary-required": "error",
|
|
1132
|
+
"best-practices/owner-required": "error",
|
|
1133
|
+
"best-practices/description-required": "warn",
|
|
1134
|
+
"best-practices/schema-required": "warn",
|
|
1135
|
+
"naming/service-id-format": "error",
|
|
1136
|
+
"naming/event-id-format": "error",
|
|
1137
|
+
"versions/consistent-format": "error",
|
|
1138
|
+
"versions/no-deprecated": "error",
|
|
1139
|
+
"versions/no-deprecated-references": "warn",
|
|
1140
|
+
"structure/duplicate-resource-ids": "error"
|
|
1141
|
+
};
|
|
1142
|
+
var PLURAL_TO_SINGULAR = {
|
|
1143
|
+
events: "event",
|
|
1144
|
+
commands: "command",
|
|
1145
|
+
queries: "query",
|
|
1146
|
+
services: "service",
|
|
1147
|
+
domains: "domain",
|
|
1148
|
+
entities: "entity",
|
|
1149
|
+
channels: "channel",
|
|
1150
|
+
flows: "flow",
|
|
1151
|
+
users: "user",
|
|
1152
|
+
teams: "team"
|
|
1153
|
+
};
|
|
1154
|
+
var loadEventCatalogConfig = (rootDir) => {
|
|
1155
|
+
const configPath = import_path2.default.join(rootDir, "eventcatalog.config.js");
|
|
1156
|
+
if (!import_fs.default.existsSync(configPath)) {
|
|
1157
|
+
return {};
|
|
1158
|
+
}
|
|
1159
|
+
try {
|
|
1160
|
+
delete require.cache[require.resolve(configPath)];
|
|
1161
|
+
const config = require(configPath);
|
|
1162
|
+
if (!config.dependencies || typeof config.dependencies !== "object") {
|
|
1163
|
+
return {};
|
|
1164
|
+
}
|
|
1165
|
+
const dependencies = {};
|
|
1166
|
+
for (const [pluralKey, entries] of Object.entries(config.dependencies)) {
|
|
1167
|
+
const singularType = PLURAL_TO_SINGULAR[pluralKey];
|
|
1168
|
+
if (!singularType || !Array.isArray(entries)) continue;
|
|
1169
|
+
dependencies[singularType] = entries.filter((entry) => entry && typeof entry.id === "string").map((entry) => ({ id: entry.id, version: entry.version }));
|
|
1170
|
+
}
|
|
1171
|
+
return dependencies;
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
console.warn(`Warning: Could not load eventcatalog.config.js: ${error instanceof Error ? error.message : String(error)}`);
|
|
1174
|
+
return {};
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
var loadConfig = (rootDir) => {
|
|
1178
|
+
const configPath = import_path2.default.join(rootDir, ".eventcatalogrc.js");
|
|
1179
|
+
if (!import_fs.default.existsSync(configPath)) {
|
|
1180
|
+
return {
|
|
1181
|
+
rules: DEFAULT_RULES,
|
|
1182
|
+
ignorePatterns: DEFAULT_IGNORE_PATTERNS,
|
|
1183
|
+
overrides: []
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
delete require.cache[require.resolve(configPath)];
|
|
1188
|
+
const config = require(configPath);
|
|
1189
|
+
const mergedConfig = {
|
|
1190
|
+
rules: { ...DEFAULT_RULES, ...config.rules },
|
|
1191
|
+
ignorePatterns: [...DEFAULT_IGNORE_PATTERNS, ...config.ignorePatterns || []],
|
|
1192
|
+
overrides: config.overrides || []
|
|
1193
|
+
};
|
|
1194
|
+
return mergedConfig;
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
console.warn(`Warning: Could not load .eventcatalogrc.js: ${error instanceof Error ? error.message : String(error)}`);
|
|
1197
|
+
return {
|
|
1198
|
+
rules: DEFAULT_RULES,
|
|
1199
|
+
ignorePatterns: DEFAULT_IGNORE_PATTERNS,
|
|
1200
|
+
overrides: []
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
var parseRuleConfig = (rule) => {
|
|
1205
|
+
if (Array.isArray(rule)) {
|
|
1206
|
+
return {
|
|
1207
|
+
severity: rule[0],
|
|
1208
|
+
options: rule[1]
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
severity: rule,
|
|
1213
|
+
options: {}
|
|
1214
|
+
};
|
|
1215
|
+
};
|
|
1216
|
+
var shouldIgnoreFile = (filePath, ignorePatterns) => {
|
|
1217
|
+
if (!ignorePatterns || ignorePatterns.length === 0) {
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
1221
|
+
for (const pattern of ignorePatterns) {
|
|
1222
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
|
|
1223
|
+
if (regex.test(normalizedPath)) {
|
|
1224
|
+
return true;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
return false;
|
|
1228
|
+
};
|
|
1229
|
+
var getEffectiveRules = (filePath, config) => {
|
|
1230
|
+
let effectiveRules = { ...config.rules };
|
|
1231
|
+
if (config.overrides) {
|
|
1232
|
+
for (const override of config.overrides) {
|
|
1233
|
+
const matchesFile = override.files.some((pattern) => {
|
|
1234
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"));
|
|
1235
|
+
return regex.test(filePath);
|
|
1236
|
+
});
|
|
1237
|
+
if (matchesFile) {
|
|
1238
|
+
effectiveRules = { ...effectiveRules, ...override.rules };
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const parsedRules = {};
|
|
1243
|
+
for (const [ruleName, ruleValue] of Object.entries(effectiveRules)) {
|
|
1244
|
+
parsedRules[ruleName] = parseRuleConfig(ruleValue);
|
|
1245
|
+
}
|
|
1246
|
+
return parsedRules;
|
|
1247
|
+
};
|
|
1248
|
+
var applyRuleSeverity = (errors, rules) => {
|
|
1249
|
+
const result = [];
|
|
1250
|
+
for (const error of errors) {
|
|
1251
|
+
const ruleName = mapErrorToRuleName(error);
|
|
1252
|
+
const rule = rules[ruleName];
|
|
1253
|
+
if (!rule || rule.severity === "off") {
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
result.push({
|
|
1257
|
+
...error,
|
|
1258
|
+
severity: rule.severity === "warn" ? "warning" : "error"
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
return result;
|
|
1262
|
+
};
|
|
1263
|
+
var mapErrorToRuleName = (error) => {
|
|
1264
|
+
if (error.rule) {
|
|
1265
|
+
return error.rule;
|
|
1266
|
+
}
|
|
1267
|
+
if (error.type === "schema") {
|
|
1268
|
+
if (error.field === "summary") {
|
|
1269
|
+
return "best-practices/summary-required";
|
|
1270
|
+
}
|
|
1271
|
+
if (error.field === "owners") {
|
|
1272
|
+
return "best-practices/owner-required";
|
|
1273
|
+
}
|
|
1274
|
+
if (error.message.includes("email") || error.message.includes("Invalid email")) {
|
|
1275
|
+
return "schema/valid-email";
|
|
1276
|
+
}
|
|
1277
|
+
if (error.message.includes("version") || error.message.includes("semantic")) {
|
|
1278
|
+
return "schema/valid-semver";
|
|
1279
|
+
}
|
|
1280
|
+
if (error.message.includes("Required") || error.message.includes("Expected")) {
|
|
1281
|
+
return "schema/required-fields";
|
|
1282
|
+
}
|
|
1283
|
+
return "schema/required-fields";
|
|
1284
|
+
}
|
|
1285
|
+
if (error.type === "reference") {
|
|
1286
|
+
if (error.message.includes("user") || error.message.includes("team")) {
|
|
1287
|
+
return "refs/owner-exists";
|
|
1288
|
+
}
|
|
1289
|
+
if (error.message.includes("version")) {
|
|
1290
|
+
return "refs/valid-version-range";
|
|
1291
|
+
}
|
|
1292
|
+
return "refs/resource-exists";
|
|
1293
|
+
}
|
|
1294
|
+
return "schema/required-fields";
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
// src/cli/index.ts
|
|
1298
|
+
var program = new import_commander.Command();
|
|
1299
|
+
program.name("eventcatalog-linter").description("Lint your EventCatalog for frontmatter and reference validation").version("0.1.0").argument("[directory]", "EventCatalog directory to lint", ".").option("-v, --verbose", "Show verbose output", false).option("--fail-on-warning", "Exit with non-zero code on warnings", false).action(async (directory, options) => {
|
|
1300
|
+
const rootDir = import_path3.default.resolve(directory);
|
|
1301
|
+
const spinner = (0, import_ora.default)("Loading configuration...").start();
|
|
1302
|
+
try {
|
|
1303
|
+
const config = loadConfig(rootDir);
|
|
1304
|
+
const dependencies = loadEventCatalogConfig(rootDir);
|
|
1305
|
+
spinner.text = "Scanning EventCatalog files...";
|
|
1306
|
+
const allFiles = await scanCatalogFiles(rootDir);
|
|
1307
|
+
const files = allFiles.filter((file) => !shouldIgnoreFile(file.relativePath, config.ignorePatterns || []));
|
|
1308
|
+
const ignoredCount = allFiles.length - files.length;
|
|
1309
|
+
if (ignoredCount > 0) {
|
|
1310
|
+
spinner.text = `Found ${files.length} catalog files (${ignoredCount} ignored)`;
|
|
1311
|
+
} else {
|
|
1312
|
+
spinner.text = `Found ${files.length} catalog files`;
|
|
1313
|
+
}
|
|
1314
|
+
if (files.length === 0) {
|
|
1315
|
+
spinner.warn("No EventCatalog files found");
|
|
1316
|
+
process.exit(0);
|
|
1317
|
+
}
|
|
1318
|
+
spinner.text = "Parsing frontmatter...";
|
|
1319
|
+
const { parsed, errors: parseErrors } = await parseAllFiles(files);
|
|
1320
|
+
spinner.text = "Validating catalog...";
|
|
1321
|
+
const rawValidationErrors = validateCatalog(parsed, dependencies);
|
|
1322
|
+
const validationErrors = parsed.flatMap((parsedFile) => {
|
|
1323
|
+
const fileErrors = rawValidationErrors.filter((error) => error.file === parsedFile.file.relativePath);
|
|
1324
|
+
const effectiveRules = getEffectiveRules(parsedFile.file.relativePath, config);
|
|
1325
|
+
return applyRuleSeverity(fileErrors, effectiveRules);
|
|
1326
|
+
});
|
|
1327
|
+
spinner.stop();
|
|
1328
|
+
const summary = reportErrors(validationErrors, parseErrors, options.verbose);
|
|
1329
|
+
if (summary.totalErrors === 0) {
|
|
1330
|
+
console.log(import_chalk2.default.dim(`
|
|
1331
|
+
${files.length} files checked`));
|
|
1332
|
+
}
|
|
1333
|
+
if (summary.totalErrors > 0 || options.failOnWarning && summary.totalWarnings > 0) {
|
|
1334
|
+
process.exit(1);
|
|
1335
|
+
}
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
spinner.fail("An error occurred");
|
|
1338
|
+
console.error(import_chalk2.default.red(error instanceof Error ? error.message : String(error)));
|
|
1339
|
+
process.exit(1);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
program.parse();
|
|
1343
|
+
//# sourceMappingURL=index.js.map
|