@eventcatalog/core 4.7.4 → 4.7.6

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 (59) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +3 -2
  3. package/dist/analytics/analytics.cjs +1 -1
  4. package/dist/analytics/analytics.js +2 -2
  5. package/dist/analytics/log-build.cjs +1 -1
  6. package/dist/analytics/log-build.js +3 -3
  7. package/dist/{chunk-7QTCSRAC.js → chunk-6BBLQLGL.js} +1 -1
  8. package/dist/chunk-DDALKGTF.js +7 -0
  9. package/dist/chunk-JJPB6EOZ.js +159 -0
  10. package/dist/chunk-MLIR3NL4.js +279 -0
  11. package/dist/{chunk-ZXVQXBTT.js → chunk-MNAPKH63.js} +24 -1
  12. package/dist/{chunk-TLWM7WRZ.js → chunk-Q534DWPZ.js} +1 -1
  13. package/dist/{chunk-ZIABX6SP.js → chunk-RZAJNR7O.js} +1 -1
  14. package/dist/chunk-WRNH5C3U.js +118 -0
  15. package/dist/{chunk-FDXJIZ74.js → chunk-YXANZO6Y.js} +1 -1
  16. package/dist/constants.cjs +1 -1
  17. package/dist/constants.js +1 -1
  18. package/dist/eventcatalog.cjs +560 -211
  19. package/dist/eventcatalog.config.d.cts +9 -3
  20. package/dist/eventcatalog.config.d.ts +9 -3
  21. package/dist/eventcatalog.js +40 -42
  22. package/dist/federation/content-cache.d.cts +5 -0
  23. package/dist/federation/content-cache.d.ts +5 -0
  24. package/dist/federation/diagnostics.cjs +187 -0
  25. package/dist/federation/diagnostics.d.cts +28 -0
  26. package/dist/federation/diagnostics.d.ts +28 -0
  27. package/dist/federation/diagnostics.js +14 -0
  28. package/dist/federation/entitlement.cjs +31 -0
  29. package/dist/federation/entitlement.d.cts +12 -0
  30. package/dist/federation/entitlement.d.ts +12 -0
  31. package/dist/federation/entitlement.js +6 -0
  32. package/dist/federation/federate.cjs +447 -118
  33. package/dist/federation/federate.d.cts +13 -2
  34. package/dist/federation/federate.d.ts +13 -2
  35. package/dist/federation/federate.js +9 -4
  36. package/dist/federation/filesystem-source-provider.d.cts +5 -0
  37. package/dist/federation/filesystem-source-provider.d.ts +5 -0
  38. package/dist/federation/github-source-provider.d.cts +5 -0
  39. package/dist/federation/github-source-provider.d.ts +5 -0
  40. package/dist/federation/output-transaction.cjs +152 -0
  41. package/dist/federation/output-transaction.d.cts +7 -0
  42. package/dist/federation/output-transaction.d.ts +7 -0
  43. package/dist/federation/output-transaction.js +6 -0
  44. package/dist/federation/public-assets.d.cts +5 -0
  45. package/dist/federation/public-assets.d.ts +5 -0
  46. package/dist/federation/source-provider.d.cts +5 -0
  47. package/dist/federation/source-provider.d.ts +5 -0
  48. package/dist/federation/source-provider.js +2 -2
  49. package/dist/federation/types.d.cts +5 -0
  50. package/dist/federation/types.d.ts +5 -0
  51. package/dist/generate.cjs +24 -1
  52. package/dist/generate.js +3 -3
  53. package/dist/utils/cli-logger.cjs +24 -1
  54. package/dist/utils/cli-logger.d.cts +6 -0
  55. package/dist/utils/cli-logger.d.ts +6 -0
  56. package/dist/utils/cli-logger.js +2 -2
  57. package/package.json +4 -4
  58. package/dist/chunk-RIUHZZRA.js +0 -204
  59. package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +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,291 @@ 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/entitlement.ts
