@eventcatalog/core 4.7.4 → 4.7.5

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 (37) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-FDXJIZ74.js → chunk-2BYTIH4L.js} +1 -1
  6. package/dist/{chunk-7QTCSRAC.js → chunk-EUZT4RKY.js} +1 -1
  7. package/dist/{chunk-RIUHZZRA.js → chunk-GYYYAUJD.js} +128 -56
  8. package/dist/chunk-JJPB6EOZ.js +159 -0
  9. package/dist/{chunk-ZIABX6SP.js → chunk-QJRY6Q7S.js} +1 -1
  10. package/dist/{chunk-ZXVQXBTT.js → chunk-QQPH6RCB.js} +24 -1
  11. package/dist/chunk-WRNH5C3U.js +118 -0
  12. package/dist/{chunk-TLWM7WRZ.js → chunk-ZBPMCEXS.js} +1 -1
  13. package/dist/constants.cjs +1 -1
  14. package/dist/constants.js +1 -1
  15. package/dist/eventcatalog.cjs +540 -194
  16. package/dist/eventcatalog.config.d.cts +9 -3
  17. package/dist/eventcatalog.config.d.ts +9 -3
  18. package/dist/eventcatalog.js +37 -39
  19. package/dist/federation/diagnostics.cjs +187 -0
  20. package/dist/federation/diagnostics.d.cts +23 -0
  21. package/dist/federation/diagnostics.d.ts +23 -0
  22. package/dist/federation/diagnostics.js +14 -0
  23. package/dist/federation/federate.cjs +442 -117
  24. package/dist/federation/federate.d.cts +8 -1
  25. package/dist/federation/federate.d.ts +8 -1
  26. package/dist/federation/federate.js +6 -2
  27. package/dist/federation/output-transaction.cjs +152 -0
  28. package/dist/federation/output-transaction.d.cts +3 -0
  29. package/dist/federation/output-transaction.d.ts +3 -0
  30. package/dist/federation/output-transaction.js +6 -0
  31. package/dist/generate.cjs +24 -1
  32. package/dist/generate.js +3 -3
  33. package/dist/utils/cli-logger.cjs +24 -1
  34. package/dist/utils/cli-logger.d.cts +6 -0
  35. package/dist/utils/cli-logger.d.ts +6 -0
  36. package/dist/utils/cli-logger.js +2 -2
  37. package/package.json +3 -3
@@ -31,12 +31,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var federate_exports = {};
32
32
  __export(federate_exports, {
33
33
  FederationConflictError: () => FederationConflictError,
34
+ FederationDiagnosticError: () => FederationDiagnosticError,
34
35
  federateCatalog: () => federateCatalog
35
36
  });
36
37
  module.exports = __toCommonJS(federate_exports);
37
38
  var import_node_crypto4 = require("crypto");
38
- var import_promises6 = __toESM(require("fs/promises"), 1);
39
- var import_node_path6 = __toESM(require("path"), 1);
39
+ var import_promises7 = __toESM(require("fs/promises"), 1);
40
+ var import_node_path7 = __toESM(require("path"), 1);
40
41
  var import_sdk3 = __toESM(require("@eventcatalog/sdk"), 1);
41
42
 
42
43
  // src/eventcatalog-config-file-utils.js
@@ -149,28 +150,287 @@ var createFederationContentCache = (projectDirectory, options = {}) => {
149
150
  };
150
151
  };
151
152
 
