@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
package/LICENSE ADDED
@@ -0,0 +1,95 @@
1
+ EventCatalog Core is distributed under a mixed-license model.
2
+
3
+ The EventCatalog Commercial License below applies to these source paths:
4
+
5
+ - `eventcatalog/src/enterprise/**`
6
+ - `src/federation/**`
7
+
8
+ The EventCatalog Commercial License also applies to any portion of a compiled,
9
+ bundled, copied, minified, or otherwise generated artifact derived from those
10
+ paths, regardless of its output location, including artifacts under `dist/**`
11
+ and `bin/**`.
12
+
13
+ The MIT License below applies to all remaining portions of this package.
14
+
15
+ MIT License
16
+
17
+ Copyright (c) 2022-2026 boyney123
18
+
19
+ Permission is hereby granted, free of charge, to any person obtaining a copy
20
+ of this software and associated documentation files (the "Software"), to deal
21
+ in the Software without restriction, including without limitation the rights
22
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23
+ copies of the Software, and to permit persons to whom the Software is
24
+ furnished to do so, subject to the following conditions:
25
+
26
+ The above copyright notice and this permission notice shall be included in all
27
+ copies or substantial portions of the Software.
28
+
29
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ SOFTWARE.
36
+
37
+ ---
38
+
39
+ The EventCatalog Commercial License (the "Commercial License")
40
+ Copyright (c) 2022-2026 EventCatalog Ltd
41
+
42
+ With regard to the EventCatalog Software:
43
+
44
+ This software and associated documentation files (the "Software") may only be
45
+ used in production, if you (and any entity that you represent) have agreed to,
46
+ and are in compliance with, the EventCatalog Terms of Service available
47
+ at https://www.eventcatalog.dev/terms, or other agreements governing the use of
48
+ the Software, as mutually agreed by you and EventCatalog Ltd ("EventCatalog"),
49
+ and otherwise have a valid EventCatalog Starter, Scale, or Enterprise
50
+ subscription ("Commercial Subscription").
51
+
52
+ Subject to the foregoing sentence, you are free to modify this Software and
53
+ publish patches to the Software. You agree that EventCatalog and/or its
54
+ licensors (as applicable) retain all right, title and interest in and to all
55
+ such modifications and/or patches, and all such modifications and/or patches
56
+ may only be used, copied, modified, displayed, distributed, or otherwise
57
+ exploited with a valid Commercial Subscription.
58
+
59
+ A 14-day free trial of premium features is available at
60
+ https://eventcatalog.cloud/ to help you evaluate the Software before purchasing
61
+ a Commercial Subscription.
62
+
63
+ Notwithstanding the foregoing, you may copy and modify the Software for
64
+ development and testing purposes, without requiring a subscription. You agree
65
+ that EventCatalog and/or its licensors (as applicable) retain all right, title
66
+ and interest in and to all such modifications. You are not granted any other
67
+ rights beyond what is expressly stated herein. Subject to the foregoing, it is
68
+ forbidden to copy, merge, publish, distribute, sublicense, and/or sell the
69
+ Software.
70
+
71
+ You may not move, change, disable, or circumvent the license key functionality
72
+ in the Software, and you may not remove or obscure any functionality in the
73
+ Software that is protected by the license key.
74
+
75
+ This Software is source-available for the purposes of transparency and
76
+ auditability. Source availability does not constitute a grant of rights to use
77
+ the Software in production without a valid Commercial Subscription.
78
+
79
+ This Commercial License applies only to the part of this Software that is not
80
+ distributed under the MIT license. Any part of this Software distributed under
81
+ the MIT license is copyrighted under the MIT license. The full text of this
82
+ Commercial License shall be included in all copies or substantial portions of
83
+ the Software.
84
+
85
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
86
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
87
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
88
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
89
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
90
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
91
+ SOFTWARE.
92
+
93
+ For all third party components incorporated into the EventCatalog Software,
94
+ those components are licensed under the original license provided by the owner
95
+ of the applicable component.
package/README.md CHANGED
@@ -35,7 +35,7 @@
35
35
  <div align="center">
36
36
 
