@eventcatalog/cli 0.5.2 → 0.5.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/cli/index.js +210 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +207 -17
- package/dist/cli/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/cli/index.js
CHANGED
|
@@ -1251,7 +1251,10 @@ var snapshotList = async (opts) => {
|
|
|
1251
1251
|
// src/cli/governance/rules.ts
|
|
1252
1252
|
var import_node_fs5 = __toESM(require("fs"));
|
|
1253
1253
|
var import_node_path4 = __toESM(require("path"));
|
|
1254
|
+
var import_node_crypto2 = require("crypto");
|
|
1254
1255
|
var import_js_yaml = __toESM(require("js-yaml"));
|
|
1256
|
+
var import_semver = require("semver");
|
|
1257
|
+
var import_sdk6 = __toESM(require("@eventcatalog/sdk"));
|
|
1255
1258
|
var loadGovernanceConfig = (catalogDir) => {
|
|
1256
1259
|
const yamlPath = import_node_path4.default.join(catalogDir, "governance.yaml");
|
|
1257
1260
|
const ymlPath = import_node_path4.default.join(catalogDir, "governance.yml");
|
|
@@ -1309,15 +1312,16 @@ var buildMessageMap = (snapshot2) => {
|
|
|
1309
1312
|
for (const msg of snapshot2.resources.messages.queries) map.set(msg.id, msg);
|
|
1310
1313
|
return map;
|
|
1311
1314
|
};
|
|
1312
|
-
var
|
|
1315
|
+
var buildServiceIndex = (snapshot2, direction) => {
|
|
1313
1316
|
const index = /* @__PURE__ */ new Map();
|
|
1314
1317
|
for (const service of snapshot2.resources.services) {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1318
|
+
const pointers = service[direction];
|
|
1319
|
+
if (!pointers) continue;
|
|
1320
|
+
for (const pointer of pointers) {
|
|
1321
|
+
let entries = index.get(pointer.id);
|
|
1322
|
+
if (!entries) {
|
|
1323
|
+
entries = [];
|
|
1324
|
+
index.set(pointer.id, entries);
|
|
1321
1325
|
}
|
|
1322
1326
|
const entry = {
|
|
1323
1327
|
id: service.id,
|
|
@@ -1326,17 +1330,85 @@ var buildProducerIndex = (snapshot2) => {
|
|
|
1326
1330
|
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1327
1331
|
entry.owners = service.owners;
|
|
1328
1332
|
}
|
|
1329
|
-
|
|
1333
|
+
entries.push(entry);
|
|
1330
1334
|
}
|
|
1331
1335
|
}
|
|
1332
1336
|
return index;
|
|
1333
1337
|
};
|
|
1338
|
+
var getMessageTypeKey = (resourceId, type) => `${type}:${resourceId}`;
|
|
1339
|
+
var buildLatestMessageVersionMap = (snapshot2) => {
|
|
1340
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1341
|
+
for (const event of snapshot2.resources.messages.events) {
|
|
1342
|
+
versions.set(getMessageTypeKey(event.id, "event"), event.version);
|
|
1343
|
+
}
|
|
1344
|
+
for (const command of snapshot2.resources.messages.commands) {
|
|
1345
|
+
versions.set(getMessageTypeKey(command.id, "command"), command.version);
|
|
1346
|
+
}
|
|
1347
|
+
for (const query of snapshot2.resources.messages.queries) {
|
|
1348
|
+
versions.set(getMessageTypeKey(query.id, "query"), query.version);
|
|
1349
|
+
}
|
|
1350
|
+
return versions;
|
|
1351
|
+
};
|
|
1352
|
+
var getTargetMessageVersion = (resourceChange) => {
|
|
1353
|
+
if (resourceChange.changeType === "versioned") {
|
|
1354
|
+
return resourceChange.newVersion || resourceChange.version;
|
|
1355
|
+
}
|
|
1356
|
+
return resourceChange.version;
|
|
1357
|
+
};
|
|
1358
|
+
var pointerTargetsChangedVersion = (pointer, resourceChange, latestMessageVersions) => {
|
|
1359
|
+
if (pointer.id !== resourceChange.resourceId) return false;
|
|
1360
|
+
const targetVersion = getTargetMessageVersion(resourceChange);
|
|
1361
|
+
const pointerVersion = pointer.version;
|
|
1362
|
+
if (!pointerVersion || pointerVersion === "latest") {
|
|
1363
|
+
const latestVersion = latestMessageVersions.get(getMessageTypeKey(resourceChange.resourceId, resourceChange.type));
|
|
1364
|
+
if (!latestVersion) return true;
|
|
1365
|
+
return latestVersion === targetVersion;
|
|
1366
|
+
}
|
|
1367
|
+
if ((0, import_semver.validRange)(pointerVersion)) {
|
|
1368
|
+
try {
|
|
1369
|
+
return (0, import_semver.satisfies)(targetVersion, pointerVersion);
|
|
1370
|
+
} catch {
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return pointerVersion === targetVersion;
|
|
1375
|
+
};
|
|
1376
|
+
var getServicesForSchemaChange = (snapshot2, direction, resourceChange, latestMessageVersions) => {
|
|
1377
|
+
const matches = [];
|
|
1378
|
+
for (const service of snapshot2.resources.services) {
|
|
1379
|
+
const pointers = service[direction];
|
|
1380
|
+
if (!pointers) continue;
|
|
1381
|
+
const hasMatch = pointers.some((pointer) => pointerTargetsChangedVersion(pointer, resourceChange, latestMessageVersions));
|
|
1382
|
+
if (!hasMatch) continue;
|
|
1383
|
+
const entry = {
|
|
1384
|
+
id: service.id,
|
|
1385
|
+
version: service.version
|
|
1386
|
+
};
|
|
1387
|
+
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1388
|
+
entry.owners = service.owners;
|
|
1389
|
+
}
|
|
1390
|
+
matches.push(entry);
|
|
1391
|
+
}
|
|
1392
|
+
return matches;
|
|
1393
|
+
};
|
|
1394
|
+
var matchesSchemaChangeResource = (schemaChange, resources) => {
|
|
1395
|
+
return resources.some((resource) => {
|
|
1396
|
+
if (resource === "*") return true;
|
|
1397
|
+
if (resource.startsWith("message:")) return schemaChange.resourceChange.resourceId === resource.slice(8);
|
|
1398
|
+
if (resource.startsWith("consumes:"))
|
|
1399
|
+
return schemaChange.consumerServices.some((service) => service.id === resource.slice(9));
|
|
1400
|
+
if (resource.startsWith("produces:"))
|
|
1401
|
+
return schemaChange.producerServices.some((service) => service.id === resource.slice(9));
|
|
1402
|
+
if (resource.startsWith("service:")) return schemaChange.producerServices.some((service) => service.id === resource.slice(8));
|
|
1403
|
+
return false;
|
|
1404
|
+
});
|
|
1405
|
+
};
|
|
1334
1406
|
var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets, baseSnapshot) => {
|
|
1335
1407
|
const deprecationRules = config.rules.filter((rule) => rule.when.includes("message_deprecated"));
|
|
1336
1408
|
if (deprecationRules.length === 0) return [];
|
|
1337
1409
|
const targetMessages = buildMessageMap(targetSnapshot);
|
|
1338
1410
|
const baseMessages = baseSnapshot ? buildMessageMap(baseSnapshot) : void 0;
|
|
1339
|
-
const producerIndex =
|
|
1411
|
+
const producerIndex = buildServiceIndex(targetSnapshot, "sends");
|
|
1340
1412
|
const deprecatedResources = diff.resources.filter((rc) => {
|
|
1341
1413
|
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1342
1414
|
if (!rc.changedFields?.includes("deprecated")) return false;
|
|
@@ -1363,6 +1435,29 @@ var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets,
|
|
|
1363
1435
|
}
|
|
1364
1436
|
return results;
|
|
1365
1437
|
};
|
|
1438
|
+
var evaluateSchemaChangeRules = (diff, config, targetSnapshot) => {
|
|
1439
|
+
const schemaRules = config.rules.filter((rule) => rule.when.includes("schema_changed"));
|
|
1440
|
+
if (schemaRules.length === 0) return [];
|
|
1441
|
+
const schemaChangedResources = diff.resources.filter((rc) => {
|
|
1442
|
+
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1443
|
+
return rc.changedFields?.includes("schemaHash");
|
|
1444
|
+
});
|
|
1445
|
+
if (schemaChangedResources.length === 0) return [];
|
|
1446
|
+
const latestMessageVersions = buildLatestMessageVersionMap(targetSnapshot);
|
|
1447
|
+
const schemaChanges = schemaChangedResources.map((resourceChange) => ({
|
|
1448
|
+
resourceChange,
|
|
1449
|
+
producerServices: getServicesForSchemaChange(targetSnapshot, "sends", resourceChange, latestMessageVersions),
|
|
1450
|
+
consumerServices: getServicesForSchemaChange(targetSnapshot, "receives", resourceChange, latestMessageVersions)
|
|
1451
|
+
}));
|
|
1452
|
+
const results = [];
|
|
1453
|
+
for (const rule of schemaRules) {
|
|
1454
|
+
const matched = schemaChanges.filter((schemaChange) => matchesSchemaChangeResource(schemaChange, rule.resources));
|
|
1455
|
+
if (matched.length > 0) {
|
|
1456
|
+
results.push({ rule, trigger: "schema_changed", matchedChanges: [], schemaChanges: matched });
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return results;
|
|
1460
|
+
};
|
|
1366
1461
|
var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
1367
1462
|
const results = [];
|
|
1368
1463
|
const targetMessageSets = targetSnapshot ? buildServiceMessageSets(targetSnapshot) : void 0;
|
|
@@ -1382,6 +1477,7 @@ var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
|
1382
1477
|
}
|
|
1383
1478
|
if (targetSnapshot && targetMessageSets) {
|
|
1384
1479
|
results.push(...evaluateDeprecationRules(diff, config, targetSnapshot, targetMessageSets, baseSnapshot));
|
|
1480
|
+
results.push(...evaluateSchemaChangeRules(diff, config, targetSnapshot));
|
|
1385
1481
|
}
|
|
1386
1482
|
return results;
|
|
1387
1483
|
};
|
|
@@ -1400,9 +1496,51 @@ var resolveEnvVars = (value) => {
|
|
|
1400
1496
|
return envValue;
|
|
1401
1497
|
});
|
|
1402
1498
|
};
|
|
1499
|
+
var readSchemaDetails = async (sdk, resourceId, version2, type) => {
|
|
1500
|
+
if (!MESSAGE_RESOURCE_TYPES.has(type)) return {};
|
|
1501
|
+
try {
|
|
1502
|
+
const schema = await sdk.getSchemaForMessage(resourceId, version2);
|
|
1503
|
+
if (!schema) return {};
|
|
1504
|
+
return {
|
|
1505
|
+
content: schema.schema,
|
|
1506
|
+
schemaPath: schema.fileName,
|
|
1507
|
+
schemaHash: (0, import_node_crypto2.createHash)("sha256").update(schema.schema).digest("hex")
|
|
1508
|
+
};
|
|
1509
|
+
} catch {
|
|
1510
|
+
return {};
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir) => {
|
|
1514
|
+
const baseSDK = (0, import_sdk6.default)(baseCatalogDir);
|
|
1515
|
+
const targetSDK = (0, import_sdk6.default)(targetCatalogDir);
|
|
1516
|
+
const promises = [];
|
|
1517
|
+
for (const result of results) {
|
|
1518
|
+
if (!result.schemaChanges) continue;
|
|
1519
|
+
for (const sc of result.schemaChanges) {
|
|
1520
|
+
const { resourceId, version: version2, type, changeType, previousVersion, newVersion } = sc.resourceChange;
|
|
1521
|
+
const baseVersion = changeType === "versioned" ? previousVersion || version2 : version2;
|
|
1522
|
+
const targetVersion = changeType === "versioned" ? newVersion || version2 : version2;
|
|
1523
|
+
promises.push(
|
|
1524
|
+
(async () => {
|
|
1525
|
+
const [before, after] = await Promise.all([
|
|
1526
|
+
readSchemaDetails(baseSDK, resourceId, baseVersion, type),
|
|
1527
|
+
readSchemaDetails(targetSDK, resourceId, targetVersion, type)
|
|
1528
|
+
]);
|
|
1529
|
+
sc.before = before.content;
|
|
1530
|
+
sc.after = after.content;
|
|
1531
|
+
sc.beforeSchemaPath = before.schemaPath;
|
|
1532
|
+
sc.afterSchemaPath = after.schemaPath;
|
|
1533
|
+
sc.beforeSchemaHash = before.schemaHash;
|
|
1534
|
+
sc.afterSchemaHash = after.schemaHash;
|
|
1535
|
+
})()
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
await Promise.all(promises);
|
|
1540
|
+
};
|
|
1403
1541
|
|
|
1404
1542
|
// src/cli/governance/actions.ts
|
|
1405
|
-
var
|
|
1543
|
+
var import_node_crypto3 = require("crypto");
|
|
1406
1544
|
var buildMessageTypeMap = (snapshot2) => {
|
|
1407
1545
|
const map = /* @__PURE__ */ new Map();
|
|
1408
1546
|
for (const event of snapshot2.resources.messages.events) {
|
|
@@ -1426,7 +1564,7 @@ var buildServiceOwnersMap = (snapshot2) => {
|
|
|
1426
1564
|
return map;
|
|
1427
1565
|
};
|
|
1428
1566
|
var executeGovernanceActions = async (results, opts = {}) => {
|
|
1429
|
-
const { messageTypes, status, serviceOwners } = opts;
|
|
1567
|
+
const { messageTypes, status, serviceOwners, baseRef, targetRef } = opts;
|
|
1430
1568
|
const webhookCalls = [];
|
|
1431
1569
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1432
1570
|
for (const result of results) {
|
|
@@ -1439,6 +1577,46 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1439
1577
|
headers[key] = resolveEnvVars(value);
|
|
1440
1578
|
}
|
|
1441
1579
|
}
|
|
1580
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1581
|
+
for (const sc of result.schemaChanges) {
|
|
1582
|
+
const messageType = messageTypes?.get(sc.resourceChange.resourceId) || "message";
|
|
1583
|
+
const payload = {
|
|
1584
|
+
specversion: "1.0",
|
|
1585
|
+
type: "eventcatalog.governance.schema_changed",
|
|
1586
|
+
source: "eventcatalog/governance",
|
|
1587
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1588
|
+
time: now,
|
|
1589
|
+
datacontenttype: "application/json",
|
|
1590
|
+
data: {
|
|
1591
|
+
schemaVersion: 1,
|
|
1592
|
+
...status && { status },
|
|
1593
|
+
summary: `Schema changed for ${messageType} ${sc.resourceChange.resourceId}`,
|
|
1594
|
+
message: {
|
|
1595
|
+
id: sc.resourceChange.resourceId,
|
|
1596
|
+
version: sc.resourceChange.version,
|
|
1597
|
+
type: messageType
|
|
1598
|
+
},
|
|
1599
|
+
schema: {
|
|
1600
|
+
beforeHash: sc.beforeSchemaHash ?? null,
|
|
1601
|
+
afterHash: sc.afterSchemaHash ?? null,
|
|
1602
|
+
beforePath: sc.beforeSchemaPath ?? null,
|
|
1603
|
+
afterPath: sc.afterSchemaPath ?? null
|
|
1604
|
+
},
|
|
1605
|
+
refs: {
|
|
1606
|
+
base: baseRef ?? null,
|
|
1607
|
+
target: targetRef ?? null
|
|
1608
|
+
},
|
|
1609
|
+
consumers: sc.consumerServices,
|
|
1610
|
+
producers: sc.producerServices
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
webhookCalls.push({
|
|
1614
|
+
urlTemplate: action.url,
|
|
1615
|
+
request: fetch(url, { method: "POST", headers, body: JSON.stringify(payload) })
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1442
1620
|
if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1443
1621
|
for (const dc of result.deprecationChanges) {
|
|
1444
1622
|
const messageType = messageTypes?.get(dc.resourceChange.resourceId) || "message";
|
|
@@ -1448,7 +1626,7 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1448
1626
|
specversion: "1.0",
|
|
1449
1627
|
type: `eventcatalog.governance.message_deprecated`,
|
|
1450
1628
|
source: "eventcatalog/governance",
|
|
1451
|
-
id: (0,
|
|
1629
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1452
1630
|
time: now,
|
|
1453
1631
|
datacontenttype: "application/json",
|
|
1454
1632
|
data: {
|
|
@@ -1483,7 +1661,7 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1483
1661
|
specversion: "1.0",
|
|
1484
1662
|
type: `eventcatalog.governance.${result.trigger}`,
|
|
1485
1663
|
source: "eventcatalog/governance",
|
|
1486
|
-
id: (0,
|
|
1664
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1487
1665
|
time: now,
|
|
1488
1666
|
datacontenttype: "application/json",
|
|
1489
1667
|
data: {
|
|
@@ -1531,7 +1709,14 @@ var formatGovernanceOutput = (results) => {
|
|
|
1531
1709
|
const lines = ["Governance:", ""];
|
|
1532
1710
|
for (const result of results) {
|
|
1533
1711
|
lines.push(` Rule "${result.rule.name}" triggered (${result.trigger}):`);
|
|
1534
|
-
if (result.
|
|
1712
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1713
|
+
for (const sc of result.schemaChanges) {
|
|
1714
|
+
const consumers = sc.consumerServices.length > 0 ? sc.consumerServices.map((c2) => c2.id).join(", ") : "no known consumers";
|
|
1715
|
+
lines.push(
|
|
1716
|
+
` ! Schema changed for ${sc.resourceChange.resourceId} (${sc.resourceChange.type}) \u2014 consumers: ${consumers}`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
} else if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1535
1720
|
for (const dc of result.deprecationChanges) {
|
|
1536
1721
|
const producers = dc.producerServices.length > 0 ? dc.producerServices.map((p) => p.id).join(", ") : "unknown producer";
|
|
1537
1722
|
lines.push(` ! ${dc.resourceChange.resourceId} (${dc.resourceChange.type}) deprecated by ${producers}`);
|
|
@@ -1554,7 +1739,7 @@ var import_node_child_process = require("child_process");
|
|
|
1554
1739
|
var import_node_fs6 = require("fs");
|
|
1555
1740
|
var import_node_os = require("os");
|
|
1556
1741
|
var import_dotenv = __toESM(require("dotenv"));
|
|
1557
|
-
var
|
|
1742
|
+
var import_sdk7 = __toESM(require("@eventcatalog/sdk"));
|
|
1558
1743
|
var import_license = require("@eventcatalog/license");
|
|
1559
1744
|
var BRANCH_NAME_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
1560
1745
|
var extractBranchToTempDir = (branch, catalogDir, tempDirs) => {
|
|
@@ -1588,15 +1773,17 @@ var governanceCheck = async (opts) => {
|
|
|
1588
1773
|
const baseTmpDir = extractBranchToTempDir(baseBranch, dir, tempDirs);
|
|
1589
1774
|
const baseSnapshotDir = trackTempDir("ec-snap-base-");
|
|
1590
1775
|
const targetSnapshotDir = trackTempDir("ec-snap-target-");
|
|
1591
|
-
const baseSDK = (0,
|
|
1776
|
+
const baseSDK = (0, import_sdk7.default)(baseTmpDir);
|
|
1592
1777
|
const baseResult = await baseSDK.createSnapshot({ label: `base-${baseBranch}`, outputDir: baseSnapshotDir });
|
|
1593
1778
|
let targetResult;
|
|
1779
|
+
let targetCatalogDir;
|
|
1594
1780
|
if (opts.target) {
|
|
1595
|
-
|
|
1596
|
-
const targetSDK = (0,
|
|
1781
|
+
targetCatalogDir = extractBranchToTempDir(opts.target, dir, tempDirs);
|
|
1782
|
+
const targetSDK = (0, import_sdk7.default)(targetCatalogDir);
|
|
1597
1783
|
targetResult = await targetSDK.createSnapshot({ label: `target-${opts.target}`, outputDir: targetSnapshotDir });
|
|
1598
1784
|
} else {
|
|
1599
|
-
|
|
1785
|
+
targetCatalogDir = dir;
|
|
1786
|
+
const targetSDK = (0, import_sdk7.default)(dir);
|
|
1600
1787
|
targetResult = await targetSDK.createSnapshot({ label: "current", outputDir: targetSnapshotDir });
|
|
1601
1788
|
}
|
|
1602
1789
|
const diff = await baseSDK.diffSnapshots(baseResult.filePath, targetResult.filePath);
|
|
@@ -1605,12 +1792,15 @@ var governanceCheck = async (opts) => {
|
|
|
1605
1792
|
return "No governance.yaml (or governance.yml) found or no rules defined.";
|
|
1606
1793
|
}
|
|
1607
1794
|
const results = evaluateGovernanceRules(diff, config, targetResult.snapshot, baseResult.snapshot);
|
|
1795
|
+
await enrichSchemaContent(results, baseTmpDir, targetCatalogDir);
|
|
1608
1796
|
const messageTypes = buildMessageTypeMap(targetResult.snapshot);
|
|
1609
1797
|
const serviceOwners = buildServiceOwnersMap(targetResult.snapshot);
|
|
1610
1798
|
const actionOutput = await executeGovernanceActions(results, {
|
|
1611
1799
|
messageTypes,
|
|
1612
1800
|
status: opts.status,
|
|
1613
|
-
serviceOwners
|
|
1801
|
+
serviceOwners,
|
|
1802
|
+
baseRef: baseBranch,
|
|
1803
|
+
targetRef: opts.target || "working-directory"
|
|
1614
1804
|
});
|
|
1615
1805
|
if (opts.format === "json") {
|
|
1616
1806
|
return JSON.stringify({ baseBranch, target: opts.target || "working directory", results, diff: diff.summary }, null, 2);
|