152
- // src/federation/public-assets.ts
153
- var import_node_crypto2 = require("crypto");
153
+ // src/federation/diagnostics.ts
154
+ var federationRuleDefaults = {
155
+ "federation/duplicate-source": "error",
156
+ "federation/type-collision": "error",
157
+ "federation/pointer-type-mismatch": "error",
158
+ "federation/facet-disagreement": "error",
159
+ "federation/asset-collision": "warn",
160
+ "federation/missing-resource": "warn",
161
+ "federation/unresolved-version": "warn"
162
+ };
163
+ var resolveFederationRules = (rules = {}) => {
164
+ for (const [rule, level] of Object.entries(rules)) {
165
+ if (!Object.hasOwn(federationRuleDefaults, rule)) throw new Error(`Unknown federation rule "${rule}".`);
166
+ if (level !== "off" && level !== "warn" && level !== "error") {
167
+ throw new Error(`Invalid level "${String(level)}" for federation rule "${rule}". Expected off, warn, or error.`);
168
+ }
169
+ }
170
+ return { ...federationRuleDefaults, ...rules };
171
+ };
172
+ var getExternalResource = (external) => `${external.id}${external.version === void 0 ? "" : `@${external.version}`}`;
173
+ var getVersionedResource = (id, version) => `${id}${version == null ? "" : `@${version}`}`;
174
+ var getDocumentedResourceType = (type) => {
175
+ const usesAn = type === "adr" || type === "agent" || type === "entity" || type === "event";
176
+ return `documented as ${usesAn ? "an" : "a"} ${type}`;
177
+ };
178
+ var getConflictDiagnostic = (conflict, graph) => {
179
+ const catalogs = { label: "catalogs", value: conflict.sources.join(", ") };
180
+ switch (conflict.kind) {
181
+ case "duplicate-source":
182
+ return {
183
+ severity: "error",
184
+ message: "Resource has multiple owners",
185
+ rule: "federation/duplicate-source",
186
+ attributes: [
187
+ { label: "resource", value: conflict.id },
188
+ catalogs,
189
+ { label: "resolution", value: "assign a single owning catalog" }
190
+ ]
191
+ };
192
+ case "type-collision": {
193
+ const types = [
194
+ ...new Map(
195
+ graph.entities.filter((entity) => entity.id === conflict.id).map(
196
+ (entity) => [
197
+ `${entity.resolvedFrom.source}:${entity.type}`,
198
+ { label: entity.resolvedFrom.source, value: getDocumentedResourceType(entity.type) }
199
+ ]
200
+ )
201
+ ).values()
202
+ ];
203
+ return {
204
+ severity: "error",
205
+ message: "Resource ID has conflicting types",
206
+ rule: "federation/type-collision",
207
+ attributes: [{ label: "resource", value: conflict.id }, ...types.length > 0 ? types : [catalogs]]
208
+ };
209
+ }
210
+ case "pointer-type-mismatch": {
211
+ const typeMismatch = /^Expected (.+) but found (.+)$/.exec(conflict.detail ?? "");
212
+ return {
213
+ severity: "error",
214
+ message: "Reference type does not match resource",
215
+ rule: "federation/pointer-type-mismatch",
216
+ attributes: [
217
+ { label: "resource", value: conflict.id },
218
+ ...typeMismatch ? [
219
+ { label: "expected type", value: typeMismatch[1] },
220
+ { label: "actual type", value: typeMismatch[2] }
221
+ ] : conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
222
+ catalogs
223
+ ]
224
+ };
225
+ }
226
+ case "facet-disagreement":
227
+ return {
228
+ severity: "error",
229
+ message: "Catalogs disagree about this resource",
230
+ rule: "federation/facet-disagreement",
231
+ attributes: [
232
+ { label: "resource", value: conflict.id },
233
+ ...conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
234
+ catalogs
235
+ ]
236
+ };
237
+ }
238
+ };
239
+ var getFederationDiagnostics = (graph, rules = {}) => {
240
+ const resolvedRules = resolveFederationRules(rules);
241
+ const diagnostics = graph.conflicts.map((conflict) => getConflictDiagnostic(conflict, graph));
242
+ for (const external of graph.externals) {
243
+ const resource = getExternalResource(external);
244
+ for (const referencedBy of external.referencedBy) {
245
+ const referrer = graph.entities.find((entity) => entity.id === referencedBy);
246
+ diagnostics.push({
247
+ severity: "warning",
248
+ message: "Referenced EventCatalog resource does not exist",
249
+ rule: "federation/missing-resource",
250
+ attributes: [
251
+ { label: "source catalog", value: referrer?.resolvedFrom.source ?? "unknown" },
252
+ { label: "referenced by", value: referencedBy },
253
+ { label: "missing resource", value: resource }
254
+ ]
255
+ });
256
+ }
257
+ }
258
+ for (const edge of graph.edges.filter((edge2) => edge2.status === "unresolved")) {
259
+ const availableVersions = [
260
+ ...new Set(graph.entities.filter((entity) => entity.id === edge.to).map((entity) => entity.version ?? "unversioned"))
261
+ ].sort((left, right) => left.localeCompare(right, void 0, { numeric: true }));
262
+ diagnostics.push({
263
+ severity: "warning",
264
+ message: "Referenced EventCatalog resource version does not exist",
265
+ rule: "federation/unresolved-version",
266
+ attributes: [
267
+ { label: "source catalog", value: edge.fromResolvedFrom.source },
268
+ { label: "referenced by", value: getVersionedResource(edge.from, edge.fromVersion) },
269
+ { label: "resource", value: edge.to },
270
+ { label: "requested version", value: edge.pointer ?? "unspecified" },
271
+ { label: "available versions", value: availableVersions.join(", ") }
272
+ ]
273
+ });
274
+ }
275
+ for (const warning of graph.warnings) {
276
+ diagnostics.push({
277
+ severity: "warning",
278
+ message: "Asset collision",
279
+ rule: "federation/asset-collision",
280
+ attributes: [
281
+ { label: "asset", value: warning.path },
282
+ { label: "sources", value: warning.sources.join(", ") },
283
+ { label: "winner", value: warning.winner },
284
+ { label: "resolution", value: "last configured source wins" }
285
+ ]
286
+ });
287
+ }
288
+ return diagnostics.flatMap((diagnostic) => {
289
+ const level = resolvedRules[diagnostic.rule];
290
+ if (level === "off") return [];
291
+ return [{ ...diagnostic, severity: level === "error" ? "error" : "warning" }];
292
+ }).sort(
293
+ (left, right) => (left.severity === right.severity ? 0 : left.severity === "error" ? -1 : 1) || left.rule.localeCompare(right.rule) || (left.attributes[0]?.value ?? "").localeCompare(right.attributes[0]?.value ?? "")
294
+ );
295
+ };
296
+
297
+ // src/federation/output-transaction.ts
154
298
  var import_promises3 = __toESM(require("fs/promises"), 1);
155
299
  var import_node_path3 = __toESM(require("path"), 1);
