@eventcatalog/core 4.7.3 → 4.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-SMDTRRED.js → chunk-2BYTIH4L.js} +1 -1
- package/dist/chunk-A2RZR3U4.js +29 -0
- package/dist/{chunk-XXJX3WWF.js → chunk-EUZT4RKY.js} +1 -1
- package/dist/chunk-GYYYAUJD.js +276 -0
- package/dist/chunk-JJPB6EOZ.js +159 -0
- package/dist/{chunk-GINIJSFI.js → chunk-QJRY6Q7S.js} +1 -1
- package/dist/{chunk-IALEXWSE.js → chunk-QQPH6RCB.js} +24 -1
- package/dist/chunk-SDZQJTJW.js +90 -0
- package/dist/chunk-WRNH5C3U.js +118 -0
- package/dist/{chunk-V4FAVGI7.js → chunk-ZBPMCEXS.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +707 -255
- package/dist/eventcatalog.config.d.cts +12 -6
- package/dist/eventcatalog.config.d.ts +12 -6
- package/dist/eventcatalog.js +42 -42
- package/dist/federation/diagnostics.cjs +187 -0
- package/dist/federation/diagnostics.d.cts +23 -0
- package/dist/federation/diagnostics.d.ts +23 -0
- package/dist/federation/diagnostics.js +14 -0
- package/dist/federation/federate.cjs +605 -174
- package/dist/federation/federate.d.cts +8 -1
- package/dist/federation/federate.d.ts +8 -1
- package/dist/federation/federate.js +8 -2
- package/dist/federation/filesystem-source-provider.cjs +124 -0
- package/dist/federation/filesystem-source-provider.d.cts +7 -0
- package/dist/federation/filesystem-source-provider.d.ts +7 -0
- package/dist/federation/filesystem-source-provider.js +6 -0
- package/dist/federation/output-transaction.cjs +152 -0
- package/dist/federation/output-transaction.d.cts +3 -0
- package/dist/federation/output-transaction.d.ts +3 -0
- package/dist/federation/output-transaction.js +6 -0
- package/dist/federation/source-provider.cjs +265 -0
- package/dist/federation/source-provider.d.cts +11 -0
- package/dist/federation/source-provider.d.ts +11 -0
- package/dist/federation/source-provider.js +8 -0
- package/dist/generate.cjs +24 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +24 -1
- package/dist/utils/cli-logger.d.cts +6 -0
- package/dist/utils/cli-logger.d.ts +6 -0
- package/dist/utils/cli-logger.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-ZR6AH5Z2.js +0 -204
package/dist/eventcatalog.cjs
CHANGED
|
@@ -29,10 +29,10 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
29
29
|
// src/eventcatalog.ts
|
|
30
30
|
var import_commander = require("commander");
|
|
31
31
|
var import_node_child_process2 = require("child_process");
|
|
32
|
-
var
|
|
32
|
+
var import_node_path17 = require("path");
|
|
33
33
|
var import_node_http = __toESM(require("http"), 1);
|
|
34
34
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
35
|
-
var
|
|
35
|
+
var import_node_path18 = __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.7.
|
|
147
|
+
var version = "4.7.5";
|
|
148
148
|
|
|
149
149
|
// src/constants.ts
|
|
150
150
|
var VERSION = version;
|
|
@@ -183,6 +183,29 @@ var logger = {
|
|
|
183
183
|
warning: (message, tag = "warn") => {
|
|
184
184
|
console.log(formatMessage(tag, message, import_picocolors.default.yellow));
|
|
185
185
|
},
|
|
186
|
+
line: (message = "") => {
|
|
187
|
+
console.log(message);
|
|
188
|
+
},
|
|
189
|
+
diagnostic: (severity, message, rule, attributes = []) => {
|
|
190
|
+
const isError = severity === "error";
|
|
191
|
+
const color = isError ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
192
|
+
const icon = isError ? "\u2716" : "\u26A0";
|
|
193
|
+
const label = `${icon} ${severity.padEnd(7)} ${message}`;
|
|
194
|
+
const ruleSpacing = " ".repeat(Math.max(2, 72 - label.length));
|
|
195
|
+
console.log(` ${color(icon)} ${color(severity.padEnd(7))} ${message}${ruleSpacing}${import_picocolors.default.gray(rule)}`);
|
|
196
|
+
for (const attribute of attributes) {
|
|
197
|
+
console.log(` - ${import_picocolors.default.dim(`${attribute.label}:`)} ${attribute.value}`);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
diagnosticSummary: (errors, warnings) => {
|
|
201
|
+
const total = errors + warnings;
|
|
202
|
+
const color = errors > 0 ? import_picocolors.default.red : import_picocolors.default.yellow;
|
|
203
|
+
const icon = errors > 0 ? "\u2716" : "\u26A0";
|
|
204
|
+
const problems = `${total} problem${total === 1 ? "" : "s"}`;
|
|
205
|
+
const errorCount = `${errors} error${errors === 1 ? "" : "s"}`;
|
|
206
|
+
const warningCount = `${warnings} warning${warnings === 1 ? "" : "s"}`;
|
|
207
|
+
console.log(color(`${icon} ${problems} (${errorCount}, ${warningCount})`));
|
|
208
|
+
},
|
|
186
209
|
dim: (message) => {
|
|
187
210
|
console.log(import_picocolors.default.dim(message));
|
|
188
211
|
}
|
|
@@ -1310,22 +1333,22 @@ function getAvroTypeName(type) {
|
|
|
1310
1333
|
function walkAvroRecord(schema, prefix, fields) {
|
|
1311
1334
|
if (!schema.fields || !Array.isArray(schema.fields)) return;
|
|
1312
1335
|
for (const field of schema.fields) {
|
|
1313
|
-
const
|
|
1336
|
+
const path18 = prefix ? `${prefix}.${field.name}` : field.name;
|
|
1314
1337
|
const isOptional = Array.isArray(field.type) && field.type.includes("null");
|
|
1315
1338
|
const typeName = getAvroTypeName(field.type);
|
|
1316
1339
|
fields.push({
|
|
1317
|
-
path:
|
|
1340
|
+
path: path18,
|
|
1318
1341
|
type: typeName,
|
|
1319
1342
|
description: field.doc || "",
|
|
1320
1343
|
required: !isOptional
|
|
1321
1344
|
});
|
|
1322
1345
|
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;
|
|
1323
1346
|
if (innerType) {
|
|
1324
|
-
walkAvroRecord(innerType,
|
|
1347
|
+
walkAvroRecord(innerType, path18, fields);
|
|
1325
1348
|
}
|
|
1326
1349
|
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;
|
|
1327
1350
|
if (arrayType && typeof arrayType.items === "object" && arrayType.items.type === "record") {
|
|
1328
|
-
walkAvroRecord(arrayType.items, `${
|
|
1351
|
+
walkAvroRecord(arrayType.items, `${path18}[]`, fields);
|
|
1329
1352
|
}
|
|
1330
1353
|
}
|
|
1331
1354
|
}
|
|
@@ -1356,32 +1379,32 @@ function walkJsonSchema(node, prefix, requiredList, rootSchema, fields) {
|
|
|
1356
1379
|
}
|
|
1357
1380
|
if (!node.properties) return;
|
|
1358
1381
|
for (const [name, prop] of Object.entries(node.properties)) {
|
|
1359
|
-
const
|
|
1382
|
+
const path18 = prefix ? `${prefix}.${name}` : name;
|
|
1360
1383
|
const isRequired = requiredList.includes(name);
|
|
1361
1384
|
if (prop.$ref) {
|
|
1362
1385
|
const resolved = resolveLocalRef(prop.$ref, rootSchema);
|
|
1363
1386
|
if (resolved) {
|
|
1364
1387
|
const rawRefType = resolved.type || "object";
|
|
1365
1388
|
const type2 = Array.isArray(rawRefType) ? [...rawRefType].sort().join(" | ") : rawRefType;
|
|
1366
|
-
fields.push({ path:
|
|
1389
|
+
fields.push({ path: path18, type: type2, description: resolved.description || "", required: isRequired });
|
|
1367
1390
|
if (resolved.properties) {
|
|
1368
|
-
walkJsonSchema(resolved,
|
|
1391
|
+
walkJsonSchema(resolved, path18, resolved.required || [], rootSchema, fields);
|
|
1369
1392
|
}
|
|
1370
1393
|
} else {
|
|
1371
|
-
fields.push({ path:
|
|
1394
|
+
fields.push({ path: path18, type: "$ref", description: "", required: isRequired });
|
|
1372
1395
|
}
|
|
1373
1396
|
continue;
|
|
1374
1397
|
}
|
|
1375
1398
|
const rawType = prop.type || (prop.enum ? "enum" : prop.$ref ? "$ref" : "object");
|
|
1376
1399
|
const typeList = Array.isArray(rawType) ? rawType : [rawType];
|
|
1377
1400
|
const type = Array.isArray(rawType) ? [...rawType].sort().join(" | ") : rawType;
|
|
1378
|
-
fields.push({ path:
|
|
1401
|
+
fields.push({ path: path18, type, description: prop.description || "", required: isRequired });
|
|
1379
1402
|
if (typeList.includes("object") && prop.properties) {
|
|
1380
|
-
walkJsonSchema(prop,
|
|
1403
|
+
walkJsonSchema(prop, path18, prop.required || [], rootSchema, fields);
|
|
1381
1404
|
}
|
|
1382
1405
|
if (typeList.includes("array") && prop.items) {
|
|
1383
1406
|
if (prop.items.type === "object" && prop.items.properties) {
|
|
1384
|
-
walkJsonSchema(prop.items, `${
|
|
1407
|
+
walkJsonSchema(prop.items, `${path18}[]`, prop.items.required || [], rootSchema, fields);
|
|
1385
1408
|
}
|
|
1386
1409
|
}
|
|
1387
1410
|
}
|
|
@@ -1900,10 +1923,10 @@ var createAstroDevLineFilter = () => {
|
|
|
1900
1923
|
};
|
|
1901
1924
|
|
|
1902
1925
|
// src/federation/federate.ts
|
|
1903
|
-
var
|
|
1904
|
-
var
|
|
1905
|
-
var
|
|
1906
|
-
var
|
|
1926
|
+
var import_node_crypto4 = require("crypto");
|
|
1927
|
+
var import_promises9 = __toESM(require("fs/promises"), 1);
|
|
1928
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
1929
|
+
var import_sdk3 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
1907
1930
|
|
|
1908
1931
|
// src/federation/content-cache.ts
|
|
1909
1932
|
var import_node_crypto = require("crypto");
|
|
@@ -1946,125 +1969,272 @@ var createFederationContentCache = (projectDirectory, options = {}) => {
|
|
|
1946
1969
|
};
|
|
1947
1970
|
};
|
|
1948
1971
|
|
|
1949
|
-
// src/federation/
|
|
1950
|
-
var
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
};
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
}
|
|
1969
|
-
var
|
|
1970
|
-
var
|
|
1971
|
-
const
|
|
1972
|
-
return `
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
};
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1972
|
+
// src/federation/diagnostics.ts
|
|
1973
|
+
var federationRuleDefaults = {
|
|
1974
|
+
"federation/duplicate-source": "error",
|
|
1975
|
+
"federation/type-collision": "error",
|
|
1976
|
+
"federation/pointer-type-mismatch": "error",
|
|
1977
|
+
"federation/facet-disagreement": "error",
|
|
1978
|
+
"federation/asset-collision": "warn",
|
|
1979
|
+
"federation/missing-resource": "warn",
|
|
1980
|
+
"federation/unresolved-version": "warn"
|
|
1981
|
+
};
|
|
1982
|
+
var resolveFederationRules = (rules = {}) => {
|
|
1983
|
+
for (const [rule, level] of Object.entries(rules)) {
|
|
1984
|
+
if (!Object.hasOwn(federationRuleDefaults, rule)) throw new Error(`Unknown federation rule "${rule}".`);
|
|
1985
|
+
if (level !== "off" && level !== "warn" && level !== "error") {
|
|
1986
|
+
throw new Error(`Invalid level "${String(level)}" for federation rule "${rule}". Expected off, warn, or error.`);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
return { ...federationRuleDefaults, ...rules };
|
|
1990
|
+
};
|
|
1991
|
+
var getExternalResource = (external) => `${external.id}${external.version === void 0 ? "" : `@${external.version}`}`;
|
|
1992
|
+
var getVersionedResource = (id, version2) => `${id}${version2 == null ? "" : `@${version2}`}`;
|
|
1993
|
+
var getDocumentedResourceType = (type) => {
|
|
1994
|
+
const usesAn = type === "adr" || type === "agent" || type === "entity" || type === "event";
|
|
1995
|
+
return `documented as ${usesAn ? "an" : "a"} ${type}`;
|
|
1996
|
+
};
|
|
1997
|
+
var getConflictDiagnostic = (conflict, graph) => {
|
|
1998
|
+
const catalogs = { label: "catalogs", value: conflict.sources.join(", ") };
|
|
1999
|
+
switch (conflict.kind) {
|
|
2000
|
+
case "duplicate-source":
|
|
2001
|
+
return {
|
|
2002
|
+
severity: "error",
|
|
2003
|
+
message: "Resource has multiple owners",
|
|
2004
|
+
rule: "federation/duplicate-source",
|
|
2005
|
+
attributes: [
|
|
2006
|
+
{ label: "resource", value: conflict.id },
|
|
2007
|
+
catalogs,
|
|
2008
|
+
{ label: "resolution", value: "assign a single owning catalog" }
|
|
2009
|
+
]
|
|
2010
|
+
};
|
|
2011
|
+
case "type-collision": {
|
|
2012
|
+
const types = [
|
|
2013
|
+
...new Map(
|
|
2014
|
+
graph.entities.filter((entity) => entity.id === conflict.id).map(
|
|
2015
|
+
(entity) => [
|
|
2016
|
+
`${entity.resolvedFrom.source}:${entity.type}`,
|
|
2017
|
+
{ label: entity.resolvedFrom.source, value: getDocumentedResourceType(entity.type) }
|
|
2018
|
+
]
|
|
2019
|
+
)
|
|
2020
|
+
).values()
|
|
2021
|
+
];
|
|
2022
|
+
return {
|
|
2023
|
+
severity: "error",
|
|
2024
|
+
message: "Resource ID has conflicting types",
|
|
2025
|
+
rule: "federation/type-collision",
|
|
2026
|
+
attributes: [{ label: "resource", value: conflict.id }, ...types.length > 0 ? types : [catalogs]]
|
|
2027
|
+
};
|
|
1989
2028
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
};
|
|
2006
|
-
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
2007
|
-
const { owner, repository } = parseGitHubSource(source);
|
|
2008
|
-
const catalogPath = import_node_path12.default.posix.normalize(source.path ?? ".");
|
|
2009
|
-
const directory = await import_promises5.default.mkdtemp(import_node_path12.default.join(import_node_os4.default.tmpdir(), "eventcatalog-federation-"));
|
|
2010
|
-
const env = getGitEnvironment(token);
|
|
2011
|
-
try {
|
|
2012
|
-
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
2013
|
-
await git(["init", "--quiet"]);
|
|
2014
|
-
await git(["remote", "add", "origin", `https://github.com/${owner}/${repository}.git`]);
|
|
2015
|
-
if (catalogPath !== ".") {
|
|
2016
|
-
await git(["sparse-checkout", "init", "--cone"]);
|
|
2017
|
-
await git(["sparse-checkout", "set", catalogPath]);
|
|
2029
|
+
case "pointer-type-mismatch": {
|
|
2030
|
+
const typeMismatch = /^Expected (.+) but found (.+)$/.exec(conflict.detail ?? "");
|
|
2031
|
+
return {
|
|
2032
|
+
severity: "error",
|
|
2033
|
+
message: "Reference type does not match resource",
|
|
2034
|
+
rule: "federation/pointer-type-mismatch",
|
|
2035
|
+
attributes: [
|
|
2036
|
+
{ label: "resource", value: conflict.id },
|
|
2037
|
+
...typeMismatch ? [
|
|
2038
|
+
{ label: "expected type", value: typeMismatch[1] },
|
|
2039
|
+
{ label: "actual type", value: typeMismatch[2] }
|
|
2040
|
+
] : conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
|
|
2041
|
+
catalogs
|
|
2042
|
+
]
|
|
2043
|
+
};
|
|
2018
2044
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2045
|
+
case "facet-disagreement":
|
|
2046
|
+
return {
|
|
2047
|
+
severity: "error",
|
|
2048
|
+
message: "Catalogs disagree about this resource",
|
|
2049
|
+
rule: "federation/facet-disagreement",
|
|
2050
|
+
attributes: [
|
|
2051
|
+
{ label: "resource", value: conflict.id },
|
|
2052
|
+
...conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
|
|
2053
|
+
catalogs
|
|
2054
|
+
]
|
|
2055
|
+
};
|
|
2024
2056
|
}
|
|
2025
2057
|
};
|
|
2026
|
-
var
|
|
2027
|
-
const
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2058
|
+
var getFederationDiagnostics = (graph, rules = {}) => {
|
|
2059
|
+
const resolvedRules = resolveFederationRules(rules);
|
|
2060
|
+
const diagnostics = graph.conflicts.map((conflict) => getConflictDiagnostic(conflict, graph));
|
|
2061
|
+
for (const external of graph.externals) {
|
|
2062
|
+
const resource = getExternalResource(external);
|
|
2063
|
+
for (const referencedBy of external.referencedBy) {
|
|
2064
|
+
const referrer = graph.entities.find((entity) => entity.id === referencedBy);
|
|
2065
|
+
diagnostics.push({
|
|
2066
|
+
severity: "warning",
|
|
2067
|
+
message: "Referenced EventCatalog resource does not exist",
|
|
2068
|
+
rule: "federation/missing-resource",
|
|
2069
|
+
attributes: [
|
|
2070
|
+
{ label: "source catalog", value: referrer?.resolvedFrom.source ?? "unknown" },
|
|
2071
|
+
{ label: "referenced by", value: referencedBy },
|
|
2072
|
+
{ label: "missing resource", value: resource }
|
|
2073
|
+
]
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2033
2076
|
}
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2077
|
+
for (const edge of graph.edges.filter((edge2) => edge2.status === "unresolved")) {
|
|
2078
|
+
const availableVersions = [
|
|
2079
|
+
...new Set(graph.entities.filter((entity) => entity.id === edge.to).map((entity) => entity.version ?? "unversioned"))
|
|
2080
|
+
].sort((left, right) => left.localeCompare(right, void 0, { numeric: true }));
|
|
2081
|
+
diagnostics.push({
|
|
2082
|
+
severity: "warning",
|
|
2083
|
+
message: "Referenced EventCatalog resource version does not exist",
|
|
2084
|
+
rule: "federation/unresolved-version",
|
|
2085
|
+
attributes: [
|
|
2086
|
+
{ label: "source catalog", value: edge.fromResolvedFrom.source },
|
|
2087
|
+
{ label: "referenced by", value: getVersionedResource(edge.from, edge.fromVersion) },
|
|
2088
|
+
{ label: "resource", value: edge.to },
|
|
2089
|
+
{ label: "requested version", value: edge.pointer ?? "unspecified" },
|
|
2090
|
+
{ label: "available versions", value: availableVersions.join(", ") }
|
|
2091
|
+
]
|
|
2092
|
+
});
|
|
2044
2093
|
}
|
|
2045
|
-
|
|
2094
|
+
for (const warning of graph.warnings) {
|
|
2095
|
+
diagnostics.push({
|
|
2096
|
+
severity: "warning",
|
|
2097
|
+
message: "Asset collision",
|
|
2098
|
+
rule: "federation/asset-collision",
|
|
2099
|
+
attributes: [
|
|
2100
|
+
{ label: "asset", value: warning.path },
|
|
2101
|
+
{ label: "sources", value: warning.sources.join(", ") },
|
|
2102
|
+
{ label: "winner", value: warning.winner },
|
|
2103
|
+
{ label: "resolution", value: "last configured source wins" }
|
|
2104
|
+
]
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
return diagnostics.flatMap((diagnostic) => {
|
|
2108
|
+
const level = resolvedRules[diagnostic.rule];
|
|
2109
|
+
if (level === "off") return [];
|
|
2110
|
+
return [{ ...diagnostic, severity: level === "error" ? "error" : "warning" }];
|
|
2111
|
+
}).sort(
|
|
2112
|
+
(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 ?? "")
|
|
2113
|
+
);
|
|
2046
2114
|
};
|
|
2047
|
-
var
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2115
|
+
var getFederationDiagnosticCounts = (diagnostics) => diagnostics.reduce(
|
|
2116
|
+
(counts, diagnostic) => ({
|
|
2117
|
+
errors: counts.errors + (diagnostic.severity === "error" ? 1 : 0),
|
|
2118
|
+
warnings: counts.warnings + (diagnostic.severity === "warning" ? 1 : 0)
|
|
2119
|
+
}),
|
|
2120
|
+
{ errors: 0, warnings: 0 }
|
|
2121
|
+
);
|
|
2122
|
+
var getVisibleFederationDiagnostics = (diagnostics, verbose) => diagnostics.filter((diagnostic) => verbose || diagnostic.severity === "error");
|
|
2123
|
+
|
|
2124
|
+
// src/federation/output-transaction.ts
|
|
2125
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
2126
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2127
|
+
var getSafePublicPath = (publicDirectory, relativePath) => {
|
|
2128
|
+
const normalizedPath = import_node_path12.default.posix.normalize(relativePath);
|
|
2129
|
+
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("../");
|
|
2130
|
+
if (unsafe) return void 0;
|
|
2131
|
+
const destinationPath = import_node_path12.default.resolve(publicDirectory, relativePath);
|
|
2132
|
+
const relativeDestinationPath = import_node_path12.default.relative(publicDirectory, destinationPath);
|
|
2133
|
+
return relativeDestinationPath !== "" && relativeDestinationPath !== ".." && !relativeDestinationPath.startsWith(`..${import_node_path12.default.sep}`) && !import_node_path12.default.isAbsolute(relativeDestinationPath) ? destinationPath : void 0;
|
|
2134
|
+
};
|
|
2135
|
+
var beginFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths) => {
|
|
2136
|
+
const transactionDirectory = await import_promises5.default.mkdtemp(import_node_path12.default.join(projectDirectory, ".eventcatalog-federation-transaction-"));
|
|
2137
|
+
const previousOutDir = import_node_path12.default.join(transactionDirectory, "federated");
|
|
2138
|
+
const publicBackupDirectory = import_node_path12.default.join(transactionDirectory, "public");
|
|
2139
|
+
const publicDirectory = import_node_path12.default.resolve(projectDirectory, "public");
|
|
2140
|
+
const publicSnapshots = [];
|
|
2141
|
+
const missingPublicDirectories = /* @__PURE__ */ new Set();
|
|
2142
|
+
let hadPreviousOutDir = false;
|
|
2143
|
+
try {
|
|
2144
|
+
for (const relativePath of new Set(relativePublicPaths)) {
|
|
2145
|
+
const destinationPath = getSafePublicPath(publicDirectory, relativePath);
|
|
2146
|
+
if (!destinationPath) continue;
|
|
2147
|
+
try {
|
|
2148
|
+
const stat = await import_promises5.default.lstat(destinationPath);
|
|
2149
|
+
if (!stat.isFile()) continue;
|
|
2150
|
+
const backupPath = import_node_path12.default.join(publicBackupDirectory, `${publicSnapshots.length}`);
|
|
2151
|
+
await import_promises5.default.mkdir(import_node_path12.default.dirname(backupPath), { recursive: true });
|
|
2152
|
+
await import_promises5.default.copyFile(destinationPath, backupPath);
|
|
2153
|
+
publicSnapshots.push({ backupPath, destinationPath, state: "file" });
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
const code = error.code;
|
|
2156
|
+
if (code === "ENOTDIR") continue;
|
|
2157
|
+
if (code !== "ENOENT") throw error;
|
|
2158
|
+
publicSnapshots.push({ destinationPath, state: "missing" });
|
|
2159
|
+
let directory = import_node_path12.default.dirname(destinationPath);
|
|
2160
|
+
while (directory !== publicDirectory) {
|
|
2161
|
+
try {
|
|
2162
|
+
await import_promises5.default.lstat(directory);
|
|
2163
|
+
break;
|
|
2164
|
+
} catch (directoryError) {
|
|
2165
|
+
const directoryCode = directoryError.code;
|
|
2166
|
+
if (directoryCode === "ENOTDIR") break;
|
|
2167
|
+
if (directoryCode !== "ENOENT") throw directoryError;
|
|
2168
|
+
missingPublicDirectories.add(directory);
|
|
2169
|
+
directory = import_node_path12.default.dirname(directory);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
try {
|
|
2175
|
+
await import_promises5.default.rename(outDir, previousOutDir);
|
|
2176
|
+
hadPreviousOutDir = true;
|
|
2177
|
+
} catch (error) {
|
|
2178
|
+
if (error.code !== "ENOENT") throw error;
|
|
2179
|
+
}
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
await import_promises5.default.rm(transactionDirectory, { recursive: true, force: true });
|
|
2182
|
+
throw error;
|
|
2183
|
+
}
|
|
2053
2184
|
return {
|
|
2054
|
-
async
|
|
2055
|
-
|
|
2056
|
-
const ref = source.ref ?? "main";
|
|
2057
|
-
return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
|
|
2185
|
+
commit: async () => {
|
|
2186
|
+
await import_promises5.default.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
2058
2187
|
},
|
|
2059
|
-
async
|
|
2060
|
-
|
|
2061
|
-
const
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2188
|
+
rollback: async () => {
|
|
2189
|
+
const rollbackErrors = [];
|
|
2190
|
+
const attempt = async (operation) => {
|
|
2191
|
+
try {
|
|
2192
|
+
await operation();
|
|
2193
|
+
} catch (error) {
|
|
2194
|
+
rollbackErrors.push(error);
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
await attempt(() => import_promises5.default.rm(outDir, { recursive: true, force: true }));
|
|
2198
|
+
if (hadPreviousOutDir) await attempt(() => import_promises5.default.rename(previousOutDir, outDir));
|
|
2199
|
+
for (const snapshot of publicSnapshots) {
|
|
2200
|
+
if (snapshot.state === "missing") {
|
|
2201
|
+
await attempt(() => import_promises5.default.rm(snapshot.destinationPath, { force: true }));
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
await attempt(async () => {
|
|
2205
|
+
await import_promises5.default.mkdir(import_node_path12.default.dirname(snapshot.destinationPath), { recursive: true });
|
|
2206
|
+
await import_promises5.default.copyFile(snapshot.backupPath, snapshot.destinationPath);
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
for (const directory of [...missingPublicDirectories].sort((left, right) => right.length - left.length)) {
|
|
2210
|
+
await attempt(async () => {
|
|
2211
|
+
try {
|
|
2212
|
+
await import_promises5.default.rmdir(directory);
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
await attempt(() => import_promises5.default.rm(transactionDirectory, { recursive: true, force: true }));
|
|
2219
|
+
if (rollbackErrors.length > 0) throw new AggregateError(rollbackErrors, "Failed to restore the previous federation output");
|
|
2065
2220
|
}
|
|
2066
2221
|
};
|
|
2067
2222
|
};
|
|
2223
|
+
var withFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths, update) => {
|
|
2224
|
+
const transaction = await beginFederationOutputTransaction(projectDirectory, outDir, relativePublicPaths);
|
|
2225
|
+
try {
|
|
2226
|
+
const result = await update();
|
|
2227
|
+
await transaction.commit();
|
|
2228
|
+
return result;
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
try {
|
|
2231
|
+
await transaction.rollback();
|
|
2232
|
+
} catch (rollbackError) {
|
|
2233
|
+
throw new AggregateError([error, rollbackError], "Federation failed and the previous output could not be restored");
|
|
2234
|
+
}
|
|
2235
|
+
throw error;
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2068
2238
|
|
|
2069
2239
|
// src/federation/public-assets.ts
|
|
2070
2240
|
var import_node_crypto2 = require("crypto");
|
|
@@ -2196,6 +2366,232 @@ var composePublicAssets = async ({
|
|
|
2196
2366
|
return { files, copied, skipped, overwritten, removed };
|
|
2197
2367
|
};
|
|
2198
2368
|
|
|
2369
|
+
// src/federation/filesystem-source-provider.ts
|
|
2370
|
+
var import_node_crypto3 = require("crypto");
|
|
2371
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
2372
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
2373
|
+
var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
|
|
2374
|
+
var FILESYSTEM_SOURCE_PREFIX = "file:";
|
|
2375
|
+
var isWithinDirectory = (directory, target) => {
|
|
2376
|
+
const relativePath = import_node_path14.default.relative(directory, target);
|
|
2377
|
+
return relativePath === "" || !relativePath.startsWith(`..${import_node_path14.default.sep}`) && relativePath !== ".." && !import_node_path14.default.isAbsolute(relativePath);
|
|
2378
|
+
};
|
|
2379
|
+
var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
|
|
2380
|
+
const portablePath = filePath.replaceAll("\\", "/");
|
|
2381
|
+
const normalizedPath = import_node_path14.default.posix.normalize(portablePath);
|
|
2382
|
+
const isUnsafe = filePath.includes("\\") || filePath.includes("\0") || import_node_path14.default.posix.isAbsolute(normalizedPath) || /^[a-zA-Z]:\//.test(normalizedPath) || normalizedPath === ".." || normalizedPath.startsWith("../") || !allowRoot && (normalizedPath === "." || normalizedPath === "");
|
|
2383
|
+
if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
|
|
2384
|
+
return normalizedPath;
|
|
2385
|
+
};
|
|
2386
|
+
var getSourceRoot = (projectDirectory, source) => {
|
|
2387
|
+
if (!source.source.startsWith(FILESYSTEM_SOURCE_PREFIX)) {
|
|
2388
|
+
throw new Error(`Unsupported federation source "${source.source}". Expected file:path/to/catalog.`);
|
|
2389
|
+
}
|
|
2390
|
+
const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
|
|
2391
|
+
if (!locator.trim()) throw new Error(`Filesystem federation source "${source.id}" requires a path after "file:".`);
|
|
2392
|
+
return import_node_path14.default.resolve(projectDirectory, locator);
|
|
2393
|
+
};
|
|
2394
|
+
var getCatalogDirectory = async (projectDirectory, source) => {
|
|
2395
|
+
if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
|
|
2396
|
+
const sourceRoot = getSourceRoot(projectDirectory, source);
|
|
2397
|
+
const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
|
|
2398
|
+
const catalogDirectory = import_node_path14.default.resolve(sourceRoot, ...catalogPath.split("/"));
|
|
2399
|
+
if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
|
|
2400
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises7.default.realpath(sourceRoot), import_promises7.default.realpath(catalogDirectory)]);
|
|
2404
|
+
if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
|
|
2405
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
2406
|
+
}
|
|
2407
|
+
if (!(await import_promises7.default.stat(realCatalogDirectory)).isDirectory()) {
|
|
2408
|
+
throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
|
|
2409
|
+
}
|
|
2410
|
+
return realCatalogDirectory;
|
|
2411
|
+
} catch (error) {
|
|
2412
|
+
if (error.code === "ENOENT") {
|
|
2413
|
+
throw new Error(`Filesystem federation source "${source.id}" does not exist: ${catalogDirectory}`, { cause: error });
|
|
2414
|
+
}
|
|
2415
|
+
throw error;
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
|
|
2419
|
+
const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
|
|
2420
|
+
const filePath = import_node_path14.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
|
|
2421
|
+
if (!isWithinDirectory(catalogDirectory, filePath)) {
|
|
2422
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
2423
|
+
}
|
|
2424
|
+
try {
|
|
2425
|
+
const realFilePath = await import_promises7.default.realpath(filePath);
|
|
2426
|
+
if (!isWithinDirectory(catalogDirectory, realFilePath)) {
|
|
2427
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
2428
|
+
}
|
|
2429
|
+
return realFilePath;
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
if (error.code === "ENOENT") {
|
|
2432
|
+
throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`, { cause: error });
|
|
2433
|
+
}
|
|
2434
|
+
throw error;
|
|
2435
|
+
}
|
|
2436
|
+
};
|
|
2437
|
+
var createFileSystemSourceProvider = (projectDirectory) => ({
|
|
2438
|
+
async resolve(source) {
|
|
2439
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
2440
|
+
const localIndex = await (0, import_sdk.default)(catalogDirectory).buildIndex({
|
|
2441
|
+
source: source.id,
|
|
2442
|
+
commit: "local",
|
|
2443
|
+
includeFederated: false
|
|
2444
|
+
});
|
|
2445
|
+
const snapshot = (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(localIndex)).digest("hex").slice(0, 12);
|
|
2446
|
+
const index = { ...localIndex, commit: `local:${snapshot}` };
|
|
2447
|
+
const bytes = Buffer.from(JSON.stringify(index));
|
|
2448
|
+
return { bytes, index, commit: index.commit, generated: true };
|
|
2449
|
+
},
|
|
2450
|
+
async fetchContent({ source, path: artifactPath }) {
|
|
2451
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
2452
|
+
return import_promises7.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
// src/federation/github-source-provider.ts
|
|
2457
|
+
var import_node_child_process = require("child_process");
|
|
2458
|
+
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
2459
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
2460
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
2461
|
+
var import_node_util = require("util");
|
|
2462
|
+
var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
2463
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
2464
|
+
var parseGitHubSource = (source) => {
|
|
2465
|
+
const match = /^github:([^/]+)\/(.+)$/.exec(source.source);
|
|
2466
|
+
if (!match) throw new Error(`Unsupported federation source "${source.source}". Expected github:owner/repository.`);
|
|
2467
|
+
return { owner: match[1], repository: match[2] };
|
|
2468
|
+
};
|
|
2469
|
+
var assertSafeCatalogPath = (source) => {
|
|
2470
|
+
const catalogPath = source.path ?? ".";
|
|
2471
|
+
const normalized = import_node_path15.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
|
|
2472
|
+
if (catalogPath.includes("\\") || import_node_path15.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
2473
|
+
throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
var encodePath = (value) => value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
|
|
2477
|
+
var rawUrl = (source, ref, filePath) => {
|
|
2478
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
2479
|
+
return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/${encodeURIComponent(
|
|
2480
|
+
ref
|
|
2481
|
+
)}/${encodePath(filePath)}`;
|
|
2482
|
+
};
|
|
2483
|
+
var contentsApiUrl = (source, ref, filePath) => {
|
|
2484
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
2485
|
+
return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/contents/${encodePath(
|
|
2486
|
+
filePath
|
|
2487
|
+
)}?ref=${encodeURIComponent(ref)}`;
|
|
2488
|
+
};
|
|
2489
|
+
var fetchBytes = async (source, ref, filePath, fetcher, token) => {
|
|
2490
|
+
const url = token ? contentsApiUrl(source, ref, filePath) : rawUrl(source, ref, filePath);
|
|
2491
|
+
const response = token ? await fetcher(url, {
|
|
2492
|
+
headers: {
|
|
2493
|
+
Accept: "application/vnd.github.raw+json",
|
|
2494
|
+
Authorization: `Bearer ${token}`,
|
|
2495
|
+
"X-GitHub-Api-Version": "2026-03-10"
|
|
2496
|
+
}
|
|
2497
|
+
}) : await fetcher(url);
|
|
2498
|
+
if (response.status === 404) return void 0;
|
|
2499
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
2500
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2501
|
+
};
|
|
2502
|
+
var getGitEnvironment = (token) => {
|
|
2503
|
+
if (!token) return process.env;
|
|
2504
|
+
const inheritedCount = Number.parseInt(process.env.GIT_CONFIG_COUNT ?? "0", 10);
|
|
2505
|
+
const configIndex = Number.isInteger(inheritedCount) && inheritedCount >= 0 ? inheritedCount : 0;
|
|
2506
|
+
return {
|
|
2507
|
+
...process.env,
|
|
2508
|
+
GIT_CONFIG_COUNT: String(configIndex + 1),
|
|
2509
|
+
[`GIT_CONFIG_KEY_${configIndex}`]: "http.https://github.com/.extraheader",
|
|
2510
|
+
[`GIT_CONFIG_VALUE_${configIndex}`]: `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`
|
|
2511
|
+
};
|
|
2512
|
+
};
|
|
2513
|
+
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
2514
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
2515
|
+
const catalogPath = import_node_path15.default.posix.normalize(source.path ?? ".");
|
|
2516
|
+
const directory = await import_promises8.default.mkdtemp(import_node_path15.default.join(import_node_os4.default.tmpdir(), "eventcatalog-federation-"));
|
|
2517
|
+
const env = getGitEnvironment(token);
|
|
2518
|
+
try {
|
|
2519
|
+
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
2520
|
+
await git(["init", "--quiet"]);
|
|
2521
|
+
await git(["remote", "add", "origin", `https://github.com/${owner}/${repository}.git`]);
|
|
2522
|
+
if (catalogPath !== ".") {
|
|
2523
|
+
await git(["sparse-checkout", "init", "--cone"]);
|
|
2524
|
+
await git(["sparse-checkout", "set", catalogPath]);
|
|
2525
|
+
}
|
|
2526
|
+
await git(["fetch", "--quiet", "--depth", "1", "origin", ref]);
|
|
2527
|
+
await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
|
|
2528
|
+
return await callback(directory);
|
|
2529
|
+
} finally {
|
|
2530
|
+
await import_promises8.default.rm(directory, { recursive: true, force: true });
|
|
2531
|
+
}
|
|
2532
|
+
};
|
|
2533
|
+
var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
|
|
2534
|
+
const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
|
|
2535
|
+
const commit = stdout.trim();
|
|
2536
|
+
const catalogDirectory = import_node_path15.default.resolve(directory, source.path ?? ".");
|
|
2537
|
+
const relativeCatalogDirectory = import_node_path15.default.relative(directory, catalogDirectory);
|
|
2538
|
+
if (relativeCatalogDirectory.startsWith("..") || import_node_path15.default.isAbsolute(relativeCatalogDirectory)) {
|
|
2539
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
2540
|
+
}
|
|
2541
|
+
const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
|
|
2542
|
+
return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
|
|
2543
|
+
});
|
|
2544
|
+
var fetchPublishedIndex = async (source, ref, fetcher, token) => {
|
|
2545
|
+
const indexPath = import_node_path15.default.posix.join(source.path ?? ".", "catalog.index.json");
|
|
2546
|
+
const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
|
|
2547
|
+
if (!bytes) return void 0;
|
|
2548
|
+
const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
|
|
2549
|
+
if (index.source !== source.id) {
|
|
2550
|
+
throw new Error(`Published index source "${index.source}" does not match configured id "${source.id}"`);
|
|
2551
|
+
}
|
|
2552
|
+
return { bytes, index, commit: index.commit, generated: false };
|
|
2553
|
+
};
|
|
2554
|
+
var createGitHubSourceProvider = (options = {}) => {
|
|
2555
|
+
const fetcher = options.fetch ?? ((url, init) => fetch(url, init));
|
|
2556
|
+
const executeFile = options.execFile ?? ((file, args, execOptions) => execFileAsync(file, args, execOptions));
|
|
2557
|
+
const configuredToken = options.token ?? process.env.EVENTCATALOG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN;
|
|
2558
|
+
const token = configuredToken?.trim() || void 0;
|
|
2559
|
+
const checkout = options.checkout ?? createCheckout(executeFile, token);
|
|
2560
|
+
return {
|
|
2561
|
+
async resolve(source) {
|
|
2562
|
+
assertSafeCatalogPath(source);
|
|
2563
|
+
const ref = source.ref ?? "main";
|
|
2564
|
+
return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
|
|
2565
|
+
},
|
|
2566
|
+
async fetchContent({ source, commit, path: artifactPath }) {
|
|
2567
|
+
assertSafeCatalogPath(source);
|
|
2568
|
+
const catalogPath = import_node_path15.default.posix.join(source.path ?? ".", artifactPath);
|
|
2569
|
+
const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
|
|
2570
|
+
if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
|
|
2571
|
+
return content;
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
};
|
|
2575
|
+
|
|
2576
|
+
// src/federation/source-provider.ts
|
|
2577
|
+
var createFederationSourceProvider = (projectDirectory, providers = {}) => {
|
|
2578
|
+
const github = providers.github ?? createGitHubSourceProvider();
|
|
2579
|
+
const filesystem = providers.filesystem ?? createFileSystemSourceProvider(projectDirectory);
|
|
2580
|
+
const getProvider = (source) => {
|
|
2581
|
+
if (source.source.startsWith("github:")) return github;
|
|
2582
|
+
if (source.source.startsWith("file:")) return filesystem;
|
|
2583
|
+
throw new Error(`Unsupported federation source "${source.source}" for "${source.id}". Supported protocols: github:, file:.`);
|
|
2584
|
+
};
|
|
2585
|
+
return {
|
|
2586
|
+
async resolve(source) {
|
|
2587
|
+
return getProvider(source).resolve(source);
|
|
2588
|
+
},
|
|
2589
|
+
async fetchContent(request) {
|
|
2590
|
+
return getProvider(request.source).fetchContent(request);
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
};
|
|
2594
|
+
|
|
2199
2595
|
// src/federation/federate.ts
|
|
2200
2596
|
var FederationConflictError = class extends Error {
|
|
2201
2597
|
conflicts;
|
|
@@ -2207,6 +2603,16 @@ ${details}`);
|
|
|
2207
2603
|
this.conflicts = conflicts;
|
|
2208
2604
|
}
|
|
2209
2605
|
};
|
|
2606
|
+
var FederationDiagnosticError = class extends Error {
|
|
2607
|
+
diagnostics;
|
|
2608
|
+
constructor(diagnostics) {
|
|
2609
|
+
const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
|
|
2610
|
+
super(`Federation diagnostics prevent hydration:
|
|
2611
|
+
${details}`);
|
|
2612
|
+
this.name = "FederationDiagnosticError";
|
|
2613
|
+
this.diagnostics = diagnostics;
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2210
2616
|
var validateSources = (sources) => {
|
|
2211
2617
|
const ids = /* @__PURE__ */ new Set();
|
|
2212
2618
|
for (const source of sources) {
|
|
@@ -2215,19 +2621,46 @@ var validateSources = (sources) => {
|
|
|
2215
2621
|
ids.add(source.id);
|
|
2216
2622
|
}
|
|
2217
2623
|
};
|
|
2624
|
+
var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
|
|
2625
|
+
const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
|
|
2626
|
+
const externalsByPointer = /* @__PURE__ */ new Map();
|
|
2627
|
+
for (const edge of edges.filter((edge2) => edge2.status === "external")) {
|
|
2628
|
+
const version2 = edge.pointer ?? void 0;
|
|
2629
|
+
const key = `${edge.to}@${version2 ?? ""}`;
|
|
2630
|
+
const external = externalsByPointer.get(key) ?? {
|
|
2631
|
+
id: edge.to,
|
|
2632
|
+
...version2 === void 0 ? {} : { version: version2 },
|
|
2633
|
+
referencedBy: []
|
|
2634
|
+
};
|
|
2635
|
+
if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
|
|
2636
|
+
externalsByPointer.set(key, external);
|
|
2637
|
+
}
|
|
2638
|
+
return {
|
|
2639
|
+
...graph,
|
|
2640
|
+
entities: [
|
|
2641
|
+
...graph.entities,
|
|
2642
|
+
...ownershipGraph.entities.filter(
|
|
2643
|
+
(entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
|
|
2644
|
+
)
|
|
2645
|
+
],
|
|
2646
|
+
edges,
|
|
2647
|
+
conflicts: ownershipGraph.conflicts,
|
|
2648
|
+
externals: [...externalsByPointer.values()]
|
|
2649
|
+
};
|
|
2650
|
+
};
|
|
2218
2651
|
var writeLock = async (lockPath, lock) => {
|
|
2219
2652
|
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
2220
2653
|
try {
|
|
2221
|
-
await
|
|
2654
|
+
await import_promises9.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
|
|
2222
2655
|
`, "utf8");
|
|
2223
|
-
await
|
|
2656
|
+
await import_promises9.default.rename(temporaryPath, lockPath);
|
|
2224
2657
|
} finally {
|
|
2225
|
-
await
|
|
2658
|
+
await import_promises9.default.rm(temporaryPath, { force: true });
|
|
2226
2659
|
}
|
|
2227
2660
|
};
|
|
2228
2661
|
var readLock = async (lockPath) => {
|
|
2229
2662
|
try {
|
|
2230
|
-
return JSON.parse(await
|
|
2663
|
+
return JSON.parse(await import_promises9.default.readFile(lockPath, "utf8"));
|
|
2231
2664
|
} catch (error) {
|
|
2232
2665
|
if (error.code === "ENOENT") return void 0;
|
|
2233
2666
|
throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
|
|
@@ -2235,7 +2668,7 @@ var readLock = async (lockPath) => {
|
|
|
2235
2668
|
};
|
|
2236
2669
|
var pathExists2 = async (filePath) => {
|
|
2237
2670
|
try {
|
|
2238
|
-
await
|
|
2671
|
+
await import_promises9.default.access(filePath);
|
|
2239
2672
|
return true;
|
|
2240
2673
|
} catch (error) {
|
|
2241
2674
|
if (error.code === "ENOENT") return false;
|
|
@@ -2243,20 +2676,27 @@ var pathExists2 = async (filePath) => {
|
|
|
2243
2676
|
}
|
|
2244
2677
|
};
|
|
2245
2678
|
var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
2246
|
-
const outDir =
|
|
2247
|
-
const lockPath =
|
|
2679
|
+
const outDir = import_node_path16.default.join(projectDirectory, "federated");
|
|
2680
|
+
const lockPath = import_node_path16.default.join(projectDirectory, "eventcatalog.lock");
|
|
2248
2681
|
const previousLock = await readLock(lockPath);
|
|
2249
2682
|
const hadFederatedOutput = await pathExists2(outDir);
|
|
2250
2683
|
const hadLock = previousLock !== void 0;
|
|
2251
2684
|
if (!hadFederatedOutput && !hadLock) return;
|
|
2252
|
-
|
|
2253
|
-
const publicResult = await composePublicAssets({
|
|
2685
|
+
const publicResult = await withFederationOutputTransaction(
|
|
2254
2686
|
projectDirectory,
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2687
|
+
outDir,
|
|
2688
|
+
Object.keys(previousLock?.publicFiles ?? {}),
|
|
2689
|
+
async () => {
|
|
2690
|
+
const result = await composePublicAssets({
|
|
2691
|
+
projectDirectory,
|
|
2692
|
+
federatedDirectory: outDir,
|
|
2693
|
+
assets: [],
|
|
2694
|
+
previousFiles: previousLock?.publicFiles
|
|
2695
|
+
});
|
|
2696
|
+
await import_promises9.default.rm(lockPath, { force: true });
|
|
2697
|
+
return result;
|
|
2698
|
+
}
|
|
2699
|
+
);
|
|
2260
2700
|
onProgress?.({
|
|
2261
2701
|
type: "cleanup:complete",
|
|
2262
2702
|
federated: hadFederatedOutput,
|
|
@@ -2278,8 +2718,9 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
2278
2718
|
);
|
|
2279
2719
|
}
|
|
2280
2720
|
validateSources(sources);
|
|
2721
|
+
const rules = resolveFederationRules(config.federation?.rules);
|
|
2281
2722
|
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
2282
|
-
const provider = options.provider ??
|
|
2723
|
+
const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
|
|
2283
2724
|
const resolvedSources = [];
|
|
2284
2725
|
for (const [index, source] of sources.entries()) {
|
|
2285
2726
|
const current = index + 1;
|
|
@@ -2301,13 +2742,13 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
2301
2742
|
throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
|
|
2302
2743
|
}
|
|
2303
2744
|
}
|
|
2304
|
-
const outDir =
|
|
2305
|
-
const lockPath =
|
|
2745
|
+
const outDir = import_node_path16.default.join(projectDirectory, "federated");
|
|
2746
|
+
const lockPath = import_node_path16.default.join(projectDirectory, "eventcatalog.lock");
|
|
2306
2747
|
const previousLock = await readLock(lockPath);
|
|
2307
2748
|
const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
|
|
2308
2749
|
const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
|
|
2309
2750
|
options.onProgress?.({ type: "local:start" });
|
|
2310
|
-
const localIndex = await (0,
|
|
2751
|
+
const localIndex = await (0, import_sdk3.default)(projectDirectory).buildIndex({
|
|
2311
2752
|
source: config.cId,
|
|
2312
2753
|
commit: "local",
|
|
2313
2754
|
hashContent: false,
|
|
@@ -2315,61 +2756,80 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
2315
2756
|
});
|
|
2316
2757
|
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
2317
2758
|
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
2318
|
-
const ownershipGraph = (0,
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2759
|
+
const ownershipGraph = (0, import_sdk3.resolve)([localIndex, ...remoteIndexes]);
|
|
2760
|
+
const graph = (0, import_sdk3.resolve)(remoteIndexes);
|
|
2761
|
+
const diagnosticGraph = createDiagnosticGraph(
|
|
2762
|
+
graph,
|
|
2763
|
+
ownershipGraph,
|
|
2764
|
+
new Set(remoteIndexes.map((index) => index.source)),
|
|
2765
|
+
localIndex.source
|
|
2766
|
+
);
|
|
2767
|
+
const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
|
|
2768
|
+
options.onProgress?.({ type: "resolved", graph, diagnostics });
|
|
2769
|
+
const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
|
|
2770
|
+
if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
|
|
2771
|
+
const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
2772
|
+
if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
|
|
2773
|
+
const publicPaths = [
|
|
2774
|
+
...Object.keys(previousLock?.publicFiles ?? {}),
|
|
2775
|
+
...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
|
|
2776
|
+
];
|
|
2777
|
+
const { hydrateResult, publicResult } = await withFederationOutputTransaction(
|
|
2778
|
+
projectDirectory,
|
|
2330
2779
|
outDir,
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2780
|
+
publicPaths,
|
|
2781
|
+
async () => {
|
|
2782
|
+
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
2783
|
+
options.onProgress?.({ type: "hydrating", outDir });
|
|
2784
|
+
let hydratedFiles = 0;
|
|
2785
|
+
let cachedFiles = 0;
|
|
2786
|
+
const hydrateResult2 = await (0, import_sdk3.hydrate)(graph, {
|
|
2787
|
+
outDir,
|
|
2788
|
+
cache: createFederationContentCache(projectDirectory, {
|
|
2789
|
+
read: options.useCache !== false,
|
|
2790
|
+
onHit: () => {
|
|
2791
|
+
cachedFiles += 1;
|
|
2792
|
+
options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
|
|
2793
|
+
}
|
|
2794
|
+
}),
|
|
2795
|
+
fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
|
|
2796
|
+
const source = sourcesById.get(sourceId);
|
|
2797
|
+
if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
|
|
2798
|
+
const content = await provider.fetchContent({ source, commit, path: artifactPath });
|
|
2799
|
+
hydratedFiles += 1;
|
|
2800
|
+
options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
|
|
2801
|
+
return content;
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
const publicResult2 = await composePublicAssets({
|
|
2805
|
+
projectDirectory,
|
|
2806
|
+
federatedDirectory: outDir,
|
|
2807
|
+
assets: graph.assets,
|
|
2808
|
+
previousFiles: previousLock?.publicFiles,
|
|
2809
|
+
collisionPaths: new Set(
|
|
2810
|
+
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
2811
|
+
)
|
|
2812
|
+
});
|
|
2813
|
+
options.onProgress?.({ type: "public:complete", result: publicResult2 });
|
|
2814
|
+
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
2815
|
+
await writeLock(lockPath, {
|
|
2816
|
+
lockVersion: 1,
|
|
2817
|
+
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
2818
|
+
id: source.id,
|
|
2819
|
+
digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
|
|
2820
|
+
commit: resolved.commit,
|
|
2821
|
+
resolvedAt
|
|
2822
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
2823
|
+
publicFiles: publicResult2.files
|
|
2824
|
+
});
|
|
2825
|
+
return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
|
|
2346
2826
|
}
|
|
2347
|
-
|
|
2348
|
-
const publicResult = await composePublicAssets({
|
|
2349
|
-
projectDirectory,
|
|
2350
|
-
federatedDirectory: outDir,
|
|
2351
|
-
assets: graph.assets,
|
|
2352
|
-
previousFiles: previousLock?.publicFiles,
|
|
2353
|
-
collisionPaths: new Set(
|
|
2354
|
-
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
2355
|
-
)
|
|
2356
|
-
});
|
|
2357
|
-
options.onProgress?.({ type: "public:complete", result: publicResult });
|
|
2358
|
-
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
2359
|
-
await writeLock(lockPath, {
|
|
2360
|
-
lockVersion: 1,
|
|
2361
|
-
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
2362
|
-
id: source.id,
|
|
2363
|
-
digest: `sha256:${(0, import_node_crypto3.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
|
|
2364
|
-
commit: resolved.commit,
|
|
2365
|
-
resolvedAt
|
|
2366
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
2367
|
-
publicFiles: publicResult.files
|
|
2368
|
-
});
|
|
2827
|
+
);
|
|
2369
2828
|
const result = {
|
|
2370
2829
|
sources: sources.length,
|
|
2371
2830
|
resources,
|
|
2372
2831
|
graph,
|
|
2832
|
+
diagnostics,
|
|
2373
2833
|
hydrate: hydrateResult,
|
|
2374
2834
|
public: publicResult,
|
|
2375
2835
|
outDir,
|
|
@@ -2381,14 +2841,14 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
2381
2841
|
|
|
2382
2842
|
// src/eventcatalog.ts
|
|
2383
2843
|
var import_license = require("@eventcatalog/license");
|
|
2384
|
-
var currentDir =
|
|
2844
|
+
var currentDir = import_node_path18.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2385
2845
|
var program = new import_commander.Command().version(VERSION);
|
|
2386
|
-
var dir =
|
|
2387
|
-
var core =
|
|
2388
|
-
var eventCatalogDir =
|
|
2846
|
+
var dir = import_node_path18.default.resolve(process.env.PROJECT_DIR || process.cwd());
|
|
2847
|
+
var core = import_node_path18.default.resolve(process.env.CATALOG_DIR || (0, import_node_path17.join)(dir, ".eventcatalog-core"));
|
|
2848
|
+
var eventCatalogDir = import_node_path18.default.resolve((0, import_node_path17.join)(currentDir, "../eventcatalog/"));
|
|
2389
2849
|
var getInstalledEventCatalogVersion = () => {
|
|
2390
2850
|
try {
|
|
2391
|
-
const pkg = import_fs3.default.readFileSync((0,
|
|
2851
|
+
const pkg = import_fs3.default.readFileSync((0, import_node_path17.join)(dir, "package.json"), "utf8");
|
|
2392
2852
|
const json = JSON.parse(pkg);
|
|
2393
2853
|
return json.dependencies["@eventcatalog/core"];
|
|
2394
2854
|
} catch (error) {
|
|
@@ -2454,12 +2914,12 @@ var startDevPrewarm = ({
|
|
|
2454
2914
|
var buildDevSearchIndex = async ({ config }) => {
|
|
2455
2915
|
const result = await buildSearchIndex({
|
|
2456
2916
|
projectDir: dir,
|
|
2457
|
-
outDir:
|
|
2458
|
-
searchOutputPath:
|
|
2917
|
+
outDir: import_node_path18.default.join(core, "public"),
|
|
2918
|
+
searchOutputPath: import_node_path18.default.join(core, "public", "pagefind"),
|
|
2459
2919
|
config,
|
|
2460
2920
|
isServer: false
|
|
2461
2921
|
});
|
|
2462
|
-
logger.info(`Indexed ${result.records} page(s) into ${
|
|
2922
|
+
logger.info(`Indexed ${result.records} page(s) into ${import_node_path18.default.relative(core, result.outputPath)}`, "search");
|
|
2463
2923
|
};
|
|
2464
2924
|
var warnIfIndexedSearchUsesAuth = async () => {
|
|
2465
2925
|
if (!await isAuthEnabled()) {
|
|
@@ -2574,12 +3034,12 @@ var copyCore = () => {
|
|
|
2574
3034
|
import_fs3.default.cpSync(eventCatalogDir, core, {
|
|
2575
3035
|
recursive: true,
|
|
2576
3036
|
filter: (src) => {
|
|
2577
|
-
const relativePath =
|
|
2578
|
-
const pathParts = relativePath.split(
|
|
3037
|
+
const relativePath = import_node_path18.default.relative(eventCatalogDir, src);
|
|
3038
|
+
const pathParts = relativePath.split(import_node_path18.default.sep);
|
|
2579
3039
|
return !pathParts.some((part) => [".astro", "dist", "node_modules"].includes(part));
|
|
2580
3040
|
}
|
|
2581
3041
|
});
|
|
2582
|
-
const coreNodeModules =
|
|
3042
|
+
const coreNodeModules = import_node_path18.default.join(core, "node_modules");
|
|
2583
3043
|
const installedCoreNodeModules = resolveInstalledCoreNodeModules(currentDir);
|
|
2584
3044
|
linkCoreNodeModules({ coreNodeModules, installedCoreNodeModules });
|
|
2585
3045
|
};
|
|
@@ -2638,8 +3098,8 @@ program.command("dev").description("Run development server of EventCatalog").opt
|
|
|
2638
3098
|
logger.info("Setting up EventCatalog...", "eventcatalog");
|
|
2639
3099
|
const isServer = await isOutputServer();
|
|
2640
3100
|
logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
|
|
2641
|
-
if (import_fs3.default.existsSync(
|
|
2642
|
-
import_dotenv.default.config({ path:
|
|
3101
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3102
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2643
3103
|
}
|
|
2644
3104
|
if (options.debug) {
|
|
2645
3105
|
logger.info("Debug mode enabled", "debug");
|
|
@@ -2719,8 +3179,8 @@ program.command("build").description("Run build of EventCatalog").action(async (
|
|
|
2719
3179
|
logger.info("Building EventCatalog...", "build");
|
|
2720
3180
|
const isServer = await isOutputServer();
|
|
2721
3181
|
logger.info(isServer ? "EventCatalog is running in Server Mode" : "EventCatalog is running in Static Mode", "config");
|
|
2722
|
-
if (import_fs3.default.existsSync(
|
|
2723
|
-
import_dotenv.default.config({ path:
|
|
3182
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3183
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2724
3184
|
}
|
|
2725
3185
|
await verifyRequiredFieldsAreInCatalogConfigFile(dir);
|
|
2726
3186
|
copyCore();
|
|
@@ -2770,7 +3230,7 @@ program.command("build").description("Run build of EventCatalog").action(async (
|
|
|
2770
3230
|
if (await isIndexedSearchEnabled()) {
|
|
2771
3231
|
await warnIfIndexedSearchUsesAuth();
|
|
2772
3232
|
const config = await getEventCatalogConfigFile(dir);
|
|
2773
|
-
const outDir =
|
|
3233
|
+
const outDir = import_node_path18.default.resolve(dir, await getProjectOutDir());
|
|
2774
3234
|
logger.info("Building indexed search...", "search");
|
|
2775
3235
|
const result = await buildSearchIndex({
|
|
2776
3236
|
projectDir: dir,
|
|
@@ -2778,7 +3238,7 @@ program.command("build").description("Run build of EventCatalog").action(async (
|
|
|
2778
3238
|
config,
|
|
2779
3239
|
isServer
|
|
2780
3240
|
});
|
|
2781
|
-
logger.info(`Indexed ${result.records} page(s) into ${
|
|
3241
|
+
logger.info(`Indexed ${result.records} page(s) into ${import_node_path18.default.relative(dir, result.outputPath)}`, "search");
|
|
2782
3242
|
}
|
|
2783
3243
|
});
|
|
2784
3244
|
var previewCatalog = async ({
|
|
@@ -2805,7 +3265,7 @@ var startServerCatalog = async ({
|
|
|
2805
3265
|
isEventCatalogStarter = false,
|
|
2806
3266
|
isEventCatalogScale = false
|
|
2807
3267
|
}) => {
|
|
2808
|
-
const serverEntryPath =
|
|
3268
|
+
const serverEntryPath = import_node_path18.default.join(dir, "dist", "server", "entry.mjs");
|
|
2809
3269
|
await runCommandWithFilteredOutput({
|
|
2810
3270
|
command: `node "${serverEntryPath}"`,
|
|
2811
3271
|
cwd: core,
|
|
@@ -2822,8 +3282,8 @@ var startServerCatalog = async ({
|
|
|
2822
3282
|
program.command("preview").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
|
|
2823
3283
|
logger.welcome();
|
|
2824
3284
|
logger.info("Starting preview of your build...", "preview");
|
|
2825
|
-
if (import_fs3.default.existsSync(
|
|
2826
|
-
import_dotenv.default.config({ path:
|
|
3285
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3286
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2827
3287
|
}
|
|
2828
3288
|
const canEmbedPages = await (0, import_license.isFeatureEnabled)(
|
|
2829
3289
|
"@eventcatalog/backstage-plugin-eventcatalog",
|
|
@@ -2841,8 +3301,8 @@ program.command("preview").description("Serves the contents of your eventcatalog
|
|
|
2841
3301
|
program.command("start").description("Serves the contents of your eventcatalog build directory").action(async (options, command) => {
|
|
2842
3302
|
logger.welcome();
|
|
2843
3303
|
logger.info("Starting preview of your build...", "preview");
|
|
2844
|
-
if (import_fs3.default.existsSync(
|
|
2845
|
-
import_dotenv.default.config({ path:
|
|
3304
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3305
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2846
3306
|
}
|
|
2847
3307
|
const canEmbedPages = await (0, import_license.isFeatureEnabled)(
|
|
2848
3308
|
"@eventcatalog/backstage-plugin-eventcatalog",
|
|
@@ -2869,26 +3329,26 @@ program.command("start").description("Serves the contents of your eventcatalog b
|
|
|
2869
3329
|
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) => {
|
|
2870
3330
|
logger.welcome();
|
|
2871
3331
|
logger.info("Exporting EventCatalog...", "export");
|
|
2872
|
-
if (import_fs3.default.existsSync(
|
|
2873
|
-
import_dotenv.default.config({ path:
|
|
3332
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3333
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2874
3334
|
}
|
|
2875
3335
|
const { default: initSDK } = await import("@eventcatalog/sdk");
|
|
2876
3336
|
const sdk = initSDK(dir);
|
|
2877
3337
|
const catalog = await sdk.dumpCatalog({ includeMarkdown: options.includeMarkdown });
|
|
2878
|
-
const exportsDir =
|
|
3338
|
+
const exportsDir = import_node_path18.default.join(dir, "exports");
|
|
2879
3339
|
ensureDir(exportsDir);
|
|
2880
3340
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2881
|
-
const exportFile =
|
|
3341
|
+
const exportFile = import_node_path18.default.join(exportsDir, `catalog-${date}.json`);
|
|
2882
3342
|
import_fs3.default.writeFileSync(exportFile, JSON.stringify(catalog, null, 2), "utf-8");
|
|
2883
3343
|
logger.info(`Catalog exported to ${exportFile}`, "export");
|
|
2884
3344
|
});
|
|
2885
3345
|
program.command("generate [siteDir]").description("Start the generator scripts.").action(async () => {
|
|
2886
|
-
if (import_fs3.default.existsSync(
|
|
2887
|
-
import_dotenv.default.config({ path:
|
|
3346
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3347
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2888
3348
|
}
|
|
2889
3349
|
await generate(dir);
|
|
2890
3350
|
});
|
|
2891
|
-
var reportFederationProgress = (event) => {
|
|
3351
|
+
var reportFederationProgress = (event, verbose = false) => {
|
|
2892
3352
|
switch (event.type) {
|
|
2893
3353
|
case "configured":
|
|
2894
3354
|
if (event.sources > 0)
|
|
@@ -2928,38 +3388,36 @@ var reportFederationProgress = (event) => {
|
|
|
2928
3388
|
);
|
|
2929
3389
|
return;
|
|
2930
3390
|
case "resolved":
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
3391
|
+
const counts = getFederationDiagnosticCounts(event.diagnostics);
|
|
3392
|
+
const showDiagnosticDetails = verbose || counts.errors > 0;
|
|
3393
|
+
if (counts.errors === 0) {
|
|
3394
|
+
logger.success(
|
|
3395
|
+
`Graph resolved: ${event.graph.entities.length} remote resources, ${event.graph.edges.length} relationships`,
|
|
2934
3396
|
"federation"
|
|
2935
3397
|
);
|
|
2936
|
-
for (const conflict of event.graph.conflicts.slice(0, 10)) {
|
|
2937
|
-
logger.error(`${conflict.id}: ${conflict.sources.join(", ")}`, "federation");
|
|
2938
|
-
}
|
|
2939
|
-
if (event.graph.conflicts.length > 10) {
|
|
2940
|
-
logger.error(`...and ${event.graph.conflicts.length - 10} more`, "federation");
|
|
2941
|
-
}
|
|
2942
|
-
return;
|
|
2943
3398
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3399
|
+
if (counts.errors + counts.warnings === 0) return;
|
|
3400
|
+
if (showDiagnosticDetails) {
|
|
3401
|
+
const diagnostics = getVisibleFederationDiagnostics(event.diagnostics, verbose);
|
|
3402
|
+
logger.info("Federation diagnostics", "federation");
|
|
3403
|
+
logger.line();
|
|
3404
|
+
for (const diagnostic of diagnostics) {
|
|
3405
|
+
logger.diagnostic(diagnostic.severity, diagnostic.message, diagnostic.rule, diagnostic.attributes);
|
|
3406
|
+
logger.line();
|
|
3407
|
+
}
|
|
3408
|
+
logger.diagnosticSummary(counts.errors, counts.warnings);
|
|
3409
|
+
} else {
|
|
2949
3410
|
logger.warning(
|
|
2950
|
-
`${
|
|
3411
|
+
`${counts.warnings} problem${counts.warnings === 1 ? "" : "s"} (0 errors, ${counts.warnings} warning${counts.warnings === 1 ? "" : "s"})`,
|
|
2951
3412
|
"federation"
|
|
2952
3413
|
);
|
|
2953
3414
|
}
|
|
2954
|
-
if (
|
|
2955
|
-
logger.warning
|
|
2956
|
-
`${event.graph.externals.length} reference${event.graph.externals.length === 1 ? "" : "s"} remain external`,
|
|
2957
|
-
"federation"
|
|
2958
|
-
);
|
|
3415
|
+
if (!verbose && counts.warnings > 0) {
|
|
3416
|
+
logger.info("Run with --verbose to see warning details.", "federation");
|
|
2959
3417
|
}
|
|
2960
3418
|
return;
|
|
2961
3419
|
case "hydrating":
|
|
2962
|
-
logger.info(`Hydrating federated content into ${
|
|
3420
|
+
logger.info(`Hydrating federated content into ${import_node_path18.default.relative(dir, event.outDir)}/...`, "federation");
|
|
2963
3421
|
return;
|
|
2964
3422
|
case "hydrate:cache":
|
|
2965
3423
|
if (event.files === 1 || event.files % 25 === 0) {
|
|
@@ -2976,25 +3434,19 @@ var reportFederationProgress = (event) => {
|
|
|
2976
3434
|
`Public assets: ${event.result.copied} copied, ${event.result.skipped} preserved from the main catalog, ${event.result.removed} stale removed`,
|
|
2977
3435
|
"federation"
|
|
2978
3436
|
);
|
|
2979
|
-
if (event.result.overwritten > 0) {
|
|
2980
|
-
logger.warning(
|
|
2981
|
-
`${event.result.overwritten} public asset conflict${event.result.overwritten === 1 ? "" : "s"} resolved using the last configured source`,
|
|
2982
|
-
"federation"
|
|
2983
|
-
);
|
|
2984
|
-
}
|
|
2985
3437
|
return;
|
|
2986
3438
|
case "complete":
|
|
2987
3439
|
logger.success(
|
|
2988
3440
|
`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)`,
|
|
2989
3441
|
"federation"
|
|
2990
3442
|
);
|
|
2991
|
-
logger.info(`
|
|
3443
|
+
logger.info(`Recorded resolved source state in ${import_node_path18.default.relative(dir, event.result.lockPath)}`, "federation");
|
|
2992
3444
|
}
|
|
2993
3445
|
};
|
|
2994
|
-
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) => {
|
|
3446
|
+
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.").option("-v, --verbose", "Show detailed federation diagnostics.").action(async (commandOptions) => {
|
|
2995
3447
|
logger.welcome();
|
|
2996
|
-
if (import_fs3.default.existsSync(
|
|
2997
|
-
import_dotenv.default.config({ path:
|
|
3448
|
+
if (import_fs3.default.existsSync(import_node_path18.default.join(dir, ".env"))) {
|
|
3449
|
+
import_dotenv.default.config({ path: import_node_path18.default.join(dir, ".env") });
|
|
2998
3450
|
}
|
|
2999
3451
|
logger.info("Starting federation...", "federation");
|
|
3000
3452
|
let cleanedPreviousOutput = false;
|
|
@@ -3003,7 +3455,7 @@ program.command("federate").description("Fetch, resolve, and hydrate the catalog
|
|
|
3003
3455
|
isFederationEnabled: import_license.isEventCatalogScaleEnabled,
|
|
3004
3456
|
onProgress: (event) => {
|
|
3005
3457
|
if (event.type === "cleanup:complete") cleanedPreviousOutput = true;
|
|
3006
|
-
reportFederationProgress(event);
|
|
3458
|
+
reportFederationProgress(event, commandOptions.verbose);
|
|
3007
3459
|
}
|
|
3008
3460
|
});
|
|
3009
3461
|
if (!result && !cleanedPreviousOutput) logger.warning("No federation sources configured; nothing to do.", "federation");
|
|
@@ -3017,6 +3469,6 @@ program.addHelpText(
|
|
|
3017
3469
|
`
|
|
3018
3470
|
);
|
|
3019
3471
|
program.parseAsync().then(() => process.exit(0)).catch((err) => {
|
|
3020
|
-
if (!(err instanceof FederationConflictError)) console.error(err);
|
|
3472
|
+
if (!(err instanceof FederationConflictError) && !(err instanceof FederationDiagnosticError)) console.error(err);
|
|
3021
3473
|
process.exit(1);
|
|
3022
3474
|
});
|