@eventcatalog/core 4.7.3 → 4.7.4
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-XXJX3WWF.js → chunk-7QTCSRAC.js} +1 -1
- package/dist/chunk-A2RZR3U4.js +29 -0
- package/dist/{chunk-SMDTRRED.js → chunk-FDXJIZ74.js} +1 -1
- package/dist/{chunk-ZR6AH5Z2.js → chunk-RIUHZZRA.js} +4 -4
- package/dist/chunk-SDZQJTJW.js +90 -0
- package/dist/{chunk-V4FAVGI7.js → chunk-TLWM7WRZ.js} +1 -1
- package/dist/{chunk-GINIJSFI.js → chunk-ZIABX6SP.js} +1 -1
- package/dist/{chunk-IALEXWSE.js → chunk-ZXVQXBTT.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +315 -209
- package/dist/eventcatalog.config.d.cts +3 -3
- package/dist/eventcatalog.config.d.ts +3 -3
- package/dist/eventcatalog.js +11 -9
- package/dist/federation/federate.cjs +269 -163
- package/dist/federation/federate.js +3 -1
- 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/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 +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 +3 -3
|
@@ -79,11 +79,11 @@ type GeneratorConfig = string | Record<string, unknown> | [string, Record<string
|
|
|
79
79
|
type FederationSourceConfig = {
|
|
80
80
|
/** Stable source id used by the lockfile and federation graph. */
|
|
81
81
|
id: string;
|
|
82
|
-
/** Source locator, for example github:acme/payments. */
|
|
82
|
+
/** Source locator, for example github:acme/payments or file:../payments. */
|
|
83
83
|
source: string;
|
|
84
|
-
/** Directory containing the source catalog. @default '.' */
|
|
84
|
+
/** Directory containing the source catalog, relative to the GitHub repository or filesystem source. @default '.' */
|
|
85
85
|
path?: string;
|
|
86
|
-
/** Branch or tag to federate. @default 'main' */
|
|
86
|
+
/** Branch or tag to federate from a GitHub source. Not supported for filesystem sources. @default 'main' */
|
|
87
87
|
ref?: string;
|
|
88
88
|
/** Materialize source files or retain graph references only. @default 'hydrate' */
|
|
89
89
|
mode?: 'hydrate' | 'reference';
|
|
@@ -79,11 +79,11 @@ type GeneratorConfig = string | Record<string, unknown> | [string, Record<string
|
|
|
79
79
|
type FederationSourceConfig = {
|
|
80
80
|
/** Stable source id used by the lockfile and federation graph. */
|
|
81
81
|
id: string;
|
|
82
|
-
/** Source locator, for example github:acme/payments. */
|
|
82
|
+
/** Source locator, for example github:acme/payments or file:../payments. */
|
|
83
83
|
source: string;
|
|
84
|
-
/** Directory containing the source catalog. @default '.' */
|
|
84
|
+
/** Directory containing the source catalog, relative to the GitHub repository or filesystem source. @default '.' */
|
|
85
85
|
path?: string;
|
|
86
|
-
/** Branch or tag to federate. @default 'main' */
|
|
86
|
+
/** Branch or tag to federate from a GitHub source. Not supported for filesystem sources. @default 'main' */
|
|
87
87
|
ref?: string;
|
|
88
88
|
/** Materialize source files or retain graph references only. @default 'hydrate' */
|
|
89
89
|
mode?: 'hydrate' | 'reference';
|
package/dist/eventcatalog.js
CHANGED
|
@@ -2,18 +2,20 @@ import {
|
|
|
2
2
|
runMigrations
|
|
3
3
|
} from "./chunk-XUAF2H54.js";
|
|
4
4
|
import "./chunk-CA4U2JP7.js";
|
|
5
|
+
import {
|
|
6
|
+
log_build_default
|
|
7
|
+
} from "./chunk-ZIABX6SP.js";
|
|
8
|
+
import "./chunk-FDXJIZ74.js";
|
|
9
|
+
import "./chunk-K2XIENVT.js";
|
|
5
10
|
import {
|
|
6
11
|
FederationConflictError,
|
|
7
12
|
federateCatalog
|
|
8
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RIUHZZRA.js";
|
|
14
|
+
import "./chunk-A2RZR3U4.js";
|
|
9
15
|
import "./chunk-R7Z5ALYI.js";
|
|
16
|
+
import "./chunk-SDZQJTJW.js";
|
|
10
17
|
import "./chunk-352FGP6W.js";
|
|
11
18
|
import "./chunk-5EPNFDT5.js";
|
|
12
|
-
import {
|
|
13
|
-
log_build_default
|
|
14
|
-
} from "./chunk-GINIJSFI.js";
|
|
15
|
-
import "./chunk-SMDTRRED.js";
|
|
16
|
-
import "./chunk-K2XIENVT.js";
|
|
17
19
|
import {
|
|
18
20
|
getProjectOutDir,
|
|
19
21
|
isAuthEnabled,
|
|
@@ -22,10 +24,10 @@ import {
|
|
|
22
24
|
} from "./chunk-B7HCX5HM.js";
|
|
23
25
|
import {
|
|
24
26
|
generate
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-7QTCSRAC.js";
|
|
26
28
|
import {
|
|
27
29
|
logger
|
|
28
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-ZXVQXBTT.js";
|
|
29
31
|
import {
|
|
30
32
|
resolve_catalog_dependencies_default
|
|
31
33
|
} from "./chunk-LHR4G2UO.js";
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
import "./chunk-W5JQON7Z.js";
|
|
46
48
|
import {
|
|
47
49
|
VERSION
|
|
48
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-TLWM7WRZ.js";
|
|
49
51
|
import {
|
|
50
52
|
linkCoreNodeModules,
|
|
51
53
|
resolveInstalledCoreNodeModules
|
|
@@ -34,10 +34,10 @@ __export(federate_exports, {
|
|
|
34
34
|
federateCatalog: () => federateCatalog
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(federate_exports);
|
|
37
|
-
var
|
|
38
|
-
var
|
|
39
|
-
var
|
|
40
|
-
var
|
|
37
|
+
var import_node_crypto4 = require("crypto");
|
|
38
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
39
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
40
|
+
var import_sdk3 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
41
41
|
|
|
42
42
|
// src/eventcatalog-config-file-utils.js
|
|
43
43
|
var import_promises = require("fs/promises");
|
|
@@ -149,13 +149,230 @@ var createFederationContentCache = (projectDirectory, options = {}) => {
|
|
|
149
149
|
};
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
+
// src/federation/public-assets.ts
|
|
153
|
+
var import_node_crypto2 = require("crypto");
|
|
154
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
155
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
156
|
+
var getContentHash2 = (content) => `sha256:${(0, import_node_crypto2.createHash)("sha256").update(content).digest("hex")}`;
|
|
157
|
+
var getFileHash = async (filePath) => {
|
|
158
|
+
try {
|
|
159
|
+
const stat = await import_promises3.default.lstat(filePath);
|
|
160
|
+
return stat.isFile() ? getContentHash2(await import_promises3.default.readFile(filePath)) : void 0;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error.code === "ENOENT") return void 0;
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var getSafePath = (directory, relativePath) => {
|
|
167
|
+
const normalizedPath = import_node_path3.default.posix.normalize(relativePath);
|
|
168
|
+
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("../");
|
|
169
|
+
if (unsafe) return void 0;
|
|
170
|
+
const resolvedDirectory = import_node_path3.default.resolve(directory);
|
|
171
|
+
const resolvedPath = import_node_path3.default.resolve(resolvedDirectory, relativePath);
|
|
172
|
+
const relativeResolvedPath = import_node_path3.default.relative(resolvedDirectory, resolvedPath);
|
|
173
|
+
if (relativeResolvedPath === "" || relativeResolvedPath === ".." || relativeResolvedPath.startsWith(`..${import_node_path3.default.sep}`) || import_node_path3.default.isAbsolute(relativeResolvedPath)) {
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
return resolvedPath;
|
|
177
|
+
};
|
|
178
|
+
var listFiles = async (directory, relativeDirectory = "") => {
|
|
179
|
+
let entries;
|
|
180
|
+
try {
|
|
181
|
+
entries = await import_promises3.default.readdir(import_node_path3.default.join(directory, relativeDirectory), { withFileTypes: true });
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (error.code === "ENOENT") return [];
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
const files = await Promise.all(
|
|
187
|
+
entries.map(async (entry) => {
|
|
188
|
+
const relativePath = import_node_path3.default.join(relativeDirectory, entry.name);
|
|
189
|
+
if (entry.isDirectory()) return listFiles(directory, relativePath);
|
|
190
|
+
return entry.isFile() ? [relativePath.split(import_node_path3.default.sep).join("/")] : [];
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
return files.flat().sort();
|
|
194
|
+
};
|
|
195
|
+
var pathExists = async (filePath) => {
|
|
196
|
+
try {
|
|
197
|
+
await import_promises3.default.lstat(filePath);
|
|
198
|
+
return true;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error.code === "ENOENT") return false;
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var hasBlockingParent = async (publicDirectory, destinationPath) => {
|
|
205
|
+
let currentPath = import_node_path3.default.dirname(destinationPath);
|
|
206
|
+
while (currentPath !== publicDirectory) {
|
|
207
|
+
try {
|
|
208
|
+
if (!(await import_promises3.default.lstat(currentPath)).isDirectory()) return true;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error.code !== "ENOENT") throw error;
|
|
211
|
+
}
|
|
212
|
+
currentPath = import_node_path3.default.dirname(currentPath);
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
};
|
|
216
|
+
var pruneEmptyDirectories = async (publicDirectory, filePath) => {
|
|
217
|
+
let currentPath = import_node_path3.default.dirname(filePath);
|
|
218
|
+
while (currentPath !== publicDirectory) {
|
|
219
|
+
try {
|
|
220
|
+
await import_promises3.default.rmdir(currentPath);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
223
|
+
if (error.code === "ENOTEMPTY") return;
|
|
224
|
+
}
|
|
225
|
+
currentPath = import_node_path3.default.dirname(currentPath);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var composePublicAssets = async ({
|
|
229
|
+
projectDirectory,
|
|
230
|
+
federatedDirectory,
|
|
231
|
+
assets,
|
|
232
|
+
previousFiles = {},
|
|
233
|
+
collisionPaths = /* @__PURE__ */ new Set()
|
|
234
|
+
}) => {
|
|
235
|
+
const publicDirectory = import_node_path3.default.resolve(projectDirectory, "public");
|
|
236
|
+
const federatedPublicDirectory = import_node_path3.default.resolve(federatedDirectory, "public");
|
|
237
|
+
const sourceFiles = await listFiles(federatedPublicDirectory);
|
|
238
|
+
const sourceFileSet = new Set(sourceFiles);
|
|
239
|
+
const managedFiles = /* @__PURE__ */ new Set();
|
|
240
|
+
for (const [relativePath, previousFile] of Object.entries(previousFiles)) {
|
|
241
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
242
|
+
if (destinationPath && await getFileHash(destinationPath) === previousFile.hash) managedFiles.add(relativePath);
|
|
243
|
+
}
|
|
244
|
+
let removed = 0;
|
|
245
|
+
for (const relativePath of managedFiles) {
|
|
246
|
+
if (sourceFileSet.has(relativePath)) continue;
|
|
247
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
248
|
+
if (!destinationPath) continue;
|
|
249
|
+
await import_promises3.default.rm(destinationPath, { force: true });
|
|
250
|
+
await pruneEmptyDirectories(publicDirectory, destinationPath);
|
|
251
|
+
removed += 1;
|
|
252
|
+
}
|
|
253
|
+
const publicAssetsByPath = new Map(
|
|
254
|
+
assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => [asset.path.slice("public/".length), asset])
|
|
255
|
+
);
|
|
256
|
+
const files = {};
|
|
257
|
+
let copied = 0;
|
|
258
|
+
let skipped = 0;
|
|
259
|
+
let overwritten = 0;
|
|
260
|
+
for (const relativePath of sourceFiles) {
|
|
261
|
+
const sourcePath = getSafePath(federatedPublicDirectory, relativePath);
|
|
262
|
+
const destinationPath = getSafePath(publicDirectory, relativePath);
|
|
263
|
+
if (!sourcePath || !destinationPath) throw new Error(`Unsafe federated public asset path "${relativePath}"`);
|
|
264
|
+
const mainCatalogOwnsPath = await pathExists(destinationPath) && !managedFiles.has(relativePath) || await hasBlockingParent(publicDirectory, destinationPath);
|
|
265
|
+
if (mainCatalogOwnsPath) {
|
|
266
|
+
skipped += 1;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const asset = publicAssetsByPath.get(relativePath);
|
|
270
|
+
if (!asset) throw new Error(`Cannot identify the source of federated public asset "${relativePath}"`);
|
|
271
|
+
const content = await import_promises3.default.readFile(sourcePath);
|
|
272
|
+
await import_promises3.default.mkdir(import_node_path3.default.dirname(destinationPath), { recursive: true });
|
|
273
|
+
await import_promises3.default.writeFile(destinationPath, content);
|
|
274
|
+
files[relativePath] = { source: asset.resolvedFrom.source, hash: getContentHash2(content) };
|
|
275
|
+
copied += 1;
|
|
276
|
+
if (collisionPaths.has(`public/${relativePath}`)) overwritten += 1;
|
|
277
|
+
}
|
|
278
|
+
await import_promises3.default.rm(federatedPublicDirectory, { recursive: true, force: true });
|
|
279
|
+
return { files, copied, skipped, overwritten, removed };
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/federation/filesystem-source-provider.ts
|
|
283
|
+
var import_node_crypto3 = require("crypto");
|
|
284
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
285
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
286
|
+
var import_sdk = __toESM(require("@eventcatalog/sdk"), 1);
|
|
287
|
+
var FILESYSTEM_SOURCE_PREFIX = "file:";
|
|
288
|
+
var isWithinDirectory = (directory, target) => {
|
|
289
|
+
const relativePath = import_node_path4.default.relative(directory, target);
|
|
290
|
+
return relativePath === "" || !relativePath.startsWith(`..${import_node_path4.default.sep}`) && relativePath !== ".." && !import_node_path4.default.isAbsolute(relativePath);
|
|
291
|
+
};
|
|
292
|
+
var assertPortableRelativePath = (filePath, label, allowRoot = true) => {
|
|
293
|
+
const portablePath = filePath.replaceAll("\\", "/");
|
|
294
|
+
const normalizedPath = import_node_path4.default.posix.normalize(portablePath);
|
|
295
|
+
const isUnsafe = filePath.includes("\\") || filePath.includes("\0") || import_node_path4.default.posix.isAbsolute(normalizedPath) || /^[a-zA-Z]:\//.test(normalizedPath) || normalizedPath === ".." || normalizedPath.startsWith("../") || !allowRoot && (normalizedPath === "." || normalizedPath === "");
|
|
296
|
+
if (isUnsafe) throw new Error(`${label} "${filePath}" escapes its filesystem source`);
|
|
297
|
+
return normalizedPath;
|
|
298
|
+
};
|
|
299
|
+
var getSourceRoot = (projectDirectory, source) => {
|
|
300
|
+
if (!source.source.startsWith(FILESYSTEM_SOURCE_PREFIX)) {
|
|
301
|
+
throw new Error(`Unsupported federation source "${source.source}". Expected file:path/to/catalog.`);
|
|
302
|
+
}
|
|
303
|
+
const locator = source.source.slice(FILESYSTEM_SOURCE_PREFIX.length);
|
|
304
|
+
if (!locator.trim()) throw new Error(`Filesystem federation source "${source.id}" requires a path after "file:".`);
|
|
305
|
+
return import_node_path4.default.resolve(projectDirectory, locator);
|
|
306
|
+
};
|
|
307
|
+
var getCatalogDirectory = async (projectDirectory, source) => {
|
|
308
|
+
if (source.ref) throw new Error(`Filesystem federation source "${source.id}" does not support "ref".`);
|
|
309
|
+
const sourceRoot = getSourceRoot(projectDirectory, source);
|
|
310
|
+
const catalogPath = assertPortableRelativePath(source.path ?? ".", "Catalog path");
|
|
311
|
+
const catalogDirectory = import_node_path4.default.resolve(sourceRoot, ...catalogPath.split("/"));
|
|
312
|
+
if (!isWithinDirectory(sourceRoot, catalogDirectory)) {
|
|
313
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const [realSourceRoot, realCatalogDirectory] = await Promise.all([import_promises4.default.realpath(sourceRoot), import_promises4.default.realpath(catalogDirectory)]);
|
|
317
|
+
if (!isWithinDirectory(realSourceRoot, realCatalogDirectory)) {
|
|
318
|
+
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
319
|
+
}
|
|
320
|
+
if (!(await import_promises4.default.stat(realCatalogDirectory)).isDirectory()) {
|
|
321
|
+
throw new Error(`Filesystem federation source "${source.id}" is not a directory: ${catalogDirectory}`);
|
|
322
|
+
}
|
|
323
|
+
return realCatalogDirectory;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (error.code === "ENOENT") {
|
|
326
|
+
throw new Error(`Filesystem federation source "${source.id}" does not exist: ${catalogDirectory}`, { cause: error });
|
|
327
|
+
}
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
var getArtifactPath = async (catalogDirectory, source, artifactPath) => {
|
|
332
|
+
const normalizedPath = assertPortableRelativePath(artifactPath, "Federated artifact path", false);
|
|
333
|
+
const filePath = import_node_path4.default.resolve(catalogDirectory, ...normalizedPath.split("/"));
|
|
334
|
+
if (!isWithinDirectory(catalogDirectory, filePath)) {
|
|
335
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const realFilePath = await import_promises4.default.realpath(filePath);
|
|
339
|
+
if (!isWithinDirectory(catalogDirectory, realFilePath)) {
|
|
340
|
+
throw new Error(`Federated artifact path "${artifactPath}" escapes source "${source.id}"`);
|
|
341
|
+
}
|
|
342
|
+
return realFilePath;
|
|
343
|
+
} catch (error) {
|
|
344
|
+
if (error.code === "ENOENT") {
|
|
345
|
+
throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`, { cause: error });
|
|
346
|
+
}
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var createFileSystemSourceProvider = (projectDirectory) => ({
|
|
351
|
+
async resolve(source) {
|
|
352
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
353
|
+
const localIndex = await (0, import_sdk.default)(catalogDirectory).buildIndex({
|
|
354
|
+
source: source.id,
|
|
355
|
+
commit: "local",
|
|
356
|
+
includeFederated: false
|
|
357
|
+
});
|
|
358
|
+
const snapshot = (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(localIndex)).digest("hex").slice(0, 12);
|
|
359
|
+
const index = { ...localIndex, commit: `local:${snapshot}` };
|
|
360
|
+
const bytes = Buffer.from(JSON.stringify(index));
|
|
361
|
+
return { bytes, index, commit: index.commit, generated: true };
|
|
362
|
+
},
|
|
363
|
+
async fetchContent({ source, path: artifactPath }) {
|
|
364
|
+
const catalogDirectory = await getCatalogDirectory(projectDirectory, source);
|
|
365
|
+
return import_promises4.default.readFile(await getArtifactPath(catalogDirectory, source, artifactPath));
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
152
369
|
// src/federation/github-source-provider.ts
|
|
153
370
|
var import_node_child_process = require("child_process");
|
|
154
|
-
var
|
|
371
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
155
372
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
156
|
-
var
|
|
373
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
157
374
|
var import_node_util = require("util");
|
|
158
|
-
var
|
|
375
|
+
var import_sdk2 = __toESM(require("@eventcatalog/sdk"), 1);
|
|
159
376
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
160
377
|
var parseGitHubSource = (source) => {
|
|
161
378
|
const match = /^github:([^/]+)\/(.+)$/.exec(source.source);
|
|
@@ -164,8 +381,8 @@ var parseGitHubSource = (source) => {
|
|
|
164
381
|
};
|
|
165
382
|
var assertSafeCatalogPath = (source) => {
|
|
166
383
|
const catalogPath = source.path ?? ".";
|
|
167
|
-
const normalized =
|
|
168
|
-
if (catalogPath.includes("\\") ||
|
|
384
|
+
const normalized = import_node_path5.default.posix.normalize(catalogPath.replaceAll("\\", "/"));
|
|
385
|
+
if (catalogPath.includes("\\") || import_node_path5.default.posix.isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
169
386
|
throw new Error(`Catalog path "${catalogPath}" escapes source "${source.id}"`);
|
|
170
387
|
}
|
|
171
388
|
};
|
|
@@ -208,8 +425,8 @@ var getGitEnvironment = (token) => {
|
|
|
208
425
|
};
|
|
209
426
|
var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
210
427
|
const { owner, repository } = parseGitHubSource(source);
|
|
211
|
-
const catalogPath =
|
|
212
|
-
const directory = await
|
|
428
|
+
const catalogPath = import_node_path5.default.posix.normalize(source.path ?? ".");
|
|
429
|
+
const directory = await import_promises5.default.mkdtemp(import_node_path5.default.join(import_node_os2.default.tmpdir(), "eventcatalog-federation-"));
|
|
213
430
|
const env = getGitEnvironment(token);
|
|
214
431
|
try {
|
|
215
432
|
const git = (args) => executeFile("git", args, { cwd: directory, env, encoding: "utf8" });
|
|
@@ -223,25 +440,25 @@ var createCheckout = (executeFile, token) => async (source, ref, callback) => {
|
|
|
223
440
|
await git(["checkout", "--quiet", "--detach", "FETCH_HEAD"]);
|
|
224
441
|
return await callback(directory);
|
|
225
442
|
} finally {
|
|
226
|
-
await
|
|
443
|
+
await import_promises5.default.rm(directory, { recursive: true, force: true });
|
|
227
444
|
}
|
|
228
445
|
};
|
|
229
446
|
var generateIndex = async (source, ref, checkout, executeFile) => checkout(source, ref, async (directory) => {
|
|
230
447
|
const { stdout } = await executeFile("git", ["rev-parse", "HEAD"], { cwd: directory, encoding: "utf8" });
|
|
231
448
|
const commit = stdout.trim();
|
|
232
|
-
const catalogDirectory =
|
|
233
|
-
const relativeCatalogDirectory =
|
|
234
|
-
if (relativeCatalogDirectory.startsWith("..") ||
|
|
449
|
+
const catalogDirectory = import_node_path5.default.resolve(directory, source.path ?? ".");
|
|
450
|
+
const relativeCatalogDirectory = import_node_path5.default.relative(directory, catalogDirectory);
|
|
451
|
+
if (relativeCatalogDirectory.startsWith("..") || import_node_path5.default.isAbsolute(relativeCatalogDirectory)) {
|
|
235
452
|
throw new Error(`Catalog path "${source.path}" escapes source "${source.id}"`);
|
|
236
453
|
}
|
|
237
|
-
const index = await (0,
|
|
454
|
+
const index = await (0, import_sdk2.default)(catalogDirectory).buildIndex({ source: source.id, commit });
|
|
238
455
|
return { bytes: Buffer.from(JSON.stringify(index)), index, commit, generated: true };
|
|
239
456
|
});
|
|
240
457
|
var fetchPublishedIndex = async (source, ref, fetcher, token) => {
|
|
241
|
-
const indexPath =
|
|
458
|
+
const indexPath = import_node_path5.default.posix.join(source.path ?? ".", "catalog.index.json");
|
|
242
459
|
const bytes = await fetchBytes(source, ref, indexPath, fetcher, token);
|
|
243
460
|
if (!bytes) return void 0;
|
|
244
|
-
const index = (0,
|
|
461
|
+
const index = (0, import_sdk2.parseIndex)(JSON.parse(bytes.toString("utf8")));
|
|
245
462
|
if (index.source !== source.id) {
|
|
246
463
|
throw new Error(`Published index source "${index.source}" does not match configured id "${source.id}"`);
|
|
247
464
|
}
|
|
@@ -261,7 +478,7 @@ var createGitHubSourceProvider = (options = {}) => {
|
|
|
261
478
|
},
|
|
262
479
|
async fetchContent({ source, commit, path: artifactPath }) {
|
|
263
480
|
assertSafeCatalogPath(source);
|
|
264
|
-
const catalogPath =
|
|
481
|
+
const catalogPath = import_node_path5.default.posix.join(source.path ?? ".", artifactPath);
|
|
265
482
|
const content = await fetchBytes(source, commit, catalogPath, fetcher, token);
|
|
266
483
|
if (!content) throw new Error(`Federated artifact not found for "${source.id}": ${artifactPath}`);
|
|
267
484
|
return content;
|
|
@@ -269,134 +486,23 @@ var createGitHubSourceProvider = (options = {}) => {
|
|
|
269
486
|
};
|
|
270
487
|
};
|
|
271
488
|
|
|
272
|
-
// src/federation/
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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;
|
|
489
|
+
// src/federation/source-provider.ts
|
|
490
|
+
var createFederationSourceProvider = (projectDirectory, providers = {}) => {
|
|
491
|
+
const github = providers.github ?? createGitHubSourceProvider();
|
|
492
|
+
const filesystem = providers.filesystem ?? createFileSystemSourceProvider(projectDirectory);
|
|
493
|
+
const getProvider = (source) => {
|
|
494
|
+
if (source.source.startsWith("github:")) return github;
|
|
495
|
+
if (source.source.startsWith("file:")) return filesystem;
|
|
496
|
+
throw new Error(`Unsupported federation source "${source.source}" for "${source.id}". Supported protocols: github:, file:.`);
|
|
497
|
+
};
|
|
498
|
+
return {
|
|
499
|
+
async resolve(source) {
|
|
500
|
+
return getProvider(source).resolve(source);
|
|
501
|
+
},
|
|
502
|
+
async fetchContent(request) {
|
|
503
|
+
return getProvider(request.source).fetchContent(request);
|
|
388
504
|
}
|
|
389
|
-
|
|
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 };
|
|
505
|
+
};
|
|
400
506
|
};
|
|
401
507
|
|
|
402
508
|
// src/federation/federate.ts
|
|
@@ -421,16 +527,16 @@ var validateSources = (sources) => {
|
|
|
421
527
|
var writeLock = async (lockPath, lock) => {
|
|
422
528
|
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
423
529
|
try {
|
|
424
|
-
await
|
|
530
|
+
await import_promises6.default.writeFile(temporaryPath, `${JSON.stringify(lock, null, 2)}
|
|
425
531
|
`, "utf8");
|
|
426
|
-
await
|
|
532
|
+
await import_promises6.default.rename(temporaryPath, lockPath);
|
|
427
533
|
} finally {
|
|
428
|
-
await
|
|
534
|
+
await import_promises6.default.rm(temporaryPath, { force: true });
|
|
429
535
|
}
|
|
430
536
|
};
|
|
431
537
|
var readLock = async (lockPath) => {
|
|
432
538
|
try {
|
|
433
|
-
return JSON.parse(await
|
|
539
|
+
return JSON.parse(await import_promises6.default.readFile(lockPath, "utf8"));
|
|
434
540
|
} catch (error) {
|
|
435
541
|
if (error.code === "ENOENT") return void 0;
|
|
436
542
|
throw new Error(`Cannot read federation lock at "${lockPath}"`, { cause: error });
|
|
@@ -438,7 +544,7 @@ var readLock = async (lockPath) => {
|
|
|
438
544
|
};
|
|
439
545
|
var pathExists2 = async (filePath) => {
|
|
440
546
|
try {
|
|
441
|
-
await
|
|
547
|
+
await import_promises6.default.access(filePath);
|
|
442
548
|
return true;
|
|
443
549
|
} catch (error) {
|
|
444
550
|
if (error.code === "ENOENT") return false;
|
|
@@ -446,20 +552,20 @@ var pathExists2 = async (filePath) => {
|
|
|
446
552
|
}
|
|
447
553
|
};
|
|
448
554
|
var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
449
|
-
const outDir =
|
|
450
|
-
const lockPath =
|
|
555
|
+
const outDir = import_node_path6.default.join(projectDirectory, "federated");
|
|
556
|
+
const lockPath = import_node_path6.default.join(projectDirectory, "eventcatalog.lock");
|
|
451
557
|
const previousLock = await readLock(lockPath);
|
|
452
558
|
const hadFederatedOutput = await pathExists2(outDir);
|
|
453
559
|
const hadLock = previousLock !== void 0;
|
|
454
560
|
if (!hadFederatedOutput && !hadLock) return;
|
|
455
|
-
await
|
|
561
|
+
await import_promises6.default.rm(outDir, { recursive: true, force: true });
|
|
456
562
|
const publicResult = await composePublicAssets({
|
|
457
563
|
projectDirectory,
|
|
458
564
|
federatedDirectory: outDir,
|
|
459
565
|
assets: [],
|
|
460
566
|
previousFiles: previousLock?.publicFiles
|
|
461
567
|
});
|
|
462
|
-
await
|
|
568
|
+
await import_promises6.default.rm(lockPath, { force: true });
|
|
463
569
|
onProgress?.({
|
|
464
570
|
type: "cleanup:complete",
|
|
465
571
|
federated: hadFederatedOutput,
|
|
@@ -482,7 +588,7 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
482
588
|
}
|
|
483
589
|
validateSources(sources);
|
|
484
590
|
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
485
|
-
const provider = options.provider ??
|
|
591
|
+
const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
|
|
486
592
|
const resolvedSources = [];
|
|
487
593
|
for (const [index, source] of sources.entries()) {
|
|
488
594
|
const current = index + 1;
|
|
@@ -504,13 +610,13 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
504
610
|
throw new Error(`Failed to federate source "${source.id}": ${message}`, { cause: error });
|
|
505
611
|
}
|
|
506
612
|
}
|
|
507
|
-
const outDir =
|
|
508
|
-
const lockPath =
|
|
613
|
+
const outDir = import_node_path6.default.join(projectDirectory, "federated");
|
|
614
|
+
const lockPath = import_node_path6.default.join(projectDirectory, "eventcatalog.lock");
|
|
509
615
|
const previousLock = await readLock(lockPath);
|
|
510
616
|
const resources = resolvedSources.reduce((total, source) => total + source.resolved.index.resources.length, 0);
|
|
511
617
|
const remoteIndexes = resolvedSources.map(({ resolved }) => resolved.index);
|
|
512
618
|
options.onProgress?.({ type: "local:start" });
|
|
513
|
-
const localIndex = await (0,
|
|
619
|
+
const localIndex = await (0, import_sdk3.default)(projectDirectory).buildIndex({
|
|
514
620
|
source: config.cId,
|
|
515
621
|
commit: "local",
|
|
516
622
|
hashContent: false,
|
|
@@ -518,18 +624,18 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
518
624
|
});
|
|
519
625
|
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
520
626
|
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
521
|
-
const ownershipGraph = (0,
|
|
627
|
+
const ownershipGraph = (0, import_sdk3.resolve)([localIndex, ...remoteIndexes]);
|
|
522
628
|
if (ownershipGraph.conflicts.length > 0) {
|
|
523
629
|
options.onProgress?.({ type: "resolved", graph: ownershipGraph });
|
|
524
630
|
throw new FederationConflictError(ownershipGraph.conflicts);
|
|
525
631
|
}
|
|
526
|
-
const graph = (0,
|
|
632
|
+
const graph = (0, import_sdk3.resolve)(remoteIndexes);
|
|
527
633
|
options.onProgress?.({ type: "resolved", graph });
|
|
528
634
|
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
529
635
|
options.onProgress?.({ type: "hydrating", outDir });
|
|
530
636
|
let hydratedFiles = 0;
|
|
531
637
|
let cachedFiles = 0;
|
|
532
|
-
const hydrateResult = await (0,
|
|
638
|
+
const hydrateResult = await (0, import_sdk3.hydrate)(graph, {
|
|
533
639
|
outDir,
|
|
534
640
|
cache: createFederationContentCache(projectDirectory, {
|
|
535
641
|
read: options.useCache !== false,
|
|
@@ -563,7 +669,7 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
563
669
|
lockVersion: 1,
|
|
564
670
|
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
565
671
|
id: source.id,
|
|
566
|
-
digest: `sha256:${(0,
|
|
672
|
+
digest: `sha256:${(0, import_node_crypto4.createHash)("sha256").update(resolved.bytes).digest("hex")}`,
|
|
567
673
|
commit: resolved.commit,
|
|
568
674
|
resolvedAt
|
|
569
675
|
})).sort((left, right) => left.id.localeCompare(right.id)),
|