300
+ var getSafePublicPath = (publicDirectory, relativePath) => {
301
+ const normalizedPath = import_node_path3.default.posix.normalize(relativePath);
302
+ const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") || import_node_path3.default.posix.isAbsolute(relativePath) || /^[a-zA-Z]:\//.test(relativePath) || normalizedPath !== relativePath || normalizedPath === ".." || normalizedPath.startsWith("../");
303
+ if (unsafe) return void 0;
304
+ const destinationPath = import_node_path3.default.resolve(publicDirectory, relativePath);
305
+ const relativeDestinationPath = import_node_path3.default.relative(publicDirectory, destinationPath);
306
+ return relativeDestinationPath !== "" && relativeDestinationPath !== ".." && !relativeDestinationPath.startsWith(`..${import_node_path3.default.sep}`) && !import_node_path3.default.isAbsolute(relativeDestinationPath) ? destinationPath : void 0;
307
+ };
308
+ var beginFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths) => {
309
+ const transactionDirectory = await import_promises3.default.mkdtemp(import_node_path3.default.join(projectDirectory, ".eventcatalog-federation-transaction-"));
310
+ const previousOutDir = import_node_path3.default.join(transactionDirectory, "federated");
311
+ const publicBackupDirectory = import_node_path3.default.join(transactionDirectory, "public");
312
+ const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
313
+ const publicSnapshots = [];
314
+ const missingPublicDirectories = /* @__PURE__ */ new Set();
315
+ let hadPreviousOutDir = false;
316
+ try {
317
+ for (const relativePath of new Set(relativePublicPaths)) {
318
+ const destinationPath = getSafePublicPath(publicDirectory, relativePath);
319
+ if (!destinationPath) continue;
320
+ try {
321
+ const stat = await import_promises3.default.lstat(destinationPath);
322
+ if (!stat.isFile()) continue;
323
+ const backupPath = import_node_path3.default.join(publicBackupDirectory, `${publicSnapshots.length}`);
324
+ await import_promises3.default.mkdir(import_node_path3.default.dirname(backupPath), { recursive: true });
325
+ await import_promises3.default.copyFile(destinationPath, backupPath);
326
+ publicSnapshots.push({ backupPath, destinationPath, state: "file" });
327
+ } catch (error) {
328
+ const code = error.code;
329
+ if (code === "ENOTDIR") continue;
330
+ if (code !== "ENOENT") throw error;
331
+ publicSnapshots.push({ destinationPath, state: "missing" });
332
+ let directory = import_node_path3.default.dirname(destinationPath);
333
+ while (directory !== publicDirectory) {
334
+ try {
335
+ await import_promises3.default.lstat(directory);
336
+ break;
337
+ } catch (directoryError) {
338
+ const directoryCode = directoryError.code;
339
+ if (directoryCode === "ENOTDIR") break;
340
+ if (directoryCode !== "ENOENT") throw directoryError;
341
+ missingPublicDirectories.add(directory);
342
+ directory = import_node_path3.default.dirname(directory);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ try {
348
+ await import_promises3.default.rename(outDir, previousOutDir);
349
+ hadPreviousOutDir = true;
350
+ } catch (error) {
351
+ if (error.code !== "ENOENT") throw error;
352
+ }
353
+ } catch (error) {
354
+ await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true });
355
+ throw error;
356
+ }
357
+ return {
358
+ commit: async () => {
359
+ await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
360
+ },
361
+ rollback: async () => {
362
+ const rollbackErrors = [];
363
+ const attempt = async (operation) => {
364
+ try {
365
+ await operation();
366
+ } catch (error) {
367
+ rollbackErrors.push(error);
368
+ }
369
+ };
370
+ await attempt(() => import_promises3.default.rm(outDir, { recursive: true, force: true }));
371
+ if (hadPreviousOutDir) await attempt(() => import_promises3.default.rename(previousOutDir, outDir));
372
+ for (const snapshot of publicSnapshots) {
373
+ if (snapshot.state === "missing") {
374
+ await attempt(() => import_promises3.default.rm(snapshot.destinationPath, { force: true }));
375
+ continue;
376
+ }
377
+ await attempt(async () => {
378
+ await import_promises3.default.mkdir(import_node_path3.default.dirname(snapshot.destinationPath), { recursive: true });
379
+ await import_promises3.default.copyFile(snapshot.backupPath, snapshot.destinationPath);
380
+ });
381
+ }
382
+ for (const directory of [...missingPublicDirectories].sort((left, right) => right.length - left.length)) {
383
+ await attempt(async () => {
384
+ try {
385
+ await import_promises3.default.rmdir(directory);
386
+ } catch (error) {
387
+ if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
388
+ }
389
+ });
390
+ }
391
+ await attempt(() => import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }));
392
+ if (rollbackErrors.length > 0) throw new AggregateError(rollbackErrors, "Failed to restore the previous federation output");
393
+ }
394
+ };
395
+ };
396
+ var withFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths, update) => {
397
+ const transaction = await beginFederationOutputTransaction(projectDirectory, outDir, relativePublicPaths);
398
+ try {
399
+ const result = await update();
400
+ await transaction.commit();
401
+ return result;
402
+ } catch (error) {
403
+ try {
404
+ await transaction.rollback();
405
+ } catch (rollbackError) {
406
+ throw new AggregateError([error, rollbackError], "Federation failed and the previous output could not be restored");
407
+ }
408
+ throw error;
409
+ }
410
+ };
411
+
412
+ // src/federation/public-assets.ts
413
+ var import_node_crypto2 = require("crypto");
414
+ var import_promises4 = __toESM(require("fs/promises"), 1);
415
+ var import_node_path4 = __toESM(require("path"), 1);
156
416
  var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
