@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
|
@@ -31,13 +31,14 @@ 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
|
-
var
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
38
|
+
var import_node_crypto4 = require("crypto");
|
|
39
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
40
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
41
|
+
var import_sdk3 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
41
42
|
|
|
42
43
|
// src/eventcatalog-config-file-utils.js
|
|
43
44
|
var import_promises = require("fs/promises");
|
|
@@ -149,125 +150,264 @@ var createFederationContentCache = (projectDirectory, options = {}) => {
|
|
|
149
150
|
};
|
|
150
151
|
};
|
|
151
152
|
|
|
152
|
-
// src/federation/
|
|
153
|
-
var
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const match = /^github:([^/]+)\/(.+)$/.exec(source.source);
|
|
162
|
-
if (!match) throw new Error(`Unsupported federation source "${source.source}". Expected github:owner/repository.`);
|
|
163
|
-
return { owner: match[1], repository: match[2] };
|
|
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"
|
|
164
162
|
};
|
|
165
|
-
var
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
}
|
|
170
169
|
}
|
|
170
|
+
return { ...federationRuleDefaults, ...rules };
|
|
171
171
|
};
|
|
172
|
-
var
|
|
173
|
-
var
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)}/${encodePath(filePath)}`;
|
|
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}`;
|
|
178
177
|
};
|
|
179
|
-
var
|
|
180
|
-
const {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
};
|
|
192
209
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
210
|
-
const { owner, repository } = parseGitHubSource(source);
|
|
211
|
-
const catalogPath = import_node_path3.default.posix.normalize(source.path ?? ".");
|
|
212
|
-
const directory = await import_promises3.default.mkdtemp(import_node_path3.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
|
|
213
|
-
const env = getGitEnvironment(token);
|
|
214
|
-
try {
|
|
215
|
-
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
216
|
-
await git(["init", "--quiet"]);
|
|
217
|
-
await git(["remote", "add", "origin", `https://github.com/${owner}/${repository}.git`]);
|
|
218
|
-
if (catalogPath !== ".") {
|
|
219
|
-
await git(["sparse-checkout", "init", "--cone"]);
|
|
220
|
-
await git(["sparse-checkout", "set", catalogPath]);
|
|
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
|
+
};
|
|
221
225
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
+
};
|
|
227
237
|
}
|
|
228
238
|
};
|
|
229
|
-
var
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
}
|
|
236
257
|
}
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
});
|
|
247
274
|
}
|
|
248
|
-
|
|
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
|
+
);
|
|
249
295
|
};
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
296
|
+
|
|
297
|
+
// src/federation/output-transaction.ts
|
|
298
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
299
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
300
|
+
var getSafePublicPath = (publicDirectory, relativePath) => {
|
|
301
|
+
const normalizedPath = import_node_path3.default.posix.normalize(relativePath);
|
|
302
|
+
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("../");
|
|
303
|
+
if (unsafe) return void 0;
|
|
304
|
+
const destinationPath = import_node_path3.default.resolve(publicDirectory, relativePath);
|
|
305
|
+
const relativeDestinationPath = import_node_path3.default.relative(publicDirectory, destinationPath);
|
|
306
|
+
return relativeDestinationPath !== "" && relativeDestinationPath !== ".." && !relativeDestinationPath.startsWith(`..${import_node_path3.default.sep}`) && !import_node_path3.default.isAbsolute(relativeDestinationPath) ? destinationPath : void 0;
|
|
307
|
+
};
|
|
308
|
+
var beginFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths) => {
|
|
309
|
+
const transactionDirectory = await import_promises3.default.mkdtemp(import_node_path3.default.join(projectDirectory, ".eventcatalog-federation-transaction-"));
|
|
310
|
+
const previousOutDir = import_node_path3.default.join(transactionDirectory, "federated");
|
|
311
|
+
const publicBackupDirectory = import_node_path3.default.join(transactionDirectory, "public");
|
|
312
|
+
const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
|
|
313
|
+
const publicSnapshots = [];
|
|
314
|
+
const missingPublicDirectories = /* @__PURE__ */ new Set();
|
|
315
|
+
let hadPreviousOutDir = false;
|
|
316
|
+
try {
|
|
317
|
+
for (const relativePath of new Set(relativePublicPaths)) {
|
|
318
|
+
const destinationPath = getSafePublicPath(publicDirectory, relativePath);
|
|
319
|
+
if (!destinationPath) continue;
|
|
320
|
+
try {
|
|
321
|
+
const stat = await import_promises3.default.lstat(destinationPath);
|
|
322
|
+
if (!stat.isFile()) continue;
|
|
323
|
+
const backupPath = import_node_path3.default.join(publicBackupDirectory, `${publicSnapshots.length}`);
|
|
324
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(backupPath), { recursive: true });
|
|
325
|
+
await import_promises3.default.copyFile(destinationPath, backupPath);
|
|
326
|
+
publicSnapshots.push({ backupPath, destinationPath, state: "file" });
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const code = error.code;
|
|
329
|
+
if (code === "ENOTDIR") continue;
|
|
330
|
+
if (code !== "ENOENT") throw error;
|
|
331
|
+
publicSnapshots.push({ destinationPath, state: "missing" });
|
|
332
|
+
let directory = import_node_path3.default.dirname(destinationPath);
|
|
333
|
+
while (directory !== publicDirectory) {
|
|
334
|
+
try {
|
|
335
|
+
await import_promises3.default.lstat(directory);
|
|
336
|
+
break;
|
|
337
|
+
} catch (directoryError) {
|
|
338
|
+
const directoryCode = directoryError.code;
|
|
339
|
+
if (directoryCode === "ENOTDIR") break;
|
|
340
|
+
if (directoryCode !== "ENOENT") throw directoryError;
|
|
341
|
+
missingPublicDirectories.add(directory);
|
|
342
|
+
directory = import_node_path3.default.dirname(directory);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await import_promises3.default.rename(outDir, previousOutDir);
|
|
349
|
+
hadPreviousOutDir = true;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error.code !== "ENOENT") throw error;
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true });
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
256
357
|
return {
|
|
257
|
-
async
|
|
258
|
-
|
|
259
|
-
const ref = source.ref ?? "main";
|
|
260
|
-
return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
|
|
358
|
+
commit: async () => {
|
|
359
|
+
await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
261
360
|
},
|
|
262
|
-
async
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
361
|
+
rollback: async () => {
|
|
362
|
+
const rollbackErrors = [];
|
|
363
|
+
const attempt = async (operation) => {
|
|
364
|
+
try {
|
|
365
|
+
await operation();
|
|
366
|
+
} catch (error) {
|
|
367
|
+
rollbackErrors.push(error);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
await attempt(() => import_promises3.default.rm(outDir, { recursive: true, force: true }));
|
|
371
|
+
if (hadPreviousOutDir) await attempt(() => import_promises3.default.rename(previousOutDir, outDir));
|
|
372
|
+
for (const snapshot of publicSnapshots) {
|
|
373
|
+
if (snapshot.state === "missing") {
|
|
374
|
+
await attempt(() => import_promises3.default.rm(snapshot.destinationPath, { force: true }));
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
await attempt(async () => {
|
|
378
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(snapshot.destinationPath), { recursive: true });
|
|
379
|
+
await import_promises3.default.copyFile(snapshot.backupPath, snapshot.destinationPath);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
for (const directory of [...missingPublicDirectories].sort((left, right) => right.length - left.length)) {
|
|
383
|
+
await attempt(async () => {
|
|
384
|
+
try {
|
|
385
|
+
await import_promises3.default.rmdir(directory);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
await attempt(() => import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }));
|
|
392
|
+
if (rollbackErrors.length > 0) throw new AggregateError(rollbackErrors, "Failed to restore the previous federation output");
|
|
268
393
|
}
|
|
269
394
|
};
|
|
270
395
|
};
|
|
396
|
+
var withFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths, update) => {
|
|
397
|
+
const transaction = await beginFederationOutputTransaction(projectDirectory, outDir, relativePublicPaths);
|
|
398
|
+
try {
|
|
399
|
+
const result = await update();
|
|
400
|
+
await transaction.commit();
|
|
401
|
+
return result;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
try {
|
|
404
|
+
await transaction.rollback();
|
|
405
|
+
} catch (rollbackError) {
|
|
406
|
+
throw new AggregateError([error, rollbackError], "Federation failed and the previous output could not be restored");
|
|
407
|
+
}
|
|
408
|
+
throw error;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
271
411
|
|
|
272
412
|
// src/federation/public-assets.ts
|
|
273
413
|
var import_node_crypto2 = require("crypto");
|
|
@@ -399,6 +539,232 @@ var composePublicAssets = async ({
|
|
|
399
539
|
return { files, copied, skipped, overwritten, removed };
|
|
400
540
|
};
|
|
401
541
|
|
|
542
|
+
// src/federation/filesystem-source-provider.ts
|
|
543
|
+
var import_node_crypto3 = require("crypto");
|
|
544
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
545
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
546
|
+
var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
|
|
547
|
+
var FILESYSTEM_SOURCE_PREFIX = "file:";
|
|
548
|
+
var isWithinDirectory = (directory, target) => {
|
|
549
|
+
const relativePath = import_node_path5.default.relative(directory, target);
|
|
550
|
+
return relativePath === "" || !relativePath.startsWith(`..${import_node_path5.default.sep}`) && relativePath !== ".." && !import_node_path5.default.isAbsolute(relativePath);
|
|
551
|
+
};
|
|
552
|
+
var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
|
|
553
|
+
const portablePath = filePath.replaceAll("\\", "/");
|
|
554
|
+
const normalizedPath = import_node_path5.default.posix.normalize(portablePath);
|
|
555
|
+
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 === "");
|
|
556
|
+
if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
|
|
557
|
+
return normalizedPath;
|
|
558
|
+
};
|
|
559
|
+
var getSourceRoot = (projectDirectory, source) => {
|
|
560
|
+
if (!source.source.startsWith(FILESYSTEM_SOURCE_PREFIX)) {
|
|
561
|
+
throw new Error(`Unsupported federation source "${source.source}". Expected file:path/to/catalog.`);
|
|
562
|
+
}
|
|
563
|
+
const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
|
|
564
|
+
if (!locator.trim()) throw new Error(`Filesystem federation source "${source.id}" requires a path after "file:".`);
|
|
565
|
+
return import_node_path5.default.resolve(projectDirectory, locator);
|
|
566
|
+
};
|
|
567
|
+
var getCatalogDirectory = async (projectDirectory, source) => {
|
|
568
|
+
if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
|
|
569
|
+
const sourceRoot = getSourceRoot(projectDirectory, source);
|
|
570
|
+
const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
|
|
571
|
+
const catalogDirectory = import_node_path5.default.resolve(sourceRoot, ...catalogPath.split("/"));
|
|
572
|
+
if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
|
|
573
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises5.default.realpath(sourceRoot), import_promises5.default.realpath(catalogDirectory)]);
|
|
577
|
+
if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
|
|
578
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
579
|
+
}
|
|
580
|
+
if (!(await import_promises5.default.stat(realCatalogDirectory)).isDirectory()) {
|
|
581
|
+
throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
|
|
582
|
+
}
|
|
583
|
+
return realCatalogDirectory;
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (error.code === "ENOENT") {
|
|
586
|
+
throw new Error(`Filesystem federation source "${source.id}" does not exist: ${catalogDirectory}`, { cause: error });
|
|
587
|
+
}
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
|
|
592
|
+
const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
|
|
593
|
+
const filePath = import_node_path5.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
|
|
594
|
+
if (!isWithinDirectory(catalogDirectory, filePath)) {
|
|
595
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
const realFilePath = await import_promises5.default.realpath(filePath);
|
|
599
|
+
if (!isWithinDirectory(catalogDirectory, realFilePath)) {
|
|
600
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
601
|
+
}
|
|
602
|
+
return realFilePath;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (error.code === "ENOENT") {
|
|
605
|
+
throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`, { cause: error });
|
|
606
|
+
}
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var createFileSystemSourceProvider = (projectDirectory) => ({
|
|
611
|
+
async resolve(source) {
|
|
612
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
613
|
+
const localIndex = await (0, import_sdk.default)(catalogDirectory).buildIndex({
|
|
614
|
+
source: source.id,
|
|
615
|
+
commit: "local",
|
|
616
|
+
includeFederated: false
|
|
617
|
+
});
|
|
618
|
+
const snapshot = (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(localIndex)).digest("hex").slice(0, 12);
|
|
619
|
+
const index = { ...localIndex, commit: `local:${snapshot}` };
|
|
620
|
+
const bytes = Buffer.from(JSON.stringify(index));
|
|
621
|
+
return { bytes, index, commit: index.commit, generated: true };
|
|
622
|
+
},
|
|
623
|
+
async fetchContent({ source, path: artifactPath }) {
|
|
624
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
625
|
+
return import_promises5.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// src/federation/github-source-provider.ts
|
|
630
|
+
var import_node_child_process = require("child_process");
|
|
631
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
632
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
633
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
634
|
+
var import_node_util = require("util");
|
|
635
|
+
var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
636
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
637
|
+
var parseGitHubSource = (source) => {
|
|
638
|
+
const match = /^github:([^/]+)\/(.+)$/.exec(source.source);
|
|
639
|
+
if (!match) throw new Error(`Unsupported federation source "${source.source}". Expected github:owner/repository.`);
|
|
640
|
+
return { owner: match[1], repository: match[2] };
|
|
641
|
+
};
|
|
642
|
+
var assertSafeCatalogPath = (source) => {
|
|
643
|
+
const catalogPath = source.path ?? ".";
|
|
644
|
+
const normalized = import_node_path6.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
|
|
645
|
+
if (catalogPath.includes("\\") || import_node_path6.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
646
|
+
throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
var encodePath = (value) => value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
|
|
650
|
+
var rawUrl = (source, ref, filePath) => {
|
|
651
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
652
|
+
return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/${encodeURIComponent(
|
|
653
|
+
ref
|
|
654
|
+
)}/${encodePath(filePath)}`;
|
|
655
|
+
};
|
|
656
|
+
var contentsApiUrl = (source, ref, filePath) => {
|
|
657
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
658
|
+
return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/contents/${encodePath(
|
|
659
|
+
filePath
|
|
660
|
+
)}?ref=${encodeURIComponent(ref)}`;
|
|
661
|
+
};
|
|
662
|
+
var fetchBytes = async (source, ref, filePath, fetcher, token) => {
|
|
663
|
+
const url = token ? contentsApiUrl(source, ref, filePath) : rawUrl(source, ref, filePath);
|
|
664
|
+
const response = token ? await fetcher(url, {
|
|
665
|
+
headers: {
|
|
666
|
+
Accept: "application/vnd.github.raw+json",
|
|
667
|
+
Authorization: `Bearer ${token}`,
|
|
668
|
+
"X-GitHub-Api-Version": "2026-03-10"
|
|
669
|
+
}
|
|
670
|
+
}) : await fetcher(url);
|
|
671
|
+
if (response.status === 404) return void 0;
|
|
672
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
673
|
+
return Buffer.from(await response.arrayBuffer());
|
|
674
|
+
};
|
|
675
|
+
var getGitEnvironment = (token) => {
|
|
676
|
+
if (!token) return process.env;
|
|
677
|
+
const inheritedCount = Number.parseInt(process.env.GIT_CONFIG_COUNT ?? "0", 10);
|
|
678
|
+
const configIndex = Number.isInteger(inheritedCount) && inheritedCount >= 0 ? inheritedCount : 0;
|
|
679
|
+
return {
|
|
680
|
+
...process.env,
|
|
681
|
+
GIT_CONFIG_COUNT: String(configIndex + 1),
|
|
682
|
+
[`GIT_CONFIG_KEY_${configIndex}`]: "http.https://github.com/.extraheader",
|
|
683
|
+
[`GIT_CONFIG_VALUE_${configIndex}`]: `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`
|
|
684
|
+
};
|
|
685
|
+
};
|
|
686
|
+
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
687
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
688
|
+
const catalogPath = import_node_path6.default.posix.normalize(source.path ?? ".");
|
|
689
|
+
const directory = await import_promises6.default.mkdtemp(import_node_path6.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
|
|
690
|
+
const env = getGitEnvironment(token);
|
|
691
|
+
try {
|
|
692
|
+
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
693
|
+
await git(["init", "--quiet"]);
|
|
694
|
+
await git(["remote", "add", "origin", `https://github.com/${owner}/${repository}.git`]);
|
|
695
|
+
if (catalogPath !== ".") {
|
|
696
|
+
await git(["sparse-checkout", "init", "--cone"]);
|
|
697
|
+
await git(["sparse-checkout", "set", catalogPath]);
|
|
698
|
+
}
|
|
699
|
+
await git(["fetch", "--quiet", "--depth", "1", "origin", ref]);
|
|
700
|
+
await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
|
|
701
|
+
return await callback(directory);
|
|
702
|
+
} finally {
|
|
703
|
+
await import_promises6.default.rm(directory, { recursive: true, force: true });
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
|
|
707
|
+
const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
|
|
708
|
+
const commit = stdout.trim();
|
|
709
|
+
const catalogDirectory = import_node_path6.default.resolve(directory, source.path ?? ".");
|
|
710
|
+
const relativeCatalogDirectory = import_node_path6.default.relative(directory, catalogDirectory);
|
|
711
|
+
if (relativeCatalogDirectory.startsWith("..") || import_node_path6.default.isAbsolute(relativeCatalogDirectory)) {
|
|
712
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
713
|
+
}
|
|
714
|
+
const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
|
|
715
|
+
return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
|
|
716
|
+
});
|
|
717
|
+
var fetchPublishedIndex = async (source, ref, fetcher, token) => {
|
|
718
|
+
const indexPath = import_node_path6.default.posix.join(source.path ?? ".", "catalog.index.json");
|
|
719
|
+
const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
|
|
720
|
+
if (!bytes) return void 0;
|
|
721
|
+
const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
|
|
722
|
+
if (index.source !== source.id) {
|
|
723
|
+
throw new Error(`Published index source "${index.source}" does not match configured id "${source.id}"`);
|
|
724
|
+
}
|
|
725
|
+
return { bytes, index, commit: index.commit, generated: false };
|
|
726
|
+
};
|
|
727
|
+
var createGitHubSourceProvider = (options = {}) => {
|
|
728
|
+
const fetcher = options.fetch ?? ((url, init) => fetch(url, init));
|
|
729
|
+
const executeFile = options.execFile ?? ((file, args, execOptions) => execFileAsync(file, args, execOptions));
|
|
730
|
+
const configuredToken = options.token ?? process.env.EVENTCATALOG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN;
|
|
731
|
+
const token = configuredToken?.trim() || void 0;
|
|
732
|
+
const checkout = options.checkout ?? createCheckout(executeFile, token);
|
|
733
|
+
return {
|
|
734
|
+
async resolve(source) {
|
|
735
|
+
assertSafeCatalogPath(source);
|
|
736
|
+
const ref = source.ref ?? "main";
|
|
737
|
+
return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
|
|
738
|
+
},
|
|
739
|
+
async fetchContent({ source, commit, path: artifactPath }) {
|
|
740
|
+
assertSafeCatalogPath(source);
|
|
741
|
+
const catalogPath = import_node_path6.default.posix.join(source.path ?? ".", artifactPath);
|
|
742
|
+
const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
|
|
743
|
+
if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
|
|
744
|
+
return content;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// src/federation/source-provider.ts
|
|
750
|
+
var createFederationSourceProvider = (projectDirectory, providers = {}) => {
|
|
751
|
+
const github = providers.github ?? createGitHubSourceProvider();
|
|
752
|
+
const filesystem = providers.filesystem ?? createFileSystemSourceProvider(projectDirectory);
|
|
753
|
+
const getProvider = (source) => {
|
|
754
|
+
if (source.source.startsWith("github:")) return github;
|
|
755
|
+
if (source.source.startsWith("file:")) return filesystem;
|
|
756
|
+
throw new Error(`Unsupported federation source "${source.source}" for "${source.id}". Supported protocols: github:, file:.`);
|
|
757
|
+
};
|
|
758
|
+
return {
|
|
759
|
+
async resolve(source) {
|
|
760
|
+
return getProvider(source).resolve(source);
|
|
761
|
+
},
|
|
762
|
+
async fetchContent(request) {
|
|
763
|
+
return getProvider(request.source).fetchContent(request);
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
};
|
|
767
|
+
|
|
402
768
|
// src/federation/federate.ts
|
|
403
769
|
var FederationConflictError = class extends Error {
|
|
404
770
|
conflicts;
|
|
@@ -410,6 +776,16 @@ ${details}`);
|
|
|
410
776
|
this.conflicts = conflicts;
|
|
411
777
|
}
|
|
412
778
|
};
|
|
779
|
+
var FederationDiagnosticError = class extends Error {
|
|
780
|
+
diagnostics;
|
|
781
|
+
constructor(diagnostics) {
|
|
782
|
+
const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
|
|
783
|
+
super(`Federation diagnostics prevent hydration:
|
|
784
|
+
${details}`);
|
|
785
|
+
this.name = "FederationDiagnosticError";
|
|
786
|
+
this.diagnostics = diagnostics;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
413
789
|
var validateSources = (sources) => {
|
|
414
790
|
const ids = /* @__PURE__ */ new Set();
|
|
415
791
|
for (const source of sources) {
|
|
@@ -418,19 +794,46 @@ var validateSources = (sources) => {
|
|
|
418
794
|
ids.add(source.id);
|
|
419
795
|
}
|
|
420
796
|
};
|
|
797
|
+
var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
|
|
798
|
+
const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
|
|
799
|
+
const externalsByPointer = /* @__PURE__ */ new Map();
|
|
800
|
+
for (const edge of edges.filter((edge2) => edge2.status === "external")) {
|
|
801
|
+
const version = edge.pointer ?? void 0;
|
|
802
|
+
const key = `${edge.to}@${version ?? ""}`;
|
|
803
|
+
const external = externalsByPointer.get(key) ?? {
|
|
804
|
+
id: edge.to,
|
|
805
|
+
...version === void 0 ? {} : { version },
|
|
806
|
+
referencedBy: []
|
|
807
|
+
};
|
|
808
|
+
if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
|
|
809
|
+
externalsByPointer.set(key, external);
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
...graph,
|
|
813
|
+
entities: [
|
|
814
|
+
...graph.entities,
|
|
815
|
+
...ownershipGraph.entities.filter(
|
|
816
|
+
(entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
|
|
817
|
+
)
|
|
818
|
+
],
|
|
819
|
+
edges,
|
|
820
|
+
conflicts: ownershipGraph.conflicts,
|
|
821
|
+
externals: [...externalsByPointer.values()]
|
|
822
|
+
};
|
|
823
|
+
};
|
|
421
824
|
var writeLock = async (lockPath, lock) => {
|
|
422
825
|
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
423
826
|
try {
|
|
424
|
-
await
|
|
827
|
+
await import_promises7.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
|
|
425
828
|
`, "utf8");
|
|
426
|
-
await
|
|
829
|
+
await import_promises7.default.rename(temporaryPath, lockPath);
|
|
427
830
|
} finally {
|
|
428
|
-
await
|
|
831
|
+
await import_promises7.default.rm(temporaryPath, { force: true });
|
|
429
832
|
}
|
|
430
833
|
};
|
|
431
834
|
var readLock = async (lockPath) => {
|
|
432
835
|
try {
|
|
433
|
-
return JSON.parse(await
|
|
836
|
+
return JSON.parse(await import_promises7.default.readFile(lockPath, "utf8"));
|
|
434
837
|
} catch (error) {
|
|
435
838
|
if (error.code === "ENOENT") return void 0;
|
|
436
839
|
throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
|
|
@@ -438,7 +841,7 @@ var readLock = async (lockPath) => {
|
|
|
438
841
|
};
|
|
439
842
|
var pathExists2 = async (filePath) => {
|
|
440
843
|
try {
|
|
441
|
-
await
|
|
844
|
+
await import_promises7.default.access(filePath);
|
|
442
845
|
return true;
|
|
443
846
|
} catch (error) {
|
|
444
847
|
if (error.code === "ENOENT") return false;
|
|
@@ -446,20 +849,27 @@ var pathExists2 = async (filePath) => {
|
|
|
446
849
|
}
|
|
447
850
|
};
|
|
448
851
|
var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
449
|
-
const outDir =
|
|
450
|
-
const lockPath =
|
|
852
|
+
const outDir = import_node_path7.default.join(projectDirectory, "federated");
|
|
853
|
+
const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
|
|
451
854
|
const previousLock = await readLock(lockPath);
|
|
452
855
|
const hadFederatedOutput = await pathExists2(outDir);
|
|
453
856
|
const hadLock = previousLock !== void 0;
|
|
454
857
|
if (!hadFederatedOutput && !hadLock) return;
|
|
455
|
-
|
|
456
|
-
const publicResult = await composePublicAssets({
|
|
858
|
+
const publicResult = await withFederationOutputTransaction(
|
|
457
859
|
projectDirectory,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
860
|
+
outDir,
|
|
861
|
+
Object.keys(previousLock?.publicFiles ?? {}),
|
|
862
|
+
async () => {
|
|
863
|
+
const result = await composePublicAssets({
|
|
864
|
+
projectDirectory,
|
|
865
|
+
federatedDirectory: outDir,
|
|
866
|
+
assets: [],
|
|
867
|
+
previousFiles: previousLock?.publicFiles
|
|
868
|
+
});
|
|
869
|
+
await import_promises7.default.rm(lockPath, { force: true });
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
);
|
|
463
873
|
onProgress?.({
|
|
464
874
|
type: "cleanup:complete",
|
|
465
875
|
federated: hadFederatedOutput,
|
|
@@ -481,8 +891,9 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
481
891
|
);
|
|
482
892
|
}
|
|
483
893
|
validateSources(sources);
|
|
894
|
+
const rules = resolveFederationRules(config.federation?.rules);
|
|
484
895
|
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
485
|
-
const provider = options.provider ??
|
|
896
|
+
const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
|
|
486
897
|
const resolvedSources = [];
|
|
487
898
|
for (const [index, source] of sources.entries()) {
|
|
488
899
|
const current = index + 1;
|
|
@@ -504,13 +915,13 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
504
915
|
throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
|
|
505
916
|
}
|
|
506
917
|
}
|
|
507
|
-
const outDir =
|
|
508
|
-
const lockPath =
|
|
918
|
+
const outDir = import_node_path7.default.join(projectDirectory, "federated");
|
|
919
|
+
const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
|
|
509
920
|
const previousLock = await readLock(lockPath);
|
|
510
921
|
const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
|
|
511
922
|
const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
|
|
512
923
|
options.onProgress?.({ type: "local:start" });
|
|
513
|
-
const localIndex = await (0,
|
|
924
|
+
const localIndex = await (0, import_sdk3.default)(projectDirectory).buildIndex({
|
|
514
925
|
source: config.cId,
|
|
515
926
|
commit: "local",
|
|
516
927
|
hashContent: false,
|
|
@@ -518,61 +929,80 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
518
929
|
});
|
|
519
930
|
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
520
931
|
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
521
|
-
const ownershipGraph = (0,
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
932
|
+
const ownershipGraph = (0, import_sdk3.resolve)([localIndex, ...remoteIndexes]);
|
|
933
|
+
const graph = (0, import_sdk3.resolve)(remoteIndexes);
|
|
934
|
+
const diagnosticGraph = createDiagnosticGraph(
|
|
935
|
+
graph,
|
|
936
|
+
ownershipGraph,
|
|
937
|
+
new Set(remoteIndexes.map((index) => index.source)),
|
|
938
|
+
localIndex.source
|
|
939
|
+
);
|
|
940
|
+
const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
|
|
941
|
+
options.onProgress?.({ type: "resolved", graph, diagnostics });
|
|
942
|
+
const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
|
|
943
|
+
if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
|
|
944
|
+
const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
945
|
+
if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
|
|
946
|
+
const publicPaths = [
|
|
947
|
+
...Object.keys(previousLock?.publicFiles ?? {}),
|
|
948
|
+
...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
|
|
949
|
+
];
|
|
950
|
+
const { hydrateResult, publicResult } = await withFederationOutputTransaction(
|
|
951
|
+
projectDirectory,
|
|
533
952
|
outDir,
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
953
|
+
publicPaths,
|
|
954
|
+
async () => {
|
|
955
|
+
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
956
|
+
options.onProgress?.({ type: "hydrating", outDir });
|
|
957
|
+
let hydratedFiles = 0;
|
|
958
|
+
let cachedFiles = 0;
|
|
959
|
+
const hydrateResult2 = await (0, import_sdk3.hydrate)(graph, {
|
|
960
|
+
outDir,
|
|
961
|
+
cache: createFederationContentCache(projectDirectory, {
|
|
962
|
+
read: options.useCache !== false,
|
|
963
|
+
onHit: () => {
|
|
964
|
+
cachedFiles += 1;
|
|
965
|
+
options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
|
|
966
|
+
}
|
|
967
|
+
}),
|
|
968
|
+
fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
|
|
969
|
+
const source = sourcesById.get(sourceId);
|
|
970
|
+
if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
|
|
971
|
+
const content = await provider.fetchContent({ source, commit, path: artifactPath });
|
|
972
|
+
hydratedFiles += 1;
|
|
973
|
+
options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
|
|
974
|
+
return content;
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
const publicResult2 = await composePublicAssets({
|
|
978
|
+
projectDirectory,
|
|
979
|
+
federatedDirectory: outDir,
|
|
980
|
+
assets: graph.assets,
|
|
981
|
+
previousFiles: previousLock?.publicFiles,
|
|
982
|
+
collisionPaths: new Set(
|
|
983
|
+
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
984
|
+
)
|
|
985
|
+
});
|
|
986
|
+
options.onProgress?.({ type: "public:complete", result: publicResult2 });
|
|
987
|
+
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
988
|
+
await writeLock(lockPath, {
|
|
989
|
+
lockVersion: 1,
|
|
990
|
+
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
991
|
+
id: source.id,
|
|
992
|
+
digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
|
|
993
|
+
commit: resolved.commit,
|
|
994
|
+
resolvedAt
|
|
995
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
996
|
+
publicFiles: publicResult2.files
|
|
997
|
+
});
|
|
998
|
+
return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
|
|
549
999
|
}
|
|
550
|
-
|
|
551
|
-
const publicResult = await composePublicAssets({
|
|
552
|
-
projectDirectory,
|
|
553
|
-
federatedDirectory: outDir,
|
|
554
|
-
assets: graph.assets,
|
|
555
|
-
previousFiles: previousLock?.publicFiles,
|
|
556
|
-
collisionPaths: new Set(
|
|
557
|
-
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
558
|
-
)
|
|
559
|
-
});
|
|
560
|
-
options.onProgress?.({ type: "public:complete", result: publicResult });
|
|
561
|
-
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
562
|
-
await writeLock(lockPath, {
|
|
563
|
-
lockVersion: 1,
|
|
564
|
-
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
565
|
-
id: source.id,
|
|
566
|
-
digest: `sha256:${(0, import_node_crypto3.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
|
|
567
|
-
commit: resolved.commit,
|
|
568
|
-
resolvedAt
|
|
569
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
570
|
-
publicFiles: publicResult.files
|
|
571
|
-
});
|
|
1000
|
+
);
|
|
572
1001
|
const result = {
|
|
573
1002
|
sources: sources.length,
|
|
574
1003
|
resources,
|
|
575
1004
|
graph,
|
|
1005
|
+
diagnostics,
|
|
576
1006
|
hydrate: hydrateResult,
|
|
577
1007
|
public: publicResult,
|
|
578
1008
|
outDir,
|
|
@@ -584,5 +1014,6 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
584
1014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
585
1015
|
0 && (module.exports = {
|
|
586
1016
|
FederationConflictError,
|
|
1017
|
+
FederationDiagnosticError,
|
|
587
1018
|
federateCatalog
|
|
588
1019
|
});
|