@eventcatalog/cli 0.5.0 → 0.5.1

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.
@@ -1237,8 +1237,10 @@ import fs from "fs";
1237
1237
  import path2 from "path";
1238
1238
  import yaml from "js-yaml";
1239
1239
  var loadGovernanceConfig = (catalogDir) => {
1240
- const configPath = path2.join(catalogDir, "governance.yaml");
1241
- if (!fs.existsSync(configPath)) {
1240
+ const yamlPath = path2.join(catalogDir, "governance.yaml");
1241
+ const ymlPath = path2.join(catalogDir, "governance.yml");
1242
+ const configPath = fs.existsSync(yamlPath) ? yamlPath : fs.existsSync(ymlPath) ? ymlPath : null;
1243
+ if (!configPath) {
1242
1244
  return { rules: [] };
1243
1245
  }
1244
1246
  const content = fs.readFileSync(configPath, "utf-8");
@@ -1269,17 +1271,82 @@ var buildServiceMessageSets = (snapshot2) => {
1269
1271
  }
1270
1272
  return { produces, consumes };
1271
1273
  };
1272
- var matchesResource = (change, resources, messageSets) => {
1274
+ var matchesResourceId = (resourceId, serviceId, resources, messageSets) => {
1273
1275
  return resources.some((r) => {
1274
1276
  if (r === "*") return true;
1275
- if (r.startsWith("service:")) return change.serviceId === r.slice(8);
1276
- if (r.startsWith("message:")) return change.resourceId === r.slice(8);
1277
- if (r.startsWith("produces:")) return messageSets?.produces.get(r.slice(9))?.has(change.resourceId) ?? false;
1278
- if (r.startsWith("consumes:")) return messageSets?.consumes.get(r.slice(9))?.has(change.resourceId) ?? false;
1277
+ if (r.startsWith("service:")) {
1278
+ if (serviceId) return serviceId === r.slice(8);
1279
+ return messageSets?.produces.get(r.slice(8))?.has(resourceId) ?? false;
1280
+ }
1281
+ if (r.startsWith("message:")) return resourceId === r.slice(8);
1282
+ if (r.startsWith("produces:")) return messageSets?.produces.get(r.slice(9))?.has(resourceId) ?? false;
1283
+ if (r.startsWith("consumes:")) return messageSets?.consumes.get(r.slice(9))?.has(resourceId) ?? false;
1279
1284
  return false;
1280
1285
  });
1281
1286
  };
1282
1287
  var REMOVED_TRIGGERS = /* @__PURE__ */ new Set(["consumer_removed", "producer_removed"]);
1288
+ var MESSAGE_RESOURCE_TYPES = /* @__PURE__ */ new Set(["event", "command", "query"]);
1289
+ var buildMessageMap = (snapshot2) => {
1290
+ const map = /* @__PURE__ */ new Map();
1291
+ for (const msg of snapshot2.resources.messages.events) map.set(msg.id, msg);
1292
+ for (const msg of snapshot2.resources.messages.commands) map.set(msg.id, msg);
1293
+ for (const msg of snapshot2.resources.messages.queries) map.set(msg.id, msg);
1294
+ return map;
1295
+ };
1296
+ var buildProducerIndex = (snapshot2) => {
1297
+ const index = /* @__PURE__ */ new Map();
1298
+ for (const service of snapshot2.resources.services) {
1299
+ if (!service.sends) continue;
1300
+ for (const s of service.sends) {
1301
+ let producers = index.get(s.id);
1302
+ if (!producers) {
1303
+ producers = [];
1304
+ index.set(s.id, producers);
1305
+ }
1306
+ const entry = {
1307
+ id: service.id,
1308
+ version: service.version
1309
+ };
1310
+ if (service.owners && Array.isArray(service.owners) && service.owners.length > 0) {
1311
+ entry.owners = service.owners;
1312
+ }
1313
+ producers.push(entry);
1314
+ }
1315
+ }
1316
+ return index;
1317
+ };
1318
+ var evaluateDeprecationRules = (diff, config, targetSnapshot, targetMessageSets, baseSnapshot) => {
1319
+ const deprecationRules = config.rules.filter((rule) => rule.when.includes("message_deprecated"));
1320
+ if (deprecationRules.length === 0) return [];
1321
+ const targetMessages = buildMessageMap(targetSnapshot);
1322
+ const baseMessages = baseSnapshot ? buildMessageMap(baseSnapshot) : void 0;
1323
+ const producerIndex = buildProducerIndex(targetSnapshot);
1324
+ const deprecatedResources = diff.resources.filter((rc) => {
1325
+ if (!MESSAGE_RESOURCE_TYPES.has(rc.type)) return false;
1326
+ if (!rc.changedFields?.includes("deprecated")) return false;
1327
+ const targetMessage = targetMessages.get(rc.resourceId);
1328
+ if (!targetMessage || !targetMessage.deprecated) return false;
1329
+ if (baseMessages) {
1330
+ const baseMessage = baseMessages.get(rc.resourceId);
1331
+ if (baseMessage && baseMessage.deprecated) return false;
1332
+ }
1333
+ return true;
1334
+ });
1335
+ if (deprecatedResources.length === 0) return [];
1336
+ const results = [];
1337
+ for (const rule of deprecationRules) {
1338
+ const matched = [];
1339
+ for (const rc of deprecatedResources) {
1340
+ if (!matchesResourceId(rc.resourceId, void 0, rule.resources, targetMessageSets)) continue;
1341
+ const producers = producerIndex.get(rc.resourceId) || [];
1342
+ matched.push({ resourceChange: rc, producerServices: producers });
1343
+ }
1344
+ if (matched.length > 0) {
1345
+ results.push({ rule, trigger: "message_deprecated", matchedChanges: [], deprecationChanges: matched });
1346
+ }
1347
+ }
1348
+ return results;
1349
+ };
1283
1350
  var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
1284
1351
  const results = [];
1285
1352
  const targetMessageSets = targetSnapshot ? buildServiceMessageSets(targetSnapshot) : void 0;
@@ -1289,12 +1356,17 @@ var evaluateGovernanceRules = (diff, config, targetSnapshot, baseSnapshot) => {
1289
1356
  const filter = TRIGGER_FILTERS[trigger];
1290
1357
  if (!filter) continue;
1291
1358
  const messageSets = REMOVED_TRIGGERS.has(trigger) && baseMessageSets ? baseMessageSets : targetMessageSets;
1292
- const matchedChanges = diff.relationships.filter((c2) => filter(c2) && matchesResource(c2, rule.resources, messageSets));
1359
+ const matchedChanges = diff.relationships.filter(
1360
+ (c2) => filter(c2) && matchesResourceId(c2.resourceId, c2.serviceId, rule.resources, messageSets)
1361
+ );
1293
1362
  if (matchedChanges.length > 0) {
1294
1363
  results.push({ rule, trigger, matchedChanges });
1295
1364
  }
1296
1365
  }
1297
1366
  }
1367
+ if (targetSnapshot && targetMessageSets) {
1368
+ results.push(...evaluateDeprecationRules(diff, config, targetSnapshot, targetMessageSets, baseSnapshot));
1369
+ }
1298
1370
  return results;
1299
1371
  };