157
417
  var getFileHash = async (filePath) => {
158
418
  try {
159
- const stat = await import_promises3.default.lstat(filePath);
160
- return stat.isFile() ? getContentHash2(await import_promises3.default.readFile(filePath)) : void 0;
419
+ const stat = await import_promises4.default.lstat(filePath);
420
+ return stat.isFile() ? getContentHash2(await import_promises4.default.readFile(filePath)) : void 0;
161
421
  } catch (error) {
162
422
  if (error.code === "ENOENT") return void 0;
163
423
  throw error;
164
424
  }
165
425
  };
166
426
  var getSafePath = (directory, relativePath) => {
167
- const normalizedPath = import_node_path3.default.posix.normalize(relativePath);
168
- const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") || import_node_path3.default.posix.isAbsolute(relativePath) || /^[a-zA-Z]:\//.test(relativePath) || normalizedPath !== relativePath || normalizedPath === ".." || normalizedPath.startsWith("../");
427
+ const normalizedPath = import_node_path4.default.posix.normalize(relativePath);
428
+ const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") || import_node_path4.default.posix.isAbsolute(relativePath) || /^[a-zA-Z]:\//.test(relativePath) || normalizedPath !== relativePath || normalizedPath === ".." || normalizedPath.startsWith("../");
169
429
  if (unsafe) return void 0;
170
- const resolvedDirectory = import_node_path3.default.resolve(directory);
171
- const resolvedPath = import_node_path3.default.resolve(resolvedDirectory, relativePath);
172
- const relativeResolvedPath = import_node_path3.default.relative(resolvedDirectory, resolvedPath);
173
- if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path3.default.sep}`) || import_node_path3.default.isAbsolute(relativeResolvedPath)) {
430
+ const resolvedDirectory = import_node_path4.default.resolve(directory);
431
+ const resolvedPath = import_node_path4.default.resolve(resolvedDirectory, relativePath);
432
+ const relativeResolvedPath = import_node_path4.default.relative(resolvedDirectory, resolvedPath);
433
+ if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relativeResolvedPath)) {
174
434
  return void 0;
175
435
  }
176
436
  return resolvedPath;
@@ -178,23 +438,23 @@ var getSafePath = (directory, relativePath) => {
178
438
  var listFiles = async (directory, relativeDirectory = "") => {
179
439
  let entries;
180
440
  try {
181
- entries = await import_promises3.default.readdir(import_node_path3.default.join(directory, relativeDirectory), { withFileTypes: true });
441
+ entries = await import_promises4.default.readdir(import_node_path4.default.join(directory, relativeDirectory), { withFileTypes: true });
182
442
  } catch (error) {
183
443
  if (error.code === "ENOENT") return [];
184
444
  throw error;
185
445
  }
186
446
  const files = await Promise.all(
187
447
  entries.map(async (entry) => {
188
- const relativePath = import_node_path3.default.join(relativeDirectory, entry.name);
448
+ const relativePath = import_node_path4.default.join(relativeDirectory, entry.name);
189
449
  if (entry.isDirectory()) return listFiles(directory, relativePath);
190
- return entry.isFile() ? [relativePath.split(import_node_path3.default.sep).join("/")] : [];
450
+ return entry.isFile() ? [relativePath.split(import_node_path4.default.sep).join("/")] : [];
191
451
  })
192
452
  );
193
453
  return files.flat().sort();
194
454
  };
195
455
  var pathExists = async (filePath) => {
196
456
  try {
197
- await import_promises3.default.lstat(filePath);
457
+ await import_promises4.default.lstat(filePath);
198
458
  return true;
199
459
  } catch (error) {
200
460
  if (error.code === "ENOENT") return false;
@@ -202,27 +462,27 @@ var pathExists = async (filePath) => {
202
462
  }
203
463
  };
204
464
  var hasBlockingParent = async (publicDirectory, destinationPath) => {
205
- let currentPath = import_node_path3.default.dirname(destinationPath);
465
+ let currentPath = import_node_path4.default.dirname(destinationPath);
206
466
  while (currentPath !== publicDirectory) {
207
467
  try {
208
- if (!(await import_promises3.default.lstat(currentPath)).isDirectory()) return true;
468
+ if (!(await import_promises4.default.lstat(currentPath)).isDirectory()) return true;
209
469
  } catch (error) {
210
470
  if (error.code !== "ENOENT") throw error;
211
471
  }
212
- currentPath = import_node_path3.default.dirname(currentPath);
472
+ currentPath = import_node_path4.default.dirname(currentPath);
213
473
  }
214
474
  return false;
215
475
  };
216
476
  var pruneEmptyDirectories = async (publicDirectory, filePath) => {
217
- let currentPath = import_node_path3.default.dirname(filePath);
477
+ let currentPath = import_node_path4.default.dirname(filePath);
218
478
  while (currentPath !== publicDirectory) {
219
479
  try {
220
- await import_promises3.default.rmdir(currentPath);
480
+ await import_promises4.default.rmdir(currentPath);
221
481
  } catch (error) {
222
482
  if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
223
483
  if (error.code === "ENOTEMPTY") return;
224
484
  }
225
- currentPath = import_node_path3.default.dirname(currentPath);
485
+ currentPath = import_node_path4.default.dirname(currentPath);
226
486
  }
227
487
  };
228
488
  var composePublicAssets = async ({
@@ -232,8 +492,8 @@ var composePublicAssets = async ({
232
492
  previousFiles = {},
233
493
  collisionPaths = /* @__PURE__ */ new Set()
234
494
  }) => {
235
- const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
236
- const federatedPublicDirectory = import_node_path3.default.resolve(federatedDirectory, "public");
495
+ const publicDirectory = import_node_path4.default.resolve(projectDirectory, "public");
496
+ const federatedPublicDirectory = import_node_path4.default.resolve(federatedDirectory, "public");
237
497
  const sourceFiles = await listFiles(federatedPublicDirectory);
238
498
  const sourceFileSet = new Set(sourceFiles);
239
499
  const managedFiles = /* @__PURE__ */ new Set();
@@ -246,7 +506,7 @@ var composePublicAssets = async ({
246
506
  if (sourceFileSet.has(relativePath)) continue;
247
507
  const destinationPath = getSafePath(publicDirectory, relativePath);
248
508
  if (!destinationPath) continue;
249
- await import_promises3.default.rm(destinationPath, { force: true });
509
+ await import_promises4.default.rm(destinationPath, { force: true });
250
510
  await pruneEmptyDirectories(publicDirectory, destinationPath);
251
511
  removed += 1;
252
512
  }
@@ -268,31 +528,31 @@ var composePublicAssets = async ({
268
528
  }
269
529
  const asset = publicAssetsByPath.get(relativePath);
270
530
  if (!asset) throw new Error(`Cannot identify the source of federated public asset "${relativePath}"`);
271
- const content = await import_promises3.default.readFile(sourcePath);
272
- await import_promises3.default.mkdir(import_node_path3.default.dirname(destinationPath), { recursive: true });
273
- await import_promises3.default.writeFile(destinationPath, content);
531
+ const content = await import_promises4.default.readFile(sourcePath);
532
+ await import_promises4.default.mkdir(import_node_path4.default.dirname(destinationPath), { recursive: true });
533
+ await import_promises4.default.writeFile(destinationPath, content);
274
534
  files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
275
535
  copied += 1;
276
536
  if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
277
537
  }
278
- await import_promises3.default.rm(federatedPublicDirectory, { recursive: true, force: true });
538
+ await import_promises4.default.rm(federatedPublicDirectory, { recursive: true, force: true });
279
539
  return { files, copied, skipped, overwritten, removed };
280
540
  };
281
541
 
282
542
  // src/federation/filesystem-source-provider.ts
283
543
  var import_node_crypto3 = require("crypto");
284
- var import_promises4 = __toESM(require("fs/promises"), 1);
285
- var import_node_path4 = __toESM(require("path"), 1);
544
+ var import_promises5 = __toESM(require("fs/promises"), 1);
545
+ var import_node_path5 = __toESM(require("path"), 1);
286
546
  var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
287
547
  var FILESYSTEM_SOURCE_PREFIX = "file:";
288
548
  var isWithinDirectory = (directory, target) => {
289
- const relativePath = import_node_path4.default.relative(directory, target);
290
- return relativePath === "" || !relativePath.startsWith(`..${import_node_path4.default.sep}`) && relativePath !== ".." && !import_node_path4.default.isAbsolute(relativePath);
549
+ const relativePath = import_node_path5.default.relative(directory, target);
550
+ return relativePath === "" || !relativePath.startsWith(`..${import_node_path5.default.sep}`) && relativePath !== ".." && !import_node_path5.default.isAbsolute(relativePath);
291
551
  };
292
552
  var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
293
553
  const portablePath = filePath.replaceAll("\\", "/");
294
- const normalizedPath = import_node_path4.default.posix.normalize(portablePath);
295
- const isUnsafe = filePath.includes("\\") || filePath.includes("\0") || import_node_path4.default.posix.isAbsolute(normalizedPath) || /^[a-zA-Z]:\//.test(normalizedPath) || normalizedPath === ".." || normalizedPath.startsWith("../") || !allowRoot && (normalizedPath === "." || normalizedPath === "");
554
+ const normalizedPath = import_node_path5.default.posix.normalize(portablePath);
555
+ const isUnsafe = filePath.includes("\\") || filePath.includes("\0") || import_node_path5.default.posix.isAbsolute(normalizedPath) || /^[a-zA-Z]:\//.test(normalizedPath) || normalizedPath === ".." || normalizedPath.startsWith("../") || !allowRoot && (normalizedPath === "." || normalizedPath === "");
296
556
  if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
297
557
  return normalizedPath;
298
558
  };
@@ -302,22 +562,22 @@ var getSourceRoot = (projectDirectory, source) => {
302
562
  }
303
563
  const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
304
564
  if (!locator.trim()) throw new Error(`Filesystem federation source "${source.id}" requires a path after "file:".`);
305
- return import_node_path4.default.resolve(projectDirectory, locator);
565
+ return import_node_path5.default.resolve(projectDirectory, locator);
306
566
  };
307
567
  var getCatalogDirectory = async (projectDirectory, source) => {
308
568
  if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
309
569
  const sourceRoot = getSourceRoot(projectDirectory, source);
310
570
  const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
311
- const catalogDirectory = import_node_path4.default.resolve(sourceRoot, ...catalogPath.split("/"));
571
+ const catalogDirectory = import_node_path5.default.resolve(sourceRoot, ...catalogPath.split("/"));
312
572
  if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
313
573
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
314
574
  }
315
575
  try {
316
- const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises4.default.realpath(sourceRoot), import_promises4.default.realpath(catalogDirectory)]);
576
+ const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises5.default.realpath(sourceRoot), import_promises5.default.realpath(catalogDirectory)]);
317
577
  if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
318
578
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
319
579
  }
320
- if (!(await import_promises4.default.stat(realCatalogDirectory)).isDirectory()) {
580
+ if (!(await import_promises5.default.stat(realCatalogDirectory)).isDirectory()) {
321
581
  throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
322
582
  }
323
583
  return realCatalogDirectory;
@@ -330,12 +590,12 @@ var getCatalogDirectory = async (projectDirectory, source) => {
330
590
  };
331
591
  var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
332
592
  const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
333
- const filePath = import_node_path4.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
593
+ const filePath = import_node_path5.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
334
594
  if (!isWithinDirectory(catalogDirectory, filePath)) {
335
595
  throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
336
596
  }
337
597
  try {
338
- const realFilePath = await import_promises4.default.realpath(filePath);
598
+ const realFilePath = await import_promises5.default.realpath(filePath);
339
599
  if (!isWithinDirectory(catalogDirectory, realFilePath)) {
340
600
  throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
341
601
  }
@@ -362,15 +622,15 @@ var createFileSystemSourceProvider = (projectDirectory) => ({
362
622
  },
363
623
  async fetchContent({ source, path: artifactPath }) {
364
624
  const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
365
- return import_promises4.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
625
+ return import_promises5.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
366
626
  }
367
627
  });
368
628
 
369
629
  // src/federation/github-source-provider.ts
370
630
  var import_node_child_process = require("child_process");
371
- var import_promises5 = __toESM(require("fs/promises"), 1);
631
+ var import_promises6 = __toESM(require("fs/promises"), 1);
372
632
  var import_node_os2 = __toESM(require("os"), 1);
373
- var import_node_path5 = __toESM(require("path"), 1);
633
+ var import_node_path6 = __toESM(require("path"), 1);
374
634
  var import_node_util = require("util");
375
635
  var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
376
636
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -381,8 +641,8 @@ var parseGitHubSource = (source) => {
381
641
  };
382
642
  var assertSafeCatalogPath = (source) => {
383
643
  const catalogPath = source.path ?? ".";
384
- const normalized = import_node_path5.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
385
- if (catalogPath.includes("\\") || import_node_path5.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
644
+ const normalized = import_node_path6.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
645
+ if (catalogPath.includes("\\") || import_node_path6.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
386
646
  throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
387
647
  }
388
648
  };
@@ -425,8 +685,8 @@ var getGitEnvironment = (token) => {
425
685
  };
426
686
  var createCheckout = (executeFile, token) => async (source, ref, callback) => {
427
687
  const { owner, repository } = parseGitHubSource(source);
428
- const catalogPath = import_node_path5.default.posix.normalize(source.path ?? ".");
429
- const directory = await import_promises5.default.mkdtemp(import_node_path5.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
688
+ const catalogPath = import_node_path6.default.posix.normalize(source.path ?? ".");
689
+ const directory = await import_promises6.default.mkdtemp(import_node_path6.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
430
690
  const env = getGitEnvironment(token);
431
691
  try {
432
692
  const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
@@ -440,22 +700,22 @@ var createCheckout = (executeFile, token) => async (source, ref, callback) => {
440
700
  await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
441
701
  return await callback(directory);
442
702
  } finally {
443
- await import_promises5.default.rm(directory, { recursive: true, force: true });
703
+ await import_promises6.default.rm(directory, { recursive: true, force: true });
444
704
  }
445
705
  };
446
706
  var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
447
707
  const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
448
708
  const commit = stdout.trim();
449
- const catalogDirectory = import_node_path5.default.resolve(directory, source.path ?? ".");
450
- const relativeCatalogDirectory = import_node_path5.default.relative(directory, catalogDirectory);
451
- if (relativeCatalogDirectory.startsWith("..") || import_node_path5.default.isAbsolute(relativeCatalogDirectory)) {
709
+ const catalogDirectory = import_node_path6.default.resolve(directory, source.path ?? ".");
710
+ const relativeCatalogDirectory = import_node_path6.default.relative(directory, catalogDirectory);
711
+ if (relativeCatalogDirectory.startsWith("..") || import_node_path6.default.isAbsolute(relativeCatalogDirectory)) {
452
712
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
453
713
  }
454
714
  const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
455
715
  return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
456
716
  });
457
717
  var fetchPublishedIndex = async (source, ref, fetcher, token) => {
458
- const indexPath = import_node_path5.default.posix.join(source.path ?? ".", "catalog.index.json");
718
+ const indexPath = import_node_path6.default.posix.join(source.path ?? ".", "catalog.index.json");
459
719
  const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
460
720
  if (!bytes) return void 0;
461
721
  const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
@@ -478,7 +738,7 @@ var createGitHubSourceProvider = (options = {}) => {
478
738
  },
479
739
  async fetchContent({ source, commit, path: artifactPath }) {
480
740
  assertSafeCatalogPath(source);
481
- const catalogPath = import_node_path5.default.posix.join(source.path ?? ".", artifactPath);
741
+ const catalogPath = import_node_path6.default.posix.join(source.path ?? ".", artifactPath);
482
742
  const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
483
743
  if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
484
744
  return content;
@@ -516,6 +776,16 @@ ${details}`);
516
776
  this.conflicts = conflicts;
