@eventcatalog/core 4.6.1 → 4.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) 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-352FGP6W.js +123 -0
  6. package/dist/{chunk-N3YKQTGX.js → chunk-4BBXOL6K.js} +1 -1
  7. package/dist/{chunk-Y7JJ2B3L.js → chunk-54KFOI6Z.js} +3 -3
  8. package/dist/chunk-5EPNFDT5.js +133 -0
  9. package/dist/{chunk-MTXVQ4VP.js → chunk-FZJDCQZE.js} +1 -1
  10. package/dist/{chunk-X7XI7NEP.js → chunk-LB5VXS6X.js} +1 -1
  11. package/dist/{chunk-U6VOVEVZ.js → chunk-PWY4SP6L.js} +1 -1
  12. package/dist/chunk-R7Z5ALYI.js +44 -0
  13. package/dist/chunk-ZR6AH5Z2.js +204 -0
  14. package/dist/constants.cjs +1 -1
  15. package/dist/constants.js +1 -1
  16. package/dist/eventcatalog.cjs +651 -51
  17. package/dist/eventcatalog.config.d.cts +18 -1
  18. package/dist/eventcatalog.config.d.ts +18 -1
  19. package/dist/eventcatalog.js +134 -7
  20. package/dist/federation/content-cache.cjs +78 -0
  21. package/dist/federation/content-cache.d.cts +9 -0
  22. package/dist/federation/content-cache.d.ts +9 -0
  23. package/dist/federation/content-cache.js +6 -0
  24. package/dist/federation/federate.cjs +588 -0
  25. package/dist/federation/federate.d.cts +81 -0
  26. package/dist/federation/federate.d.ts +81 -0
  27. package/dist/federation/federate.js +12 -0
  28. package/dist/federation/github-source-provider.cjs +157 -0
  29. package/dist/federation/github-source-provider.d.cts +24 -0
  30. package/dist/federation/github-source-provider.d.ts +24 -0
  31. package/dist/federation/github-source-provider.js +6 -0
  32. package/dist/federation/public-assets.cjs +167 -0
  33. package/dist/federation/public-assets.d.cts +24 -0
  34. package/dist/federation/public-assets.d.ts +24 -0
  35. package/dist/federation/public-assets.js +6 -0
  36. package/dist/federation/types.cjs +18 -0
  37. package/dist/federation/types.d.cts +19 -0
  38. package/dist/federation/types.d.ts +19 -0
  39. package/dist/federation/types.js +0 -0
  40. package/dist/generate.cjs +1 -1
  41. package/dist/generate.js +3 -3
  42. package/dist/utils/cli-logger.cjs +1 -1
  43. package/dist/utils/cli-logger.js +2 -2
  44. package/package.json +8 -8
@@ -28,11 +28,11 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
28
28
 
29
29
  // src/eventcatalog.ts
30
30
  var import_commander = require("commander");
31
- var import_node_child_process = require("child_process");
32
- var import_node_path10 = require("path");
31
+ var import_node_child_process2 = require("child_process");
32
+ var import_node_path14 = require("path");
33
33
  var import_node_http = __toESM(require("http"), 1);
34
34
  var import_fs3 = __toESM(require("fs"), 1);
35
- var import_node_path11 = __toESM(require("path"), 1);
35
+ var import_node_path15 = __toESM(require("path"), 1);
36
36
  var import_node_url = require("url");
37
37
 
38
38
  // src/generate.js
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
144
144
  var import_picocolors = __toESM(require("picocolors"), 1);
145
145
 
146
146
  // package.json
147
- var version = "4.6.1";
147
+ var version = "4.6.3";
148
148
 
149
149
  // src/constants.ts
150
150
  var VERSION = version;
@@ -1256,22 +1256,22 @@ function getAvroTypeName(type) {
1256
1256
  function walkAvroRecord(schema, prefix, fields) {
1257
1257
  if (!schema.fields || !Array.isArray(schema.fields)) return;
1258
1258
  for (const field of schema.fields) {
1259
- const path11 = prefix ? `${prefix}.${field.name}` : field.name;
1259
+ const path15 = prefix ? `${prefix}.${field.name}` : field.name;
1260
1260
  const isOptional = Array.isArray(field.type) && field.type.includes("null");
1261
1261
  const typeName = getAvroTypeName(field.type);
1262
1262
  fields.push({
1263
- path: path11,
1263
+ path: path15,
1264
1264
  type: typeName,
1265
1265
  description: field.doc || "",
1266
1266
  required: !isOptional
1267
1267
  });
1268
1268
  const innerType = Array.isArray(field.type) ? field.type.find((t) => typeof t === "object" && t.type === "record") : typeof field.type === "object" && field.type.type === "record" ? field.type : null;
1269
1269
  if (innerType) {
1270
- walkAvroRecord(innerType, path11, fields);
1270
+ walkAvroRecord(innerType, path15, fields);
1271
1271
  }
1272
1272
  const arrayType = Array.isArray(field.type) ? field.type.find((t) => typeof t === "object" && t.type === "array") : typeof field.type === "object" && field.type.type === "array" ? field.type : null;
1273
1273
  if (arrayType && typeof arrayType.items === "object" && arrayType.items.type === "record") {
1274
- walkAvroRecord(arrayType.items, `${path11}[]`, fields);
1274
+ walkAvroRecord(arrayType.items, `${path15}[]`, fields);
1275
1275
  }
1276
1276
  }
1277
1277
  }
@@ -1302,32 +1302,32 @@ function walkJsonSchema(node, prefix, requiredList, rootSchema, fields) {
1302
1302
  }
1303
1303
  if (!node.properties) return;
1304
1304
  for (const [name, prop] of Object.entries(node.properties)) {
1305
- const path11 = prefix ? `${prefix}.${name}` : name;
1305
+ const path15 = prefix ? `${prefix}.${name}` : name;
1306
1306
  const isRequired = requiredList.includes(name);
1307
1307
  if (prop.$ref) {
1308
1308
  const resolved = resolveLocalRef(prop.$ref, rootSchema);
1309
1309
  if (resolved) {
1310
1310
  const rawRefType = resolved.type || "object";
1311
1311
  const type2 = Array.isArray(rawRefType) ? [...rawRefType].sort().join(" | ") : rawRefType;
1312
- fields.push({ path: path11, type: type2, description: resolved.description || "", required: isRequired });
1312
+ fields.push({ path: path15, type: type2, description: resolved.description || "", required: isRequired });
1313
1313
  if (resolved.properties) {
1314
- walkJsonSchema(resolved, path11, resolved.required || [], rootSchema, fields);
1314
+ walkJsonSchema(resolved, path15, resolved.required || [], rootSchema, fields);
1315
1315
  }
1316
1316
  } else {
1317
- fields.push({ path: path11, type: "$ref", description: "", required: isRequired });
1317
+ fields.push({ path: path15, type: "$ref", description: "", required: isRequired });
1318
1318
  }
1319
1319
  continue;
1320
1320
  }
1321
1321
  const rawType = prop.type || (prop.enum ? "enum" : prop.$ref ? "$ref" : "object");
1322
1322
  const typeList = Array.isArray(rawType) ? rawType : [rawType];
1323
1323
  const type = Array.isArray(rawType) ? [...rawType].sort().join(" | ") : rawType;
1324
- fields.push({ path: path11, type, description: prop.description || "", required: isRequired });
1324
+ fields.push({ path: path15, type, description: prop.description || "", required: isRequired });
1325
1325
  if (typeList.includes("object") && prop.properties) {
1326
- walkJsonSchema(prop, path11, prop.required || [], rootSchema, fields);
1326
+ walkJsonSchema(prop, path15, prop.required || [], rootSchema, fields);
1327
1327
  }
1328
1328
  if (typeList.includes("array") && prop.items) {
1329
1329
  if (prop.items.type === "object" && prop.items.properties) {
1330
- walkJsonSchema(prop.items, `${path11}[]`, prop.items.required || [], rootSchema, fields);
1330
+ walkJsonSchema(prop.items, `${path15}[]`, prop.items.required || [], rootSchema, fields);
1331
1331
  }
1332
1332
  }
1333
1333
  }
@@ -1845,16 +1845,496 @@ var createAstroDevLineFilter = () => {
1845
1845
  };
1846
1846
  };
