@eventcatalog/core 4.6.1 → 4.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-352FGP6W.js +123 -0
- package/dist/{chunk-N3YKQTGX.js → chunk-4BBXOL6K.js} +1 -1
- package/dist/{chunk-Y7JJ2B3L.js → chunk-54KFOI6Z.js} +3 -3
- package/dist/chunk-5EPNFDT5.js +133 -0
- package/dist/{chunk-MTXVQ4VP.js → chunk-FZJDCQZE.js} +1 -1
- package/dist/{chunk-X7XI7NEP.js → chunk-LB5VXS6X.js} +1 -1
- package/dist/{chunk-U6VOVEVZ.js → chunk-PWY4SP6L.js} +1 -1
- package/dist/chunk-R7Z5ALYI.js +44 -0
- package/dist/chunk-ZR6AH5Z2.js +204 -0
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +651 -51
- package/dist/eventcatalog.config.d.cts +18 -1
- package/dist/eventcatalog.config.d.ts +18 -1
- package/dist/eventcatalog.js +134 -7
- package/dist/federation/content-cache.cjs +78 -0
- package/dist/federation/content-cache.d.cts +9 -0
- package/dist/federation/content-cache.d.ts +9 -0
- package/dist/federation/content-cache.js +6 -0
- package/dist/federation/federate.cjs +588 -0
- package/dist/federation/federate.d.cts +81 -0
- package/dist/federation/federate.d.ts +81 -0
- package/dist/federation/federate.js +12 -0
- package/dist/federation/github-source-provider.cjs +157 -0
- package/dist/federation/github-source-provider.d.cts +24 -0
- package/dist/federation/github-source-provider.d.ts +24 -0
- package/dist/federation/github-source-provider.js +6 -0
- package/dist/federation/public-assets.cjs +167 -0
- package/dist/federation/public-assets.d.cts +24 -0
- package/dist/federation/public-assets.d.ts +24 -0
- package/dist/federation/public-assets.js +6 -0
- package/dist/federation/types.cjs +18 -0
- package/dist/federation/types.d.cts +19 -0
- package/dist/federation/types.d.ts +19 -0
- package/dist/federation/types.js +0 -0
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/package.json +8 -8
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/federation/federate.ts
|
|
31
|
+
var federate_exports = {};
|
|
32
|
+
__export(federate_exports, {
|
|
33
|
+
FederationConflictError: () => FederationConflictError,
|
|
34
|
+
federateCatalog: () => federateCatalog
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(federate_exports);
|
|
37
|
+
var import_node_crypto3 = require("crypto");
|
|
38
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
39
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
40
|
+
var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
41
|
+
|
|
42
|
+
// src/eventcatalog-config-file-utils.js
|
|
43
|
+
var import_promises = require("fs/promises");
|
|
44
|
+
var import_node_fs = require("fs");
|
|
45
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
46
|
+
var import_uuid = require("uuid");
|
|
47
|
+
var import_url = require("url");
|
|
48
|
+
var import_node_os = require("os");
|
|
49
|
+
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
50
|
+
async function cleanup(projectDirectory) {
|
|
51
|
+
const filePath = import_node_path.default.join(projectDirectory, "eventcatalog.config.mjs");
|
|
52
|
+
if ((0, import_node_fs.existsSync)(filePath)) {
|
|
53
|
+
await (0, import_promises.rm)(filePath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
var findNodeModulesDirectory = (directory) => {
|
|
57
|
+
let currentDirectory = directory;
|
|
58
|
+
while (true) {
|
|
59
|
+
const nodeModulesDirectory = import_node_path.default.join(currentDirectory, "node_modules");
|
|
60
|
+
if ((0, import_node_fs.existsSync)(nodeModulesDirectory)) {
|
|
61
|
+
return nodeModulesDirectory;
|
|
62
|
+
}
|
|
63
|
+
const parentDirectory = import_node_path.default.dirname(currentDirectory);
|
|
64
|
+
if (parentDirectory === currentDirectory) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
currentDirectory = parentDirectory;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var linkNodeModulesIntoTempDirectory = async ({ projectDirectory, tempDir }) => {
|
|
71
|
+
const nodeModulesDirectory = findNodeModulesDirectory(projectDirectory);
|
|
72
|
+
if (!nodeModulesDirectory) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await (0, import_promises.symlink)(nodeModulesDirectory, import_node_path.default.join(tempDir, "node_modules"), "dir");
|
|
76
|
+
};
|
|
77
|
+
var createTemporaryConfigDirectory = async (projectDirectory) => {
|
|
78
|
+
const nodeModulesDirectory = findNodeModulesDirectory(projectDirectory);
|
|
79
|
+
if (nodeModulesDirectory) {
|
|
80
|
+
return (0, import_promises.mkdtemp)(import_node_path.default.join(nodeModulesDirectory, ".eventcatalog-config-"));
|
|
81
|
+
}
|
|
82
|
+
const tempDir = await (0, import_promises.mkdtemp)(import_node_path.default.join((0, import_node_os.tmpdir)(), "eventcatalog-config-"));
|
|
83
|
+
await linkNodeModulesIntoTempDirectory({ projectDirectory, tempDir });
|
|
84
|
+
return tempDir;
|
|
85
|
+
};
|
|
86
|
+
var getEventCatalogConfigFile = async (projectDirectory) => {
|
|
87
|
+
let tempDir;
|
|
88
|
+
try {
|
|
89
|
+
let configFilePath = import_node_path.default.join(projectDirectory, "eventcatalog.config.js");
|
|
90
|
+
const filePath = import_node_path.default.join(projectDirectory, "package.json");
|
|
91
|
+
const packageJson = JSON.parse(await (0, import_promises.readFile)(filePath, "utf-8"));
|
|
92
|
+
if (packageJson?.type !== "module") {
|
|
93
|
+
tempDir = await createTemporaryConfigDirectory(projectDirectory);
|
|
94
|
+
configFilePath = import_node_path.default.join(tempDir, "eventcatalog.config.mjs");
|
|
95
|
+
await (0, import_promises.copyFile)(import_node_path.default.join(projectDirectory, "eventcatalog.config.js"), configFilePath);
|
|
96
|
+
}
|
|
97
|
+
const configFileURL = `${(0, import_url.pathToFileURL)(configFilePath).href}?t=${Date.now()}`;
|
|
98
|
+
const config = await import(
|
|
99
|
+
/* @vite-ignore */
|
|
100
|
+
configFileURL
|
|
101
|
+
);
|
|
102
|
+
return config.default;
|
|
103
|
+
} finally {
|
|
104
|
+
await cleanup(projectDirectory);
|
|
105
|
+
if (tempDir) {
|
|
106
|
+
await (0, import_promises.rm)(tempDir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/federation/content-cache.ts
|
|
112
|
+
var import_node_crypto = require("crypto");
|
|
113
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
114
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
115
|
+
var getContentHash = (content) => `sha256:${(0, import_node_crypto.createHash)("sha256").update(content).digest("hex")}`;
|
|
116
|
+
var isContentHash = (key) => /^sha256:[a-f0-9]{64}$/i.test(key);
|
|
117
|
+
var createFederationContentCache = (projectDirectory, options = {}) => {
|
|
118
|
+
const cacheDirectory = import_node_path2.default.join(projectDirectory, ".eventcatalog-cache", "federation", "content");
|
|
119
|
+
const getCachePath = (key) => import_node_path2.default.join(cacheDirectory, encodeURIComponent(key));
|
|
120
|
+
return {
|
|
121
|
+
async get(key) {
|
|
122
|
+
if (options.read === false || !isContentHash(key)) return void 0;
|
|
123
|
+
const cachePath = getCachePath(key);
|
|
124
|
+
try {
|
|
125
|
+
const content = await import_promises2.default.readFile(cachePath);
|
|
126
|
+
if (getContentHash(content) !== key) {
|
|
127
|
+
await import_promises2.default.rm(cachePath, { force: true });
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
options.onHit?.(key);
|
|
131
|
+
return content;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.code === "ENOENT") return void 0;
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
async set(key, content) {
|
|
138
|
+
if (!isContentHash(key) || getContentHash(content) !== key) return;
|
|
139
|
+
await import_promises2.default.mkdir(cacheDirectory, { recursive: true });
|
|
140
|
+
const cachePath = getCachePath(key);
|
|
141
|
+
const temporaryPath = `${cachePath}.tmp-${process.pid}-${(0, import_node_crypto.randomUUID)()}`;
|
|
142
|
+
try {
|
|
143
|
+
await import_promises2.default.writeFile(temporaryPath, content);
|
|
144
|
+
await import_promises2.default.rename(temporaryPath, cachePath);
|
|
145
|
+
} finally {
|
|
146
|
+
await import_promises2.default.rm(temporaryPath, { force: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/federation/github-source-provider.ts
|
|
153
|
+
var import_node_child_process = require("child_process");
|
|
154
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
155
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
156
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
157
|
+
var import_node_util = require("util");
|
|
158
|
+
var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
|
|
159
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
160
|
+
var parseGitHubSource = (source) => {
|
|
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] };
|
|
164
|
+
};
|
|
165
|
+
var assertSafeCatalogPath = (source) => {
|
|
166
|
+
const catalogPath = source.path ?? ".";
|
|
167
|
+
const normalized = import_node_path3.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
|
|
168
|
+
if (catalogPath.includes("\\") || import_node_path3.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
169
|
+
throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var encodePath = (value) => value.split("/").filter(Boolean).map(encodeURIComponent).join("/");
|
|
173
|
+
var rawUrl = (source, ref, filePath) => {
|
|
174
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
175
|
+
return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/${encodeURIComponent(
|
|
176
|
+
ref
|
|
177
|
+
)}/${encodePath(filePath)}`;
|
|
178
|
+
};
|
|
179
|
+
var contentsApiUrl = (source, ref, filePath) => {
|
|
180
|
+
const { owner, repository } = parseGitHubSource(source);
|
|
181
|
+
return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/contents/${encodePath(
|
|
182
|
+
filePath
|
|
183
|
+
)}?ref=${encodeURIComponent(ref)}`;
|
|
184
|
+
};
|
|
185
|
+
var fetchBytes = async (source, ref, filePath, fetcher, token) => {
|
|
186
|
+
const url = token ? contentsApiUrl(source, ref, filePath) : rawUrl(source, ref, filePath);
|
|
187
|
+
const response = token ? await fetcher(url, {
|
|
188
|
+
headers: {
|
|
189
|
+
Accept: "application/vnd.github.raw+json",
|
|
190
|
+
Authorization: `Bearer ${token}`,
|
|
191
|
+
"X-GitHub-Api-Version": "2026-03-10"
|
|
192
|
+
}
|
|
193
|
+
}) : await fetcher(url);
|
|
194
|
+
if (response.status === 404) return void 0;
|
|
195
|
+
if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
|
196
|
+
return Buffer.from(await response.arrayBuffer());
|
|
197
|
+
};
|
|
198
|
+
var getGitEnvironment = (token) => {
|
|
199
|
+
if (!token) return process.env;
|
|
200
|
+
const inheritedCount = Number.parseInt(process.env.GIT_CONFIG_COUNT ?? "0", 10);
|
|
201
|
+
const configIndex = Number.isInteger(inheritedCount) && inheritedCount >= 0 ? inheritedCount : 0;
|
|
202
|
+
return {
|
|
203
|
+
...process.env,
|
|
204
|
+
GIT_CONFIG_COUNT: String(configIndex + 1),
|
|
205
|
+
[`GIT_CONFIG_KEY_${configIndex}`]: "http.https://github.com/.extraheader",
|
|
206
|
+
[`GIT_CONFIG_VALUE_${configIndex}`]: `AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`
|
|
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]);
|
|
221
|
+
}
|
|
222
|
+
await git(["fetch", "--quiet", "--depth", "1", "origin", ref]);
|
|
223
|
+
await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
|
|
224
|
+
return await callback(directory);
|
|
225
|
+
} finally {
|
|
226
|
+
await import_promises3.default.rm(directory, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
|
|
230
|
+
const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
|
|
231
|
+
const commit = stdout.trim();
|
|
232
|
+
const catalogDirectory = import_node_path3.default.resolve(directory, source.path ?? ".");
|
|
233
|
+
const relativeCatalogDirectory = import_node_path3.default.relative(directory, catalogDirectory);
|
|
234
|
+
if (relativeCatalogDirectory.startsWith("..") || import_node_path3.default.isAbsolute(relativeCatalogDirectory)) {
|
|
235
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
236
|
+
}
|
|
237
|
+
const index = await (0, import_sdk.default)(catalogDirectory).buildIndex({ source: source.id, commit });
|
|
238
|
+
return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
|
|
239
|
+
});
|
|
240
|
+
var fetchPublishedIndex = async (source, ref, fetcher, token) => {
|
|
241
|
+
const indexPath = import_node_path3.default.posix.join(source.path ?? ".", "catalog.index.json");
|
|
242
|
+
const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
|
|
243
|
+
if (!bytes) return void 0;
|
|
244
|
+
const index = (0, import_sdk.parseIndex)(JSON.parse(bytes.toString("utf8")));
|
|
245
|
+
if (index.source !== source.id) {
|
|
246
|
+
throw new Error(`Published index source "${index.source}" does not match configured id "${source.id}"`);
|
|
247
|
+
}
|
|
248
|
+
return { bytes, index, commit: index.commit, generated: false };
|
|
249
|
+
};
|
|
250
|
+
var createGitHubSourceProvider = (options = {}) => {
|
|
251
|
+
const fetcher = options.fetch ?? ((url, init) => fetch(url, init));
|
|
252
|
+
const executeFile = options.execFile ?? ((file, args, execOptions) => execFileAsync(file, args, execOptions));
|
|
253
|
+
const configuredToken = options.token ?? process.env.EVENTCATALOG_GITHUB_TOKEN ?? process.env.GITHUB_TOKEN;
|
|
254
|
+
const token = configuredToken?.trim() || void 0;
|
|
255
|
+
const checkout = options.checkout ?? createCheckout(executeFile, token);
|
|
256
|
+
return {
|
|
257
|
+
async resolve(source) {
|
|
258
|
+
assertSafeCatalogPath(source);
|
|
259
|
+
const ref = source.ref ?? "main";
|
|
260
|
+
return await fetchPublishedIndex(source, ref, fetcher, token) ?? generateIndex(source, ref, checkout, executeFile);
|
|
261
|
+
},
|
|
262
|
+
async fetchContent({ source, commit, path: artifactPath }) {
|
|
263
|
+
assertSafeCatalogPath(source);
|
|
264
|
+
const catalogPath = import_node_path3.default.posix.join(source.path ?? ".", artifactPath);
|
|
265
|
+
const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
|
|
266
|
+
if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
|
|
267
|
+
return content;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/federation/public-assets.ts
|
|
273
|
+
var import_node_crypto2 = require("crypto");
|
|
274
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
275
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
276
|
+
var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
|
|
277
|
+
var getFileHash = async (filePath) => {
|
|
278
|
+
try {
|
|
279
|
+
const stat = await import_promises4.default.lstat(filePath);
|
|
280
|
+
return stat.isFile() ? getContentHash2(await import_promises4.default.readFile(filePath)) : void 0;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (error.code === "ENOENT") return void 0;
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var getSafePath = (directory, relativePath) => {
|
|
287
|
+
const normalizedPath = import_node_path4.default.posix.normalize(relativePath);
|
|
288
|
+
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("../");
|
|
289
|
+
if (unsafe) return void 0;
|
|
290
|
+
const resolvedDirectory = import_node_path4.default.resolve(directory);
|
|
291
|
+
const resolvedPath = import_node_path4.default.resolve(resolvedDirectory, relativePath);
|
|
292
|
+
const relativeResolvedPath = import_node_path4.default.relative(resolvedDirectory, resolvedPath);
|
|
293
|
+
if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relativeResolvedPath)) {
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
return resolvedPath;
|
|
297
|
+
};
|
|
298
|
+
var listFiles = async (directory, relativeDirectory = "") => {
|
|
299
|
+
let entries;
|
|
300
|
+
try {
|
|
301
|
+
entries = await import_promises4.default.readdir(import_node_path4.default.join(directory, relativeDirectory), { withFileTypes: true });
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error.code === "ENOENT") return [];
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
const files = await Promise.all(
|
|
307
|
+
entries.map(async (entry) => {
|
|
308
|
+
const relativePath = import_node_path4.default.join(relativeDirectory, entry.name);
|
|
309
|
+
if (entry.isDirectory()) return listFiles(directory, relativePath);
|
|
310
|
+
return entry.isFile() ? [relativePath.split(import_node_path4.default.sep).join("/")] : [];
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
return files.flat().sort();
|
|
314
|
+
};
|
|
315
|
+
var pathExists = async (filePath) => {
|
|
316
|
+
try {
|
|
317
|
+
await import_promises4.default.lstat(filePath);
|
|
318
|
+
return true;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.code === "ENOENT") return false;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var hasBlockingParent = async (publicDirectory, destinationPath) => {
|
|
325
|
+
let currentPath = import_node_path4.default.dirname(destinationPath);
|
|
326
|
+
while (currentPath !== publicDirectory) {
|
|
327
|
+
try {
|
|
328
|
+
if (!(await import_promises4.default.lstat(currentPath)).isDirectory()) return true;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error.code !== "ENOENT") throw error;
|
|
331
|
+
}
|
|
332
|
+
currentPath = import_node_path4.default.dirname(currentPath);
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
};
|
|
336
|
+
var pruneEmptyDirectories = async (publicDirectory, filePath) => {
|
|
337
|
+
let currentPath = import_node_path4.default.dirname(filePath);
|
|
338
|
+
while (currentPath !== publicDirectory) {
|
|
339
|
+
try {
|
|
340
|
+
await import_promises4.default.rmdir(currentPath);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
343
|
+
if (error.code === "ENOTEMPTY") return;
|
|
344
|
+
}
|
|
345
|
+
currentPath = import_node_path4.default.dirname(currentPath);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var composePublicAssets = async ({
|
|
349
|
+
projectDirectory,
|
|
350
|
+
federatedDirectory,
|
|
351
|
+
assets,
|
|
352
|
+
previousFiles = {},
|
|
353
|
+
collisionPaths = /* @__PURE__ */ new Set()
|
|
354
|
+
}) => {
|
|
355
|
+
const publicDirectory = import_node_path4.default.resolve(projectDirectory, "public");
|
|
356
|
+
const federatedPublicDirectory = import_node_path4.default.resolve(federatedDirectory, "public");
|
|
357
|
+
const sourceFiles = await listFiles(federatedPublicDirectory);
|
|
358
|
+
const sourceFileSet = new Set(sourceFiles);
|
|
359
|
+
const managedFiles = /* @__PURE__ */ new Set();
|
|
360
|
+
for (const [relativePath, previousFile] of Object.entries(previousFiles)) {
|
|
361
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
362
|
+
if (destinationPath && await getFileHash(destinationPath) === previousFile.hash) managedFiles.add(relativePath);
|
|
363
|
+
}
|
|
364
|
+
let removed = 0;
|
|
365
|
+
for (const relativePath of managedFiles) {
|
|
366
|
+
if (sourceFileSet.has(relativePath)) continue;
|
|
367
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
368
|
+
if (!destinationPath) continue;
|
|
369
|
+
await import_promises4.default.rm(destinationPath, { force: true });
|
|
370
|
+
await pruneEmptyDirectories(publicDirectory, destinationPath);
|
|
371
|
+
removed += 1;
|
|
372
|
+
}
|
|
373
|
+
const publicAssetsByPath = new Map(
|
|
374
|
+
assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => [asset.path.slice("public/".length), asset])
|
|
375
|
+
);
|
|
376
|
+
const files = {};
|
|
377
|
+
let copied = 0;
|
|
378
|
+
let skipped = 0;
|
|
379
|
+
let overwritten = 0;
|
|
380
|
+
for (const relativePath of sourceFiles) {
|
|
381
|
+
const sourcePath = getSafePath(federatedPublicDirectory, relativePath);
|
|
382
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
383
|
+
if (!sourcePath || !destinationPath) throw new Error(`Unsafe federated public asset path "${relativePath}"`);
|
|
384
|
+
const mainCatalogOwnsPath = await pathExists(destinationPath) && !managedFiles.has(relativePath) || await hasBlockingParent(publicDirectory, destinationPath);
|
|
385
|
+
if (mainCatalogOwnsPath) {
|
|
386
|
+
skipped += 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const asset = publicAssetsByPath.get(relativePath);
|
|
390
|
+
if (!asset) throw new Error(`Cannot identify the source of federated public asset "${relativePath}"`);
|
|
391
|
+
const content = await import_promises4.default.readFile(sourcePath);
|
|
392
|
+
await import_promises4.default.mkdir(import_node_path4.default.dirname(destinationPath), { recursive: true });
|
|
393
|
+
await import_promises4.default.writeFile(destinationPath, content);
|
|
394
|
+
files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
|
|
395
|
+
copied += 1;
|
|
396
|
+
if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
|
|
397
|
+
}
|
|
398
|
+
await import_promises4.default.rm(federatedPublicDirectory, { recursive: true, force: true });
|
|
399
|
+
return { files, copied, skipped, overwritten, removed };
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// src/federation/federate.ts
|
|
403
|
+
var FederationConflictError = class extends Error {
|
|
404
|
+
conflicts;
|
|
405
|
+
constructor(conflicts) {
|
|
406
|
+
const details = conflicts.map((conflict) => `${conflict.id}: ${conflict.sources.join(", ")}`).join("\n");
|
|
407
|
+
super(`Federation conflicts prevent hydration:
|
|
408
|
+
${details}`);
|
|
409
|
+
this.name = "FederationConflictError";
|
|
410
|
+
this.conflicts = conflicts;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var validateSources = (sources) => {
|
|
414
|
+
const ids = /* @__PURE__ */ new Set();
|
|
415
|
+
for (const source of sources) {
|
|
416
|
+
if (!source.id?.trim()) throw new Error("Every federation source requires a stable id.");
|
|
417
|
+
if (ids.has(source.id)) throw new Error(`Federation source id "${source.id}" is configured more than once.`);
|
|
418
|
+
ids.add(source.id);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
var writeLock = async (lockPath, lock) => {
|
|
422
|
+
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
423
|
+
try {
|
|
424
|
+
await import_promises5.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
|
|
425
|
+
`, "utf8");
|
|
426
|
+
await import_promises5.default.rename(temporaryPath, lockPath);
|
|
427
|
+
} finally {
|
|
428
|
+
await import_promises5.default.rm(temporaryPath, { force: true });
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
var readLock = async (lockPath) => {
|
|
432
|
+
try {
|
|
433
|
+
return JSON.parse(await import_promises5.default.readFile(lockPath, "utf8"));
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error.code === "ENOENT") return void 0;
|
|
436
|
+
throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
var pathExists2 = async (filePath) => {
|
|
440
|
+
try {
|
|
441
|
+
await import_promises5.default.access(filePath);
|
|
442
|
+
return true;
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (error.code === "ENOENT") return false;
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
449
|
+
const outDir = import_node_path5.default.join(projectDirectory, "federated");
|
|
450
|
+
const lockPath = import_node_path5.default.join(projectDirectory, "eventcatalog.lock");
|
|
451
|
+
const previousLock = await readLock(lockPath);
|
|
452
|
+
const hadFederatedOutput = await pathExists2(outDir);
|
|
453
|
+
const hadLock = previousLock !== void 0;
|
|
454
|
+
if (!hadFederatedOutput && !hadLock) return;
|
|
455
|
+
await import_promises5.default.rm(outDir, { recursive: true, force: true });
|
|
456
|
+
const publicResult = await composePublicAssets({
|
|
457
|
+
projectDirectory,
|
|
458
|
+
federatedDirectory: outDir,
|
|
459
|
+
assets: [],
|
|
460
|
+
previousFiles: previousLock?.publicFiles
|
|
461
|
+
});
|
|
462
|
+
await import_promises5.default.rm(lockPath, { force: true });
|
|
463
|
+
onProgress?.({
|
|
464
|
+
type: "cleanup:complete",
|
|
465
|
+
federated: hadFederatedOutput,
|
|
466
|
+
publicFiles: publicResult.removed,
|
|
467
|
+
lock: hadLock
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
var federateCatalog = async (projectDirectory, options = {}) => {
|
|
471
|
+
const config = await getEventCatalogConfigFile(projectDirectory);
|
|
472
|
+
const sources = config.federation?.sources ?? [];
|
|
473
|
+
options.onProgress?.({ type: "configured", sources: sources.length });
|
|
474
|
+
if (sources.length === 0) {
|
|
475
|
+
await cleanupPreviousFederation(projectDirectory, options.onProgress);
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
if (options.isFederationEnabled && !await options.isFederationEnabled()) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
"Cannot federate catalogs: EventCatalog federation is an Enterprise feature. Visit https://www.eventcatalog.dev/pricing to enable federation."
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
validateSources(sources);
|
|
484
|
+
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
485
|
+
const provider = options.provider ?? createGitHubSourceProvider();
|
|
486
|
+
const resolvedSources = [];
|
|
487
|
+
for (const [index, source] of sources.entries()) {
|
|
488
|
+
const current = index + 1;
|
|
489
|
+
options.onProgress?.({ type: "source:start", source, current, total: sources.length });
|
|
490
|
+
try {
|
|
491
|
+
const resolved = await provider.resolve(source);
|
|
492
|
+
resolvedSources.push({ config: source, resolved });
|
|
493
|
+
options.onProgress?.({
|
|
494
|
+
type: "source:complete",
|
|
495
|
+
source,
|
|
496
|
+
current,
|
|
497
|
+
total: sources.length,
|
|
498
|
+
commit: resolved.commit,
|
|
499
|
+
resources: resolved.index.resources.length,
|
|
500
|
+
generated: resolved.generated
|
|
501
|
+
});
|
|
502
|
+
} catch (error) {
|
|
503
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
504
|
+
throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const outDir = import_node_path5.default.join(projectDirectory, "federated");
|
|
508
|
+
const lockPath = import_node_path5.default.join(projectDirectory, "eventcatalog.lock");
|
|
509
|
+
const previousLock = await readLock(lockPath);
|
|
510
|
+
const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
|
|
511
|
+
const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
|
|
512
|
+
options.onProgress?.({ type: "local:start" });
|
|
513
|
+
const localIndex = await (0, import_sdk2.default)(projectDirectory).buildIndex({
|
|
514
|
+
source: config.cId,
|
|
515
|
+
commit: "local",
|
|
516
|
+
hashContent: false,
|
|
517
|
+
includeFederated: false
|
|
518
|
+
});
|
|
519
|
+
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
520
|
+
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
521
|
+
const ownershipGraph = (0, import_sdk2.resolve)([localIndex, ...remoteIndexes]);
|
|
522
|
+
if (ownershipGraph.conflicts.length > 0) {
|
|
523
|
+
options.onProgress?.({ type: "resolved", graph: ownershipGraph });
|
|
524
|
+
throw new FederationConflictError(ownershipGraph.conflicts);
|
|
525
|
+
}
|
|
526
|
+
const graph = (0, import_sdk2.resolve)(remoteIndexes);
|
|
527
|
+
options.onProgress?.({ type: "resolved", graph });
|
|
528
|
+
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
529
|
+
options.onProgress?.({ type: "hydrating", outDir });
|
|
530
|
+
let hydratedFiles = 0;
|
|
531
|
+
let cachedFiles = 0;
|
|
532
|
+
const hydrateResult = await (0, import_sdk2.hydrate)(graph, {
|
|
533
|
+
outDir,
|
|
534
|
+
cache: createFederationContentCache(projectDirectory, {
|
|
535
|
+
read: options.useCache !== false,
|
|
536
|
+
onHit: () => {
|
|
537
|
+
cachedFiles += 1;
|
|
538
|
+
options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
|
|
539
|
+
}
|
|
540
|
+
}),
|
|
541
|
+
modes: Object.fromEntries(sources.map((source) => [source.id, source.mode ?? "hydrate"])),
|
|
542
|
+
fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
|
|
543
|
+
const source = sourcesById.get(sourceId);
|
|
544
|
+
if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
|
|
545
|
+
const content = await provider.fetchContent({ source, commit, path: artifactPath });
|
|
546
|
+
hydratedFiles += 1;
|
|
547
|
+
options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
|
|
548
|
+
return content;
|
|
549
|
+
}
|
|
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
|
+
});
|
|
572
|
+
const result = {
|
|
573
|
+
sources: sources.length,
|
|
574
|
+
resources,
|
|
575
|
+
graph,
|
|
576
|
+
hydrate: hydrateResult,
|
|
577
|
+
public: publicResult,
|
|
578
|
+
outDir,
|
|
579
|
+
lockPath
|
|
580
|
+
};
|
|
581
|
+
options.onProgress?.({ type: "complete", result });
|
|
582
|
+
return result;
|
|
583
|
+
};
|
|
584
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
585
|
+
0 && (module.exports = {
|
|
586
|
+
FederationConflictError,
|
|
587
|
+
federateCatalog
|
|
588
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ResolvedGraph, HydrateResult, Conflict } from '@eventcatalog/sdk';
|
|
2
|
+
import { FederationSourceConfig } from '../eventcatalog.config.cjs';
|
|
3
|
+
import { PublicAssetCompositionResult } from './public-assets.cjs';
|
|
4
|
+
import { FederationSourceProvider } from './types.cjs';
|
|
5
|
+
|
|
6
|
+
type FederationProgressEvent = {
|
|
7
|
+
type: 'configured';
|
|
8
|
+
sources: number;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'cleanup:complete';
|
|
11
|
+
federated: boolean;
|
|
12
|
+
publicFiles: number;
|
|
13
|
+
lock: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'cache:disabled';
|
|
16
|
+
} | {
|
|
17
|
+
type: 'source:start';
|
|
18
|
+
source: FederationSourceConfig;
|
|
19
|
+
current: number;
|
|
20
|
+
total: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'source:complete';
|
|
23
|
+
source: FederationSourceConfig;
|
|
24
|
+
current: number;
|
|
25
|
+
total: number;
|
|
26
|
+
commit: string;
|
|
27
|
+
resources: number;
|
|
28
|
+
generated: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'local:start';
|
|
31
|
+
} | {
|
|
32
|
+
type: 'local:complete';
|
|
33
|
+
resources: number;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'resolving';
|
|
36
|
+
resources: number;
|
|
37
|
+
localResources: number;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'resolved';
|
|
40
|
+
graph: ResolvedGraph;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'hydrating';
|
|
43
|
+
outDir: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'hydrate:cache';
|
|
46
|
+
files: number;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'hydrate:file';
|
|
49
|
+
files: number;
|
|
50
|
+
source: string;
|
|
51
|
+
path: string;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'public:complete';
|
|
54
|
+
result: PublicAssetCompositionResult;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'complete';
|
|
57
|
+
result: FederateCatalogResult;
|
|
58
|
+
};
|
|
59
|
+
type FederateCatalogResult = {
|
|
60
|
+
sources: number;
|
|
61
|
+
resources: number;
|
|
62
|
+
graph: ResolvedGraph;
|
|
63
|
+
hydrate: HydrateResult;
|
|
64
|
+
public: PublicAssetCompositionResult;
|
|
65
|
+
outDir: string;
|
|
66
|
+
lockPath: string;
|
|
67
|
+
};
|
|
68
|
+
type FederateCatalogOptions = {
|
|
69
|
+
provider?: FederationSourceProvider;
|
|
70
|
+
onProgress?: (event: FederationProgressEvent) => void;
|
|
71
|
+
now?: () => Date;
|
|
72
|
+
useCache?: boolean;
|
|
73
|
+
isFederationEnabled?: () => Promise<boolean>;
|
|
74
|
+
};
|
|
75
|
+
declare class FederationConflictError extends Error {
|
|
76
|
+
conflicts: Conflict[];
|
|
77
|
+
constructor(conflicts: Conflict[]);
|
|
78
|
+
}
|
|
79
|
+
declare const federateCatalog: (projectDirectory: string, options?: FederateCatalogOptions) => Promise<FederateCatalogResult | null>;
|
|
80
|
+
|
|
81
|
+
export { type FederateCatalogResult, FederationConflictError, type FederationProgressEvent, federateCatalog };
|