298
+ var import_license = require("@eventcatalog/license");
299
+ var isFederationEnabled = () => (0, import_license.isEventCatalogScaleEnabled)();
300
+
301
+ // src/federation/output-transaction.ts
154
302
  var import_promises3 = __toESM(require("fs/promises"), 1);
155
303
  var import_node_path3 = __toESM(require("path"), 1);
304
+ var getSafePublicPath = (publicDirectory, relativePath) => {
305
+ const normalizedPath = import_node_path3.default.posix.normalize(relativePath);
306
+ 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("../");
307
+ if (unsafe) return void 0;
308
+ const destinationPath = import_node_path3.default.resolve(publicDirectory, relativePath);
309
+ const relativeDestinationPath = import_node_path3.default.relative(publicDirectory, destinationPath);
310
+ return relativeDestinationPath !== "" && relativeDestinationPath !== ".." && !relativeDestinationPath.startsWith(`..${import_node_path3.default.sep}`) && !import_node_path3.default.isAbsolute(relativeDestinationPath) ? destinationPath : void 0;
311
+ };
312
+ var beginFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths) => {
313
+ const transactionDirectory = await import_promises3.default.mkdtemp(import_node_path3.default.join(projectDirectory, ".eventcatalog-federation-transaction-"));
314
+ const previousOutDir = import_node_path3.default.join(transactionDirectory, "federated");
315
+ const publicBackupDirectory = import_node_path3.default.join(transactionDirectory, "public");
316
+ const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
317
+ const publicSnapshots = [];
318
+ const missingPublicDirectories = /* @__PURE__ */ new Set();
319
+ let hadPreviousOutDir = false;
320
+ try {
321
+ for (const relativePath of new Set(relativePublicPaths)) {
322
+ const destinationPath = getSafePublicPath(publicDirectory, relativePath);
323
+ if (!destinationPath) continue;
324
+ try {
325
+ const stat = await import_promises3.default.lstat(destinationPath);
326
+ if (!stat.isFile()) continue;
327
+ const backupPath = import_node_path3.default.join(publicBackupDirectory, `${publicSnapshots.length}`);
328
+ await import_promises3.default.mkdir(import_node_path3.default.dirname(backupPath), { recursive: true });
329
+ await import_promises3.default.copyFile(destinationPath, backupPath);
330
+ publicSnapshots.push({ backupPath, destinationPath, state: "file" });
331
+ } catch (error) {
332
+ const code = error.code;
333
+ if (code === "ENOTDIR") continue;
334
+ if (code !== "ENOENT") throw error;
335
+ publicSnapshots.push({ destinationPath, state: "missing" });
336
+ let directory = import_node_path3.default.dirname(destinationPath);
337
+ while (directory !== publicDirectory) {
338
+ try {
339
+ await import_promises3.default.lstat(directory);
340
+ break;
341
+ } catch (directoryError) {
342
+ const directoryCode = directoryError.code;
343
+ if (directoryCode === "ENOTDIR") break;
344
+ if (directoryCode !== "ENOENT") throw directoryError;
345
+ missingPublicDirectories.add(directory);
346
+ directory = import_node_path3.default.dirname(directory);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ try {
352
+ await import_promises3.default.rename(outDir, previousOutDir);
353
+ hadPreviousOutDir = true;
354
+ } catch (error) {
355
+ if (error.code !== "ENOENT") throw error;
356
+ }
357
+ } catch (error) {
358
+ await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true });
359
+ throw error;
360
+ }
361
+ return {
362
+ commit: async () => {
363
+ await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
364
+ },
365
+ rollback: async () => {
366
+ const rollbackErrors = [];
367
+ const attempt = async (operation) => {
368
+ try {
369
+ await operation();
370
+ } catch (error) {
371
+ rollbackErrors.push(error);
372
+ }
373
+ };
374
+ await attempt(() => import_promises3.default.rm(outDir, { recursive: true, force: true }));
375
+ if (hadPreviousOutDir) await attempt(() => import_promises3.default.rename(previousOutDir, outDir));
376
+ for (const snapshot of publicSnapshots) {
377
+ if (snapshot.state === "missing") {
378
+ await attempt(() => import_promises3.default.rm(snapshot.destinationPath, { force: true }));
379
+ continue;
380
+ }
381
+ await attempt(async () => {
382
+ await import_promises3.default.mkdir(import_node_path3.default.dirname(snapshot.destinationPath), { recursive: true });
383
+ await import_promises3.default.copyFile(snapshot.backupPath, snapshot.destinationPath);
384
+ });
385
+ }
386
+ for (const directory of [...missingPublicDirectories].sort((left, right) => right.length - left.length)) {
387
+ await attempt(async () => {
388
+ try {
389
+ await import_promises3.default.rmdir(directory);
390
+ } catch (error) {
391
+ if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
392
+ }
393
+ });
394
+ }
395
+ await attempt(() => import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }));
396
+ if (rollbackErrors.length > 0) throw new AggregateError(rollbackErrors, "Failed to restore the previous federation output");
397
+ }
398
+ };
399
+ };
400
+ var withFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths, update) => {
401
+ const transaction = await beginFederationOutputTransaction(projectDirectory, outDir, relativePublicPaths);
402
+ try {
403
+ const result = await update();
404
+ await transaction.commit();
405
+ return result;
406
+ } catch (error) {
407
+ try {
408
+ await transaction.rollback();
409
+ } catch (rollbackError) {
410
+ throw new AggregateError([error, rollbackError], "Federation failed and the previous output could not be restored");
411
+ }
412
+ throw error;
413
+ }
414
+ };
415
+
416
+ // src/federation/public-assets.ts
417
+ var import_node_crypto2 = require("crypto");
418
+ var import_promises4 = __toESM(require("fs/promises"), 1);
419
+ var import_node_path4 = __toESM(require("path"), 1);
156
420
  var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
