@eventcatalog/cli 0.5.3 → 0.5.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/cli/index.js +259 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +256 -22
- 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");
|
|
@@ -1261,7 +1264,15 @@ var loadGovernanceConfig = (catalogDir) => {
|
|
|
1261
1264
|
}
|
|
1262
1265
|
const content = import_node_fs5.default.readFileSync(configPath, "utf-8");
|
|
1263
1266
|
const parsed = import_js_yaml.default.load(content);
|
|
1264
|
-
|
|
1267
|
+
const rules = parsed?.rules || [];
|
|
1268
|
+
for (const rule of rules) {
|
|
1269
|
+
for (const action of rule.actions) {
|
|
1270
|
+
if (action.type === "fail" && action.message !== void 0 && typeof action.message !== "string") {
|
|
1271
|
+
throw new Error(`Invalid "message" in fail action for rule "${rule.name}". Must be a string.`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
return { rules };
|
|
1265
1276
|
};
|
|
1266
1277
|
var TRIGGER_FILTERS = {
|
|
1267
1278
|
consumer_added: (c2) => c2.direction === "receives" && c2.changeType === "added",
|
|
@@ -1309,15 +1320,16 @@ var buildMessageMap = (snapshot2) => {
|
|
|
1309
1320
|
for (const msg of snapshot2.resources.messages.queries) map.set(msg.id, msg);
|
|
1310
1321
|
return map;
|
|
1311
1322
|
};
|
|
1312
|
-
var
|
|
1323
|
+
var buildServiceIndex = (snapshot2, direction) => {
|
|
1313
1324
|
const index = /* @__PURE__ */ new Map();
|
|
1314
1325
|
for (const service of snapshot2.resources.services) {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1326
|
+
const pointers = service[direction];
|
|
1327
|
+
if (!pointers) continue;
|
|
1328
|
+
for (const pointer of pointers) {
|
|
1329
|
+
let entries = index.get(pointer.id);
|
|
1330
|
+
if (!entries) {
|
|
1331
|
+
entries = [];
|
|
1332
|
+
index.set(pointer.id, entries);
|
|
1321
1333
|
}
|
|
1322
1334
|
const entry = {
|
|
1323
1335
|
id: service.id,
|
|
@@ -1326,17 +1338,85 @@ var buildProducerIndex = (snapshot2) => {
|
|
|
1326
1338
|
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1327
1339
|
entry.owners = service.owners;
|
|
1328
1340
|
}
|
|
1329
|
-
|
|
1341
|
+
entries.push(entry);
|
|
1330
1342
|
}
|
|
1331
1343
|
}
|
|
1332
1344
|
return index;
|
|
1333
1345
|
};
|
|
1346
|
+
var getMessageTypeKey = (resourceId, type) => `${type}:${resourceId}`;
|
|
1347
|
+
var buildLatestMessageVersionMap = (snapshot2) => {
|
|
1348
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1349
|
+
for (const event of snapshot2.resources.messages.events) {
|
|
1350
|
+
versions.set(getMessageTypeKey(event.id, "event"), event.version);
|
|
1351
|
+
}
|
|
1352
|
+
for (const command of snapshot2.resources.messages.commands) {
|
|
1353
|
+
versions.set(getMessageTypeKey(command.id, "command"), command.version);
|
|
1354
|
+
}
|
|
1355
|
+
for (const query of snapshot2.resources.messages.queries) {
|
|
1356
|
+
versions.set(getMessageTypeKey(query.id, "query"), query.version);
|
|
1357
|
+
}
|
|
1358
|
+
return versions;
|
|
1359
|
+
};
|
|
1360
|
+
var getTargetMessageVersion = (resourceChange) => {
|
|
1361
|
+
if (resourceChange.changeType === "versioned") {
|
|
1362
|
+
return resourceChange.newVersion || resourceChange.version;
|
|
1363
|
+
}
|
|
1364
|
+
return resourceChange.version;
|
|
1365
|
+
};
|
|
1366
|
+
var pointerTargetsChangedVersion = (pointer, resourceChange, latestMessageVersions) => {
|
|
1367
|
+
if (pointer.id !== resourceChange.resourceId) return false;
|
|
1368
|
+
const targetVersion = getTargetMessageVersion(resourceChange);
|
|
1369
|
+
const pointerVersion = pointer.version;
|
|
1370
|
+
if (!pointerVersion || pointerVersion === "latest") {
|
|
1371
|
+
const latestVersion = latestMessageVersions.get(getMessageTypeKey(resourceChange.resourceId, resourceChange.type));
|
|
1372
|
+
if (!latestVersion) return true;
|
|
1373
|
+
return latestVersion === targetVersion;
|
|
1374
|
+
}
|
|
1375
|
+
if ((0, import_semver.validRange)(pointerVersion)) {
|
|
1376
|
+
try {
|
|
1377
|
+
return (0, import_semver.satisfies)(targetVersion, pointerVersion);
|
|
1378
|
+
} catch {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return pointerVersion === targetVersion;
|
|
1383
|
+
};
|
|
1384
|
+
var getServicesForSchemaChange = (snapshot2, direction, resourceChange, latestMessageVersions) => {
|
|
1385
|
+
const matches = [];
|
|
1386
|
+
for (const service of snapshot2.resources.services) {
|
|
1387
|
+
const pointers = service[direction];
|
|
1388
|
+
if (!pointers) continue;
|
|
1389
|
+
const hasMatch = pointers.some((pointer) => pointerTargetsChangedVersion(pointer, resourceChange, latestMessageVersions));
|
|
1390
|
+
if (!hasMatch) continue;
|
|
1391
|
+
const entry = {
|
|
1392
|
+
id: service.id,
|
|
1393
|
+
version: service.version
|
|
1394
|
+
};
|
|
1395
|
+
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1396
|
+
entry.owners = service.owners;
|
|
1397
|
+
}
|
|
1398
|
+
matches.push(entry);
|
|
1399
|
+
}
|
|
1400
|
+
return matches;
|
|
1401
|
+
};
|
|
1402
|
+
var matchesSchemaChangeResource = (schemaChange, resources) => {
|
|
1403
|
+
return resources.some((resource) => {
|
|
1404
|
+
if (resource === "*") return true;
|
|
1405
|
+
if (resource.startsWith("message:")) return schemaChange.resourceChange.resourceId === resource.slice(8);
|
|
1406
|
+
if (resource.startsWith("consumes:"))
|
|
1407
|
+
return schemaChange.consumerServices.some((service) => service.id === resource.slice(9));
|
|
1408
|
+
if (resource.startsWith("produces:"))
|
|
1409
|
+
return schemaChange.producerServices.some((service) => service.id === resource.slice(9));
|
|
1410
|
+
if (resource.startsWith("service:")) return schemaChange.producerServices.some((service) => service.id === resource.slice(8));
|
|
1411
|
+
return false;
|
|
1412
|
+
});
|
|
1413
|
+
};
|
|
1334
1414
|
var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets, baseSnapshot) => {
|
|
1335
1415
|
const deprecationRules = config.rules.filter((rule) => rule.when.includes("message_deprecated"));
|
|
1336
1416
|
if (deprecationRules.length === 0) return [];
|
|
1337
1417
|
const targetMessages = buildMessageMap(targetSnapshot);
|
|
1338
1418
|
const baseMessages = baseSnapshot ? buildMessageMap(baseSnapshot) : void 0;
|
|
1339
|
-
const producerIndex =
|
|
1419
|
+
const producerIndex = buildServiceIndex(targetSnapshot, "sends");
|
|
1340
1420
|
const deprecatedResources = diff.resources.filter((rc) => {
|
|
1341
1421
|
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1342
1422
|
if (!rc.changedFields?.includes("deprecated")) return false;
|
|
@@ -1363,6 +1443,29 @@ var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets,
|
|
|
1363
1443
|
}
|
|
1364
1444
|
return results;
|
|
1365
1445
|
};
|
|
1446
|
+
var evaluateSchemaChangeRules = (diff, config, targetSnapshot) => {
|
|
1447
|
+
const schemaRules = config.rules.filter((rule) => rule.when.includes("schema_changed"));
|
|
1448
|
+
if (schemaRules.length === 0) return [];
|
|
1449
|
+
const schemaChangedResources = diff.resources.filter((rc) => {
|
|
1450
|
+
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1451
|
+
return rc.changedFields?.includes("schemaHash");
|
|
1452
|
+
});
|
|
1453
|
+
if (schemaChangedResources.length === 0) return [];
|
|
1454
|
+
const latestMessageVersions = buildLatestMessageVersionMap(targetSnapshot);
|
|
1455
|
+
const schemaChanges = schemaChangedResources.map((resourceChange) => ({
|
|
1456
|
+
resourceChange,
|
|
1457
|
+
producerServices: getServicesForSchemaChange(targetSnapshot, "sends", resourceChange, latestMessageVersions),
|
|
1458
|
+
consumerServices: getServicesForSchemaChange(targetSnapshot, "receives", resourceChange, latestMessageVersions)
|
|
1459
|
+
}));
|
|
1460
|
+
const results = [];
|
|
1461
|
+
for (const rule of schemaRules) {
|
|
1462
|
+
const matched = schemaChanges.filter((schemaChange) => matchesSchemaChangeResource(schemaChange, rule.resources));
|
|
1463
|
+
if (matched.length > 0) {
|
|
1464
|
+
results.push({ rule, trigger: "schema_changed", matchedChanges: [], schemaChanges: matched });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return results;
|
|
1468
|
+
};
|
|
1366
1469
|
var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
1367
1470
|
const results = [];
|
|
1368
1471
|
const targetMessageSets = targetSnapshot ? buildServiceMessageSets(targetSnapshot) : void 0;
|
|
@@ -1382,6 +1485,7 @@ var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
|
1382
1485
|
}
|
|
1383
1486
|
if (targetSnapshot && targetMessageSets) {
|
|
1384
1487
|
results.push(...evaluateDeprecationRules(diff, config, targetSnapshot, targetMessageSets, baseSnapshot));
|
|
1488
|
+
results.push(...evaluateSchemaChangeRules(diff, config, targetSnapshot));
|
|
1385
1489
|
}
|
|
1386
1490
|
return results;
|
|
1387
1491
|
};
|
|
@@ -1400,9 +1504,51 @@ var resolveEnvVars = (value) => {
|
|
|
1400
1504
|
return envValue;
|
|
1401
1505
|
});
|
|
1402
1506
|
};
|
|
1507
|
+
var readSchemaDetails = async (sdk, resourceId, version2, type) => {
|
|
1508
|
+
if (!MESSAGE_RESOURCE_TYPES.has(type)) return {};
|
|
1509
|
+
try {
|
|
1510
|
+
const schema = await sdk.getSchemaForMessage(resourceId, version2);
|
|
1511
|
+
if (!schema) return {};
|
|
1512
|
+
return {
|
|
1513
|
+
content: schema.schema,
|
|
1514
|
+
schemaPath: schema.fileName,
|
|
1515
|
+
schemaHash: (0, import_node_crypto2.createHash)("sha256").update(schema.schema).digest("hex")
|
|
1516
|
+
};
|
|
1517
|
+
} catch {
|
|
1518
|
+
return {};
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir) => {
|
|
1522
|
+
const baseSDK = (0, import_sdk6.default)(baseCatalogDir);
|
|
1523
|
+
const targetSDK = (0, import_sdk6.default)(targetCatalogDir);
|
|
1524
|
+
const promises = [];
|
|
1525
|
+
for (const result of results) {
|
|
1526
|
+
if (!result.schemaChanges) continue;
|
|
1527
|
+
for (const sc of result.schemaChanges) {
|
|
1528
|
+
const { resourceId, version: version2, type, changeType, previousVersion, newVersion } = sc.resourceChange;
|
|
1529
|
+
const baseVersion = changeType === "versioned" ? previousVersion || version2 : version2;
|
|
1530
|
+
const targetVersion = changeType === "versioned" ? newVersion || version2 : version2;
|
|
1531
|
+
promises.push(
|
|
1532
|
+
(async () => {
|
|
1533
|
+
const [before, after] = await Promise.all([
|
|
1534
|
+
readSchemaDetails(baseSDK, resourceId, baseVersion, type),
|
|
1535
|
+
readSchemaDetails(targetSDK, resourceId, targetVersion, type)
|
|
1536
|
+
]);
|
|
1537
|
+
sc.before = before.content;
|
|
1538
|
+
sc.after = after.content;
|
|
1539
|
+
sc.beforeSchemaPath = before.schemaPath;
|
|
1540
|
+
sc.afterSchemaPath = after.schemaPath;
|
|
1541
|
+
sc.beforeSchemaHash = before.schemaHash;
|
|
1542
|
+
sc.afterSchemaHash = after.schemaHash;
|
|
1543
|
+
})()
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
await Promise.all(promises);
|
|
1548
|
+
};
|
|
1403
1549
|
|
|
1404
1550
|
// src/cli/governance/actions.ts
|
|
1405
|
-
var
|
|
1551
|
+
var import_node_crypto3 = require("crypto");
|
|
1406
1552
|
var buildMessageTypeMap = (snapshot2) => {
|
|
1407
1553
|
const map = /* @__PURE__ */ new Map();
|
|
1408
1554
|
for (const event of snapshot2.resources.messages.events) {
|
|
@@ -1426,7 +1572,7 @@ var buildServiceOwnersMap = (snapshot2) => {
|
|
|
1426
1572
|
return map;
|
|
1427
1573
|
};
|
|
1428
1574
|
var executeGovernanceActions = async (results, opts = {}) => {
|
|
1429
|
-
const { messageTypes, status, serviceOwners } = opts;
|
|
1575
|
+
const { messageTypes, status, serviceOwners, baseRef, targetRef } = opts;
|
|
1430
1576
|
const webhookCalls = [];
|
|
1431
1577
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1432
1578
|
for (const result of results) {
|
|
@@ -1439,6 +1585,46 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1439
1585
|
headers[key] = resolveEnvVars(value);
|
|
1440
1586
|
}
|
|
1441
1587
|
}
|
|
1588
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1589
|
+
for (const sc of result.schemaChanges) {
|
|
1590
|
+
const messageType = messageTypes?.get(sc.resourceChange.resourceId) || "message";
|
|
1591
|
+
const payload = {
|
|
1592
|
+
specversion: "1.0",
|
|
1593
|
+
type: "eventcatalog.governance.schema_changed",
|
|
1594
|
+
source: "eventcatalog/governance",
|
|
1595
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1596
|
+
time: now,
|
|
1597
|
+
datacontenttype: "application/json",
|
|
1598
|
+
data: {
|
|
1599
|
+
schemaVersion: 1,
|
|
1600
|
+
...status && { status },
|
|
1601
|
+
summary: `Schema changed for ${messageType} ${sc.resourceChange.resourceId}`,
|
|
1602
|
+
message: {
|
|
1603
|
+
id: sc.resourceChange.resourceId,
|
|
1604
|
+
version: sc.resourceChange.version,
|
|
1605
|
+
type: messageType
|
|
1606
|
+
},
|
|
1607
|
+
schema: {
|
|
1608
|
+
beforeHash: sc.beforeSchemaHash ?? null,
|
|
1609
|
+
afterHash: sc.afterSchemaHash ?? null,
|
|
1610
|
+
beforePath: sc.beforeSchemaPath ?? null,
|
|
1611
|
+
afterPath: sc.afterSchemaPath ?? null
|
|
1612
|
+
},
|
|
1613
|
+
refs: {
|
|
1614
|
+
base: baseRef ?? null,
|
|
1615
|
+
target: targetRef ?? null
|
|
1616
|
+
},
|
|
1617
|
+
consumers: sc.consumerServices,
|
|
1618
|
+
producers: sc.producerServices
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
webhookCalls.push({
|
|
1622
|
+
urlTemplate: action.url,
|
|
1623
|
+
request: fetch(url, { method: "POST", headers, body: JSON.stringify(payload) })
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1442
1628
|
if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1443
1629
|
for (const dc of result.deprecationChanges) {
|
|
1444
1630
|
const messageType = messageTypes?.get(dc.resourceChange.resourceId) || "message";
|
|
@@ -1448,7 +1634,7 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1448
1634
|
specversion: "1.0",
|
|
1449
1635
|
type: `eventcatalog.governance.message_deprecated`,
|
|
1450
1636
|
source: "eventcatalog/governance",
|
|
1451
|
-
id: (0,
|
|
1637
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1452
1638
|
time: now,
|
|
1453
1639
|
datacontenttype: "application/json",
|
|
1454
1640
|
data: {
|
|
@@ -1483,7 +1669,7 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1483
1669
|
specversion: "1.0",
|
|
1484
1670
|
type: `eventcatalog.governance.${result.trigger}`,
|
|
1485
1671
|
source: "eventcatalog/governance",
|
|
1486
|
-
id: (0,
|
|
1672
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
1487
1673
|
time: now,
|
|
1488
1674
|
datacontenttype: "application/json",
|
|
1489
1675
|
data: {
|
|
@@ -1531,7 +1717,14 @@ var formatGovernanceOutput = (results) => {
|
|
|
1531
1717
|
const lines = ["Governance:", ""];
|
|
1532
1718
|
for (const result of results) {
|
|
1533
1719
|
lines.push(` Rule "${result.rule.name}" triggered (${result.trigger}):`);
|
|
1534
|
-
if (result.
|
|
1720
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1721
|
+
for (const sc of result.schemaChanges) {
|
|
1722
|
+
const consumers = sc.consumerServices.length > 0 ? sc.consumerServices.map((c2) => c2.id).join(", ") : "no known consumers";
|
|
1723
|
+
lines.push(
|
|
1724
|
+
` ! Schema changed for ${sc.resourceChange.resourceId} (${sc.resourceChange.type}) \u2014 consumers: ${consumers}`
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
} else if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1535
1728
|
for (const dc of result.deprecationChanges) {
|
|
1536
1729
|
const producers = dc.producerServices.length > 0 ? dc.producerServices.map((p) => p.id).join(", ") : "unknown producer";
|
|
1537
1730
|
lines.push(` ! ${dc.resourceChange.resourceId} (${dc.resourceChange.type}) deprecated by ${producers}`);
|
|
@@ -1547,6 +1740,17 @@ var formatGovernanceOutput = (results) => {
|
|
|
1547
1740
|
}
|
|
1548
1741
|
return lines.join("\n");
|
|
1549
1742
|
};
|
|
1743
|
+
var formatFailureOutput = (failures) => {
|
|
1744
|
+
if (failures.length === 0) return "";
|
|
1745
|
+
const lines = [];
|
|
1746
|
+
for (const f of failures) {
|
|
1747
|
+
lines.push(`FAILED: ${f.ruleName}`);
|
|
1748
|
+
for (const msg of f.messages) {
|
|
1749
|
+
lines.push(` ${msg}`);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return lines.join("\n");
|
|
1753
|
+
};
|
|
1550
1754
|
|
|
1551
1755
|
// src/cli/governance/check.ts
|
|
1552
1756
|
var import_node_path5 = __toESM(require("path"));
|
|
@@ -1554,7 +1758,7 @@ var import_node_child_process = require("child_process");
|
|
|
1554
1758
|
var import_node_fs6 = require("fs");
|
|
1555
1759
|
var import_node_os = require("os");
|
|
1556
1760
|
var import_dotenv = __toESM(require("dotenv"));
|
|
1557
|
-
var
|
|
1761
|
+
var import_sdk7 = __toESM(require("@eventcatalog/sdk"));
|
|
1558
1762
|
var import_license = require("@eventcatalog/license");
|
|
1559
1763
|
var BRANCH_NAME_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
1560
1764
|
var extractBranchToTempDir = (branch, catalogDir, tempDirs) => {
|
|
@@ -1588,32 +1792,56 @@ var governanceCheck = async (opts) => {
|
|
|
1588
1792
|
const baseTmpDir = extractBranchToTempDir(baseBranch, dir, tempDirs);
|
|
1589
1793
|
const baseSnapshotDir = trackTempDir("ec-snap-base-");
|
|
1590
1794
|
const targetSnapshotDir = trackTempDir("ec-snap-target-");
|
|
1591
|
-
const baseSDK = (0,
|
|
1795
|
+
const baseSDK = (0, import_sdk7.default)(baseTmpDir);
|
|
1592
1796
|
const baseResult = await baseSDK.createSnapshot({ label: `base-${baseBranch}`, outputDir: baseSnapshotDir });
|
|
1593
1797
|
let targetResult;
|
|
1798
|
+
let targetCatalogDir;
|
|
1594
1799
|
if (opts.target) {
|
|
1595
|
-
|
|
1596
|
-
const targetSDK = (0,
|
|
1800
|
+
targetCatalogDir = extractBranchToTempDir(opts.target, dir, tempDirs);
|
|
1801
|
+
const targetSDK = (0, import_sdk7.default)(targetCatalogDir);
|
|
1597
1802
|
targetResult = await targetSDK.createSnapshot({ label: `target-${opts.target}`, outputDir: targetSnapshotDir });
|
|
1598
1803
|
} else {
|
|
1599
|
-
|
|
1804
|
+
targetCatalogDir = dir;
|
|
1805
|
+
const targetSDK = (0, import_sdk7.default)(dir);
|
|
1600
1806
|
targetResult = await targetSDK.createSnapshot({ label: "current", outputDir: targetSnapshotDir });
|
|
1601
1807
|
}
|
|
1602
1808
|
const diff = await baseSDK.diffSnapshots(baseResult.filePath, targetResult.filePath);
|
|
1603
1809
|
const config = loadGovernanceConfig(dir);
|
|
1604
1810
|
if (config.rules.length === 0) {
|
|
1605
|
-
return "No governance.yaml (or governance.yml) found or no rules defined.";
|
|
1811
|
+
return { output: "No governance.yaml (or governance.yml) found or no rules defined.", exitCode: 0, failures: [] };
|
|
1606
1812
|
}
|
|
1607
1813
|
const results = evaluateGovernanceRules(diff, config, targetResult.snapshot, baseResult.snapshot);
|
|
1814
|
+
for (const result of results) {
|
|
1815
|
+
const failActions = result.rule.actions.filter((a) => a.type === "fail");
|
|
1816
|
+
if (failActions.length > 0) {
|
|
1817
|
+
result.failed = true;
|
|
1818
|
+
result.failMessages = failActions.map((a) => "message" in a && a.message ? resolveEnvVars(a.message) : void 0).filter((m) => m !== void 0);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
await enrichSchemaContent(results, baseTmpDir, targetCatalogDir);
|
|
1608
1822
|
const messageTypes = buildMessageTypeMap(targetResult.snapshot);
|
|
1609
1823
|
const serviceOwners = buildServiceOwnersMap(targetResult.snapshot);
|
|
1610
1824
|
const actionOutput = await executeGovernanceActions(results, {
|
|
1611
1825
|
messageTypes,
|
|
1612
1826
|
status: opts.status,
|
|
1613
|
-
serviceOwners
|
|
1827
|
+
serviceOwners,
|
|
1828
|
+
baseRef: baseBranch,
|
|
1829
|
+
targetRef: opts.target || "working-directory"
|
|
1614
1830
|
});
|
|
1831
|
+
const failures = results.filter((r) => r.failed).map((r) => ({ ruleName: r.rule.name, messages: r.failMessages || [] }));
|
|
1615
1832
|
if (opts.format === "json") {
|
|
1616
|
-
|
|
1833
|
+
const jsonOutput = {
|
|
1834
|
+
baseBranch,
|
|
1835
|
+
target: opts.target || "working directory",
|
|
1836
|
+
results,
|
|
1837
|
+
summary: {
|
|
1838
|
+
rulesTriggered: results.length,
|
|
1839
|
+
failures: failures.length,
|
|
1840
|
+
passed: failures.length === 0
|
|
1841
|
+
},
|
|
1842
|
+
diff: diff.summary
|
|
1843
|
+
};
|
|
1844
|
+
return { output: JSON.stringify(jsonOutput, null, 2), exitCode: failures.length > 0 ? 1 : 0, failures };
|
|
1617
1845
|
}
|
|
1618
1846
|
const targetLabel = opts.target || "working directory";
|
|
1619
1847
|
const lines = [`Governance check: comparing ${targetLabel} against ${baseBranch}`, ""];
|
|
@@ -1629,7 +1857,12 @@ var governanceCheck = async (opts) => {
|
|
|
1629
1857
|
lines.push("");
|
|
1630
1858
|
lines.push(parts.join(", ") + ".");
|
|
1631
1859
|
}
|
|
1632
|
-
|
|
1860
|
+
const failureOutput = formatFailureOutput(failures);
|
|
1861
|
+
if (failureOutput) {
|
|
1862
|
+
lines.push("");
|
|
1863
|
+
lines.push(failureOutput);
|
|
1864
|
+
}
|
|
1865
|
+
return { output: lines.join("\n"), exitCode: failures.length > 0 ? 1 : 0, failures };
|
|
1633
1866
|
} finally {
|
|
1634
1867
|
for (const d of tempDirs) {
|
|
1635
1868
|
(0, import_node_fs6.rmSync)(d, { recursive: true, force: true });
|
|
@@ -1755,7 +1988,8 @@ governance.command("check").description("Compare catalog against a base branch a
|
|
|
1755
1988
|
status: opts.status,
|
|
1756
1989
|
dir
|
|
1757
1990
|
});
|
|
1758
|
-
console.log(result);
|
|
1991
|
+
console.log(result.output);
|
|
1992
|
+
process.exitCode = result.exitCode;
|
|
1759
1993
|
} catch (error) {
|
|
1760
1994
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1761
1995
|
process.exit(1);
|