@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.mjs
CHANGED
|
@@ -1235,7 +1235,10 @@ var snapshotList = async (opts) => {
|
|
|
1235
1235
|
// src/cli/governance/rules.ts
|
|
1236
1236
|
import fs from "fs";
|
|
1237
1237
|
import path2 from "path";
|
|
1238
|
+
import { createHash } from "crypto";
|
|
1238
1239
|
import yaml from "js-yaml";
|
|
1240
|
+
import { satisfies, validRange } from "semver";
|
|
1241
|
+
import createSDK6 from "@eventcatalog/sdk";
|
|
1239
1242
|
var loadGovernanceConfig = (catalogDir) => {
|
|
1240
1243
|
const yamlPath = path2.join(catalogDir, "governance.yaml");
|
|
1241
1244
|
const ymlPath = path2.join(catalogDir, "governance.yml");
|
|
@@ -1245,7 +1248,15 @@ var loadGovernanceConfig = (catalogDir) => {
|
|
|
1245
1248
|
}
|
|
1246
1249
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
1247
1250
|
const parsed = yaml.load(content);
|
|
1248
|
-
|
|
1251
|
+
const rules = parsed?.rules || [];
|
|
1252
|
+
for (const rule of rules) {
|
|
1253
|
+
for (const action of rule.actions) {
|
|
1254
|
+
if (action.type === "fail" && action.message !== void 0 && typeof action.message !== "string") {
|
|
1255
|
+
throw new Error(`Invalid "message" in fail action for rule "${rule.name}". Must be a string.`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return { rules };
|
|
1249
1260
|
};
|
|
1250
1261
|
var TRIGGER_FILTERS = {
|
|
1251
1262
|
consumer_added: (c2) => c2.direction === "receives" && c2.changeType === "added",
|
|
@@ -1293,15 +1304,16 @@ var buildMessageMap = (snapshot2) => {
|
|
|
1293
1304
|
for (const msg of snapshot2.resources.messages.queries) map.set(msg.id, msg);
|
|
1294
1305
|
return map;
|
|
1295
1306
|
};
|
|
1296
|
-
var
|
|
1307
|
+
var buildServiceIndex = (snapshot2, direction) => {
|
|
1297
1308
|
const index = /* @__PURE__ */ new Map();
|
|
1298
1309
|
for (const service of snapshot2.resources.services) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1310
|
+
const pointers = service[direction];
|
|
1311
|
+
if (!pointers) continue;
|
|
1312
|
+
for (const pointer of pointers) {
|
|
1313
|
+
let entries = index.get(pointer.id);
|
|
1314
|
+
if (!entries) {
|
|
1315
|
+
entries = [];
|
|
1316
|
+
index.set(pointer.id, entries);
|
|
1305
1317
|
}
|
|
1306
1318
|
const entry = {
|
|
1307
1319
|
id: service.id,
|
|
@@ -1310,17 +1322,85 @@ var buildProducerIndex = (snapshot2) => {
|
|
|
1310
1322
|
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1311
1323
|
entry.owners = service.owners;
|
|
1312
1324
|
}
|
|
1313
|
-
|
|
1325
|
+
entries.push(entry);
|
|
1314
1326
|
}
|
|
1315
1327
|
}
|
|
1316
1328
|
return index;
|
|
1317
1329
|
};
|
|
1330
|
+
var getMessageTypeKey = (resourceId, type) => `${type}:${resourceId}`;
|
|
1331
|
+
var buildLatestMessageVersionMap = (snapshot2) => {
|
|
1332
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1333
|
+
for (const event of snapshot2.resources.messages.events) {
|
|
1334
|
+
versions.set(getMessageTypeKey(event.id, "event"), event.version);
|
|
1335
|
+
}
|
|
1336
|
+
for (const command of snapshot2.resources.messages.commands) {
|
|
1337
|
+
versions.set(getMessageTypeKey(command.id, "command"), command.version);
|
|
1338
|
+
}
|
|
1339
|
+
for (const query of snapshot2.resources.messages.queries) {
|
|
1340
|
+
versions.set(getMessageTypeKey(query.id, "query"), query.version);
|
|
1341
|
+
}
|
|
1342
|
+
return versions;
|
|
1343
|
+
};
|
|
1344
|
+
var getTargetMessageVersion = (resourceChange) => {
|
|
1345
|
+
if (resourceChange.changeType === "versioned") {
|
|
1346
|
+
return resourceChange.newVersion || resourceChange.version;
|
|
1347
|
+
}
|
|
1348
|
+
return resourceChange.version;
|
|
1349
|
+
};
|
|
1350
|
+
var pointerTargetsChangedVersion = (pointer, resourceChange, latestMessageVersions) => {
|
|
1351
|
+
if (pointer.id !== resourceChange.resourceId) return false;
|
|
1352
|
+
const targetVersion = getTargetMessageVersion(resourceChange);
|
|
1353
|
+
const pointerVersion = pointer.version;
|
|
1354
|
+
if (!pointerVersion || pointerVersion === "latest") {
|
|
1355
|
+
const latestVersion = latestMessageVersions.get(getMessageTypeKey(resourceChange.resourceId, resourceChange.type));
|
|
1356
|
+
if (!latestVersion) return true;
|
|
1357
|
+
return latestVersion === targetVersion;
|
|
1358
|
+
}
|
|
1359
|
+
if (validRange(pointerVersion)) {
|
|
1360
|
+
try {
|
|
1361
|
+
return satisfies(targetVersion, pointerVersion);
|
|
1362
|
+
} catch {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
return pointerVersion === targetVersion;
|
|
1367
|
+
};
|
|
1368
|
+
var getServicesForSchemaChange = (snapshot2, direction, resourceChange, latestMessageVersions) => {
|
|
1369
|
+
const matches = [];
|
|
1370
|
+
for (const service of snapshot2.resources.services) {
|
|
1371
|
+
const pointers = service[direction];
|
|
1372
|
+
if (!pointers) continue;
|
|
1373
|
+
const hasMatch = pointers.some((pointer) => pointerTargetsChangedVersion(pointer, resourceChange, latestMessageVersions));
|
|
1374
|
+
if (!hasMatch) continue;
|
|
1375
|
+
const entry = {
|
|
1376
|
+
id: service.id,
|
|
1377
|
+
version: service.version
|
|
1378
|
+
};
|
|
1379
|
+
if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
|
|
1380
|
+
entry.owners = service.owners;
|
|
1381
|
+
}
|
|
1382
|
+
matches.push(entry);
|
|
1383
|
+
}
|
|
1384
|
+
return matches;
|
|
1385
|
+
};
|
|
1386
|
+
var matchesSchemaChangeResource = (schemaChange, resources) => {
|
|
1387
|
+
return resources.some((resource) => {
|
|
1388
|
+
if (resource === "*") return true;
|
|
1389
|
+
if (resource.startsWith("message:")) return schemaChange.resourceChange.resourceId === resource.slice(8);
|
|
1390
|
+
if (resource.startsWith("consumes:"))
|
|
1391
|
+
return schemaChange.consumerServices.some((service) => service.id === resource.slice(9));
|
|
1392
|
+
if (resource.startsWith("produces:"))
|
|
1393
|
+
return schemaChange.producerServices.some((service) => service.id === resource.slice(9));
|
|
1394
|
+
if (resource.startsWith("service:")) return schemaChange.producerServices.some((service) => service.id === resource.slice(8));
|
|
1395
|
+
return false;
|
|
1396
|
+
});
|
|
1397
|
+
};
|
|
1318
1398
|
var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets, baseSnapshot) => {
|
|
1319
1399
|
const deprecationRules = config.rules.filter((rule) => rule.when.includes("message_deprecated"));
|
|
1320
1400
|
if (deprecationRules.length === 0) return [];
|
|
1321
1401
|
const targetMessages = buildMessageMap(targetSnapshot);
|
|
1322
1402
|
const baseMessages = baseSnapshot ? buildMessageMap(baseSnapshot) : void 0;
|
|
1323
|
-
const producerIndex =
|
|
1403
|
+
const producerIndex = buildServiceIndex(targetSnapshot, "sends");
|
|
1324
1404
|
const deprecatedResources = diff.resources.filter((rc) => {
|
|
1325
1405
|
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1326
1406
|
if (!rc.changedFields?.includes("deprecated")) return false;
|
|
@@ -1347,6 +1427,29 @@ var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets,
|
|
|
1347
1427
|
}
|
|
1348
1428
|
return results;
|
|
1349
1429
|
};
|
|
1430
|
+
var evaluateSchemaChangeRules = (diff, config, targetSnapshot) => {
|
|
1431
|
+
const schemaRules = config.rules.filter((rule) => rule.when.includes("schema_changed"));
|
|
1432
|
+
if (schemaRules.length === 0) return [];
|
|
1433
|
+
const schemaChangedResources = diff.resources.filter((rc) => {
|
|
1434
|
+
if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
|
|
1435
|
+
return rc.changedFields?.includes("schemaHash");
|
|
1436
|
+
});
|
|
1437
|
+
if (schemaChangedResources.length === 0) return [];
|
|
1438
|
+
const latestMessageVersions = buildLatestMessageVersionMap(targetSnapshot);
|
|
1439
|
+
const schemaChanges = schemaChangedResources.map((resourceChange) => ({
|
|
1440
|
+
resourceChange,
|
|
1441
|
+
producerServices: getServicesForSchemaChange(targetSnapshot, "sends", resourceChange, latestMessageVersions),
|
|
1442
|
+
consumerServices: getServicesForSchemaChange(targetSnapshot, "receives", resourceChange, latestMessageVersions)
|
|
1443
|
+
}));
|
|
1444
|
+
const results = [];
|
|
1445
|
+
for (const rule of schemaRules) {
|
|
1446
|
+
const matched = schemaChanges.filter((schemaChange) => matchesSchemaChangeResource(schemaChange, rule.resources));
|
|
1447
|
+
if (matched.length > 0) {
|
|
1448
|
+
results.push({ rule, trigger: "schema_changed", matchedChanges: [], schemaChanges: matched });
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return results;
|
|
1452
|
+
};
|
|
1350
1453
|
var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
1351
1454
|
const results = [];
|
|
1352
1455
|
const targetMessageSets = targetSnapshot ? buildServiceMessageSets(targetSnapshot) : void 0;
|
|
@@ -1366,6 +1469,7 @@ var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
|
|
|
1366
1469
|
}
|
|
1367
1470
|
if (targetSnapshot && targetMessageSets) {
|
|
1368
1471
|
results.push(...evaluateDeprecationRules(diff, config, targetSnapshot, targetMessageSets, baseSnapshot));
|
|
1472
|
+
results.push(...evaluateSchemaChangeRules(diff, config, targetSnapshot));
|
|
1369
1473
|
}
|
|
1370
1474
|
return results;
|
|
1371
1475
|
};
|
|
@@ -1384,6 +1488,48 @@ var resolveEnvVars = (value) => {
|
|
|
1384
1488
|
return envValue;
|
|
1385
1489
|
});
|
|
1386
1490
|
};
|
|
1491
|
+
var readSchemaDetails = async (sdk, resourceId, version2, type) => {
|
|
1492
|
+
if (!MESSAGE_RESOURCE_TYPES.has(type)) return {};
|
|
1493
|
+
try {
|
|
1494
|
+
const schema = await sdk.getSchemaForMessage(resourceId, version2);
|
|
1495
|
+
if (!schema) return {};
|
|
1496
|
+
return {
|
|
1497
|
+
content: schema.schema,
|
|
1498
|
+
schemaPath: schema.fileName,
|
|
1499
|
+
schemaHash: createHash("sha256").update(schema.schema).digest("hex")
|
|
1500
|
+
};
|
|
1501
|
+
} catch {
|
|
1502
|
+
return {};
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
var enrichSchemaContent = async (results, baseCatalogDir, targetCatalogDir) => {
|
|
1506
|
+
const baseSDK = createSDK6(baseCatalogDir);
|
|
1507
|
+
const targetSDK = createSDK6(targetCatalogDir);
|
|
1508
|
+
const promises = [];
|
|
1509
|
+
for (const result of results) {
|
|
1510
|
+
if (!result.schemaChanges) continue;
|
|
1511
|
+
for (const sc of result.schemaChanges) {
|
|
1512
|
+
const { resourceId, version: version2, type, changeType, previousVersion, newVersion } = sc.resourceChange;
|
|
1513
|
+
const baseVersion = changeType === "versioned" ? previousVersion || version2 : version2;
|
|
1514
|
+
const targetVersion = changeType === "versioned" ? newVersion || version2 : version2;
|
|
1515
|
+
promises.push(
|
|
1516
|
+
(async () => {
|
|
1517
|
+
const [before, after] = await Promise.all([
|
|
1518
|
+
readSchemaDetails(baseSDK, resourceId, baseVersion, type),
|
|
1519
|
+
readSchemaDetails(targetSDK, resourceId, targetVersion, type)
|
|
1520
|
+
]);
|
|
1521
|
+
sc.before = before.content;
|
|
1522
|
+
sc.after = after.content;
|
|
1523
|
+
sc.beforeSchemaPath = before.schemaPath;
|
|
1524
|
+
sc.afterSchemaPath = after.schemaPath;
|
|
1525
|
+
sc.beforeSchemaHash = before.schemaHash;
|
|
1526
|
+
sc.afterSchemaHash = after.schemaHash;
|
|
1527
|
+
})()
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
await Promise.all(promises);
|
|
1532
|
+
};
|
|
1387
1533
|
|
|
1388
1534
|
// src/cli/governance/actions.ts
|
|
1389
1535
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -1410,7 +1556,7 @@ var buildServiceOwnersMap = (snapshot2) => {
|
|
|
1410
1556
|
return map;
|
|
1411
1557
|
};
|
|
1412
1558
|
var executeGovernanceActions = async (results, opts = {}) => {
|
|
1413
|
-
const { messageTypes, status, serviceOwners } = opts;
|
|
1559
|
+
const { messageTypes, status, serviceOwners, baseRef, targetRef } = opts;
|
|
1414
1560
|
const webhookCalls = [];
|
|
1415
1561
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1416
1562
|
for (const result of results) {
|
|
@@ -1423,6 +1569,46 @@ var executeGovernanceActions = async (results, opts = {}) => {
|
|
|
1423
1569
|
headers[key] = resolveEnvVars(value);
|
|
1424
1570
|
}
|
|
1425
1571
|
}
|
|
1572
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1573
|
+
for (const sc of result.schemaChanges) {
|
|
1574
|
+
const messageType = messageTypes?.get(sc.resourceChange.resourceId) || "message";
|
|
1575
|
+
const payload = {
|
|
1576
|
+
specversion: "1.0",
|
|
1577
|
+
type: "eventcatalog.governance.schema_changed",
|
|
1578
|
+
source: "eventcatalog/governance",
|
|
1579
|
+
id: randomUUID2(),
|
|
1580
|
+
time: now,
|
|
1581
|
+
datacontenttype: "application/json",
|
|
1582
|
+
data: {
|
|
1583
|
+
schemaVersion: 1,
|
|
1584
|
+
...status && { status },
|
|
1585
|
+
summary: `Schema changed for ${messageType} ${sc.resourceChange.resourceId}`,
|
|
1586
|
+
message: {
|
|
1587
|
+
id: sc.resourceChange.resourceId,
|
|
1588
|
+
version: sc.resourceChange.version,
|
|
1589
|
+
type: messageType
|
|
1590
|
+
},
|
|
1591
|
+
schema: {
|
|
1592
|
+
beforeHash: sc.beforeSchemaHash ?? null,
|
|
1593
|
+
afterHash: sc.afterSchemaHash ?? null,
|
|
1594
|
+
beforePath: sc.beforeSchemaPath ?? null,
|
|
1595
|
+
afterPath: sc.afterSchemaPath ?? null
|
|
1596
|
+
},
|
|
1597
|
+
refs: {
|
|
1598
|
+
base: baseRef ?? null,
|
|
1599
|
+
target: targetRef ?? null
|
|
1600
|
+
},
|
|
1601
|
+
consumers: sc.consumerServices,
|
|
1602
|
+
producers: sc.producerServices
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
webhookCalls.push({
|
|
1606
|
+
urlTemplate: action.url,
|
|
1607
|
+
request: fetch(url, { method: "POST", headers, body: JSON.stringify(payload) })
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
continue;
|
|
1611
|
+
}
|
|
1426
1612
|
if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1427
1613
|
for (const dc of result.deprecationChanges) {
|
|
1428
1614
|
const messageType = messageTypes?.get(dc.resourceChange.resourceId) || "message";
|
|
@@ -1515,7 +1701,14 @@ var formatGovernanceOutput = (results) => {
|
|
|
1515
1701
|
const lines = ["Governance:", ""];
|
|
1516
1702
|
for (const result of results) {
|
|
1517
1703
|
lines.push(` Rule "${result.rule.name}" triggered (${result.trigger}):`);
|
|
1518
|
-
if (result.
|
|
1704
|
+
if (result.schemaChanges && result.schemaChanges.length > 0) {
|
|
1705
|
+
for (const sc of result.schemaChanges) {
|
|
1706
|
+
const consumers = sc.consumerServices.length > 0 ? sc.consumerServices.map((c2) => c2.id).join(", ") : "no known consumers";
|
|
1707
|
+
lines.push(
|
|
1708
|
+
` ! Schema changed for ${sc.resourceChange.resourceId} (${sc.resourceChange.type}) \u2014 consumers: ${consumers}`
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
} else if (result.deprecationChanges && result.deprecationChanges.length > 0) {
|
|
1519
1712
|
for (const dc of result.deprecationChanges) {
|
|
1520
1713
|
const producers = dc.producerServices.length > 0 ? dc.producerServices.map((p) => p.id).join(", ") : "unknown producer";
|
|
1521
1714
|
lines.push(` ! ${dc.resourceChange.resourceId} (${dc.resourceChange.type}) deprecated by ${producers}`);
|
|
@@ -1531,6 +1724,17 @@ var formatGovernanceOutput = (results) => {
|
|
|
1531
1724
|
}
|
|
1532
1725
|
return lines.join("\n");
|
|
1533
1726
|
};
|
|
1727
|
+
var formatFailureOutput = (failures) => {
|
|
1728
|
+
if (failures.length === 0) return "";
|
|
1729
|
+
const lines = [];
|
|
1730
|
+
for (const f of failures) {
|
|
1731
|
+
lines.push(`FAILED: ${f.ruleName}`);
|
|
1732
|
+
for (const msg of f.messages) {
|
|
1733
|
+
lines.push(` ${msg}`);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return lines.join("\n");
|
|
1737
|
+
};
|
|
1534
1738
|
|
|
1535
1739
|
// src/cli/governance/check.ts
|
|
1536
1740
|
import path3 from "path";
|
|
@@ -1538,7 +1742,7 @@ import { execSync } from "child_process";
|
|
|
1538
1742
|
import { mkdtempSync, rmSync as rmSync2 } from "fs";
|
|
1539
1743
|
import { tmpdir } from "os";
|
|
1540
1744
|
import dotenv from "dotenv";
|
|
1541
|
-
import
|
|
1745
|
+
import createSDK7 from "@eventcatalog/sdk";
|
|
1542
1746
|
import { isEventCatalogScaleEnabled } from "@eventcatalog/license";
|
|
1543
1747
|
var BRANCH_NAME_RE = /^[a-zA-Z0-9._\-/]+$/;
|
|
1544
1748
|
var extractBranchToTempDir = (branch, catalogDir, tempDirs) => {
|
|
@@ -1572,32 +1776,56 @@ var governanceCheck = async (opts) => {
|
|
|
1572
1776
|
const baseTmpDir = extractBranchToTempDir(baseBranch, dir, tempDirs);
|
|
1573
1777
|
const baseSnapshotDir = trackTempDir("ec-snap-base-");
|
|
1574
1778
|
const targetSnapshotDir = trackTempDir("ec-snap-target-");
|
|
1575
|
-
const baseSDK =
|
|
1779
|
+
const baseSDK = createSDK7(baseTmpDir);
|
|
1576
1780
|
const baseResult = await baseSDK.createSnapshot({ label: `base-${baseBranch}`, outputDir: baseSnapshotDir });
|
|
1577
1781
|
let targetResult;
|
|
1782
|
+
let targetCatalogDir;
|
|
1578
1783
|
if (opts.target) {
|
|
1579
|
-
|
|
1580
|
-
const targetSDK =
|
|
1784
|
+
targetCatalogDir = extractBranchToTempDir(opts.target, dir, tempDirs);
|
|
1785
|
+
const targetSDK = createSDK7(targetCatalogDir);
|
|
1581
1786
|
targetResult = await targetSDK.createSnapshot({ label: `target-${opts.target}`, outputDir: targetSnapshotDir });
|
|
1582
1787
|
} else {
|
|
1583
|
-
|
|
1788
|
+
targetCatalogDir = dir;
|
|
1789
|
+
const targetSDK = createSDK7(dir);
|
|
1584
1790
|
targetResult = await targetSDK.createSnapshot({ label: "current", outputDir: targetSnapshotDir });
|
|
1585
1791
|
}
|
|
1586
1792
|
const diff = await baseSDK.diffSnapshots(baseResult.filePath, targetResult.filePath);
|
|
1587
1793
|
const config = loadGovernanceConfig(dir);
|
|
1588
1794
|
if (config.rules.length === 0) {
|
|
1589
|
-
return "No governance.yaml (or governance.yml) found or no rules defined.";
|
|
1795
|
+
return { output: "No governance.yaml (or governance.yml) found or no rules defined.", exitCode: 0, failures: [] };
|
|
1590
1796
|
}
|
|
1591
1797
|
const results = evaluateGovernanceRules(diff, config, targetResult.snapshot, baseResult.snapshot);
|
|
1798
|
+
for (const result of results) {
|
|
1799
|
+
const failActions = result.rule.actions.filter((a) => a.type === "fail");
|
|
1800
|
+
if (failActions.length > 0) {
|
|
1801
|
+
result.failed = true;
|
|
1802
|
+
result.failMessages = failActions.map((a) => "message" in a && a.message ? resolveEnvVars(a.message) : void 0).filter((m) => m !== void 0);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
await enrichSchemaContent(results, baseTmpDir, targetCatalogDir);
|
|
1592
1806
|
const messageTypes = buildMessageTypeMap(targetResult.snapshot);
|
|
1593
1807
|
const serviceOwners = buildServiceOwnersMap(targetResult.snapshot);
|
|
1594
1808
|
const actionOutput = await executeGovernanceActions(results, {
|
|
1595
1809
|
messageTypes,
|
|
1596
1810
|
status: opts.status,
|
|
1597
|
-
serviceOwners
|
|
1811
|
+
serviceOwners,
|
|
1812
|
+
baseRef: baseBranch,
|
|
1813
|
+
targetRef: opts.target || "working-directory"
|
|
1598
1814
|
});
|
|
1815
|
+
const failures = results.filter((r) => r.failed).map((r) => ({ ruleName: r.rule.name, messages: r.failMessages || [] }));
|
|
1599
1816
|
if (opts.format === "json") {
|
|
1600
|
-
|
|
1817
|
+
const jsonOutput = {
|
|
1818
|
+
baseBranch,
|
|
1819
|
+
target: opts.target || "working directory",
|
|
1820
|
+
results,
|
|
1821
|
+
summary: {
|
|
1822
|
+
rulesTriggered: results.length,
|
|
1823
|
+
failures: failures.length,
|
|
1824
|
+
passed: failures.length === 0
|
|
1825
|
+
},
|
|
1826
|
+
diff: diff.summary
|
|
1827
|
+
};
|
|
1828
|
+
return { output: JSON.stringify(jsonOutput, null, 2), exitCode: failures.length > 0 ? 1 : 0, failures };
|
|
1601
1829
|
}
|
|
1602
1830
|
const targetLabel = opts.target || "working directory";
|
|
1603
1831
|
const lines = [`Governance check: comparing ${targetLabel} against ${baseBranch}`, ""];
|
|
@@ -1613,7 +1841,12 @@ var governanceCheck = async (opts) => {
|
|
|
1613
1841
|
lines.push("");
|
|
1614
1842
|
lines.push(parts.join(", ") + ".");
|
|
1615
1843
|
}
|
|
1616
|
-
|
|
1844
|
+
const failureOutput = formatFailureOutput(failures);
|
|
1845
|
+
if (failureOutput) {
|
|
1846
|
+
lines.push("");
|
|
1847
|
+
lines.push(failureOutput);
|
|
1848
|
+
}
|
|
1849
|
+
return { output: lines.join("\n"), exitCode: failures.length > 0 ? 1 : 0, failures };
|
|
1617
1850
|
} finally {
|
|
1618
1851
|
for (const d of tempDirs) {
|
|
1619
1852
|
rmSync2(d, { recursive: true, force: true });
|
|
@@ -1739,7 +1972,8 @@ governance.command("check").description("Compare catalog against a base branch a
|
|
|
1739
1972
|
status: opts.status,
|
|
1740
1973
|
dir
|
|
1741
1974
|
});
|
|
1742
|
-
console.log(result);
|
|
1975
|
+
console.log(result.output);
|
|
1976
|
+
process.exitCode = result.exitCode;
|
|
1743
1977
|
} catch (error) {
|
|
1744
1978
|
console.error(error instanceof Error ? error.message : String(error));
|
|
1745
1979
|
process.exit(1);
|