@eventcatalog/core 4.7.4 → 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-FDXJIZ74.js → chunk-2BYTIH4L.js} +1 -1
- package/dist/{chunk-7QTCSRAC.js → chunk-EUZT4RKY.js} +1 -1
- package/dist/{chunk-RIUHZZRA.js → chunk-GYYYAUJD.js} +128 -56
- package/dist/chunk-JJPB6EOZ.js +159 -0
- package/dist/{chunk-ZIABX6SP.js → chunk-QJRY6Q7S.js} +1 -1
- package/dist/{chunk-ZXVQXBTT.js → chunk-QQPH6RCB.js} +24 -1
- package/dist/chunk-WRNH5C3U.js +118 -0
- package/dist/{chunk-TLWM7WRZ.js → chunk-ZBPMCEXS.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +540 -194
- package/dist/eventcatalog.config.d.cts +9 -3
- package/dist/eventcatalog.config.d.ts +9 -3
- package/dist/eventcatalog.js +37 -39
- 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 +442 -117
- package/dist/federation/federate.d.cts +8 -1
- package/dist/federation/federate.d.ts +8 -1
- package/dist/federation/federate.js +6 -2
- 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/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
|
@@ -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,287 @@ 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/output-transaction.ts
|
|
154
298
|
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
155
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
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
commit: async () => {
|
|
359
|
+
await import_promises3.default.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
360
|
+
},
|
|
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");
|
|
393
|
+
}
|
|
394
|
+
};
|
|
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
|
+
};
|
|
411
|
+
|
|
412
|
+
// src/federation/public-assets.ts
|
|
413
|
+
var import_node_crypto2 = require("crypto");
|
|
414
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
415
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
156
416
|
var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
|
|
157
417
|
var getFileHash = async (filePath) => {
|
|
158
418
|
try {
|
|
159
|
-
const stat = await
|
|
160
|
-
return stat.isFile() ? getContentHash2(await
|
|
419
|
+
const stat = await import_promises4.default.lstat(filePath);
|
|
420
|
+
return stat.isFile() ? getContentHash2(await import_promises4.default.readFile(filePath)) : void 0;
|
|
161
421
|
} catch (error) {
|
|
162
422
|
if (error.code === "ENOENT") return void 0;
|
|
163
423
|
throw error;
|
|
164
424
|
}
|
|
165
425
|
};
|
|
166
426
|
var getSafePath = (directory, relativePath) => {
|
|
167
|
-
const normalizedPath =
|
|
168
|
-
const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") ||
|
|
427
|
+
const normalizedPath = import_node_path4.default.posix.normalize(relativePath);
|
|
428
|
+
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
429
|
if (unsafe) return void 0;
|
|
170
|
-
const resolvedDirectory =
|
|
171
|
-
const resolvedPath =
|
|
172
|
-
const relativeResolvedPath =
|
|
173
|
-
if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${
|
|
430
|
+
const resolvedDirectory = import_node_path4.default.resolve(directory);
|
|
431
|
+
const resolvedPath = import_node_path4.default.resolve(resolvedDirectory, relativePath);
|
|
432
|
+
const relativeResolvedPath = import_node_path4.default.relative(resolvedDirectory, resolvedPath);
|
|
433
|
+
if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relativeResolvedPath)) {
|
|
174
434
|
return void 0;
|
|
175
435
|
}
|
|
176
436
|
return resolvedPath;
|
|
@@ -178,23 +438,23 @@ var getSafePath = (directory, relativePath) => {
|
|
|
178
438
|
var listFiles = async (directory, relativeDirectory = "") => {
|
|
179
439
|
let entries;
|
|
180
440
|
try {
|
|
181
|
-
entries = await
|
|
441
|
+
entries = await import_promises4.default.readdir(import_node_path4.default.join(directory, relativeDirectory), { withFileTypes: true });
|
|
182
442
|
} catch (error) {
|
|
183
443
|
if (error.code === "ENOENT") return [];
|
|
184
444
|
throw error;
|
|
185
445
|
}
|
|
186
446
|
const files = await Promise.all(
|
|
187
447
|
entries.map(async (entry) => {
|
|
188
|
-
const relativePath =
|
|
448
|
+
const relativePath = import_node_path4.default.join(relativeDirectory, entry.name);
|
|
189
449
|
if (entry.isDirectory()) return listFiles(directory, relativePath);
|
|
190
|
-
return entry.isFile() ? [relativePath.split(
|
|
450
|
+
return entry.isFile() ? [relativePath.split(import_node_path4.default.sep).join("/")] : [];
|
|
191
451
|
})
|
|
192
452
|
);
|
|
193
453
|
return files.flat().sort();
|
|
194
454
|
};
|
|
195
455
|
var pathExists = async (filePath) => {
|
|
196
456
|
try {
|
|
197
|
-
await
|
|
457
|
+
await import_promises4.default.lstat(filePath);
|
|
198
458
|
return true;
|
|
199
459
|
} catch (error) {
|
|
200
460
|
if (error.code === "ENOENT") return false;
|
|
@@ -202,27 +462,27 @@ var pathExists = async (filePath) => {
|
|
|
202
462
|
}
|
|
203
463
|
};
|
|
204
464
|
var hasBlockingParent = async (publicDirectory, destinationPath) => {
|
|
205
|
-
let currentPath =
|
|
465
|
+
let currentPath = import_node_path4.default.dirname(destinationPath);
|
|
206
466
|
while (currentPath !== publicDirectory) {
|
|
207
467
|
try {
|
|
208
|
-
if (!(await
|
|
468
|
+
if (!(await import_promises4.default.lstat(currentPath)).isDirectory()) return true;
|
|
209
469
|
} catch (error) {
|
|
210
470
|
if (error.code !== "ENOENT") throw error;
|
|
211
471
|
}
|
|
212
|
-
currentPath =
|
|
472
|
+
currentPath = import_node_path4.default.dirname(currentPath);
|
|
213
473
|
}
|
|
214
474
|
return false;
|
|
215
475
|
};
|
|
216
476
|
var pruneEmptyDirectories = async (publicDirectory, filePath) => {
|
|
217
|
-
let currentPath =
|
|
477
|
+
let currentPath = import_node_path4.default.dirname(filePath);
|
|
218
478
|
while (currentPath !== publicDirectory) {
|
|
219
479
|
try {
|
|
220
|
-
await
|
|
480
|
+
await import_promises4.default.rmdir(currentPath);
|
|
221
481
|
} catch (error) {
|
|
222
482
|
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
223
483
|
if (error.code === "ENOTEMPTY") return;
|
|
224
484
|
}
|
|
225
|
-
currentPath =
|
|
485
|
+
currentPath = import_node_path4.default.dirname(currentPath);
|
|
226
486
|
}
|
|
227
487
|
};
|
|
228
488
|
var composePublicAssets = async ({
|
|
@@ -232,8 +492,8 @@ var composePublicAssets = async ({
|
|
|
232
492
|
previousFiles = {},
|
|
233
493
|
collisionPaths = /* @__PURE__ */ new Set()
|
|
234
494
|
}) => {
|
|
235
|
-
const publicDirectory =
|
|
236
|
-
const federatedPublicDirectory =
|
|
495
|
+
const publicDirectory = import_node_path4.default.resolve(projectDirectory, "public");
|
|
496
|
+
const federatedPublicDirectory = import_node_path4.default.resolve(federatedDirectory, "public");
|
|
237
497
|
const sourceFiles = await listFiles(federatedPublicDirectory);
|
|
238
498
|
const sourceFileSet = new Set(sourceFiles);
|
|
239
499
|
const managedFiles = /* @__PURE__ */ new Set();
|
|
@@ -246,7 +506,7 @@ var composePublicAssets = async ({
|
|
|
246
506
|
if (sourceFileSet.has(relativePath)) continue;
|
|
247
507
|
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
248
508
|
if (!destinationPath) continue;
|
|
249
|
-
await
|
|
509
|
+
await import_promises4.default.rm(destinationPath, { force: true });
|
|
250
510
|
await pruneEmptyDirectories(publicDirectory, destinationPath);
|
|
251
511
|
removed += 1;
|
|
252
512
|
}
|
|
@@ -268,31 +528,31 @@ var composePublicAssets = async ({
|
|
|
268
528
|
}
|
|
269
529
|
const asset = publicAssetsByPath.get(relativePath);
|
|
270
530
|
if (!asset) throw new Error(`Cannot identify the source of federated public asset "${relativePath}"`);
|
|
271
|
-
const content = await
|
|
272
|
-
await
|
|
273
|
-
await
|
|
531
|
+
const content = await import_promises4.default.readFile(sourcePath);
|
|
532
|
+
await import_promises4.default.mkdir(import_node_path4.default.dirname(destinationPath), { recursive: true });
|
|
533
|
+
await import_promises4.default.writeFile(destinationPath, content);
|
|
274
534
|
files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
|
|
275
535
|
copied += 1;
|
|
276
536
|
if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
|
|
277
537
|
}
|
|
278
|
-
await
|
|
538
|
+
await import_promises4.default.rm(federatedPublicDirectory, { recursive: true, force: true });
|
|
279
539
|
return { files, copied, skipped, overwritten, removed };
|
|
280
540
|
};
|
|
281
541
|
|
|
282
542
|
// src/federation/filesystem-source-provider.ts
|
|
283
543
|
var import_node_crypto3 = require("crypto");
|
|
284
|
-
var
|
|
285
|
-
var
|
|
544
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
545
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
286
546
|
var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
|
|
287
547
|
var FILESYSTEM_SOURCE_PREFIX = "file:";
|
|
288
548
|
var isWithinDirectory = (directory, target) => {
|
|
289
|
-
const relativePath =
|
|
290
|
-
return relativePath === "" || !relativePath.startsWith(`..${
|
|
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);
|
|
291
551
|
};
|
|
292
552
|
var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
|
|
293
553
|
const portablePath = filePath.replaceAll("\\", "/");
|
|
294
|
-
const normalizedPath =
|
|
295
|
-
const isUnsafe = filePath.includes("\\") || filePath.includes("\0") ||
|
|
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 === "");
|
|
296
556
|
if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
|
|
297
557
|
return normalizedPath;
|
|
298
558
|
};
|
|
@@ -302,22 +562,22 @@ var getSourceRoot = (projectDirectory, source) => {
|
|
|
302
562
|
}
|
|
303
563
|
const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
|
|
304
564
|
if (!locator.trim()) throw new Error(`Filesystem federation source "${source.id}" requires a path after "file:".`);
|
|
305
|
-
return
|
|
565
|
+
return import_node_path5.default.resolve(projectDirectory, locator);
|
|
306
566
|
};
|
|
307
567
|
var getCatalogDirectory = async (projectDirectory, source) => {
|
|
308
568
|
if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
|
|
309
569
|
const sourceRoot = getSourceRoot(projectDirectory, source);
|
|
310
570
|
const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
|
|
311
|
-
const catalogDirectory =
|
|
571
|
+
const catalogDirectory = import_node_path5.default.resolve(sourceRoot, ...catalogPath.split("/"));
|
|
312
572
|
if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
|
|
313
573
|
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
314
574
|
}
|
|
315
575
|
try {
|
|
316
|
-
const [realSourceRoot, realCatalogDirectory] = await Promise.all([
|
|
576
|
+
const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises5.default.realpath(sourceRoot), import_promises5.default.realpath(catalogDirectory)]);
|
|
317
577
|
if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
|
|
318
578
|
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
319
579
|
}
|
|
320
|
-
if (!(await
|
|
580
|
+
if (!(await import_promises5.default.stat(realCatalogDirectory)).isDirectory()) {
|
|
321
581
|
throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
|
|
322
582
|
}
|
|
323
583
|
return realCatalogDirectory;
|
|
@@ -330,12 +590,12 @@ var getCatalogDirectory = async (projectDirectory, source) => {
|
|
|
330
590
|
};
|
|
331
591
|
var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
|
|
332
592
|
const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
|
|
333
|
-
const filePath =
|
|
593
|
+
const filePath = import_node_path5.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
|
|
334
594
|
if (!isWithinDirectory(catalogDirectory, filePath)) {
|
|
335
595
|
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
336
596
|
}
|
|
337
597
|
try {
|
|
338
|
-
const realFilePath = await
|
|
598
|
+
const realFilePath = await import_promises5.default.realpath(filePath);
|
|
339
599
|
if (!isWithinDirectory(catalogDirectory, realFilePath)) {
|
|
340
600
|
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
341
601
|
}
|
|
@@ -362,15 +622,15 @@ var createFileSystemSourceProvider = (projectDirectory) => ({
|
|
|
362
622
|
},
|
|
363
623
|
async fetchContent({ source, path: artifactPath }) {
|
|
364
624
|
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
365
|
-
return
|
|
625
|
+
return import_promises5.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
|
|
366
626
|
}
|
|
367
627
|
});
|
|
368
628
|
|
|
369
629
|
// src/federation/github-source-provider.ts
|
|
370
630
|
var import_node_child_process = require("child_process");
|
|
371
|
-
var
|
|
631
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
372
632
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
373
|
-
var
|
|
633
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
374
634
|
var import_node_util = require("util");
|
|
375
635
|
var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
376
636
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
@@ -381,8 +641,8 @@ var parseGitHubSource = (source) => {
|
|
|
381
641
|
};
|
|
382
642
|
var assertSafeCatalogPath = (source) => {
|
|
383
643
|
const catalogPath = source.path ?? ".";
|
|
384
|
-
const normalized =
|
|
385
|
-
if (catalogPath.includes("\\") ||
|
|
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("../")) {
|
|
386
646
|
throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
|
|
387
647
|
}
|
|
388
648
|
};
|
|
@@ -425,8 +685,8 @@ var getGitEnvironment = (token) => {
|
|
|
425
685
|
};
|
|
426
686
|
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
427
687
|
const { owner, repository } = parseGitHubSource(source);
|
|
428
|
-
const catalogPath =
|
|
429
|
-
const directory = await
|
|
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-"));
|
|
430
690
|
const env = getGitEnvironment(token);
|
|
431
691
|
try {
|
|
432
692
|
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
@@ -440,22 +700,22 @@ var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
|
440
700
|
await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
|
|
441
701
|
return await callback(directory);
|
|
442
702
|
} finally {
|
|
443
|
-
await
|
|
703
|
+
await import_promises6.default.rm(directory, { recursive: true, force: true });
|
|
444
704
|
}
|
|
445
705
|
};
|
|
446
706
|
var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
|
|
447
707
|
const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
|
|
448
708
|
const commit = stdout.trim();
|
|
449
|
-
const catalogDirectory =
|
|
450
|
-
const relativeCatalogDirectory =
|
|
451
|
-
if (relativeCatalogDirectory.startsWith("..") ||
|
|
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)) {
|
|
452
712
|
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
453
713
|
}
|
|
454
714
|
const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
|
|
455
715
|
return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
|
|
456
716
|
});
|
|
457
717
|
var fetchPublishedIndex = async (source, ref, fetcher, token) => {
|
|
458
|
-
const indexPath =
|
|
718
|
+
const indexPath = import_node_path6.default.posix.join(source.path ?? ".", "catalog.index.json");
|
|
459
719
|
const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
|
|
460
720
|
if (!bytes) return void 0;
|
|
461
721
|
const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
|
|
@@ -478,7 +738,7 @@ var createGitHubSourceProvider = (options = {}) => {
|
|
|
478
738
|
},
|
|
479
739
|
async fetchContent({ source, commit, path: artifactPath }) {
|
|
480
740
|
assertSafeCatalogPath(source);
|
|
481
|
-
const catalogPath =
|
|
741
|
+
const catalogPath = import_node_path6.default.posix.join(source.path ?? ".", artifactPath);
|
|
482
742
|
const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
|
|
483
743
|
if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
|
|
484
744
|
return content;
|
|
@@ -516,6 +776,16 @@ ${details}`);
|
|
|
516
776
|
this.conflicts = conflicts;
|
|
517
777
|
}
|
|
518
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
|
+
};
|
|
519
789
|
var validateSources = (sources) => {
|
|
520
790
|
const ids = /* @__PURE__ */ new Set();
|
|
521
791
|
for (const source of sources) {
|
|
@@ -524,19 +794,46 @@ var validateSources = (sources) => {
|
|
|
524
794
|
ids.add(source.id);
|
|
525
795
|
}
|
|
526
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
|
+
};
|
|
527
824
|
var writeLock = async (lockPath, lock) => {
|
|
528
825
|
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
529
826
|
try {
|
|
530
|
-
await
|
|
827
|
+
await import_promises7.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
|
|
531
828
|
`, "utf8");
|
|
532
|
-
await
|
|
829
|
+
await import_promises7.default.rename(temporaryPath, lockPath);
|
|
533
830
|
} finally {
|
|
534
|
-
await
|
|
831
|
+
await import_promises7.default.rm(temporaryPath, { force: true });
|
|
535
832
|
}
|
|
536
833
|
};
|
|
537
834
|
var readLock = async (lockPath) => {
|
|
538
835
|
try {
|
|
539
|
-
return JSON.parse(await
|
|
836
|
+
return JSON.parse(await import_promises7.default.readFile(lockPath, "utf8"));
|
|
540
837
|
} catch (error) {
|
|
541
838
|
if (error.code === "ENOENT") return void 0;
|
|
542
839
|
throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
|
|
@@ -544,7 +841,7 @@ var readLock = async (lockPath) => {
|
|
|
544
841
|
};
|
|
545
842
|
var pathExists2 = async (filePath) => {
|
|
546
843
|
try {
|
|
547
|
-
await
|
|
844
|
+
await import_promises7.default.access(filePath);
|
|
548
845
|
return true;
|
|
549
846
|
} catch (error) {
|
|
550
847
|
if (error.code === "ENOENT") return false;
|
|
@@ -552,20 +849,27 @@ var pathExists2 = async (filePath) => {
|
|
|
552
849
|
}
|
|
553
850
|
};
|
|
554
851
|
var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
555
|
-
const outDir =
|
|
556
|
-
const lockPath =
|
|
852
|
+
const outDir = import_node_path7.default.join(projectDirectory, "federated");
|
|
853
|
+
const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
|
|
557
854
|
const previousLock = await readLock(lockPath);
|
|
558
855
|
const hadFederatedOutput = await pathExists2(outDir);
|
|
559
856
|
const hadLock = previousLock !== void 0;
|
|
560
857
|
if (!hadFederatedOutput && !hadLock) return;
|
|
561
|
-
|
|
562
|
-
const publicResult = await composePublicAssets({
|
|
858
|
+
const publicResult = await withFederationOutputTransaction(
|
|
563
859
|
projectDirectory,
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
+
);
|
|
569
873
|
onProgress?.({
|
|
570
874
|
type: "cleanup:complete",
|
|
571
875
|
federated: hadFederatedOutput,
|
|
@@ -587,6 +891,7 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
587
891
|
);
|
|
588
892
|
}
|
|
589
893
|
validateSources(sources);
|
|
894
|
+
const rules = resolveFederationRules(config.federation?.rules);
|
|
590
895
|
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
591
896
|
const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
|
|
592
897
|
const resolvedSources = [];
|
|
@@ -610,8 +915,8 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
610
915
|
throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
|
|
611
916
|
}
|
|
612
917
|
}
|
|
613
|
-
const outDir =
|
|
614
|
-
const lockPath =
|
|
918
|
+
const outDir = import_node_path7.default.join(projectDirectory, "federated");
|
|
919
|
+
const lockPath = import_node_path7.default.join(projectDirectory, "eventcatalog.lock");
|
|
615
920
|
const previousLock = await readLock(lockPath);
|
|
616
921
|
const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
|
|
617
922
|
const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
|
|
@@ -625,60 +930,79 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
625
930
|
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
626
931
|
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
627
932
|
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
933
|
const graph = (0, import_sdk3.resolve)(remoteIndexes);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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,
|
|
639
952
|
outDir,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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 };
|
|
655
999
|
}
|
|
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
|
-
});
|
|
1000
|
+
);
|
|
678
1001
|
const result = {
|
|
679
1002
|
sources: sources.length,
|
|
680
1003
|
resources,
|
|
681
1004
|
graph,
|
|
1005
|
+
diagnostics,
|
|
682
1006
|
hydrate: hydrateResult,
|
|
683
1007
|
public: publicResult,
|
|
684
1008
|
outDir,
|
|
@@ -690,5 +1014,6 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
690
1014
|
// Annotate the CommonJS export names for ESM import in node:
|
|
691
1015
|
0 && (module.exports = {
|
|
692
1016
|
FederationConflictError,
|
|
1017
|
+
FederationDiagnosticError,
|
|
693
1018
|
federateCatalog
|
|
694
1019
|
});
|