37
37
  [![main](https://github.com/event-catalog/eventcatalog/actions/workflows/verify-build.yml/badge.svg)](https://github.com/event-catalog/eventcatalog/actions/workflows/verify-build.yml)
38
- [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/event-catalog/eventcatalog/blob/main/LICENSE)
38
+ [![License](https://img.shields.io/badge/License-MIT%20%2B%20Commercial-blue.svg)](./LICENSE)
39
39
  [![npm version](https://badge.fury.io/js/@eventcatalog%2Fcore.svg)](https://badge.fury.io/js/@eventcatalog/core)
40
40
 
41
41
  </div>
@@ -205,4 +205,5 @@ This project follows the [all-contributors](https://github.com/all-contributors/
205
205
 
206
206
  # License
207
207
 
208
- MIT.
208
+ EventCatalog Core uses a mixed-license model. Most files are licensed under MIT; the Enterprise and Federation paths
209
+ identified in [LICENSE](./LICENSE) use the EventCatalog Commercial License.
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(analytics_exports);
36
36
  var import_os = __toESM(require("os"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.7.4";
39
+ var version = "4.7.6";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-FDXJIZ74.js";
4
- import "../chunk-TLWM7WRZ.js";
3
+ } from "../chunk-YXANZO6Y.js";
4
+ import "../chunk-Q534DWPZ.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
140
140
  var import_os = __toESM(require("os"), 1);
141
141
 
142
142
  // package.json
143
- var version = "4.7.4";
143
+ var version = "4.7.6";
144
144
 
145
145
  // src/constants.ts
146
146
  var VERSION = version;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-ZIABX6SP.js";
4
- import "../chunk-FDXJIZ74.js";
3
+ } from "../chunk-RZAJNR7O.js";
4
+ import "../chunk-YXANZO6Y.js";
5
5
  import "../chunk-K2XIENVT.js";
6
- import "../chunk-TLWM7WRZ.js";
6
+ import "../chunk-Q534DWPZ.js";
7
7
  import "../chunk-6QENHZZP.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-ZXVQXBTT.js";
3
+ } from "./chunk-MNAPKH63.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -0,0 +1,7 @@
1
+ // src/federation/entitlement.ts
2
+ import { isEventCatalogScaleEnabled } from "@eventcatalog/license";
3
+ var isFederationEnabled = () => isEventCatalogScaleEnabled();
4
+
5
+ export {
6
+ isFederationEnabled
7
+ };
@@ -0,0 +1,159 @@
1
+ // src/federation/diagnostics.ts
2
+ var federationRuleDefaults = {
3
+ "federation/duplicate-source": "error",
4
+ "federation/type-collision": "error",
5
+ "federation/pointer-type-mismatch": "error",
6
+ "federation/facet-disagreement": "error",
7
+ "federation/asset-collision": "warn",
8
+ "federation/missing-resource": "warn",
9
+ "federation/unresolved-version": "warn"
10
+ };
11
+ var resolveFederationRules = (rules = {}) => {
12
+ for (const [rule, level] of Object.entries(rules)) {
13
+ if (!Object.hasOwn(federationRuleDefaults, rule)) throw new Error(`Unknown federation rule "${rule}".`);
14
+ if (level !== "off" && level !== "warn" && level !== "error") {
15
+ throw new Error(`Invalid level "${String(level)}" for federation rule "${rule}". Expected off, warn, or error.`);
16
+ }
17
+ }
18
+ return { ...federationRuleDefaults, ...rules };
19
+ };
20
+ var getExternalResource = (external) => `${external.id}${external.version === void 0 ? "" : `@${external.version}`}`;
21
+ var getVersionedResource = (id, version) => `${id}${version == null ? "" : `@${version}`}`;
22
+ var getDocumentedResourceType = (type) => {
23
+ const usesAn = type === "adr" || type === "agent" || type === "entity" || type === "event";
24
+ return `documented as ${usesAn ? "an" : "a"} ${type}`;
25
+ };
26
+ var getConflictDiagnostic = (conflict, graph) => {
27
+ const catalogs = { label: "catalogs", value: conflict.sources.join(", ") };
28
+ switch (conflict.kind) {
29
+ case "duplicate-source":
30
+ return {
31
+ severity: "error",
32
+ message: "Resource has multiple owners",
33
+ rule: "federation/duplicate-source",
34
+ attributes: [
35
+ { label: "resource", value: conflict.id },
36
+ catalogs,
37
+ { label: "resolution", value: "assign a single owning catalog" }
38
+ ]
39
+ };
40
+ case "type-collision": {
41
+ const types = [
42
+ ...new Map(
43
+ graph.entities.filter((entity) => entity.id === conflict.id).map(
44
+ (entity) => [
45
+ `${entity.resolvedFrom.source}:${entity.type}`,
46
+ { label: entity.resolvedFrom.source, value: getDocumentedResourceType(entity.type) }
47
+ ]
48
+ )
49
+ ).values()
50
+ ];
51
+ return {
52
+ severity: "error",
53
+ message: "Resource ID has conflicting types",
54
+ rule: "federation/type-collision",
55
+ attributes: [{ label: "resource", value: conflict.id }, ...types.length > 0 ? types : [catalogs]]
56
+ };
57
+ }
58
+ case "pointer-type-mismatch": {
59
+ const typeMismatch = /^Expected (.+) but found (.+)$/.exec(conflict.detail ?? "");
60
+ return {
61
+ severity: "error",
62
+ message: "Reference type does not match resource",
63
+ rule: "federation/pointer-type-mismatch",
64
+ attributes: [
65
+ { label: "resource", value: conflict.id },
66
+ ...typeMismatch ? [
67
+ { label: "expected type", value: typeMismatch[1] },
68
+ { label: "actual type", value: typeMismatch[2] }
69
+ ] : conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
70
+ catalogs
71
+ ]
72
+ };
73
+ }
74
+ case "facet-disagreement":
75
+ return {
76
+ severity: "error",
77
+ message: "Catalogs disagree about this resource",
78
+ rule: "federation/facet-disagreement",
79
+ attributes: [
80
+ { label: "resource", value: conflict.id },
81
+ ...conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
82
+ catalogs
83
+ ]
84
+ };
85
+ }
86
+ };
87
+ var getFederationDiagnostics = (graph, rules = {}) => {
88
+ const resolvedRules = resolveFederationRules(rules);
89
+ const diagnostics = graph.conflicts.map((conflict) => getConflictDiagnostic(conflict, graph));
90
+ for (const external of graph.externals) {
91
+ const resource = getExternalResource(external);
92
+ for (const referencedBy of external.referencedBy) {
93
+ const referrer = graph.entities.find((entity) => entity.id === referencedBy);
94
+ diagnostics.push({
95
+ severity: "warning",
96
+ message: "Referenced EventCatalog resource does not exist",
97
+ rule: "federation/missing-resource",
98
+ attributes: [
99
+ { label: "source catalog", value: referrer?.resolvedFrom.source ?? "unknown" },
100
+ { label: "referenced by", value: referencedBy },
101
+ { label: "missing resource", value: resource }
102
+ ]
103
+ });
104
+ }
105
+ }
106
+ for (const edge of graph.edges.filter((edge2) => edge2.status === "unresolved")) {
107
+ const availableVersions = [
108
+ ...new Set(graph.entities.filter((entity) => entity.id === edge.to).map((entity) => entity.version ?? "unversioned"))
109
+ ].sort((left, right) => left.localeCompare(right, void 0, { numeric: true }));
110
+ diagnostics.push({
111
+ severity: "warning",
112
+ message: "Referenced EventCatalog resource version does not exist",
113
+ rule: "federation/unresolved-version",
114
+ attributes: [
115
+ { label: "source catalog", value: edge.fromResolvedFrom.source },
116
+ { label: "referenced by", value: getVersionedResource(edge.from, edge.fromVersion) },
117
+ { label: "resource", value: edge.to },
118
+ { label: "requested version", value: edge.pointer ?? "unspecified" },
119
+ { label: "available versions", value: availableVersions.join(", ") }
120
+ ]
121
+ });
122
+ }
123
+ for (const warning of graph.warnings) {
124
+ diagnostics.push({
125
+ severity: "warning",
126
+ message: "Asset collision",
127
+ rule: "federation/asset-collision",
128
+ attributes: [
129
+ { label: "asset", value: warning.path },
130
+ { label: "sources", value: warning.sources.join(", ") },
131
+ { label: "winner", value: warning.winner },
132
+ { label: "resolution", value: "last configured source wins" }
133
+ ]
134
+ });
135
+ }
136
+ return diagnostics.flatMap((diagnostic) => {
137
+ const level = resolvedRules[diagnostic.rule];
138
+ if (level === "off") return [];
139
+ return [{ ...diagnostic, severity: level === "error" ? "error" : "warning" }];
140
+ }).sort(
141
+ (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 ?? "")
142
+ );
143
+ };
144
+ var getFederationDiagnosticCounts = (diagnostics) => diagnostics.reduce(
145
+ (counts, diagnostic) => ({
146
+ errors: counts.errors + (diagnostic.severity === "error" ? 1 : 0),
147
+ warnings: counts.warnings + (diagnostic.severity === "warning" ? 1 : 0)
148
+ }),
149
+ { errors: 0, warnings: 0 }
150
+ );
151
+ var getVisibleFederationDiagnostics = (diagnostics, verbose) => diagnostics.filter((diagnostic) => verbose || diagnostic.severity === "error");
152
+
153
+ export {
154
+ federationRuleDefaults,
155
+ resolveFederationRules,
156
+ getFederationDiagnostics,
157
+ getFederationDiagnosticCounts,
158
+ getVisibleFederationDiagnostics
159
+ };
@@ -0,0 +1,279 @@
1
+ import {
2
+ withFederationOutputTransaction
3
+ } from "./chunk-WRNH5C3U.js";
4
+ import {
5
+ composePublicAssets
6
+ } from "./chunk-5EPNFDT5.js";
7
+ import {
8
+ createFederationSourceProvider
9
+ } from "./chunk-6K7XZAYI.js";
10
+ import {
11
+ createFederationContentCache
12
+ } from "./chunk-R7Z5ALYI.js";
13
+ import {
14
+ getFederationDiagnostics,
15
+ resolveFederationRules
16
+ } from "./chunk-JJPB6EOZ.js";
17
+ import {
18
+ isFederationEnabled
19
+ } from "./chunk-DDALKGTF.js";
20
+ import {
21
+ getEventCatalogConfigFile
22
+ } from "./chunk-6QENHZZP.js";
23
+
24
+ // src/federation/federate.ts
25
+ import { createHash } from "crypto";
26
+ import fs from "fs/promises";
27
+ import path from "path";
28
+ import createSDK, { hydrate, resolve } from "@eventcatalog/sdk";
29
+ var FederationConflictError = class extends Error {
30
+ conflicts;
31
+ constructor(conflicts) {
32
+ const details = conflicts.map((conflict) => `${conflict.id}: ${conflict.sources.join(", ")}`).join("\n");
33
+ super(`Federation conflicts prevent hydration:
34
+ ${details}`);
35
+ this.name = "FederationConflictError";
36
+ this.conflicts = conflicts;
37
+ }
38
+ };
39
+ var FederationDiagnosticError = class extends Error {
40
+ diagnostics;
41
+ constructor(diagnostics) {
42
+ const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
43
+ super(`Federation diagnostics prevent hydration:
44
+ ${details}`);
45
+ this.name = "FederationDiagnosticError";
46
+ this.diagnostics = diagnostics;
47
+ }
48
+ };
49
+ var validateSources = (sources) => {
50
+ const ids = /* @__PURE__ */ new Set();
51
+ for (const source of sources) {
52
+ if (!source.id?.trim()) throw new Error("Every federation source requires a stable id.");
53
+ if (ids.has(source.id)) throw new Error(`Federation source id "${source.id}" is configured more than once.`);
54
+ ids.add(source.id);
55
+ }
56
+ };
57
+ var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
58
+ const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
59
+ const externalsByPointer = /* @__PURE__ */ new Map();
60
+ for (const edge of edges.filter((edge2) => edge2.status === "external")) {
61
+ const version = edge.pointer ?? void 0;
62
+ const key = `${edge.to}@${version ?? ""}`;
63
+ const external = externalsByPointer.get(key) ?? {
64
+ id: edge.to,
65
+ ...version === void 0 ? {} : { version },
66
+ referencedBy: []
67
+ };
68
+ if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
69
+ externalsByPointer.set(key, external);
70
+ }
71
+ return {
72
+ ...graph,
73
+ entities: [
74
+ ...graph.entities,
75
+ ...ownershipGraph.entities.filter(
76
+ (entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
77
+ )
78
+ ],
79
+ edges,
80
+ conflicts: ownershipGraph.conflicts,
81
+ externals: [...externalsByPointer.values()]
82
+ };
83
+ };
84
+ var writeLock = async (lockPath, lock) => {
85
+ const temporaryPath = `${lockPath}.tmp-${process.pid}`;
86
+ try {
87
+ await fs.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
88
+ `, "utf8");
89
+ await fs.rename(temporaryPath, lockPath);
90
+ } finally {
91
+ await fs.rm(temporaryPath, { force: true });
92
+ }
93
+ };
94
+ var readLock = async (lockPath) => {
95
+ try {
96
+ return JSON.parse(await fs.readFile(lockPath, "utf8"));
97
+ } catch (error) {
98
+ if (error.code === "ENOENT") return void 0;
99
+ throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
100
+ }
101
+ };
102
+ var pathExists = async (filePath) => {
103
+ try {
104
+ await fs.access(filePath);
105
+ return true;
106
+ } catch (error) {
107
+ if (error.code === "ENOENT") return false;
108
+ throw error;
109
+ }
110
+ };
111
+ var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
112
+ const outDir = path.join(projectDirectory, "federated");
113
+ const lockPath = path.join(projectDirectory, "eventcatalog.lock");
114
+ const previousLock = await readLock(lockPath);
115
+ const hadFederatedOutput = await pathExists(outDir);
116
+ const hadLock = previousLock !== void 0;
117
+ if (!hadFederatedOutput && !hadLock) return;
118
+ const publicResult = await withFederationOutputTransaction(
119
+ projectDirectory,
120
+ outDir,
121
+ Object.keys(previousLock?.publicFiles ?? {}),
122
+ async () => {
123
+ const result = await composePublicAssets({
124
+ projectDirectory,
125
+ federatedDirectory: outDir,
126
+ assets: [],
127
+ previousFiles: previousLock?.publicFiles
128
+ });
129
+ await fs.rm(lockPath, { force: true });
130
+ return result;
131
+ }
132
+ );
133
+ onProgress?.({
134
+ type: "cleanup:complete",
135
+ federated: hadFederatedOutput,
136
+ publicFiles: publicResult.removed,
137
+ lock: hadLock
138
+ });
139
+ };
140
+ var federateCatalog = async (projectDirectory, options = {}) => {
141
+ const config = await getEventCatalogConfigFile(projectDirectory);
142
+ const sources = config.federation?.sources ?? [];
143
+ options.onProgress?.({ type: "configured", sources: sources.length });
144
+ if (sources.length === 0) {
145
+ await cleanupPreviousFederation(projectDirectory, options.onProgress);
146
+ return null;
147
+ }
148
+ if (!await isFederationEnabled()) {
149
+ throw new Error(
150
+ "Cannot federate catalogs: EventCatalog federation is an Enterprise feature. Visit https://www.eventcatalog.dev/pricing to enable federation."
151
+ );
152
+ }
153
+ validateSources(sources);
154
+ const rules = resolveFederationRules(config.federation?.rules);
155
+ if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
156
+ const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
157
+ const resolvedSources = [];
158
+ for (const [index, source] of sources.entries()) {
159
+ const current = index + 1;
160
+ options.onProgress?.({ type: "source:start", source, current, total: sources.length });
161
+ try {
162
+ const resolved = await provider.resolve(source);
163
+ resolvedSources.push({ config: source, resolved });
164
+ options.onProgress?.({
165
+ type: "source:complete",
166
+ source,
167
+ current,
168
+ total: sources.length,
169
+ commit: resolved.commit,
170
+ resources: resolved.index.resources.length,
171
+ generated: resolved.generated
172
+ });
173
+ } catch (error) {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
176
+ }
177
+ }
178
+ const outDir = path.join(projectDirectory, "federated");
179
+ const lockPath = path.join(projectDirectory, "eventcatalog.lock");
180
+ const previousLock = await readLock(lockPath);
181
+ const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
182
+ const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
183
+ options.onProgress?.({ type: "local:start" });
184
+ const localIndex = await createSDK(projectDirectory).buildIndex({
185
+ source: config.cId,
186
+ commit: "local",
187
+ hashContent: false,
188
+ includeFederated: false
189
+ });
190
+ options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
191
+ options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
192
+ const ownershipGraph = resolve([localIndex, ...remoteIndexes]);
193
+ const graph = resolve(remoteIndexes);
194
+ const diagnosticGraph = createDiagnosticGraph(
195
+ graph,
196
+ ownershipGraph,
197
+ new Set(remoteIndexes.map((index) => index.source)),
198
+ localIndex.source
199
+ );
200
+ const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
201
+ options.onProgress?.({ type: "resolved", graph, diagnostics });
202
+ const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
203
+ if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
204
+ const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
205
+ if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
206
+ const publicPaths = [
207
+ ...Object.keys(previousLock?.publicFiles ?? {}),
208
+ ...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
209
+ ];
210
+ const { hydrateResult, publicResult } = await withFederationOutputTransaction(
211
+ projectDirectory,
212
+ outDir,
213
+ publicPaths,
214
+ async () => {
215
+ const sourcesById = new Map(sources.map((source) => [source.id, source]));
216
+ options.onProgress?.({ type: "hydrating", outDir });
217
+ let hydratedFiles = 0;
218
+ let cachedFiles = 0;
219
+ const hydrateResult2 = await hydrate(graph, {
220
+ outDir,
221
+ cache: createFederationContentCache(projectDirectory, {
222
+ read: options.useCache !== false,
223
+ onHit: () => {
224
+ cachedFiles += 1;
225
+ options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
226
+ }
227
+ }),
228
+ fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
229
+ const source = sourcesById.get(sourceId);
230
+ if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
231
+ const content = await provider.fetchContent({ source, commit, path: artifactPath });
232
+ hydratedFiles += 1;
233
+ options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
234
+ return content;
235
+ }
236
+ });
237
+ const publicResult2 = await composePublicAssets({
238
+ projectDirectory,
239
+ federatedDirectory: outDir,
240
+ assets: graph.assets,
241
+ previousFiles: previousLock?.publicFiles,
242
+ collisionPaths: new Set(
243
+ graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
244
+ )
245
+ });
246
+ options.onProgress?.({ type: "public:complete", result: publicResult2 });
247
+ const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
248
+ await writeLock(lockPath, {
249
+ lockVersion: 1,
250
+ sources: resolvedSources.map(({ config: source, resolved }) => ({
251
+ id: source.id,
252
+ digest: `sha256:${createHash("sha256").update(resolved.bytes).digest("hex")}`,
253
+ commit: resolved.commit,
254
+ resolvedAt
255
+ })).sort((left, right) => left.id.localeCompare(right.id)),
256
+ publicFiles: publicResult2.files
257
+ });
258
+ return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
259
+ }
260
+ );
261
+ const result = {
262
+ sources: sources.length,
263
+ resources,
264
+ graph,
265
+ diagnostics,
266
+ hydrate: hydrateResult,
267
+ public: publicResult,
268
+ outDir,
269
+ lockPath
270
+ };
271
+ options.onProgress?.({ type: "complete", result });
272
+ return result;
273
+ };
274
+
275
+ export {
276
+ FederationConflictError,
277
+ FederationDiagnosticError,
278
+ federateCatalog
279
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-TLWM7WRZ.js";
3
+ } from "./chunk-Q534DWPZ.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -37,6 +37,29 @@ var logger = {
37
37
  warning: (message, tag = "warn") => {
38
38
  console.log(formatMessage(tag, message, pc.yellow));
39
39
  },
40
+ line: (message = "") => {
41
+ console.log(message);
42
+ },
43
+ diagnostic: (severity, message, rule, attributes = []) => {
44
+ const isError = severity === "error";
45
+ const color = isError ? pc.red : pc.yellow;
46
+ const icon = isError ? "\u2716" : "\u26A0";
47
+ const label = `${icon} ${severity.padEnd(7)} ${message}`;
48
+ const ruleSpacing = " ".repeat(Math.max(2, 72 - label.length));
49
+ console.log(` ${color(icon)} ${color(severity.padEnd(7))} ${message}${ruleSpacing}${pc.gray(rule)}`);
50
+ for (const attribute of attributes) {
51
+ console.log(` - ${pc.dim(`${attribute.label}:`)} ${attribute.value}`);
52
+ }
53
+ },
54
+ diagnosticSummary: (errors, warnings) => {
55
+ const total = errors + warnings;
56
+ const color = errors > 0 ? pc.red : pc.yellow;
57
+ const icon = errors > 0 ? "\u2716" : "\u26A0";
58
+ const problems = `${total} problem${total === 1 ? "" : "s"}`;
59
+ const errorCount = `${errors} error${errors === 1 ? "" : "s"}`;
60
+ const warningCount = `${warnings} warning${warnings === 1 ? "" : "s"}`;
61
+ console.log(color(`${icon} ${problems} (${errorCount}, ${warningCount})`));
62
+ },
40
63
  dim: (message) => {
41
64
  console.log(pc.dim(message));
42
65
  }
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "4.7.4";
2
+ var version = "4.7.6";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-FDXJIZ74.js";
3
+ } from "./chunk-YXANZO6Y.js";
4
4
  import {
5
5
  countResources,
6
6
  serializeCounts