@eventcatalog/cli 0.5.11 → 0.6.0
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/cli/index.js +135 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +135 -13
- package/dist/cli/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -1255,6 +1255,7 @@ var import_node_crypto2 = require("crypto");
|
|
|
1255
1255
|
var import_js_yaml = __toESM(require("js-yaml"));
|
|
1256
1256
|
var import_semver = require("semver");
|
|
1257
1257
|
var import_sdk6 = __toESM(require("@eventcatalog/sdk"));
|
|
1258
|
+
var import_breaking_changes = require("@eventcatalog/breaking-changes");
|
|
1258
1259
|
var loadGovernanceConfig = (catalogDir) => {
|
|
1259
1260
|
const yamlPath = import_node_path4.default.join(catalogDir, "governance.yaml");
|
|
1260
1261
|
const ymlPath = import_node_path4.default.join(catalogDir, "governance.yml");
|
|
@@ -1272,7 +1273,18 @@ var loadGovernanceConfig = (catalogDir) => {
|
|
|
1272
1273
|
}
|
|
1273
1274
|
}
|
|
1274
1275
|
}
|
|
1275
|
-
|
|
1276
|
+
if (parsed?.compatibility?.strategy) {
|
|
1277
|
+
const validStrategies = /* @__PURE__ */ new Set(["BACKWARD", "FORWARD", "FULL", "NONE"]);
|
|
1278
|
+
if (!validStrategies.has(parsed.compatibility.strategy)) {
|
|
1279
|
+
throw new Error(
|
|
1280
|
+
`Invalid compatibility strategy "${parsed.compatibility.strategy}". Must be one of: BACKWARD, FORWARD, FULL, NONE.`
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
...parsed?.compatibility && { compatibility: parsed.compatibility },
|
|
1286
|
+
rules
|
|
1287
|
+
};
|
|
1276
1288
|
};
|
|
1277
1289
|
var TRIGGER_FILTERS = {
|
|
1278
1290
|
consumer_added: (c2) => c2.direction === "receives" && c2.changeType === "added",
|
|
@@ -1466,6 +1478,32 @@ var evaluateSchemaChangeRules = (diff, config, targetSnapshot) => {
|
|
|
1466
1478
|
}
|
|
1467
1479
|
return results;
|
|
1468
1480
|
};
|
|
1481
|
+
var evaluateBreakingSchemaChangeRules = (diff, config, targetSnapshot) => {
|
|
1482
|
+
const breakingRules = config.rules.filter((rule) => rule.when.includes("schema_breaking_change"));
|
|
1483
|
+
if (breakingRules.length === 0) return [];
|
|
1484
|
+
const strategy = config.compatibility?.strategy;
|
|
1485
|
+
if (!strategy || strategy === "NONE") return [];
|
|
1486
|
+
const schemaChangedResources = diff.resources.filter((rc) => {
|
|
1487
|
+
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1488
|
+
return rc.changedFields?.includes("schemaHash");
|
|
1489
|
+
});
|
|
1490
|
+
if (schemaChangedResources.length === 0) return [];
|
|
1491
|
+
const latestMessageVersions = buildLatestMessageVersionMap(targetSnapshot);
|
|
1492
|
+
const breakingSchemaChanges = schemaChangedResources.map((resourceChange) => ({
|
|
1493
|
+
resourceChange,
|
|
1494
|
+
producerServices: getServicesForSchemaChange(targetSnapshot, "sends", resourceChange, latestMessageVersions),
|
|
1495
|
+
consumerServices: getServicesForSchemaChange(targetSnapshot, "receives", resourceChange, latestMessageVersions),
|
|
1496
|
+
breakingChanges: []
|
|
1497
|
+
}));
|
|
1498
|
+
const results = [];
|
|
1499
|
+
for (const rule of breakingRules) {
|
|
1500
|
+
const matched = breakingSchemaChanges.filter((sc) => matchesSchemaChangeResource(sc, rule.resources));
|
|
1501
|
+
if (matched.length > 0) {
|
|
1502
|
+
results.push({ rule, trigger: "schema_breaking_change", matchedChanges: [], breakingSchemaChanges: matched });
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
return results;
|
|
1506
|
+
};
|
|
1469
1507
|
var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
1470
1508
|
const results = [];
|
|
1471
1509
|
const targetMessageSets = targetSnapshot ? buildServiceMessageSets(targetSnapshot) : void 0;
|
|
@@ -1486,6 +1524,7 @@ var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
|
1486
1524
|
if (targetSnapshot && targetMessageSets) {
|
|
1487
1525
|
results.push(...evaluateDeprecationRules(diff, config, targetSnapshot, targetMessageSets, baseSnapshot));
|
|
1488
1526
|
results.push(...evaluateSchemaChangeRules(diff, config, targetSnapshot));
|
|
1527
|
+
results.push(...evaluateBreakingSchemaChangeRules(diff, config, targetSnapshot));
|
|
1489
1528
|
}
|
|
1490
1529
|
return results;
|
|
1491
1530
|
};
|
|
@@ -1518,7 +1557,7 @@ var readSchemaDetails = async (sdk, resourceId, version2, type) => {
|
|
|
1518
1557
|
return {};
|
|
1519
1558
|
}
|
|
1520
1559
|
};
|
|
1521
|
-
var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir) => {
|
|
1560
|
+
var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir, compatibilityStrategy) => {
|
|
1522
1561
|
const baseSDK = (0, import_sdk6.default)(baseCatalogDir);
|
|
1523
1562
|
const targetSDK = (0, import_sdk6.default)(targetCatalogDir);
|
|
1524
1563
|
const promises = [];
|
|
@@ -1544,6 +1583,40 @@ var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir) => {
|
|
|
1544
1583
|
);
|
|
1545
1584
|
}
|
|
1546
1585
|
}
|
|
1586
|
+
for (const result of results) {
|
|
1587
|
+
if (!result.breakingSchemaChanges || !compatibilityStrategy) continue;
|
|
1588
|
+
for (const bsc of result.breakingSchemaChanges) {
|
|
1589
|
+
const { resourceId, version: version2, type, changeType, previousVersion, newVersion } = bsc.resourceChange;
|
|
1590
|
+
const baseVersion = changeType === "versioned" ? previousVersion || version2 : version2;
|
|
1591
|
+
const targetVersion = changeType === "versioned" ? newVersion || version2 : version2;
|
|
1592
|
+
promises.push(
|
|
1593
|
+
(async () => {
|
|
1594
|
+
const [before, after] = await Promise.all([
|
|
1595
|
+
readSchemaDetails(baseSDK, resourceId, baseVersion, type),
|
|
1596
|
+
readSchemaDetails(targetSDK, resourceId, targetVersion, type)
|
|
1597
|
+
]);
|
|
1598
|
+
bsc.before = before.content;
|
|
1599
|
+
bsc.after = after.content;
|
|
1600
|
+
bsc.beforeSchemaPath = before.schemaPath;
|
|
1601
|
+
bsc.afterSchemaPath = after.schemaPath;
|
|
1602
|
+
bsc.beforeSchemaHash = before.schemaHash;
|
|
1603
|
+
bsc.afterSchemaHash = after.schemaHash;
|
|
1604
|
+
if (before.content && after.content) {
|
|
1605
|
+
let beforeSchema;
|
|
1606
|
+
let afterSchema;
|
|
1607
|
+
try {
|
|
1608
|
+
beforeSchema = JSON.parse(before.content);
|
|
1609
|
+
afterSchema = JSON.parse(after.content);
|
|
1610
|
+
} catch {
|
|
1611
|
+
}
|
|
1612
|
+
if (beforeSchema && afterSchema) {
|
|
1613
|
+
bsc.breakingChanges = (0, import_breaking_changes.detectBreakingChanges)(beforeSchema, afterSchema, compatibilityStrategy);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
})()
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1547
1620
|
await Promise.all(promises);
|
|
1548
1621
|
};
|
|
1549
1622
|
|
|
@@ -1572,7 +1645,7 @@ var buildServiceOwnersMap = (snapshot2) => {
|
|
|
1572
1645
|
return map;
|
|
1573
1646
|
};
|
|
1574
1647
|
var executeGovernanceActions = async (results, opts = {}) => {
|
|
1575
|
-
const { messageTypes, status, serviceOwners, baseRef, targetRef } = opts;
|
|
1648
|
+
const { messageTypes, status, serviceOwners, baseRef, targetRef, compatibilityStrategy } = opts;
|
|
1576
1649
|
const webhookCalls = [];
|
|
1577
1650
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1578
1651
|
for (const result of results) {
|
|
@@ -1625,6 +1698,48 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1625
1698
|
}
|
|
1626
1699
|
continue;
|
|
1627
1700
|
}
|
|
1701
|
+
if (result.breakingSchemaChanges && result.breakingSchemaChanges.length > 0) {
|
|
1702
|
+
for (const bsc of result.breakingSchemaChanges) {
|
|
1703
|
+
const messageType = messageTypes?.get(bsc.resourceChange.resourceId) || "message";
|
|
1704
|
+
const payload = {
|
|
1705
|
+
specversion: "1.0",
|
|
1706
|
+
type: "eventcatalog.governance.schema_breaking_change",
|
|
1707
|
+
source: "eventcatalog/governance",
|
|
1708
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1709
|
+
time: now,
|
|
1710
|
+
datacontenttype: "application/json",
|
|
1711
|
+
data: {
|
|
1712
|
+
schemaVersion: 1,
|
|
1713
|
+
...status && { status },
|
|
1714
|
+
...compatibilityStrategy && { compatibilityStrategy },
|
|
1715
|
+
summary: `Breaking schema change detected for ${messageType} ${bsc.resourceChange.resourceId}`,
|
|
1716
|
+
message: {
|
|
1717
|
+
id: bsc.resourceChange.resourceId,
|
|
1718
|
+
version: bsc.resourceChange.version,
|
|
1719
|
+
type: messageType
|
|
1720
|
+
},
|
|
1721
|
+
schema: {
|
|
1722
|
+
beforeHash: bsc.beforeSchemaHash ?? null,
|
|
1723
|
+
afterHash: bsc.afterSchemaHash ?? null,
|
|
1724
|
+
beforePath: bsc.beforeSchemaPath ?? null,
|
|
1725
|
+
afterPath: bsc.afterSchemaPath ?? null
|
|
1726
|
+
},
|
|
1727
|
+
breakingChanges: bsc.breakingChanges,
|
|
1728
|
+
refs: {
|
|
1729
|
+
base: baseRef ?? null,
|
|
1730
|
+
target: targetRef ?? null
|
|
1731
|
+
},
|
|
1732
|
+
consumers: bsc.consumerServices,
|
|
1733
|
+
producers: bsc.producerServices
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
webhookCalls.push({
|
|
1737
|
+
urlTemplate: action.url,
|
|
1738
|
+
request: fetch(url, { method: "POST", headers, body: JSON.stringify(payload) })
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1628
1743
|
if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1629
1744
|
for (const dc of result.deprecationChanges) {
|
|
1630
1745
|
const messageType = messageTypes?.get(dc.resourceChange.resourceId) || "message";
|
|
@@ -1811,31 +1926,38 @@ var governanceCheck = async (opts) => {
|
|
|
1811
1926
|
return { output: "No governance.yaml (or governance.yml) found or no rules defined.", exitCode: 0, failures: [] };
|
|
1812
1927
|
}
|
|
1813
1928
|
const results = evaluateGovernanceRules(diff, config, targetResult.snapshot, baseResult.snapshot);
|
|
1814
|
-
|
|
1929
|
+
await enrichSchemaContent(results, baseTmpDir, targetCatalogDir, config.compatibility?.strategy);
|
|
1930
|
+
const filteredResults = results.filter((r) => {
|
|
1931
|
+
if (r.trigger !== "schema_breaking_change") return true;
|
|
1932
|
+
if (!r.breakingSchemaChanges) return false;
|
|
1933
|
+
r.breakingSchemaChanges = r.breakingSchemaChanges.filter((bsc) => bsc.breakingChanges.length > 0);
|
|
1934
|
+
return r.breakingSchemaChanges.length > 0;
|
|
1935
|
+
});
|
|
1936
|
+
for (const result of filteredResults) {
|
|
1815
1937
|
const failActions = result.rule.actions.filter((a) => a.type === "fail");
|
|
1816
1938
|
if (failActions.length > 0) {
|
|
1817
1939
|
result.failed = true;
|
|
1818
1940
|
result.failMessages = failActions.map((a) => "message" in a && a.message ? resolveEnvVars(a.message) : void 0).filter((m) => m !== void 0);
|
|
1819
1941
|
}
|
|
1820
1942
|
}
|
|
1821
|
-
await enrichSchemaContent(results, baseTmpDir, targetCatalogDir);
|
|
1822
1943
|
const messageTypes = buildMessageTypeMap(targetResult.snapshot);
|
|
1823
1944
|
const serviceOwners = buildServiceOwnersMap(targetResult.snapshot);
|
|
1824
|
-
const actionOutput = await executeGovernanceActions(
|
|
1945
|
+
const actionOutput = await executeGovernanceActions(filteredResults, {
|
|
1825
1946
|
messageTypes,
|
|
1826
1947
|
status: opts.status,
|
|
1827
1948
|
serviceOwners,
|
|
1828
1949
|
baseRef: baseBranch,
|
|
1829
|
-
targetRef: opts.target || "working-directory"
|
|
1950
|
+
targetRef: opts.target || "working-directory",
|
|
1951
|
+
compatibilityStrategy: config.compatibility?.strategy
|
|
1830
1952
|
});
|
|
1831
|
-
const failures =
|
|
1953
|
+
const failures = filteredResults.filter((r) => r.failed).map((r) => ({ ruleName: r.rule.name, messages: r.failMessages || [] }));
|
|
1832
1954
|
if (opts.format === "json") {
|
|
1833
1955
|
const jsonOutput = {
|
|
1834
1956
|
baseBranch,
|
|
1835
1957
|
target: opts.target || "working directory",
|
|
1836
|
-
results,
|
|
1958
|
+
results: filteredResults,
|
|
1837
1959
|
summary: {
|
|
1838
|
-
rulesTriggered:
|
|
1960
|
+
rulesTriggered: filteredResults.length,
|
|
1839
1961
|
failures: failures.length,
|
|
1840
1962
|
passed: failures.length === 0
|
|
1841
1963
|
},
|
|
@@ -1845,14 +1967,14 @@ var governanceCheck = async (opts) => {
|
|
|
1845
1967
|
}
|
|
1846
1968
|
const targetLabel = opts.target || "working directory";
|
|
1847
1969
|
const lines = [`Governance check: comparing ${targetLabel} against ${baseBranch}`, ""];
|
|
1848
|
-
lines.push(formatGovernanceOutput(
|
|
1970
|
+
lines.push(formatGovernanceOutput(filteredResults));
|
|
1849
1971
|
if (actionOutput.length > 0) {
|
|
1850
1972
|
lines.push("");
|
|
1851
1973
|
lines.push(...actionOutput);
|
|
1852
1974
|
}
|
|
1853
|
-
if (
|
|
1975
|
+
if (filteredResults.length > 0) {
|
|
1854
1976
|
const webhookCount = actionOutput.filter((l) => l.includes("Webhook sent")).length;
|
|
1855
|
-
const parts = [`${
|
|
1977
|
+
const parts = [`${filteredResults.length} rule${filteredResults.length === 1 ? "" : "s"} triggered`];
|
|
1856
1978
|
if (webhookCount > 0) parts.push(`${webhookCount} webhook${webhookCount === 1 ? "" : "s"} sent`);
|
|
1857
1979
|
lines.push("");
|
|
1858
1980
|
lines.push(parts.join(", ") + ".");
|