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