1300
1372
  var PRODUCER_TRIGGERS = /* @__PURE__ */ new Set(["producer_added", "producer_removed"]);
@@ -1351,6 +1423,42 @@ var executeGovernanceActions = async (results, opts = {}) => {
1351
1423
  headers[key] = resolveEnvVars(value);
1352
1424
  }
1353
1425
  }
1426
+ if (result.deprecationChanges && result.deprecationChanges.length > 0) {
1427
+ for (const dc of result.deprecationChanges) {
1428
+ const messageType = messageTypes?.get(dc.resourceChange.resourceId) || "message";
1429
+ const producers = dc.producerServices.length > 0 ? dc.producerServices : [{ id: "unknown", version: "unknown" }];
1430
+ for (const producer of producers) {
1431
+ const payload = {
1432
+ specversion: "1.0",
1433
+ type: `eventcatalog.governance.message_deprecated`,
1434
+ source: "eventcatalog/governance",
1435
+ id: randomUUID2(),
1436
+ time: now,
1437
+ datacontenttype: "application/json",
1438
+ data: {
1439
+ schemaVersion: 1,
1440
+ ...status && { status },
1441
+ summary: `${dc.resourceChange.resourceId} (${messageType}) has been deprecated by ${producer.id}`,
1442
+ producer: {
1443
+ id: producer.id,
1444
+ version: producer.version,
1445
+ ...producer.owners && { owners: producer.owners }
1446
+ },
1447
+ message: {
1448
+ id: dc.resourceChange.resourceId,
1449
+ version: dc.resourceChange.version,
1450
+ type: messageType
1451
+ }
1452
+ }
1453
+ };
1454
+ webhookCalls.push({
1455
+ urlTemplate: action.url,
1456
+ request: fetch(url, { method: "POST", headers, body: JSON.stringify(payload) })
1457
+ });
1458
+ }
1459
+ }
1460
+ continue;
1461
+ }
1354
1462
  for (const change of result.matchedChanges) {
1355
1463
  const verb = getChangeVerb(result.trigger, change.changeType);
1356
1464
  const messageType = messageTypes?.get(change.resourceId) || "message";
@@ -1407,10 +1515,17 @@ var formatGovernanceOutput = (results) => {
1407
1515
  const lines = ["Governance:", ""];
1408
1516
  for (const result of results) {
1409
1517
  lines.push(` Rule "${result.rule.name}" triggered (${result.trigger}):`);
1410
- for (const change of result.matchedChanges) {
1411
- const prefix = change.changeType === "added" ? "+" : "-";
1412
- const verb = getChangeVerb(result.trigger, change.changeType);
1413
- lines.push(` ${prefix} ${change.serviceId} is ${verb} ${change.resourceId}`);
1518
+ if (result.deprecationChanges && result.deprecationChanges.length > 0) {
1519
+ for (const dc of result.deprecationChanges) {
1520
+ const producers = dc.producerServices.length > 0 ? dc.producerServices.map((p) => p.id).join(", ") : "unknown producer";
1521
+ lines.push(` ! ${dc.resourceChange.resourceId} (${dc.resourceChange.type}) deprecated by ${producers}`);
1522
+ }
1523
+ } else {
1524
+ for (const change of result.matchedChanges) {
1525
+ const prefix = change.changeType === "added" ? "+" : "-";
1526
+ const verb = getChangeVerb(result.trigger, change.changeType);
1527
+ lines.push(` ${prefix} ${change.serviceId} is ${verb} ${change.resourceId}`);
1528
+ }
1414
1529
  }
1415
1530
  lines.push("");
1416
1531
  }
@@ -1471,7 +1586,7 @@ var governanceCheck = async (opts) => {
1471
1586
  const diff = await baseSDK.diffSnapshots(baseResult.filePath, targetResult.filePath);
1472
1587
  const config = loadGovernanceConfig(dir);
1473
1588
  if (config.rules.length === 0) {
1474
- return "No governance.yaml found or no rules defined.";
1589
+ return "No governance.yaml (or governance.yml) found or no rules defined.";
1475
1590
  }
1476
1591
  const results = evaluateGovernanceRules(diff, config, targetResult.snapshot, baseResult.snapshot);
1477
1592
  const messageTypes = buildMessageTypeMap(targetResult.snapshot);