@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
|
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
|
|
|
140
140
|
var import_os = __toESM(require("os"), 1);
|
|
141
141
|
|
|
142
142
|
// package.json
|
|
143
|
-
var version = "4.7.
|
|
143
|
+
var version = "4.7.5";
|
|
144
144
|
|
|
145
145
|
// src/constants.ts
|
|
146
146
|
var VERSION = version;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-QJRY6Q7S.js";
|
|
4
|
+
import "../chunk-2BYTIH4L.js";
|
|
5
5
|
import "../chunk-K2XIENVT.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-ZBPMCEXS.js";
|
|
7
7
|
import "../chunk-6QENHZZP.js";
|
|
8
8
|
export {
|
|
9
9
|
log_build_default as default
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withFederationOutputTransaction
|
|
3
|
+
} from "./chunk-WRNH5C3U.js";
|
|
4
|
+
import {
|
|
5
|
+
composePublicAssets
|
|
6
|
+
} from "./chunk-5EPNFDT5.js";
|
|
1
7
|
import {
|
|
2
8
|
createFederationSourceProvider
|
|
3
9
|
} from "./chunk-A2RZR3U4.js";
|
|
@@ -5,8 +11,9 @@ import {
|
|
|
5
11
|
createFederationContentCache
|
|
6
12
|
} from "./chunk-R7Z5ALYI.js";
|
|
7
13
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
getFederationDiagnostics,
|
|
15
|
+
resolveFederationRules
|
|
16
|
+
} from "./chunk-JJPB6EOZ.js";
|
|
10
17
|
import {
|
|
11
18
|
getEventCatalogConfigFile
|
|
12
19
|
} from "./chunk-6QENHZZP.js";
|
|
@@ -26,6 +33,16 @@ ${details}`);
|
|
|
26
33
|
this.conflicts = conflicts;
|
|
27
34
|
}
|
|
28
35
|
};
|
|
36
|
+
var FederationDiagnosticError = class extends Error {
|
|
37
|
+
diagnostics;
|
|
38
|
+
constructor(diagnostics) {
|
|
39
|
+
const details = diagnostics.map((diagnostic) => `${diagnostic.rule}: ${diagnostic.message}`).join("\n");
|
|
40
|
+
super(`Federation diagnostics prevent hydration:
|
|
41
|
+
${details}`);
|
|
42
|
+
this.name = "FederationDiagnosticError";
|
|
43
|
+
this.diagnostics = diagnostics;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
29
46
|
var validateSources = (sources) => {
|
|
30
47
|
const ids = /* @__PURE__ */ new Set();
|
|
31
48
|
for (const source of sources) {
|
|
@@ -34,6 +51,33 @@ var validateSources = (sources) => {
|
|
|
34
51
|
ids.add(source.id);
|
|
35
52
|
}
|
|
36
53
|
};
|
|
54
|
+
var createDiagnosticGraph = (graph, ownershipGraph, remoteSourceIds, localSourceId) => {
|
|
55
|
+
const edges = ownershipGraph.edges.filter((edge) => remoteSourceIds.has(edge.fromResolvedFrom.source));
|
|
56
|
+
const externalsByPointer = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const edge of edges.filter((edge2) => edge2.status === "external")) {
|
|
58
|
+
const version = edge.pointer ?? void 0;
|
|
59
|
+
const key = `${edge.to}@${version ?? ""}`;
|
|
60
|
+
const external = externalsByPointer.get(key) ?? {
|
|
61
|
+
id: edge.to,
|
|
62
|
+
...version === void 0 ? {} : { version },
|
|
63
|
+
referencedBy: []
|
|
64
|
+
};
|
|
65
|
+
if (!external.referencedBy.includes(edge.from)) external.referencedBy.push(edge.from);
|
|
66
|
+
externalsByPointer.set(key, external);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...graph,
|
|
70
|
+
entities: [
|
|
71
|
+
...graph.entities,
|
|
72
|
+
...ownershipGraph.entities.filter(
|
|
73
|
+
(entity) => entity.resolvedFrom.source === localSourceId && entity.resolvedFrom.commit === "local"
|
|
74
|
+
)
|
|
75
|
+
],
|
|
76
|
+
edges,
|
|
77
|
+
conflicts: ownershipGraph.conflicts,
|
|
78
|
+
externals: [...externalsByPointer.values()]
|
|
79
|
+
};
|
|
80
|
+
};
|
|
37
81
|
var writeLock = async (lockPath, lock) => {
|
|
38
82
|
const temporaryPath = `${lockPath}.tmp-${process.pid}`;
|
|
39
83
|
try {
|
|
@@ -68,14 +112,21 @@ var cleanupPreviousFederation = async (projectDirectory, onProgress) => {
|
|
|
68
112
|
const hadFederatedOutput = await pathExists(outDir);
|
|
69
113
|
const hadLock = previousLock !== void 0;
|
|
70
114
|
if (!hadFederatedOutput && !hadLock) return;
|
|
71
|
-
|
|
72
|
-
const publicResult = await composePublicAssets({
|
|
115
|
+
const publicResult = await withFederationOutputTransaction(
|
|
73
116
|
projectDirectory,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
117
|
+
outDir,
|
|
118
|
+
Object.keys(previousLock?.publicFiles ?? {}),
|
|
119
|
+
async () => {
|
|
120
|
+
const result = await composePublicAssets({
|
|
121
|
+
projectDirectory,
|
|
122
|
+
federatedDirectory: outDir,
|
|
123
|
+
assets: [],
|
|
124
|
+
previousFiles: previousLock?.publicFiles
|
|
125
|
+
});
|
|
126
|
+
await fs.rm(lockPath, { force: true });
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
);
|
|
79
130
|
onProgress?.({
|
|
80
131
|
type: "cleanup:complete",
|
|
81
132
|
federated: hadFederatedOutput,
|
|
@@ -97,6 +148,7 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
97
148
|
);
|
|
98
149
|
}
|
|
99
150
|
validateSources(sources);
|
|
151
|
+
const rules = resolveFederationRules(config.federation?.rules);
|
|
100
152
|
if (options.useCache === false) options.onProgress?.({ type: "cache:disabled" });
|
|
101
153
|
const provider = options.provider ?? createFederationSourceProvider(projectDirectory);
|
|
102
154
|
const resolvedSources = [];
|
|
@@ -135,60 +187,79 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
135
187
|
options.onProgress?.({ type: "local:complete", resources: localIndex.resources.length });
|
|
136
188
|
options.onProgress?.({ type: "resolving", resources, localResources: localIndex.resources.length });
|
|
137
189
|
const ownershipGraph = resolve([localIndex, ...remoteIndexes]);
|
|
138
|
-
if (ownershipGraph.conflicts.length > 0) {
|
|
139
|
-
options.onProgress?.({ type: "resolved", graph: ownershipGraph });
|
|
140
|
-
throw new FederationConflictError(ownershipGraph.conflicts);
|
|
141
|
-
}
|
|
142
190
|
const graph = resolve(remoteIndexes);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
191
|
+
const diagnosticGraph = createDiagnosticGraph(
|
|
192
|
+
graph,
|
|
193
|
+
ownershipGraph,
|
|
194
|
+
new Set(remoteIndexes.map((index) => index.source)),
|
|
195
|
+
localIndex.source
|
|
196
|
+
);
|
|
197
|
+
const diagnostics = getFederationDiagnostics(diagnosticGraph, rules);
|
|
198
|
+
options.onProgress?.({ type: "resolved", graph, diagnostics });
|
|
199
|
+
const blockingConflicts = ownershipGraph.conflicts.filter((conflict) => rules[`federation/${conflict.kind}`] === "error");
|
|
200
|
+
if (blockingConflicts.length > 0) throw new FederationConflictError(blockingConflicts);
|
|
201
|
+
const blockingDiagnostics = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
202
|
+
if (blockingDiagnostics.length > 0) throw new FederationDiagnosticError(blockingDiagnostics);
|
|
203
|
+
const publicPaths = [
|
|
204
|
+
...Object.keys(previousLock?.publicFiles ?? {}),
|
|
205
|
+
...graph.assets.filter((asset) => asset.path.startsWith("public/")).map((asset) => asset.path.slice("public/".length))
|
|
206
|
+
];
|
|
207
|
+
const { hydrateResult, publicResult } = await withFederationOutputTransaction(
|
|
208
|
+
projectDirectory,
|
|
149
209
|
outDir,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
210
|
+
publicPaths,
|
|
211
|
+
async () => {
|
|
212
|
+
const sourcesById = new Map(sources.map((source) => [source.id, source]));
|
|
213
|
+
options.onProgress?.({ type: "hydrating", outDir });
|
|
214
|
+
let hydratedFiles = 0;
|
|
215
|
+
let cachedFiles = 0;
|
|
216
|
+
const hydrateResult2 = await hydrate(graph, {
|
|
217
|
+
outDir,
|
|
218
|
+
cache: createFederationContentCache(projectDirectory, {
|
|
219
|
+
read: options.useCache !== false,
|
|
220
|
+
onHit: () => {
|
|
221
|
+
cachedFiles += 1;
|
|
222
|
+
options.onProgress?.({ type: "hydrate:cache", files: cachedFiles });
|
|
223
|
+
}
|
|
224
|
+
}),
|
|
225
|
+
fetch: async ({ source: sourceId, commit, path: artifactPath }) => {
|
|
226
|
+
const source = sourcesById.get(sourceId);
|
|
227
|
+
if (!source) throw new Error(`Cannot fetch content for unconfigured source "${sourceId}"`);
|
|
228
|
+
const content = await provider.fetchContent({ source, commit, path: artifactPath });
|
|
229
|
+
hydratedFiles += 1;
|
|
230
|
+
options.onProgress?.({ type: "hydrate:file", files: hydratedFiles, source: sourceId, path: artifactPath });
|
|
231
|
+
return content;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const publicResult2 = await composePublicAssets({
|
|
235
|
+
projectDirectory,
|
|
236
|
+
federatedDirectory: outDir,
|
|
237
|
+
assets: graph.assets,
|
|
238
|
+
previousFiles: previousLock?.publicFiles,
|
|
239
|
+
collisionPaths: new Set(
|
|
240
|
+
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
241
|
+
)
|
|
242
|
+
});
|
|
243
|
+
options.onProgress?.({ type: "public:complete", result: publicResult2 });
|
|
244
|
+
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
245
|
+
await writeLock(lockPath, {
|
|
246
|
+
lockVersion: 1,
|
|
247
|
+
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
248
|
+
id: source.id,
|
|
249
|
+
digest: `sha256:${createHash("sha256").update(resolved.bytes).digest("hex")}`,
|
|
250
|
+
commit: resolved.commit,
|
|
251
|
+
resolvedAt
|
|
252
|
+
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
253
|
+
publicFiles: publicResult2.files
|
|
254
|
+
});
|
|
255
|
+
return { hydrateResult: hydrateResult2, publicResult: publicResult2 };
|
|
165
256
|
}
|
|
166
|
-
|
|
167
|
-
const publicResult = await composePublicAssets({
|
|
168
|
-
projectDirectory,
|
|
169
|
-
federatedDirectory: outDir,
|
|
170
|
-
assets: graph.assets,
|
|
171
|
-
previousFiles: previousLock?.publicFiles,
|
|
172
|
-
collisionPaths: new Set(
|
|
173
|
-
graph.warnings.filter((warning) => warning.kind === "asset-collision").map((warning) => warning.path)
|
|
174
|
-
)
|
|
175
|
-
});
|
|
176
|
-
options.onProgress?.({ type: "public:complete", result: publicResult });
|
|
177
|
-
const resolvedAt = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
178
|
-
await writeLock(lockPath, {
|
|
179
|
-
lockVersion: 1,
|
|
180
|
-
sources: resolvedSources.map(({ config: source, resolved }) => ({
|
|
181
|
-
id: source.id,
|
|
182
|
-
digest: `sha256:${createHash("sha256").update(resolved.bytes).digest("hex")}`,
|
|
183
|
-
commit: resolved.commit,
|
|
184
|
-
resolvedAt
|
|
185
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
186
|
-
publicFiles: publicResult.files
|
|
187
|
-
});
|
|
257
|
+
);
|
|
188
258
|
const result = {
|
|
189
259
|
sources: sources.length,
|
|
190
260
|
resources,
|
|
191
261
|
graph,
|
|
262
|
+
diagnostics,
|
|
192
263
|
hydrate: hydrateResult,
|
|
193
264
|
public: publicResult,
|
|
194
265
|
outDir,
|
|
@@ -200,5 +271,6 @@ var federateCatalog = async (projectDirectory, options = {}) => {
|
|
|
200
271
|
|
|
201
272
|
export {
|
|
202
273
|
FederationConflictError,
|
|
274
|
+
FederationDiagnosticError,
|
|
203
275
|
federateCatalog
|
|
204
276
|
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// src/federation/diagnostics.ts
|
|
2
|
+
var federationRuleDefaults = {
|
|
3
|
+
"federation/duplicate-source": "error",
|
|
4
|
+
"federation/type-collision": "error",
|
|
5
|
+
"federation/pointer-type-mismatch": "error",
|
|
6
|
+
"federation/facet-disagreement": "error",
|
|
7
|
+
"federation/asset-collision": "warn",
|
|
8
|
+
"federation/missing-resource": "warn",
|
|
9
|
+
"federation/unresolved-version": "warn"
|
|
10
|
+
};
|
|
11
|
+
var resolveFederationRules = (rules = {}) => {
|
|
12
|
+
for (const [rule, level] of Object.entries(rules)) {
|
|
13
|
+
if (!Object.hasOwn(federationRuleDefaults, rule)) throw new Error(`Unknown federation rule "${rule}".`);
|
|
14
|
+
if (level !== "off" && level !== "warn" && level !== "error") {
|
|
15
|
+
throw new Error(`Invalid level "${String(level)}" for federation rule "${rule}". Expected off, warn, or error.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { ...federationRuleDefaults, ...rules };
|
|
19
|
+
};
|
|
20
|
+
var getExternalResource = (external) => `${external.id}${external.version === void 0 ? "" : `@${external.version}`}`;
|
|
21
|
+
var getVersionedResource = (id, version) => `${id}${version == null ? "" : `@${version}`}`;
|
|
22
|
+
var getDocumentedResourceType = (type) => {
|
|
23
|
+
const usesAn = type === "adr" || type === "agent" || type === "entity" || type === "event";
|
|
24
|
+
return `documented as ${usesAn ? "an" : "a"} ${type}`;
|
|
25
|
+
};
|
|
26
|
+
var getConflictDiagnostic = (conflict, graph) => {
|
|
27
|
+
const catalogs = { label: "catalogs", value: conflict.sources.join(", ") };
|
|
28
|
+
switch (conflict.kind) {
|
|
29
|
+
case "duplicate-source":
|
|
30
|
+
return {
|
|
31
|
+
severity: "error",
|
|
32
|
+
message: "Resource has multiple owners",
|
|
33
|
+
rule: "federation/duplicate-source",
|
|
34
|
+
attributes: [
|
|
35
|
+
{ label: "resource", value: conflict.id },
|
|
36
|
+
catalogs,
|
|
37
|
+
{ label: "resolution", value: "assign a single owning catalog" }
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
case "type-collision": {
|
|
41
|
+
const types = [
|
|
42
|
+
...new Map(
|
|
43
|
+
graph.entities.filter((entity) => entity.id === conflict.id).map(
|
|
44
|
+
(entity) => [
|
|
45
|
+
`${entity.resolvedFrom.source}:${entity.type}`,
|
|
46
|
+
{ label: entity.resolvedFrom.source, value: getDocumentedResourceType(entity.type) }
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
).values()
|
|
50
|
+
];
|
|
51
|
+
return {
|
|
52
|
+
severity: "error",
|
|
53
|
+
message: "Resource ID has conflicting types",
|
|
54
|
+
rule: "federation/type-collision",
|
|
55
|
+
attributes: [{ label: "resource", value: conflict.id }, ...types.length > 0 ? types : [catalogs]]
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
case "pointer-type-mismatch": {
|
|
59
|
+
const typeMismatch = /^Expected (.+) but found (.+)$/.exec(conflict.detail ?? "");
|
|
60
|
+
return {
|
|
61
|
+
severity: "error",
|
|
62
|
+
message: "Reference type does not match resource",
|
|
63
|
+
rule: "federation/pointer-type-mismatch",
|
|
64
|
+
attributes: [
|
|
65
|
+
{ label: "resource", value: conflict.id },
|
|
66
|
+
...typeMismatch ? [
|
|
67
|
+
{ label: "expected type", value: typeMismatch[1] },
|
|
68
|
+
{ label: "actual type", value: typeMismatch[2] }
|
|
69
|
+
] : conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
|
|
70
|
+
catalogs
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
case "facet-disagreement":
|
|
75
|
+
return {
|
|
76
|
+
severity: "error",
|
|
77
|
+
message: "Catalogs disagree about this resource",
|
|
78
|
+
rule: "federation/facet-disagreement",
|
|
79
|
+
attributes: [
|
|
80
|
+
{ label: "resource", value: conflict.id },
|
|
81
|
+
...conflict.detail ? [{ label: "detail", value: conflict.detail }] : [],
|
|
82
|
+
catalogs
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var getFederationDiagnostics = (graph, rules = {}) => {
|
|
88
|
+
const resolvedRules = resolveFederationRules(rules);
|
|
89
|
+
const diagnostics = graph.conflicts.map((conflict) => getConflictDiagnostic(conflict, graph));
|
|
90
|
+
for (const external of graph.externals) {
|
|
91
|
+
const resource = getExternalResource(external);
|
|
92
|
+
for (const referencedBy of external.referencedBy) {
|
|
93
|
+
const referrer = graph.entities.find((entity) => entity.id === referencedBy);
|
|
94
|
+
diagnostics.push({
|
|
95
|
+
severity: "warning",
|
|
96
|
+
message: "Referenced EventCatalog resource does not exist",
|
|
97
|
+
rule: "federation/missing-resource",
|
|
98
|
+
attributes: [
|
|
99
|
+
{ label: "source catalog", value: referrer?.resolvedFrom.source ?? "unknown" },
|
|
100
|
+
{ label: "referenced by", value: referencedBy },
|
|
101
|
+
{ label: "missing resource", value: resource }
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const edge of graph.edges.filter((edge2) => edge2.status === "unresolved")) {
|
|
107
|
+
const availableVersions = [
|
|
108
|
+
...new Set(graph.entities.filter((entity) => entity.id === edge.to).map((entity) => entity.version ?? "unversioned"))
|
|
109
|
+
].sort((left, right) => left.localeCompare(right, void 0, { numeric: true }));
|
|
110
|
+
diagnostics.push({
|
|
111
|
+
severity: "warning",
|
|
112
|
+
message: "Referenced EventCatalog resource version does not exist",
|
|
113
|
+
rule: "federation/unresolved-version",
|
|
114
|
+
attributes: [
|
|
115
|
+
{ label: "source catalog", value: edge.fromResolvedFrom.source },
|
|
116
|
+
{ label: "referenced by", value: getVersionedResource(edge.from, edge.fromVersion) },
|
|
117
|
+
{ label: "resource", value: edge.to },
|
|
118
|
+
{ label: "requested version", value: edge.pointer ?? "unspecified" },
|
|
119
|
+
{ label: "available versions", value: availableVersions.join(", ") }
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const warning of graph.warnings) {
|
|
124
|
+
diagnostics.push({
|
|
125
|
+
severity: "warning",
|
|
126
|
+
message: "Asset collision",
|
|
127
|
+
rule: "federation/asset-collision",
|
|
128
|
+
attributes: [
|
|
129
|
+
{ label: "asset", value: warning.path },
|
|
130
|
+
{ label: "sources", value: warning.sources.join(", ") },
|
|
131
|
+
{ label: "winner", value: warning.winner },
|
|
132
|
+
{ label: "resolution", value: "last configured source wins" }
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return diagnostics.flatMap((diagnostic) => {
|
|
137
|
+
const level = resolvedRules[diagnostic.rule];
|
|
138
|
+
if (level === "off") return [];
|
|
139
|
+
return [{ ...diagnostic, severity: level === "error" ? "error" : "warning" }];
|
|
140
|
+
}).sort(
|
|
141
|
+
(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 ?? "")
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
var getFederationDiagnosticCounts = (diagnostics) => diagnostics.reduce(
|
|
145
|
+
(counts, diagnostic) => ({
|
|
146
|
+
errors: counts.errors + (diagnostic.severity === "error" ? 1 : 0),
|
|
147
|
+
warnings: counts.warnings + (diagnostic.severity === "warning" ? 1 : 0)
|
|
148
|
+
}),
|
|
149
|
+
{ errors: 0, warnings: 0 }
|
|
150
|
+
);
|
|
151
|
+
var getVisibleFederationDiagnostics = (diagnostics, verbose) => diagnostics.filter((diagnostic) => verbose || diagnostic.severity === "error");
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
federationRuleDefaults,
|
|
155
|
+
resolveFederationRules,
|
|
156
|
+
getFederationDiagnostics,
|
|
157
|
+
getFederationDiagnosticCounts,
|
|
158
|
+
getVisibleFederationDiagnostics
|
|
159
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZBPMCEXS.js";
|
|
4
4
|
|
|
5
5
|
// src/utils/cli-logger.ts
|
|
6
6
|
import pc from "picocolors";
|
|
@@ -37,6 +37,29 @@ var logger = {
|
|
|
37
37
|
warning: (message, tag = "warn") => {
|
|
38
38
|
console.log(formatMessage(tag, message, pc.yellow));
|
|
39
39
|
},
|
|
40
|
+
line: (message = "") => {
|
|
41
|
+
console.log(message);
|
|
42
|
+
},
|
|
43
|
+
diagnostic: (severity, message, rule, attributes = []) => {
|
|
44
|
+
const isError = severity === "error";
|
|
45
|
+
const color = isError ? pc.red : pc.yellow;
|
|
46
|
+
const icon = isError ? "\u2716" : "\u26A0";
|
|
47
|
+
const label = `${icon} ${severity.padEnd(7)} ${message}`;
|
|
48
|
+
const ruleSpacing = " ".repeat(Math.max(2, 72 - label.length));
|
|
49
|
+
console.log(` ${color(icon)} ${color(severity.padEnd(7))} ${message}${ruleSpacing}${pc.gray(rule)}`);
|
|
50
|
+
for (const attribute of attributes) {
|
|
51
|
+
console.log(` - ${pc.dim(`${attribute.label}:`)} ${attribute.value}`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
diagnosticSummary: (errors, warnings) => {
|
|
55
|
+
const total = errors + warnings;
|
|
56
|
+
const color = errors > 0 ? pc.red : pc.yellow;
|
|
57
|
+
const icon = errors > 0 ? "\u2716" : "\u26A0";
|
|
58
|
+
const problems = `${total} problem${total === 1 ? "" : "s"}`;
|
|
59
|
+
const errorCount = `${errors} error${errors === 1 ? "" : "s"}`;
|
|
60
|
+
const warningCount = `${warnings} warning${warnings === 1 ? "" : "s"}`;
|
|
61
|
+
console.log(color(`${icon} ${problems} (${errorCount}, ${warningCount})`));
|
|
62
|
+
},
|
|
40
63
|
dim: (message) => {
|
|
41
64
|
console.log(pc.dim(message));
|
|
42
65
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/federation/output-transaction.ts
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var getSafePublicPath = (publicDirectory, relativePath) => {
|
|
5
|
+
const normalizedPath = path.posix.normalize(relativePath);
|
|
6
|
+
const unsafe = relativePath.length === 0 || relativePath.includes("\0") || relativePath.includes("\\") || path.posix.isAbsolute(relativePath) || /^[a-zA-Z]:\//.test(relativePath) || normalizedPath !== relativePath || normalizedPath === ".." || normalizedPath.startsWith("../");
|
|
7
|
+
if (unsafe) return void 0;
|
|
8
|
+
const destinationPath = path.resolve(publicDirectory, relativePath);
|
|
9
|
+
const relativeDestinationPath = path.relative(publicDirectory, destinationPath);
|
|
10
|
+
return relativeDestinationPath !== "" && relativeDestinationPath !== ".." && !relativeDestinationPath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativeDestinationPath) ? destinationPath : void 0;
|
|
11
|
+
};
|
|
12
|
+
var beginFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths) => {
|
|
13
|
+
const transactionDirectory = await fs.mkdtemp(path.join(projectDirectory, ".eventcatalog-federation-transaction-"));
|
|
14
|
+
const previousOutDir = path.join(transactionDirectory, "federated");
|
|
15
|
+
const publicBackupDirectory = path.join(transactionDirectory, "public");
|
|
16
|
+
const publicDirectory = path.resolve(projectDirectory, "public");
|
|
17
|
+
const publicSnapshots = [];
|
|
18
|
+
const missingPublicDirectories = /* @__PURE__ */ new Set();
|
|
19
|
+
let hadPreviousOutDir = false;
|
|
20
|
+
try {
|
|
21
|
+
for (const relativePath of new Set(relativePublicPaths)) {
|
|
22
|
+
const destinationPath = getSafePublicPath(publicDirectory, relativePath);
|
|
23
|
+
if (!destinationPath) continue;
|
|
24
|
+
try {
|
|
25
|
+
const stat = await fs.lstat(destinationPath);
|
|
26
|
+
if (!stat.isFile()) continue;
|
|
27
|
+
const backupPath = path.join(publicBackupDirectory, `${publicSnapshots.length}`);
|
|
28
|
+
await fs.mkdir(path.dirname(backupPath), { recursive: true });
|
|
29
|
+
await fs.copyFile(destinationPath, backupPath);
|
|
30
|
+
publicSnapshots.push({ backupPath, destinationPath, state: "file" });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const code = error.code;
|
|
33
|
+
if (code === "ENOTDIR") continue;
|
|
34
|
+
if (code !== "ENOENT") throw error;
|
|
35
|
+
publicSnapshots.push({ destinationPath, state: "missing" });
|
|
36
|
+
let directory = path.dirname(destinationPath);
|
|
37
|
+
while (directory !== publicDirectory) {
|
|
38
|
+
try {
|
|
39
|
+
await fs.lstat(directory);
|
|
40
|
+
break;
|
|
41
|
+
} catch (directoryError) {
|
|
42
|
+
const directoryCode = directoryError.code;
|
|
43
|
+
if (directoryCode === "ENOTDIR") break;
|
|
44
|
+
if (directoryCode !== "ENOENT") throw directoryError;
|
|
45
|
+
missingPublicDirectories.add(directory);
|
|
46
|
+
directory = path.dirname(directory);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
await fs.rename(outDir, previousOutDir);
|
|
53
|
+
hadPreviousOutDir = true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code !== "ENOENT") throw error;
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
await fs.rm(transactionDirectory, { recursive: true, force: true });
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
commit: async () => {
|
|
63
|
+
await fs.rm(transactionDirectory, { recursive: true, force: true }).catch(() => void 0);
|
|
64
|
+
},
|
|
65
|
+
rollback: async () => {
|
|
66
|
+
const rollbackErrors = [];
|
|
67
|
+
const attempt = async (operation) => {
|
|
68
|
+
try {
|
|
69
|
+
await operation();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
rollbackErrors.push(error);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
await attempt(() => fs.rm(outDir, { recursive: true, force: true }));
|
|
75
|
+
if (hadPreviousOutDir) await attempt(() => fs.rename(previousOutDir, outDir));
|
|
76
|
+
for (const snapshot of publicSnapshots) {
|
|
77
|
+
if (snapshot.state === "missing") {
|
|
78
|
+
await attempt(() => fs.rm(snapshot.destinationPath, { force: true }));
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
await attempt(async () => {
|
|
82
|
+
await fs.mkdir(path.dirname(snapshot.destinationPath), { recursive: true });
|
|
83
|
+
await fs.copyFile(snapshot.backupPath, snapshot.destinationPath);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
for (const directory of [...missingPublicDirectories].sort((left, right) => right.length - left.length)) {
|
|
87
|
+
await attempt(async () => {
|
|
88
|
+
try {
|
|
89
|
+
await fs.rmdir(directory);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (!["ENOENT", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
await attempt(() => fs.rm(transactionDirectory, { recursive: true, force: true }));
|
|
96
|
+
if (rollbackErrors.length > 0) throw new AggregateError(rollbackErrors, "Failed to restore the previous federation output");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
var withFederationOutputTransaction = async (projectDirectory, outDir, relativePublicPaths, update) => {
|
|
101
|
+
const transaction = await beginFederationOutputTransaction(projectDirectory, outDir, relativePublicPaths);
|
|
102
|
+
try {
|
|
103
|
+
const result = await update();
|
|
104
|
+
await transaction.commit();
|
|
105
|
+
return result;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
try {
|
|
108
|
+
await transaction.rollback();
|
|
109
|
+
} catch (rollbackError) {
|
|
110
|
+
throw new AggregateError([error, rollbackError], "Federation failed and the previous output could not be restored");
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export {
|
|
117
|
+
withFederationOutputTransaction
|
|
118
|
+
};
|
package/dist/constants.cjs
CHANGED