157
421
  var getFileHash = async (filePath) => {
158
422
  try {
159
- const stat = await import_promises3.default.lstat(filePath);
160
- return stat.isFile() ? getContentHash2(await import_promises3.default.readFile(filePath)) : void 0;
423
+ const stat = await import_promises4.default.lstat(filePath);
424
+ return stat.isFile() ? getContentHash2(await import_promises4.default.readFile(filePath)) : void 0;
161
425
  } catch (error) {
162
426
  if (error.code === "ENOENT") return void 0;
163
427
  throw error;
164
428
  }
165
429
  };
166
430
  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("../");
431
+ const normalizedPath = import_node_path4.default.posix.normalize(relativePath);
432
+ 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
433
  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)) {
434
+ const resolvedDirectory = import_node_path4.default.resolve(directory);
435
+ const resolvedPath = import_node_path4.default.resolve(resolvedDirectory, relativePath);
436
+ const relativeResolvedPath = import_node_path4.default.relative(resolvedDirectory, resolvedPath);
437
+ if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relativeResolvedPath)) {
174
438
  return void 0;
175
439
  }
176
440
  return resolvedPath;
@@ -178,23 +442,23 @@ var getSafePath = (directory, relativePath) => {
178
442
  var listFiles = async (directory, relativeDirectory = "") => {
179
443
  let entries;
180
444
  try {
181
- entries = await import_promises3.default.readdir(import_node_path3.default.join(directory, relativeDirectory), { withFileTypes: true });
445
+ entries = await import_promises4.default.readdir(import_node_path4.default.join(directory, relativeDirectory), { withFileTypes: true });
182
446
  } catch (error) {
183
447
  if (error.code === "ENOENT") return [];
184
448
  throw error;
185
449
  }
186
450
  const files = await Promise.all(
187
451
  entries.map(async (entry) => {
188
- const relativePath = import_node_path3.default.join(relativeDirectory, entry.name);
452
+ const relativePath = import_node_path4.default.join(relativeDirectory, entry.name);
189
453
  if (entry.isDirectory()) return listFiles(directory, relativePath);
190
- return entry.isFile() ? [relativePath.split(import_node_path3.default.sep).join("/")] : [];
454
+ return entry.isFile() ? [relativePath.split(import_node_path4.default.sep).join("/")] : [];
191
455
  })
192
456
  );
193
457
  return files.flat().sort();
194
458
  };
195
459
  var pathExists = async (filePath) => {
196
460
  try {
197
- await import_promises3.default.lstat(filePath);
461
+ await import_promises4.default.lstat(filePath);
198
462
  return true;
199
463
  } catch (error) {
200
464
  if (error.code === "ENOENT") return false;
@@ -202,27 +466,27 @@ var pathExists = async (filePath) => {
202
466
  }
203
467
  };
204
468
  var hasBlockingParent = async (publicDirectory, destinationPath) => {
205
- let currentPath = import_node_path3.default.dirname(destinationPath);
469
+ let currentPath = import_node_path4.default.dirname(destinationPath);
206
470
  while (currentPath !== publicDirectory) {
207
471
  try {
208
- if (!(await import_promises3.default.lstat(currentPath)).isDirectory()) return true;
472
+ if (!(await import_promises4.default.lstat(currentPath)).isDirectory()) return true;
209
473
  } catch (error) {
210
474
  if (error.code !== "ENOENT") throw error;
211
475
  }
212
- currentPath = import_node_path3.default.dirname(currentPath);
476
+ currentPath = import_node_path4.default.dirname(currentPath);
213
477
  }
214
478
  return false;
215
479
  };
216
480
  var pruneEmptyDirectories = async (publicDirectory, filePath) => {
217
- let currentPath = import_node_path3.default.dirname(filePath);
481
+ let currentPath = import_node_path4.default.dirname(filePath);
218
482
  while (currentPath !== publicDirectory) {
219
483
  try {
220
- await import_promises3.default.rmdir(currentPath);
484
+ await import_promises4.default.rmdir(currentPath);
221
485
  } catch (error) {
222
486
  if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
223
487
  if (error.code === "ENOTEMPTY") return;
224
488
  }
225
- currentPath = import_node_path3.default.dirname(currentPath);
489
+ currentPath = import_node_path4.default.dirname(currentPath);
226
490
  }
227
491
  };
228
492
  var composePublicAssets = async ({
@@ -232,8 +496,8 @@ var composePublicAssets = async ({
232
496
  previousFiles = {},
233
497
  collisionPaths = /* @__PURE__ */ new Set()
234
498
  }) => {
235
- const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
236
- const federatedPublicDirectory = import_node_path3.default.resolve(federatedDirectory, "public");
499
+ const publicDirectory = import_node_path4.default.resolve(projectDirectory, "public");
500
+ const federatedPublicDirectory = import_node_path4.default.resolve(federatedDirectory, "public");
237
501
  const sourceFiles = await listFiles(federatedPublicDirectory);
238
502
  const sourceFileSet = new Set(sourceFiles);
239
503
  const managedFiles = /* @__PURE__ */ new Set();
@@ -246,7 +510,7 @@ var composePublicAssets = async ({
246
510
  if (sourceFileSet.has(relativePath)) continue;
247
511
  const destinationPath = getSafePath(publicDirectory, relativePath);
248
512
  if (!destinationPath) continue;
249
- await import_promises3.default.rm(destinationPath, { force: true });
513
+ await import_promises4.default.rm(destinationPath, { force: true });
250
514
  await pruneEmptyDirectories(publicDirectory, destinationPath);
251
515
  removed += 1;
252
516
  }
@@ -268,31 +532,31 @@ var composePublicAssets = async ({
268
532
  }
269
533
  const asset = publicAssetsByPath.get(relativePath);
270
534
  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);
535
+ const content = await import_promises4.default.readFile(sourcePath);
536
+ await import_promises4.default.mkdir(import_node_path4.default.dirname(destinationPath), { recursive: true });
537
+ await import_promises4.default.writeFile(destinationPath, content);
274
538
  files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
275
539
  copied += 1;
276
540
  if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
277
541
  }
278
- await import_promises3.default.rm(federatedPublicDirectory, { recursive: true, force: true });
542
+ await import_promises4.default.rm(federatedPublicDirectory, { recursive: true, force: true });
279
543
  return { files, copied, skipped, overwritten, removed };
280
544
  };
281
545
 
282
546
  // src/federation/filesystem-source-provider.ts
283
547
  var import_node_crypto3 = require("crypto");
284
- var import_promises4 = __toESM(require("fs/promises"), 1);
285
- var import_node_path4 = __toESM(require("path"), 1);
548
+ var import_promises5 = __toESM(require("fs/promises"), 1);
549
+ var import_node_path5 = __toESM(require("path"), 1);
286
550
  var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
287
551
  var FILESYSTEM_SOURCE_PREFIX = "file:";
288
552
  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);
553
+ const relativePath = import_node_path5.default.relative(directory, target);
554
+ return relativePath === "" || !relativePath.startsWith(`..${import_node_path5.default.sep}`) && relativePath !== ".." && !import_node_path5.default.isAbsolute(relativePath);
291
555
  };
292
556
  var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
293
557
  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 === "");
558
+ const normalizedPath = import_node_path5.default.posix.normalize(portablePath);
559
+ 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
560
  if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
297
561
  return normalizedPath;
298
562
  };