517
777
  }
518
778
  };
779
+ var FederationDiagnosticError = class extends Error {
780
+ diagnostics;
781
+ constructor(diagnostics) {
782
+ const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
783
+ super(`Federation diagnostics prevent hydration:
784
+ ${details}`);
785
+ this.name = "FederationDiagnosticError";
786
+ this.diagnostics = diagnostics;
787
+ }
788
+ };
519
789
  var validateSources = (sources) => {
520
790
  const ids = /* @__PURE__ */ new Set();
521
791
  for (const source of sources) {
@@ -524,19 +794,46 @@ var validateSources = (sources) => {
524
794
  ids.add(source.id);
525
795
  }
526
796
  };
797
+ var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
798
+ const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
799
+ const externalsByPointer = /* @__PURE__ */ new Map();
800
+ for (const edge of edges.filter((edge2) => edge2.status === "external")) {
801
+ const version = edge.pointer ?? void 0;
802
+ const key = `${edge.to}@${version ?? ""}`;
803
+ const external = externalsByPointer.get(key) ?? {
804
+ id: edge.to,
805
+ ...version === void 0 ? {} : { version },
806
+ referencedBy: []
807
+ };
808
+ if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
809
+ externalsByPointer.set(key, external);
810
+ }
811
+ return {
812
+ ...graph,
813
+ entities: [
814
+ ...graph.entities,
815
+ ...ownershipGraph.entities.filter(
816
+ (entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
817
+ )
818
+ ],
819
+ edges,
820
+ conflicts: ownershipGraph.conflicts,
821
+ externals: [...externalsByPointer.values()]
822
+ };
823
+ };
527
824
  var writeLock = async (lockPath, lock) => {
528
825
  const temporaryPath = `${lockPath}.tmp-${process.pid}`;
529
826
  try {
530
- await import_promises6.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
827
+ await import_promises7.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
531
828
  `, "utf8");
532
- await import_promises6.default.rename(temporaryPath, lockPath);
829
+ await import_promises7.default.rename(temporaryPath, lockPath);
533
830
  } finally {
534
- await import_promises6.default.rm(temporaryPath, { force: true });
831
+ await import_promises7.default.rm(temporaryPath, { force: true });
535
832
  }
536
833
  };
537
834
  var readLock = async (lockPath) => {
538
835
  try {
539
- return JSON.parse(await import_promises6.default.readFile(lockPath, "utf8"));
836
+ return JSON.parse(await import_promises7.default.readFile(lockPath, "utf8"));
540
837
  } catch (error) {
541
838
  if (error.code === "ENOENT") return void 0;
542
839
  throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
@@ -544,7 +841,7 @@ var readLock = async (lockPath) => {
544
841
  };
545
842
  var pathExists2 = async (filePath) => {
546
843
  try {
547
- await import_promises6.default.access(filePath);
844
+ await import_promises7.default.access(filePath);
548
845
  return true;
549
846
  } catch (error) {
550
847
  if (error.code === "ENOENT") return false;
@@ -552,20 +849,27 @@ var pathExists2 = async (filePath) => {
552
849
  }
553
850
  };
554
851
  var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
555
- const outDir = import_node_path6.default.join(projectDirectory, "federated");
556
- const lockPath = import_node_path6.default.join(projectDirectory, "eventcatalog.lock");
852
+ const outDir = import_node_path7.default.join(projectDirectory, "federated");
853
+ const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
557
854
  const previousLock = await readLock(lockPath);
558
855
  const hadFederatedOutput = await pathExists2(outDir);
559
856
  const hadLock = previousLock !== void 0;
560
857
  if (!hadFederatedOutput && !hadLock) return;
561
- await import_promises6.default.rm(outDir, { recursive: true, force: true });
562
- const publicResult = await composePublicAssets({
858
+ const publicResult = await withFederationOutputTransaction(
563
859
  projectDirectory,
564
- federatedDirectory: outDir,
565
- assets: [],
566
- previousFiles: previousLock?.publicFiles
567
- });
568
- await import_promises6.default.rm(lockPath, { force: true });
860
+ outDir,
861
+ Object.keys(previousLock?.publicFiles ?? {}),
862
+ async () => {
863
+ const result = await composePublicAssets({
864
+ projectDirectory,
865
+ federatedDirectory: outDir,
866
+ assets: [],
867
+ previousFiles: previousLock?.publicFiles
868
+ });
869
+ await import_promises7.default.rm(lockPath, { force: true });
870
+ return result;
871
+ }
872
+ );
569
873
  onProgress?.({
570
874
  type: "cleanup:complete",
571
875
  federated: hadFederatedOutput,
@@ -587,6 +891,7 @@ var federateCatalog = async (projectDirectory, options = {}) => {
587
891
  );
588
892
  }
589
893
  validateSources(sources);
894
+ const rules = resolveFederationRules(config.federation?.rules);
590
895
  if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
591
896
  const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
592
897
  const resolvedSources = [];
@@ -610,8 +915,8 @@ var federateCatalog = async (projectDirectory, options = {}) => {
610
915
  throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
611
916
  }
612
917
  }
613
- const outDir = import_node_path6.default.join(projectDirectory, "federated");
614
- const lockPath = import_node_path6.default.join(projectDirectory, "eventcatalog.lock");
918
+ const outDir = import_node_path7.default.join(projectDirectory, "federated");
919
+ const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
615
920
  const previousLock = await readLock(lockPath);
616
921
  const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
617
922
  const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
@@ -625,60 +930,79 @@ var federateCatalog = async (projectDirectory, options = {}) => {
625
930
  options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
626
931
  options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
627
932
  const ownershipGraph = (0, import_sdk3.resolve)([localIndex, ...remoteIndexes]);
628
- if (ownershipGraph.conflicts.length > 0) {
629
- options.onProgress?.({ type: "resolved", graph: ownershipGraph });
630
- throw new FederationConflictError(ownershipGraph.conflicts);
631
- }
632
933
  const graph = (0, import_sdk3.resolve)(remoteIndexes);
633
- options.onProgress?.({ type: "resolved", graph });
634
- const sourcesById = new Map(sources.map((source) => [source.id, source]));
635
- options.onProgress?.({ type: "hydrating", outDir });
636
- let hydratedFiles = 0;
637
- let cachedFiles = 0;
638
- const hydrateResult = await (0, import_sdk3.hydrate)(graph, {
934
+ const diagnosticGraph = createDiagnosticGraph(
935
+ graph,
936
+ ownershipGraph,
937
+ new Set(remoteIndexes.map((index) => index.source)),
938
+ localIndex.source
939
+ );
940
+ const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
941
+ options.onProgress?.({ type: "resolved", graph, diagnostics });
942
+ const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
943
+ if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
944
+ const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
945
+ if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
946
+ const publicPaths = [
947
+ ...Object.keys(previousLock?.publicFiles ?? {}),
948
+ ...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
949
+ ];
950
+ const { hydrateResult, publicResult } = await withFederationOutputTransaction(
951
+ projectDirectory,
639
952
  outDir,
640
- cache: createFederationContentCache(projectDirectory, {
641
- read: options.useCache !== false,
642
- onHit: () => {
643
- cachedFiles += 1;
644
- options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
645
- }
646
- }),
647
- modes: Object.fromEntries(sources.map((source) => [source.id, source.mode ?? "hydrate"])),
648
- fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
649
- const source = sourcesById.get(sourceId);
650
- if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
651
- const content = await provider.fetchContent({ source, commit, path: artifactPath });
652
- hydratedFiles += 1;
653
- options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
654
- return content;
953
+ publicPaths,
954
+ async () => {
955
+ const sourcesById = new Map(sources.map((source) => [source.id, source]));
956
+ options.onProgress?.({ type: "hydrating", outDir });
957
+ let hydratedFiles = 0;
958
+ let cachedFiles = 0;
959
+ const hydrateResult2 = await (0, import_sdk3.hydrate)(graph, {
960
+ outDir,
961
+ cache: createFederationContentCache(projectDirectory, {
962
+ read: options.useCache !== false,
963
+ onHit: () => {
964
+ cachedFiles += 1;
965
+ options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
966
+ }
967
+ }),
968
+ fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
969
+ const source = sourcesById.get(sourceId);
970
+ if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
971
+ const content = await provider.fetchContent({ source, commit, path: artifactPath });
972
+ hydratedFiles += 1;
973
+ options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
974
+ return content;
975
+ }
976
+ });
977
+ const publicResult2 = await composePublicAssets({
978
+ projectDirectory,
979
+ federatedDirectory: outDir,
980
+ assets: graph.assets,
981
+ previousFiles: previousLock?.publicFiles,
982
+ collisionPaths: new Set(
983
+ graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
984
+ )
985
+ });
986
+ options.onProgress?.({ type: "public:complete", result: publicResult2 });
987
+ const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
988
+ await writeLock(lockPath, {
989
+ lockVersion: 1,
990
+ sources: resolvedSources.map(({ config: source, resolved }) => ({
991
+ id: source.id,
992
+ digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
993
+ commit: resolved.commit,
994
+ resolvedAt
995
+ })).sort((left, right) => left.id.localeCompare(right.id)),
996
+ publicFiles: publicResult2.files
997
+ });
998
+ return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
655
999
  }
656
- });
657
- const publicResult = await composePublicAssets({
658
- projectDirectory,
659
- federatedDirectory: outDir,
660
- assets: graph.assets,
661
- previousFiles: previousLock?.publicFiles,
662
- collisionPaths: new Set(
663
- graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
664
- )
665
- });
666
- options.onProgress?.({ type: "public:complete", result: publicResult });
667
- const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
668
- await writeLock(lockPath, {
669
- lockVersion: 1,
670
- sources: resolvedSources.map(({ config: source, resolved }) => ({
671
- id: source.id,
672
- digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
673
- commit: resolved.commit,
674
- resolvedAt
675
- })).sort((left, right) => left.id.localeCompare(right.id)),
676
- publicFiles: publicResult.files
677
- });
1000
+ );
678
1001
  const result = {
679
1002
  sources: sources.length,
680
1003
  resources,
681
1004
  graph,
1005
+ diagnostics,
682
1006
  hydrate: hydrateResult,
683
1007
  public: publicResult,
684
1008
  outDir,
@@ -690,5 +1014,6 @@ var federateCatalog = async (projectDirectory, options = {}) => {
690
1014
  // Annotate the CommonJS export names for ESM import in node:
691
1015
  0 && (module.exports = {
692
1016
  FederationConflictError,
1017
+ FederationDiagnosticError,
693
1018
  federateCatalog
694
1019
  });