1847
1847
 
1848
+ // src/federation/federate.ts
1849
+ var import_node_crypto3 = require("crypto");
1850
+ var import_promises6 = __toESM(require("fs/promises"), 1);
1851
+ var import_node_path13 = __toESM(require("path"), 1);
1852
+ var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
1853
+
1854
+ // src/federation/content-cache.ts
1855
+ var import_node_crypto = require("crypto");
1856
+ var import_promises3 = __toESM(require("fs/promises"), 1);
1857
+ var import_node_path10 = __toESM(require("path"), 1);
1858
+ var getContentHash = (content) => `sha256:${(0, import_node_crypto.createHash)("sha256").update(content).digest("hex")}`;
1859
+ var isContentHash = (key) => /^sha256:[a-f0-9]{64}$/i.test(key);
1860
+ var createFederationContentCache = (projectDirectory, options = {}) => {
1861
+ const cacheDirectory = import_node_path10.default.join(projectDirectory, ".eventcatalog-cache", "federation", "content");
1862
+ const getCachePath = (key) => import_node_path10.default.join(cacheDirectory, encodeURIComponent(key));
1863
+ return {
1864
+ async get(key) {
1865
+ if (options.read === false || !isContentHash(key)) return void 0;
1866
+ const cachePath = getCachePath(key);
1867
+ try {
1868
+ const content = await import_promises3.default.readFile(cachePath);
1869
+ if (getContentHash(content) !== key) {
1870
+ await import_promises3.default.rm(cachePath, { force: true });
1871
+ return void 0;
1872
+ }
1873
+ options.onHit?.(key);
1874
+ return content;
1875
+ } catch (error) {
1876
+ if (error.code === "ENOENT") return void 0;
1877
+ throw error;
1878
+ }
1879
+ },
1880
+ async set(key, content) {
1881
+ if (!isContentHash(key) || getContentHash(content) !== key) return;
1882
+ await import_promises3.default.mkdir(cacheDirectory, { recursive: true });
1883
+ const cachePath = getCachePath(key);
1884
+ const temporaryPath = `${cachePath}.tmp-${process.pid}-${(0, import_node_crypto.randomUUID)()}`;
1885
+ try {
1886
+ await import_promises3.default.writeFile(temporaryPath, content);
1887
+ await import_promises3.default.rename(temporaryPath, cachePath);
1888
+ } finally {
1889
+ await import_promises3.default.rm(temporaryPath, { force: true });
1890
+ }
1891
+ }
1892
+ };
1893
+ };
1894
+
1895
+ // src/federation/github-source-provider.ts
1896
+ var import_node_child_process = require("child_process");
1897
+ var import_promises4 = __toESM(require("fs/promises"), 1);
1898
+ var import_node_os4 = __toESM(require("os"), 1);
1899
+ var import_node_path11 = __toESM(require("path"), 1);
1900
+ var import_node_util = require("util");
1901
+ var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
1902
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1903
+ var parseGitHubSource = (source) => {
1904
+ const match = /^github:([^/]+)\/(.+)$/.exec(source.source);
1905
+ if (!match) throw new Error(`Unsupported federation source "${source.source}". Expected github:owner/repository.`);
1906
+ return { owner: match[1], repository: match[2] };
1907
+ };
1908
+ var assertSafeCatalogPath = (source) => {
1909
+ const catalogPath = source.path ?? ".";
1910
+ const normalized = import_node_path11.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
1911
+ if (catalogPath.includes("\\") || import_node_path11.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
1912
+ throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
1913
+ }
1914
+ };
1915
+ var encodePath = (value) => value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
1916
+ var rawUrl = (source, ref, filePath) => {
1917
+ const { owner, repository } = parseGitHubSource(source);
1918
+ return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/${encodeURIComponent(
1919
+ ref
1920
+ )}/${encodePath(filePath)}`;
1921
+ };
1922
+ var contentsApiUrl = (source, ref, filePath) => {
1923
+ const { owner, repository } = parseGitHubSource(source);
1924
+ return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/contents/${encodePath(
1925
+ filePath
1926
+ )}?ref=${encodeURIComponent(ref)}`;
1927
+ };
1928
+ var fetchBytes = async (source, ref, filePath, fetcher, token) => {
1929
+ const url = token ? contentsApiUrl(source, ref, filePath) : rawUrl(source, ref, filePath);
1930
+ const response = token ? await fetcher(url, {
1931
+ headers: {
1932
+ Accept: "application/vnd.github.raw+json",
1933
+ Authorization: `Bearer ${token}`,
1934
+ "X-GitHub-Api-Version": "2026-03-10"
1935
+ }
1936
+ }) : await fetcher(url);
1937
+ if (response.status === 404) return void 0;
1938
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
1939
+ return Buffer.from(await response.arrayBuffer());
1940
+ };
1941
+ var getGitEnvironment = (token) => {
1942
+ if (!token) return process.env;
1943
+ const inheritedCount = Number.parseInt(process.env.GIT_CONFIG_COUNT ?? "0", 10);
1944
+ const configIndex = Number.isInteger(inheritedCount) && inheritedCount >= 0 ? inheritedCount : 0;
1945
+ return {
1946
+ ...process.env,
1947
+ GIT_CONFIG_COUNT: String(configIndex + 1),
1948
+ [`GIT_CONFIG_KEY_${configIndex}`]: "http.https://github.com/.extraheader",
1949
+ [`GIT_CONFIG_VALUE_${configIndex}`]: `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`
1950
+ };
1951
+ };
1952
+ var createCheckout = (executeFile, token) => async (source, ref, callback) => {
1953
+ const { owner, repository } = parseGitHubSource(source);
1954
+ const catalogPath = import_node_path11.default.posix.normalize(source.path ?? ".");
1955
+ const directory = await import_promises4.default.mkdtemp(import_node_path11.default.join(import_node_os4.default.tmpdir(), "eventcatalog-federation-"));
1956
+ const env = getGitEnvironment(token);
1957
+ try {
1958
+ const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
1959
+ await git(["init", "--quiet"]);
1960
+ await git(["remote", "add", "origin", `https://github.com/${owner}/${repository}.git`]);
1961
+ if (catalogPath !== ".") {
1962
+ await git(["sparse-checkout", "init", "--cone"]);
1963
+ await git(["sparse-checkout", "set", catalogPath]);
1964
+ }
1965
+ await git(["fetch", "--quiet", "--depth", "1", "origin", ref]);
1966
+ await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
1967
+ return await callback(directory);
1968
+ } finally {
1969
+ await import_promises4.default.rm(directory, { recursive: true, force: true });
1970
+ }
1971
+ };
1972
+ var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
1973
+ const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
1974
+ const commit = stdout.trim();
1975
+ const catalogDirectory = import_node_path11.default.resolve(directory, source.path ?? ".");
1976
+ const relativeCatalogDirectory = import_node_path11.default.relative(directory, catalogDirectory);
1977
+ if (relativeCatalogDirectory.startsWith("..") || import_node_path11.default.isAbsolute(relativeCatalogDirectory)) {
1978
+ throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
1979
+ }
1980
+ const index = await (0, import_sdk.default)(catalogDirectory).buildIndex({ source: source.id, commit });
1981
+ return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
1982
+ });
1983
+ var fetchPublishedIndex = async (source, ref, fetcher, token) => {
1984
+ const indexPath = import_node_path11.default.posix.join(source.path ?? ".", "catalog.index.json");
1985
+ const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
1986
+ if (!bytes) return void 0;
1987
+ const index = (0, import_sdk.parseIndex)(JSON.parse(bytes.toString("utf8")));
1988
+ if (index.source !== source.id) {
1989
+ throw new Error(`Published index source "${index.source}" does not match configured id "${source.id}"`);
1990
+ }
1991
+ return { bytes, index, commit: index.commit, generated: false };
1992
+ };
1993
+ var createGitHubSourceProvider = (options = {}) => {
1994
+ const fetcher = options.fetch ?? ((url, init) => fetch(url, init));
1995
+ const executeFile = options.execFile ?? ((file, args, execOptions) => execFileAsync(file, args, execOptions));
1996
+ const configuredToken = options.token ?? process.env.EVENTCATALOG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN;
1997
+ const token = configuredToken?.trim() || void 0;
1998
+ const checkout = options.checkout ?? createCheckout(executeFile, token);
1999
+ return {
2000
+ async resolve(source) {
2001
+ assertSafeCatalogPath(source);
2002
+ const ref = source.ref ?? "main";
2003
+ return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
2004
+ },
2005
+ async fetchContent({ source, commit, path: artifactPath }) {
2006
+ assertSafeCatalogPath(source);
2007
+ const catalogPath = import_node_path11.default.posix.join(source.path ?? ".", artifactPath);
2008
+ const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
2009
+ if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
2010
+ return content;
2011
+ }
2012
+ };
2013
+ };
2014
+
2015
+ // src/federation/public-assets.ts
2016
+ var import_node_crypto2 = require("crypto");
2017
+ var import_promises5 = __toESM(require("fs/promises"), 1);
2018
+ var import_node_path12 = __toESM(require("path"), 1);
2019
+ var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
2020
+ var getFileHash = async (filePath) => {
2021
+ try {
2022
+ const stat = await import_promises5.default.lstat(filePath);
2023
+ return stat.isFile() ? getContentHash2(await import_promises5.default.readFile(filePath)) : void 0;
2024
+ } catch (error) {
2025
+ if (error.code === "ENOENT") return void 0;
2026
+ throw error;
2027
+ }
2028
+ };
2029
+ var getSafePath = (directory, relativePath) => {
2030
+ const normalizedPath = import_node_path12.default.posix.normalize(relativePath);
2031
+ const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") || import_node_path12.default.posix.isAbsolute(relativePath) || /^[a-zA-Z]:\//.test(relativePath) || normalizedPath !== relativePath || normalizedPath === ".." || normalizedPath.startsWith("../");
2032
+ if (unsafe) return void 0;
2033
+ const resolvedDirectory = import_node_path12.default.resolve(directory);
2034
+ const resolvedPath = import_node_path12.default.resolve(resolvedDirectory, relativePath);
2035
+ const relativeResolvedPath = import_node_path12.default.relative(resolvedDirectory, resolvedPath);
2036
+ if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path12.default.sep}`) || import_node_path12.default.isAbsolute(relativeResolvedPath)) {
2037
+ return void 0;
2038
+ }
2039
+ return resolvedPath;
2040
+ };
2041
+ var listFiles = async (directory, relativeDirectory = "") => {
2042
+ let entries;
2043
+ try {
2044
+ entries = await import_promises5.default.readdir(import_node_path12.default.join(directory, relativeDirectory), { withFileTypes: true });
2045
+ } catch (error) {
2046
+ if (error.code === "ENOENT") return [];
2047
+ throw error;
2048
+ }
2049
+ const files = await Promise.all(
2050
+ entries.map(async (entry) => {
2051
+ const relativePath = import_node_path12.default.join(relativeDirectory, entry.name);
2052
+ if (entry.isDirectory()) return listFiles(directory, relativePath);
2053
+ return entry.isFile() ? [relativePath.split(import_node_path12.default.sep).join("/")] : [];
2054
+ })
2055
+ );
2056
+ return files.flat().sort();
2057
+ };
2058
+ var pathExists = async (filePath) => {
2059
+ try {
2060
+ await import_promises5.default.lstat(filePath);
2061
+ return true;
2062
+ } catch (error) {
2063
+ if (error.code === "ENOENT") return false;
2064
+ throw error;
2065
+ }
2066
+ };
2067
+ var hasBlockingParent = async (publicDirectory, destinationPath) => {
2068
+ let currentPath = import_node_path12.default.dirname(destinationPath);
2069
+ while (currentPath !== publicDirectory) {
2070
+ try {
2071
+ if (!(await import_promises5.default.lstat(currentPath)).isDirectory()) return true;
2072
+ } catch (error) {
2073
+ if (error.code !== "ENOENT") throw error;
2074
+ }
2075
+ currentPath = import_node_path12.default.dirname(currentPath);
2076
+ }
2077
+ return false;
2078
+ };
2079
+ var pruneEmptyDirectories = async (publicDirectory, filePath) => {
2080
+ let currentPath = import_node_path12.default.dirname(filePath);
2081
+ while (currentPath !== publicDirectory) {
2082
+ try {
2083
+ await import_promises5.default.rmdir(currentPath);
2084
+ } catch (error) {
2085
+ if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
2086
+ if (error.code === "ENOTEMPTY") return;
2087
+ }
2088
+ currentPath = import_node_path12.default.dirname(currentPath);
2089
+ }
2090
+ };
2091
+ var composePublicAssets = async ({
2092
+ projectDirectory,
2093
+ federatedDirectory,
2094
+ assets,
2095
+ previousFiles = {},
2096
+ collisionPaths = /* @__PURE__ */ new Set()
2097
+ }) => {
2098
+ const publicDirectory = import_node_path12.default.resolve(projectDirectory, "public");
2099
+ const federatedPublicDirectory = import_node_path12.default.resolve(federatedDirectory, "public");
2100
+ const sourceFiles = await listFiles(federatedPublicDirectory);
2101
+ const sourceFileSet = new Set(sourceFiles);
2102
+ const managedFiles = /* @__PURE__ */ new Set();
2103
+ for (const [relativePath, previousFile] of Object.entries(previousFiles)) {
2104
+ const destinationPath = getSafePath(publicDirectory, relativePath);
2105
+ if (destinationPath && await getFileHash(destinationPath) === previousFile.hash) managedFiles.add(relativePath);
2106
+ }
2107
+ let removed = 0;
2108
+ for (const relativePath of managedFiles) {
2109
+ if (sourceFileSet.has(relativePath)) continue;
2110
+ const destinationPath = getSafePath(publicDirectory, relativePath);
2111
+ if (!destinationPath) continue;
2112
+ await import_promises5.default.rm(destinationPath, { force: true });
2113
+ await pruneEmptyDirectories(publicDirectory, destinationPath);
2114
+ removed += 1;
2115
+ }
2116
+ const publicAssetsByPath = new Map(
2117
+ assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => [asset.path.slice("public/".length), asset])
2118
+ );
2119
+ const files = {};
2120
+ let copied = 0;
2121
+ let skipped = 0;
2122
+ let overwritten = 0;
2123
+ for (const relativePath of sourceFiles) {
2124
+ const sourcePath = getSafePath(federatedPublicDirectory, relativePath);
2125
+ const destinationPath = getSafePath(publicDirectory, relativePath);
2126
+ if (!sourcePath || !destinationPath) throw new Error(`Unsafe federated public asset path "${relativePath}"`);
2127
+ const mainCatalogOwnsPath = await pathExists(destinationPath) && !managedFiles.has(relativePath) || await hasBlockingParent(publicDirectory, destinationPath);
2128
+ if (mainCatalogOwnsPath) {
2129
+ skipped += 1;
2130
+ continue;
2131
+ }
2132
+ const asset = publicAssetsByPath.get(relativePath);
2133
+ if (!asset) throw new Error(`Cannot identify the source of federated public asset "${relativePath}"`);
2134
+ const content = await import_promises5.default.readFile(sourcePath);
2135
+ await import_promises5.default.mkdir(import_node_path12.default.dirname(destinationPath), { recursive: true });
2136
+ await import_promises5.default.writeFile(destinationPath, content);
2137
+ files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
2138
+ copied += 1;
2139
+ if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
2140
+ }
2141
+ await import_promises5.default.rm(federatedPublicDirectory, { recursive: true, force: true });
2142
+ return { files, copied, skipped, overwritten, removed };
2143
+ };
2144
+
2145
+ // src/federation/federate.ts
2146
+ var FederationConflictError = class extends Error {
2147
+ conflicts;
2148
+ constructor(conflicts) {
2149
+ const details = conflicts.map((conflict) => `${conflict.id}: ${conflict.sources.join(", ")}`).join("\n");
2150
+ super(`Federation conflicts prevent hydration:
2151
+ ${details}`);
2152
+ this.name = "FederationConflictError";
2153
+ this.conflicts = conflicts;
2154
+ }
2155
+ };
2156
+ var validateSources = (sources) => {
2157
+ const ids = /* @__PURE__ */ new Set();
2158
+ for (const source of sources) {
2159
+ if (!source.id?.trim()) throw new Error("Every federation source requires a stable id.");
2160
+ if (ids.has(source.id)) throw new Error(`Federation source id "${source.id}" is configured more than once.`);
2161
+ ids.add(source.id);
2162
+ }
2163
+ };
2164
+ var writeLock = async (lockPath, lock) => {
2165
+ const temporaryPath = `${lockPath}.tmp-${process.pid}`;
2166
+ try {
2167
+ await import_promises6.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
2168
+ `, "utf8");
2169
+ await import_promises6.default.rename(temporaryPath, lockPath);
2170
+ } finally {
2171
+ await import_promises6.default.rm(temporaryPath, { force: true });
2172
+ }
2173
+ };
2174
+ var readLock = async (lockPath) => {
2175
+ try {
2176
+ return JSON.parse(await import_promises6.default.readFile(lockPath, "utf8"));
2177
+ } catch (error) {
2178
+ if (error.code === "ENOENT") return void 0;
2179
+ throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
2180
+ }
2181
+ };
2182
+ var pathExists2 = async (filePath) => {
2183
+ try {
2184
+ await import_promises6.default.access(filePath);
2185
+ return true;
2186
+ } catch (error) {
2187
+ if (error.code === "ENOENT") return false;
2188
+ throw error;
2189
+ }
2190
+ };
2191
+ var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
2192
+ const outDir = import_node_path13.default.join(projectDirectory, "federated");
2193
+ const lockPath = import_node_path13.default.join(projectDirectory, "eventcatalog.lock");
2194
+ const previousLock = await readLock(lockPath);
2195
+ const hadFederatedOutput = await pathExists2(outDir);
2196
+ const hadLock = previousLock !== void 0;
2197
+ if (!hadFederatedOutput && !hadLock) return;
2198
+ await import_promises6.default.rm(outDir, { recursive: true, force: true });
2199
+ const publicResult = await composePublicAssets({
2200
+ projectDirectory,
2201
+ federatedDirectory: outDir,
2202
+ assets: [],
2203
+ previousFiles: previousLock?.publicFiles
2204
+ });
2205
+ await import_promises6.default.rm(lockPath, { force: true });
2206
+ onProgress?.({
2207
+ type: "cleanup:complete",
2208
+ federated: hadFederatedOutput,
2209
+ publicFiles: publicResult.removed,
2210
+ lock: hadLock
2211
+ });
2212
+ };
2213
+ var federateCatalog = async (projectDirectory, options = {}) => {
2214
+ const config = await getEventCatalogConfigFile(projectDirectory);
2215
+ const sources = config.federation?.sources ?? [];
2216
+ options.onProgress?.({ type: "configured", sources: sources.length });
2217
+ if (sources.length === 0) {
2218
+ await cleanupPreviousFederation(projectDirectory, options.onProgress);
2219
+ return null;
2220
+ }
2221
+ if (options.isFederationEnabled && !await options.isFederationEnabled()) {
2222
+ throw new Error(
2223
+ "Cannot federate catalogs: EventCatalog federation is an Enterprise feature. Visit https://www.eventcatalog.dev/pricing to enable federation."
2224
+ );
2225
+ }
2226
+ validateSources(sources);
2227
+ if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
2228
+ const provider = options.provider ?? createGitHubSourceProvider();
2229
+ const resolvedSources = [];
2230
+ for (const [index, source] of sources.entries()) {
2231
+ const current = index + 1;
2232
+ options.onProgress?.({ type: "source:start", source, current, total: sources.length });
2233
+ try {
2234
+ const resolved = await provider.resolve(source);
2235
+ resolvedSources.push({ config: source, resolved });
2236
+ options.onProgress?.({
2237
+ type: "source:complete",
2238
+ source,
2239
+ current,
2240
+ total: sources.length,
2241
+ commit: resolved.commit,
2242
+ resources: resolved.index.resources.length,
2243
+ generated: resolved.generated
2244
+ });
2245
+ } catch (error) {
2246
+ const message = error instanceof Error ? error.message : String(error);
2247
+ throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
2248
+ }
2249
+ }
2250
+ const outDir = import_node_path13.default.join(projectDirectory, "federated");
2251
+ const lockPath = import_node_path13.default.join(projectDirectory, "eventcatalog.lock");
2252
+ const previousLock = await readLock(lockPath);
2253
+ const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
2254
+ const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
2255
+ options.onProgress?.({ type: "local:start" });
2256
+ const localIndex = await (0, import_sdk2.default)(projectDirectory).buildIndex({
2257
+ source: config.cId,
2258
+ commit: "local",
2259
+ hashContent: false,
2260
+ includeFederated: false
2261
+ });
2262
+ options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
2263
+ options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
2264
+ const ownershipGraph = (0, import_sdk2.resolve)([localIndex, ...remoteIndexes]);
2265
+ if (ownershipGraph.conflicts.length > 0) {
2266
+ options.onProgress?.({ type: "resolved", graph: ownershipGraph });
2267
+ throw new FederationConflictError(ownershipGraph.conflicts);
2268
+ }
2269
+ const graph = (0, import_sdk2.resolve)(remoteIndexes);
2270
+ options.onProgress?.({ type: "resolved", graph });
2271
+ const sourcesById = new Map(sources.map((source) => [source.id, source]));
2272
+ options.onProgress?.({ type: "hydrating", outDir });
2273
+ let hydratedFiles = 0;
2274
+ let cachedFiles = 0;
2275
+ const hydrateResult = await (0, import_sdk2.hydrate)(graph, {
2276
+ outDir,
2277
+ cache: createFederationContentCache(projectDirectory, {
2278
+ read: options.useCache !== false,
2279
+ onHit: () => {
2280
+ cachedFiles += 1;
2281
+ options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
2282
+ }
2283
+ }),
2284
+ modes: Object.fromEntries(sources.map((source) => [source.id, source.mode ?? "hydrate"])),
2285
+ fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
2286
+ const source = sourcesById.get(sourceId);
2287
+ if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
2288
+ const content = await provider.fetchContent({ source, commit, path: artifactPath });
2289
+ hydratedFiles += 1;
2290
+ options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
2291
+ return content;
2292
+ }
2293
+ });
2294
+ const publicResult = await composePublicAssets({
2295
+ projectDirectory,
2296
+ federatedDirectory: outDir,
2297
+ assets: graph.assets,
2298
+ previousFiles: previousLock?.publicFiles,
2299
+ collisionPaths: new Set(
2300
+ graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
2301
+ )
2302
+ });
2303
+ options.onProgress?.({ type: "public:complete", result: publicResult });
2304
+ const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
2305
+ await writeLock(lockPath, {
2306
+ lockVersion: 1,
2307
+ sources: resolvedSources.map(({ config: source, resolved }) => ({
2308
+ id: source.id,
2309
+ digest: `sha256:${(0, import_node_crypto3.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
2310
+ commit: resolved.commit,
2311
+ resolvedAt
2312
+ })).sort((left, right) => left.id.localeCompare(right.id)),
2313
+ publicFiles: publicResult.files
2314
+ });
2315
+ const result = {
2316
+ sources: sources.length,
2317
+ resources,
2318
+ graph,
2319
+ hydrate: hydrateResult,
2320
+ public: publicResult,
2321
+ outDir,
2322
+ lockPath
2323
+ };
2324
+ options.onProgress?.({ type: "complete", result });
2325
+ return result;
2326
+ };
2327
+
1848
2328
  // src/eventcatalog.ts
1849
2329
  var import_license = require("@eventcatalog/license");
1850
- var currentDir = import_node_path11.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
2330
+ var currentDir = import_node_path15.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
1851
2331
  var program = new import_commander.Command().version(VERSION);
1852
- var dir = import_node_path11.default.resolve(process.env.PROJECT_DIR || process.cwd());
1853
- var core = import_node_path11.default.resolve(process.env.CATALOG_DIR || (0, import_node_path10.join)(dir, ".eventcatalog-core"));
1854
- var eventCatalogDir = import_node_path11.default.resolve((0, import_node_path10.join)(currentDir, "../eventcatalog/"));
2332
+ var dir = import_node_path15.default.resolve(process.env.PROJECT_DIR || process.cwd());
2333
+ var core = import_node_path15.default.resolve(process.env.CATALOG_DIR || (0, import_node_path14.join)(dir, ".eventcatalog-core"));
2334
+ var eventCatalogDir = import_node_path15.default.resolve((0, import_node_path14.join)(currentDir, "../eventcatalog/"));
1855
2335
  var getInstalledEventCatalogVersion = () => {
1856
2336
  try {
1857
- const pkg = import_fs3.default.readFileSync((0, import_node_path10.join)(dir, "package.json"), "utf8");
2337
+ const pkg = import_fs3.default.readFileSync((0, import_node_path14.join)(dir, "package.json"), "utf8");
1858
2338
  const json = JSON.parse(pkg);
1859
2339
  return json.dependencies["@eventcatalog/core"];
1860
2340
  } catch (error) {
@@ -1884,7 +2364,7 @@ var startDevPrewarm = ({
1884
2364
  initialDelayMs = 500
1885
2365
  }) => {
1886
2366
  let attempt = 0;
1887
- const hit = (requestPath) => new Promise((resolve2) => {
2367
+ const hit = (requestPath) => new Promise((resolve3) => {
1888
2368
  const req = import_node_http.default.get(
1889
2369
  {
1890
2370
  hostname: "127.0.0.1",
@@ -1894,13 +2374,13 @@ var startDevPrewarm = ({
1894
2374
  },
1895
2375
  (res) => {
1896
2376
  res.resume();
1897
- resolve2(true);
2377
+ resolve3(true);
1898
2378
  }
1899
2379
  );
1900
- req.on("error", () => resolve2(false));
2380
+ req.on("error", () => resolve3(false));
1901
2381
  req.on("timeout", () => {
1902
2382
  req.destroy();
1903
- resolve2(false);
2383
+ resolve3(false);
1904
2384
  });
1905
2385
  });
1906
2386
  const tick = async () => {
@@ -1920,12 +2400,12 @@ var startDevPrewarm = ({
1920
2400
  var buildDevSearchIndex = async ({ config }) => {
1921
2401
  const result = await buildSearchIndex({
1922
2402
  projectDir: dir,
1923
- outDir: import_node_path11.default.join(core, "public"),
1924
- searchOutputPath: import_node_path11.default.join(core, "public", "pagefind"),
2403
+ outDir: import_node_path15.default.join(core, "public"),
2404
+ searchOutputPath: import_node_path15.default.join(core, "public", "pagefind"),
1925
2405
  config,
1926
2406
  isServer: false
1927
2407
  });
1928
- logger.info(`Indexed ${result.records} page(s) into ${import_node_path11.default.relative(core, result.outputPath)}`, "search");
2408
+ logger.info(`Indexed ${result.records} page(s) into ${import_node_path15.default.relative(core, result.outputPath)}`, "search");
1929
2409
  };
1930
2410
  var warnIfIndexedSearchUsesAuth = async () => {
1931
2411
  if (!await isAuthEnabled()) {
@@ -1982,8 +2462,8 @@ var runCommandWithFilteredOutput = async ({
1982
2462
  shouldFilterLine,
1983
2463
  transformLine = (line) => line
1984
2464
  }) => {
1985
- await new Promise((resolve2, reject) => {
1986
- const child = (0, import_node_child_process.spawn)(command, {
2465
+ await new Promise((resolve3, reject) => {
2466
+ const child = (0, import_node_child_process2.spawn)(command, {
1987
2467
  cwd,
1988
2468
  env: {
1989
2469
  ...process.env,
@@ -2025,7 +2505,7 @@ var runCommandWithFilteredOutput = async ({
2025
2505
  stdoutBuffer = flush(stdoutBuffer, process.stdout, true);
2026
2506
  stderrBuffer = flush(stderrBuffer, process.stderr, true);
2027
2507
  if (code === 0) {
2028
- resolve2();
2508
+ resolve3();
2029
2509
  return;
2030
2510
  }
2031
2511
  reject(new Error(`Command failed with exit code ${code}: ${command}`));
@@ -2040,12 +2520,12 @@ var copyCore = () => {
2040
2520
  import_fs3.default.cpSync(eventCatalogDir, core, {
2041
2521
  recursive: true,
2042
2522
  filter: (src) => {
2043
- const relativePath = import_node_path11.default.relative(eventCatalogDir, src);
2044
- const pathParts = relativePath.split(import_node_path11.default.sep);
2523
+ const relativePath = import_node_path15.default.relative(eventCatalogDir, src);
2524
+ const pathParts = relativePath.split(import_node_path15.default.sep);
2045
2525
  return !pathParts.some((part) => [".astro", "dist", "node_modules"].includes(part));
2046
2526
  }
2047
2527
  });
2048
- const coreNodeModules = import_node_path11.default.join(core, "node_modules");
2528
+ const coreNodeModules = import_node_path15.default.join(core, "node_modules");
2049
2529
  const installedCoreNodeModules = resolveInstalledCoreNodeModules(currentDir);
2050
2530
  linkCoreNodeModules({ coreNodeModules, installedCoreNodeModules });
2051
2531
  };
@@ -2104,8 +2584,8 @@ program.command("dev").description("Run development server of EventCatalog").opt
2104
2584
  logger.info("Setting up EventCatalog...", "eventcatalog");
2105
2585
  const isServer = await isOutputServer();
2106
2586
  logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
2107
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2108
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2587
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2588
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2109
2589
  }
2110
2590
  if (options.debug) {
2111
2591
  logger.info("Debug mode enabled", "debug");
@@ -2185,8 +2665,8 @@ program.command("build").description("Run build of EventCatalog").action(async (
2185
2665
  logger.info("Building EventCatalog...", "build");
2186
2666
  const isServer = await isOutputServer();
2187
2667
  logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
2188
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2189
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2668
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2669
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2190
2670
  }
2191
2671
  await verifyRequiredFieldsAreInCatalogConfigFile(dir);
2192
2672
  copyCore();
@@ -2236,7 +2716,7 @@ program.command("build").description("Run build of EventCatalog").action(async (
2236
2716
  if (await isIndexedSearchEnabled()) {
2237
2717
  await warnIfIndexedSearchUsesAuth();
2238
2718
  const config = await getEventCatalogConfigFile(dir);
2239
- const outDir = import_node_path11.default.resolve(dir, await getProjectOutDir());
2719
+ const outDir = import_node_path15.default.resolve(dir, await getProjectOutDir());
2240
2720
  logger.info("Building indexed search...", "search");
2241
2721
  const result = await buildSearchIndex({
2242
2722
  projectDir: dir,
@@ -2244,7 +2724,7 @@ program.command("build").description("Run build of EventCatalog").action(async (
2244
2724
  config,
2245
2725
  isServer
2246
2726
  });
2247
- logger.info(`Indexed ${result.records} page(s) into ${import_node_path11.default.relative(dir, result.outputPath)}`, "search");
2727
+ logger.info(`Indexed ${result.records} page(s) into ${import_node_path15.default.relative(dir, result.outputPath)}`, "search");
2248
2728
  }
2249
2729
  });
2250
2730
  var previewCatalog = async ({
@@ -2271,7 +2751,7 @@ var startServerCatalog = async ({
2271
2751
  isEventCatalogStarter = false,
2272
2752
  isEventCatalogScale = false
2273
2753
  }) => {
2274
- const serverEntryPath = import_node_path11.default.join(dir, "dist", "server", "entry.mjs");
2754
+ const serverEntryPath = import_node_path15.default.join(dir, "dist", "server", "entry.mjs");
2275
2755
  await runCommandWithFilteredOutput({
2276
2756
  command: `node "${serverEntryPath}"`,
2277
2757
  cwd: core,
@@ -2288,8 +2768,8 @@ var startServerCatalog = async ({
2288
2768
  program.command("preview").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
2289
2769
  logger.welcome();
2290
2770
  logger.info("Starting preview of your build...", "preview");
2291
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2292
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2771
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2772
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2293
2773
  }
2294
2774
  const canEmbedPages = await (0, import_license.isFeatureEnabled)(
2295
2775
  "@eventcatalog/backstage-plugin-eventcatalog",
@@ -2307,8 +2787,8 @@ program.command("preview").description("Serves the contents of your eventcatalog
2307
2787
  program.command("start").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
2308
2788
  logger.welcome();
2309
2789
  logger.info("Starting preview of your build...", "preview");
2310
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2311
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2790
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2791
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2312
2792
  }
2313
2793
  const canEmbedPages = await (0, import_license.isFeatureEnabled)(
2314
2794
  "@eventcatalog/backstage-plugin-eventcatalog",
@@ -2335,25 +2815,145 @@ program.command("start").description("Serves the contents of your eventcatalog b
2335
2815
  program.command("export").description("Export your EventCatalog using the SDK dumpCatalog function").option("--include-markdown", "Include markdown content in the export", false).action(async (options) => {
2336
2816
  logger.welcome();
2337
2817
  logger.info("Exporting EventCatalog...", "export");
2338
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2339
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2818
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2819
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2340
2820
  }
2341
2821
  const { default: initSDK } = await import("@eventcatalog/sdk");
2342
2822
  const sdk = initSDK(dir);
2343
2823
  const catalog = await sdk.dumpCatalog({ includeMarkdown: options.includeMarkdown });
2344
- const exportsDir = import_node_path11.default.join(dir, "exports");
2824
+ const exportsDir = import_node_path15.default.join(dir, "exports");
2345
2825
  ensureDir(exportsDir);
2346
2826
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2347
- const exportFile = import_node_path11.default.join(exportsDir, `catalog-${date}.json`);
2827
+ const exportFile = import_node_path15.default.join(exportsDir, `catalog-${date}.json`);
2348
2828
  import_fs3.default.writeFileSync(exportFile, JSON.stringify(catalog, null, 2), "utf-8");
2349
2829
  logger.info(`Catalog exported to ${exportFile}`, "export");
2350
2830
  });
2351
2831
  program.command("generate [siteDir]").description("Start the generator scripts.").action(async () => {
2352
- if (import_fs3.default.existsSync(import_node_path11.default.join(dir, ".env"))) {
2353
- import_dotenv.default.config({ path: import_node_path11.default.join(dir, ".env") });
2832
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2833
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2354
2834
  }
2355
2835
  await generate(dir);
2356
2836
  });
2837
+ var reportFederationProgress = (event) => {
2838
+ switch (event.type) {
2839
+ case "configured":
2840
+ if (event.sources > 0)
2841
+ logger.info(`Found ${event.sources} configured source${event.sources === 1 ? "" : "s"}`, "federation");
2842
+ return;
2843
+ case "cleanup:complete":
2844
+ logger.success(
2845
+ `No sources configured; removed previous federation output${event.publicFiles > 0 ? ` and ${event.publicFiles} managed public file${event.publicFiles === 1 ? "" : "s"}` : ""}.`,
2846
+ "federation"
2847
+ );
2848
+ return;
2849
+ case "cache:disabled":
2850
+ logger.info("Content cache disabled; all federated files will be downloaded.", "federation");
2851
+ return;
2852
+ case "source:start":
2853
+ logger.info(`[${event.current}/${event.total}] Fetching and indexing ${event.source.id}...`, "federation");
2854
+ return;
2855
+ case "source:complete":
2856
+ logger.success(
2857
+ `[${event.current}/${event.total}] ${event.source.id}: ${event.resources} resources at ${event.commit.slice(0, 7)}${event.generated ? " (index generated)" : " (published index)"}`,
2858
+ "federation"
2859
+ );
2860
+ return;
2861
+ case "local:start":
2862
+ logger.info("Indexing central catalog ownership...", "federation");
2863
+ return;
2864
+ case "local:complete":
2865
+ logger.success(
2866
+ `Central catalog: ${event.resources} local resource${event.resources === 1 ? "" : "s"} found (excluding federated/)`,
2867
+ "federation"
2868
+ );
2869
+ return;
2870
+ case "resolving":
2871
+ logger.info(
2872
+ `Validating ownership across ${event.localResources} local resource${event.localResources === 1 ? "" : "s"} and ${event.resources} remote resource${event.resources === 1 ? "" : "s"}...`,
2873
+ "federation"
2874
+ );
2875
+ return;
2876
+ case "resolved":
2877
+ if (event.graph.conflicts.length > 0) {
2878
+ logger.error(
2879
+ `${event.graph.conflicts.length} ownership conflict${event.graph.conflicts.length === 1 ? "" : "s"} found`,
2880
+ "federation"
2881
+ );
2882
+ for (const conflict of event.graph.conflicts.slice(0, 10)) {
2883
+ logger.error(`${conflict.id}: ${conflict.sources.join(", ")}`, "federation");
2884
+ }
2885
+ if (event.graph.conflicts.length > 10) {
2886
+ logger.error(`...and ${event.graph.conflicts.length - 10} more`, "federation");
2887
+ }
2888
+ return;
2889
+ }
2890
+ logger.success(
2891
+ `Graph resolved: ${event.graph.entities.length} remote resources, ${event.graph.edges.length} relationships`,
2892
+ "federation"
2893
+ );
2894
+ if (event.graph.warnings.length > 0) {
2895
+ logger.warning(
2896
+ `${event.graph.warnings.length} federation warning${event.graph.warnings.length === 1 ? "" : "s"}`,
2897
+ "federation"
2898
+ );
2899
+ }
2900
+ if (event.graph.externals.length > 0) {
2901
+ logger.warning(
2902
+ `${event.graph.externals.length} reference${event.graph.externals.length === 1 ? "" : "s"} remain external`,
2903
+ "federation"
2904
+ );
2905
+ }
2906
+ return;
2907
+ case "hydrating":
2908
+ logger.info(`Hydrating federated content into ${import_node_path15.default.relative(dir, event.outDir)}/...`, "federation");
2909
+ return;
2910
+ case "hydrate:cache":
2911
+ if (event.files === 1 || event.files % 25 === 0) {
2912
+ logger.info(`Reused ${event.files} cached file${event.files === 1 ? "" : "s"}...`, "federation");
2913
+ }
2914
+ return;
2915
+ case "hydrate:file":
2916
+ if (event.files === 1 || event.files % 25 === 0) {
2917
+ logger.info(`Fetched ${event.files} federated file${event.files === 1 ? "" : "s"}...`, "federation");
2918
+ }
2919
+ return;
2920
+ case "public:complete":
2921
+ logger.success(
2922
+ `Public assets: ${event.result.copied} copied, ${event.result.skipped} preserved from the main catalog, ${event.result.removed} stale removed`,
2923
+ "federation"
2924
+ );
2925
+ if (event.result.overwritten > 0) {
2926
+ logger.warning(
2927
+ `${event.result.overwritten} public asset conflict${event.result.overwritten === 1 ? "" : "s"} resolved using the last configured source`,
2928
+ "federation"
2929
+ );
2930
+ }
2931
+ return;
2932
+ case "complete":
2933
+ logger.success(
2934
+ `Federation complete: ${event.result.sources} sources, ${event.result.resources} remote resources, ${event.result.hydrate.written} files written (${event.result.hydrate.fetched} downloaded, ${event.result.hydrate.written - event.result.hydrate.fetched} cached)`,
2935
+ "federation"
2936
+ );
2937
+ logger.info(`Pinned source commits in ${import_node_path15.default.relative(dir, event.result.lockPath)}`, "federation");
2938
+ }
2939
+ };
2940
+ program.command("federate").description("Fetch, resolve, and hydrate the catalogs configured in federation.sources.").option("--no-cache", "Download all federation content and refresh the cache.").action(async (commandOptions) => {
2941
+ logger.welcome();
2942
+ if (import_fs3.default.existsSync(import_node_path15.default.join(dir, ".env"))) {
2943
+ import_dotenv.default.config({ path: import_node_path15.default.join(dir, ".env") });
2944
+ }
2945
+ logger.info("Starting federation...", "federation");
2946
+ let cleanedPreviousOutput = false;
2947
+ const result = await federateCatalog(dir, {
2948
+ useCache: commandOptions.cache,
2949
+ isFederationEnabled: import_license.isEventCatalogScaleEnabled,
2950
+ onProgress: (event) => {
2951
+ if (event.type === "cleanup:complete") cleanedPreviousOutput = true;
2952
+ reportFederationProgress(event);
2953
+ }
2954
+ });
2955
+ if (!result && !cleanedPreviousOutput) logger.warning("No federation sources configured; nothing to do.", "federation");
2956
+ });
2357
2957
  program.addHelpText(
2358
2958
  "after",
2359
2959
  `
@@ -2363,6 +2963,6 @@ program.addHelpText(
2363
2963
  `
2364
2964
  );
2365
2965
  program.parseAsync().then(() => process.exit(0)).catch((err) => {
2366
- console.error(err);
2966
+ if (!(err instanceof FederationConflictError)) console.error(err);
2367
2967
  process.exit(1);
2368
2968
  });