@@ -302,22 +566,22 @@ var getSourceRoot = (projectDirectory, source) => {
302
566
  }
303
567
  const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
304
568
  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);
569
+ return import_node_path5.default.resolve(projectDirectory, locator);
306
570
  };
307
571
  var getCatalogDirectory = async (projectDirectory, source) => {
308
572
  if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
309
573
  const sourceRoot = getSourceRoot(projectDirectory, source);
310
574
  const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
311
- const catalogDirectory = import_node_path4.default.resolve(sourceRoot, ...catalogPath.split("/"));
575
+ const catalogDirectory = import_node_path5.default.resolve(sourceRoot, ...catalogPath.split("/"));
312
576
  if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
313
577
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
314
578
  }
315
579
  try {
316
- const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises4.default.realpath(sourceRoot), import_promises4.default.realpath(catalogDirectory)]);
580
+ const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises5.default.realpath(sourceRoot), import_promises5.default.realpath(catalogDirectory)]);
317
581
  if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
318
582
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
319
583
  }
320
- if (!(await import_promises4.default.stat(realCatalogDirectory)).isDirectory()) {
584
+ if (!(await import_promises5.default.stat(realCatalogDirectory)).isDirectory()) {
321
585
  throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
322
586
  }
323
587
  return realCatalogDirectory;
@@ -330,12 +594,12 @@ var getCatalogDirectory = async (projectDirectory, source) => {
330
594
  };
331
595
  var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
332
596
  const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
333
- const filePath = import_node_path4.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
597
+ const filePath = import_node_path5.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
334
598
  if (!isWithinDirectory(catalogDirectory, filePath)) {
335
599
  throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
336
600
  }
337
601
  try {
338
- const realFilePath = await import_promises4.default.realpath(filePath);
602
+ const realFilePath = await import_promises5.default.realpath(filePath);
339
603
  if (!isWithinDirectory(catalogDirectory, realFilePath)) {
340
604
  throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
341
605
  }
@@ -362,15 +626,15 @@ var createFileSystemSourceProvider = (projectDirectory) => ({
362
626
  },
363
627
  async fetchContent({ source, path: artifactPath }) {
364
628
  const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
365
- return import_promises4.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
629
+ return import_promises5.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
366
630
  }
367
631
  });
368
632
 
369
633
  // src/federation/github-source-provider.ts
370
634
  var import_node_child_process = require("child_process");
371
- var import_promises5 = __toESM(require("fs/promises"), 1);
635
+ var import_promises6 = __toESM(require("fs/promises"), 1);
372
636
  var import_node_os2 = __toESM(require("os"), 1);
373
- var import_node_path5 = __toESM(require("path"), 1);
637
+ var import_node_path6 = __toESM(require("path"), 1);
374
638
  var import_node_util = require("util");
375
639
  var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
376
640
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -381,8 +645,8 @@ var parseGitHubSource = (source) => {
381
645
  };
382
646
  var assertSafeCatalogPath = (source) => {
383
647
  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("../")) {
648
+ const normalized = import_node_path6.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
649
+ if (catalogPath.includes("\\") || import_node_path6.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
386
650
  throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
387
651
  }
388
652
  };
@@ -425,8 +689,8 @@ var getGitEnvironment = (token) => {
425
689
  };
426
690
  var createCheckout = (executeFile, token) => async (source, ref, callback) => {
427
691
  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-"));
692
+ const catalogPath = import_node_path6.default.posix.normalize(source.path ?? ".");
693
+ const directory = await import_promises6.default.mkdtemp(import_node_path6.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
430
694
  const env = getGitEnvironment(token);
431
695
  try {
432
696
  const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
@@ -440,22 +704,22 @@ var createCheckout = (executeFile, token) => async (source, ref, callback) => {
440
704
  await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
441
705
  return await callback(directory);
442
706
  } finally {
443
- await import_promises5.default.rm(directory, { recursive: true, force: true });
707
+ await import_promises6.default.rm(directory, { recursive: true, force: true });
444
708
  }
445
709
  };
446
710
  var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
447
711
  const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
448
712
  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)) {
713
+ const catalogDirectory = import_node_path6.default.resolve(directory, source.path ?? ".");
714
+ const relativeCatalogDirectory = import_node_path6.default.relative(directory, catalogDirectory);
715
+ if (relativeCatalogDirectory.startsWith("..") || import_node_path6.default.isAbsolute(relativeCatalogDirectory)) {
452
716
  throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
453
717
  }
454
718
  const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
455
719
  return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
456
720
  });
457
721
  var fetchPublishedIndex = async (source, ref, fetcher, token) => {
458
- const indexPath = import_node_path5.default.posix.join(source.path ?? ".", "catalog.index.json");
722
+ const indexPath = import_node_path6.default.posix.join(source.path ?? ".", "catalog.index.json");
459
723
  const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
460
724
  if (!bytes) return void 0;
461
725
  const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
@@ -478,7 +742,7 @@ var createGitHubSourceProvider = (options = {}) => {
478
742
  },
479
743
  async fetchContent({ source, commit, path: artifactPath }) {
480
744
  assertSafeCatalogPath(source);
481
- const catalogPath = import_node_path5.default.posix.join(source.path ?? ".", artifactPath);
745
+ const catalogPath = import_node_path6.default.posix.join(source.path ?? ".", artifactPath);
482
746
  const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
483
747
  if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
484
748
  return content;
@@ -516,6 +780,16 @@ ${details}`);
516
780
  this.conflicts = conflicts;
517
781
  }
518
782
  };
783
+ var FederationDiagnosticError = class extends Error {
784
+ diagnostics;
785
+ constructor(diagnostics) {
786
+ const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
787
+ super(`Federation diagnostics prevent hydration:
788
+ ${details}`);
789
+ this.name = "FederationDiagnosticError";
790
+ this.diagnostics = diagnostics;
791
+ }
792
+ };
519
793
  var validateSources = (sources) => {
520
794
  const ids = /* @__PURE__ */ new Set();
521
795
  for (const source of sources) {
@@ -524,19 +798,46 @@ var validateSources = (sources) => {
524
798
  ids.add(source.id);
525
799
  }
526
800
  };
801
+ var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
802
+ const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
803
+ const externalsByPointer = /* @__PURE__ */ new Map();
804
+ for (const edge of edges.filter((edge2) => edge2.status === "external")) {
805
+ const version = edge.pointer ?? void 0;
806
+ const key = `${edge.to}@${version ?? ""}`;
807
+ const external = externalsByPointer.get(key) ?? {
808
+ id: edge.to,
809
+ ...version === void 0 ? {} : { version },
810
+ referencedBy: []
811
+ };
812
+ if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
813
+ externalsByPointer.set(key, external);
814
+ }
815
+ return {
816
+ ...graph,
817
+ entities: [
818
+ ...graph.entities,
819
+ ...ownershipGraph.entities.filter(
820
+ (entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
821
+ )
822
+ ],
823
+ edges,
824
+ conflicts: ownershipGraph.conflicts,
825
+ externals: [...externalsByPointer.values()]
826
+ };
827
+ };
527
828
  var writeLock = async (lockPath, lock) => {
528
829
  const temporaryPath = `${lockPath}.tmp-${process.pid}`;
529
830
  try {
530
- await import_promises6.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
831
+ await import_promises7.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
531
832
  `, "utf8");
532
- await import_promises6.default.rename(temporaryPath, lockPath);
833
+ await import_promises7.default.rename(temporaryPath, lockPath);
533
834
  } finally {
534
- await import_promises6.default.rm(temporaryPath, { force: true });
835
+ await import_promises7.default.rm(temporaryPath, { force: true });
535
836
  }
536
837
  };
537
838
  var readLock = async (lockPath) => {
538
839
  try {
539
- return JSON.parse(await import_promises6.default.readFile(lockPath, "utf8"));
840
+ return JSON.parse(await import_promises7.default.readFile(lockPath, "utf8"));
540
841
  } catch (error) {
541
842
  if (error.code === "ENOENT") return void 0;
542
843
  throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
@@ -544,7 +845,7 @@ var readLock = async (lockPath) => {
544
845
  };
545
846
  var pathExists2 = async (filePath) => {
546
847
  try {
547
- await import_promises6.default.access(filePath);
848
+ await import_promises7.default.access(filePath);
548
849
  return true;
549
850
  } catch (error) {
550
851
  if (error.code === "ENOENT") return false;
@@ -552,20 +853,27 @@ var pathExists2 = async (filePath) => {
552
853
  }
553
854
  };
554
855
  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");
856
+ const outDir = import_node_path7.default.join(projectDirectory, "federated");
857
+ const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
557
858
  const previousLock = await readLock(lockPath);
558
859
  const hadFederatedOutput = await pathExists2(outDir);
559
860
  const hadLock = previousLock !== void 0;
560
861
  if (!hadFederatedOutput && !hadLock) return;
561
- await import_promises6.default.rm(outDir, { recursive: true, force: true });
562
- const publicResult = await composePublicAssets({
862
+ const publicResult = await withFederationOutputTransaction(
563
863
  projectDirectory,
564
- federatedDirectory: outDir,
565
- assets: [],
566
- previousFiles: previousLock?.publicFiles
567
- });
568
- await import_promises6.default.rm(lockPath, { force: true });
864
+ outDir,
865
+ Object.keys(previousLock?.publicFiles ?? {}),
866
+ async () => {
867
+ const result = await composePublicAssets({
868
+ projectDirectory,
869
+ federatedDirectory: outDir,
870
+ assets: [],
871
+ previousFiles: previousLock?.publicFiles
872
+ });
873
+ await import_promises7.default.rm(lockPath, { force: true });
874
+ return result;
875
+ }
876
+ );
569
877
  onProgress?.({
570
878
  type: "cleanup:complete",
571
879
  federated: hadFederatedOutput,
@@ -581,12 +889,13 @@ var federateCatalog = async (projectDirectory, options = {}) => {
581
889
  await cleanupPreviousFederation(projectDirectory, options.onProgress);
582
890
  return null;
583
891
  }
584
- if (options.isFederationEnabled && !await options.isFederationEnabled()) {
892
+ if (!await isFederationEnabled()) {
585
893
  throw new Error(
586
894
  "Cannot federate catalogs: EventCatalog federation is an Enterprise feature. Visit https://www.eventcatalog.dev/pricing to enable federation."
587
895
  );
588
896
  }
589
897
  validateSources(sources);
898
+ const rules = resolveFederationRules(config.federation?.rules);
590
899
  if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
591
900
  const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
592
901
  const resolvedSources = [];
@@ -610,8 +919,8 @@ var federateCatalog = async (projectDirectory, options = {}) => {
610
919
  throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
611
920
  }
612
921
  }
613
- const outDir = import_node_path6.default.join(projectDirectory, "federated");
614
- const lockPath = import_node_path6.default.join(projectDirectory, "eventcatalog.lock");
922
+ const outDir = import_node_path7.default.join(projectDirectory, "federated");
923
+ const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
615
924
  const previousLock = await readLock(lockPath);
616
925
  const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
617
926
  const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
@@ -625,60 +934,79 @@ var federateCatalog = async (projectDirectory, options = {}) => {
625
934
  options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
626
935
  options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
627
936
  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
937
  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, {
938
+ const diagnosticGraph = createDiagnosticGraph(
939
+ graph,
940
+ ownershipGraph,
941
+ new Set(remoteIndexes.map((index) => index.source)),
942
+ localIndex.source
943
+ );
944
+ const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
945
+ options.onProgress?.({ type: "resolved", graph, diagnostics });
946
+ const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
947
+ if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
948
+ const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
949
+ if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
950
+ const publicPaths = [
951
+ ...Object.keys(previousLock?.publicFiles ?? {}),
952
+ ...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
953
+ ];
954
+ const { hydrateResult, publicResult } = await withFederationOutputTransaction(
955
+ projectDirectory,
639
956
  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;
957
+ publicPaths,
958
+ async () => {
959
+ const sourcesById = new Map(sources.map((source) => [source.id, source]));
960
+ options.onProgress?.({ type: "hydrating", outDir });
961
+ let hydratedFiles = 0;
962
+ let cachedFiles = 0;
963
+ const hydrateResult2 = await (0, import_sdk3.hydrate)(graph, {
964
+ outDir,
965
+ cache: createFederationContentCache(projectDirectory, {
966
+ read: options.useCache !== false,
967
+ onHit: () => {
968
+ cachedFiles += 1;
969
+ options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
970
+ }
971
+ }),
972
+ fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
973
+ const source = sourcesById.get(sourceId);
974
+ if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
975
+ const content = await provider.fetchContent({ source, commit, path: artifactPath });
976
+ hydratedFiles += 1;
977
+ options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
978
+ return content;
979
+ }
980
+ });
981
+ const publicResult2 = await composePublicAssets({
982
+ projectDirectory,
983
+ federatedDirectory: outDir,
984
+ assets: graph.assets,
985
+ previousFiles: previousLock?.publicFiles,
986
+ collisionPaths: new Set(
987
+ graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
988
+ )
989
+ });
990
+ options.onProgress?.({ type: "public:complete", result: publicResult2 });
991
+ const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
992
+ await writeLock(lockPath, {
993
+ lockVersion: 1,
994
+ sources: resolvedSources.map(({ config: source, resolved }) => ({
995
+ id: source.id,
996
+ digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
997
+ commit: resolved.commit,
998
+ resolvedAt
999
+ })).sort((left, right) => left.id.localeCompare(right.id)),
1000
+ publicFiles: publicResult2.files
1001
+ });
1002
+ return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
655
1003
  }
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
- });
1004
+ );
678
1005
  const result = {
679
1006
  sources: sources.length,
680
1007
  resources,
681
1008
  graph,
1009
+ diagnostics,
682
1010
  hydrate: hydrateResult,
683
1011
  public: publicResult,
684
1012
  outDir,
@@ -690,5 +1018,6 @@ var federateCatalog = async (projectDirectory, options = {}) => {
690
1018
  // Annotate the CommonJS export names for ESM import in node:
691
1019
  0 && (module.exports = {
692
1020
  FederationConflictError,
1021
+ FederationDiagnosticError,
693
1022
  federateCatalog
694
1023
  });