@eventcatalog/core 5.2.1 → 5.4.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.
Files changed (47) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/count-resources.cjs +10 -0
  4. package/dist/analytics/count-resources.js +1 -1
  5. package/dist/analytics/log-build.cjs +11 -1
  6. package/dist/analytics/log-build.js +4 -4
  7. package/dist/{chunk-HVCPU4V3.js → chunk-GAKU752C.js} +1 -1
  8. package/dist/{chunk-VEQLCFU6.js → chunk-GQQFHJSY.js} +2 -2
  9. package/dist/{chunk-QSD6WMYE.js → chunk-NSVRGATQ.js} +1 -1
  10. package/dist/{chunk-OVEXZEJJ.js → chunk-VZNPE26F.js} +1 -1
  11. package/dist/{chunk-MESHMXLR.js → chunk-WG276AIE.js} +1 -1
  12. package/dist/{chunk-ZAZHAVKB.js → chunk-YRZK7HKX.js} +10 -0
  13. package/dist/constants.cjs +1 -1
  14. package/dist/constants.js +1 -1
  15. package/dist/docs/development/guides/fitness-functions/01-what-are-fitness-functions.md +116 -0
  16. package/dist/docs/development/guides/fitness-functions/02-create-fitness-function.md +222 -0
  17. package/dist/docs/development/guides/fitness-functions/03-global-and-resource-functions.md +198 -0
  18. package/dist/docs/development/guides/fitness-functions/04-run-in-ci.md +223 -0
  19. package/dist/docs/development/guides/fitness-functions/05-api-reference.md +271 -0
  20. package/dist/docs/development/guides/fitness-functions/06-examples.md +354 -0
  21. package/dist/docs/development/guides/fitness-functions/_category_.json +11 -0
  22. package/dist/eventcatalog.cjs +25 -1
  23. package/dist/eventcatalog.js +26 -12
  24. package/dist/federation/federate.js +3 -3
  25. package/dist/generate.cjs +1 -1
  26. package/dist/generate.js +3 -3
  27. package/dist/runtime-dependencies/@eventcatalog__fitness.mjs +3 -0
  28. package/dist/runtime-dependencies/@modelcontextprotocol__client.mjs +3 -0
  29. package/dist/runtime-dependencies/@modelcontextprotocol__server.mjs +3 -0
  30. package/dist/runtime-dependencies/manifest.json +3 -4
  31. package/dist/utils/cli-logger.cjs +1 -1
  32. package/dist/utils/cli-logger.js +2 -2
  33. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +73 -74
  34. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +71 -73
  35. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +1 -1
  36. package/eventcatalog/src/features/mcp/custom-tool-input.ts +38 -0
  37. package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -4
  38. package/eventcatalog/src/features/mcp/mcp-server.ts +24 -17
  39. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +4 -3
  40. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +2 -4
  41. package/package.json +10 -7
  42. package/dist/runtime-dependencies/@modelcontextprotocol__sdk.mjs +0 -3
  43. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__mcp.js.mjs +0 -3
  44. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__server__webStandardStreamableHttp.js.mjs +0 -3
  45. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__types.js.mjs +0 -3
  46. package/eventcatalog/src/features/studio/server/tool-icons.ts +0 -30
  47. package/dist/{chunk-NCEZDQSW.js → chunk-QJ7KKN2D.js} +7 -7
@@ -0,0 +1,354 @@
1
+ ---
2
+ sidebar_position: 6
3
+ sidebar_label: Examples
4
+ title: Fitness function examples
5
+ description: Ready-to-use fitness functions for team ownership, shared databases, coupling, circular commands, deprecated messages and business-critical services.
6
+ ---
7
+
8
+ These fitness functions check rules teams commonly agree on. Copy one into a `fitness/` folder in your catalog, change the names and limits to match your architecture, and run `npx eventcatalog fitness check`.
9
+
10
+ Each one only works with the relationships and metadata your catalog records. A rule about data stores needs `writesTo` on your services, a rule about deprecation needs `deprecated` on your messages, and so on. If a rule passes when you expect it to fail, check that your catalog documents what it reads.
11
+
12
+ | Example | Checks | Severity |
13
+ |---|---|---|
14
+ | [Every service is owned by a team](#every-service-is-owned-by-a-team) | Services list a team, not only people, in `owners` | `error` |
15
+ | [Each data store has one writer](#each-data-store-has-one-writer) | No data store is written to by more than one service | `warning` |
16
+ | [Services stay loosely coupled](#services-stay-loosely-coupled) | No service receives more than a set number of messages | `warning` |
17
+ | [No circular commands](#no-circular-commands) | Services don't send commands to each other in a loop | `error` |
18
+ | [No deprecated messages](#no-deprecated-messages) | No service sends or receives a deprecated message | `warning` |
19
+ | [Business-critical services are documented](#business-critical-services-are-documented) | Services with a "Business Critical" badge have a repository, owners and a specification | `error` |
20
+ | [Domain boundaries](#domain-boundaries) | A domain's services only use its own data stores | `error` |
21
+
22
+ ## Every service is owned by a team
23
+
24
+ `owners` can list [teams and users](/docs/development/guides/owners/what-are-teams-and-users). A service owned only by a person loses its owner when that person moves on. This rule checks each owner with `catalog.getTeam`, and fails services that don't have at least one team.
25
+
26
+ ```ts title="fitness/team-ownership.fitness.ts"
27
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
28
+
29
+ export default defineFitnessFunction({
30
+ id: 'services-owned-by-teams',
31
+ name: 'Every service is owned by a team',
32
+ description: 'People move on. A team keeps owning the service when they do.',
33
+
34
+ async run({ catalog }) {
35
+ const services = await catalog.getServices({ latestOnly: true });
36
+ const violations = [];
37
+
38
+ for (const service of services) {
39
+ // owners can list teams and users. Keep the ones that are teams.
40
+ const teams = [];
41
+ for (const owner of service.owners ?? []) {
42
+ if (await catalog.getTeam(owner)) teams.push(owner);
43
+ }
44
+
45
+ if (teams.length === 0) {
46
+ violations.push({
47
+ message: `${service.name} is not owned by a team`,
48
+ resources: [{ type: 'service', id: service.id, version: service.version }],
49
+ suggestion: 'Add the owning team to the owners field',
50
+ });
51
+ }
52
+ }
53
+
54
+ return violations;
55
+ },
56
+ });
57
+ ```
58
+
59
+ ```
60
+ ✗ Every service is owned by a team 1 violation
61
+ • Payment Worker is not owned by a team
62
+ Resources: payment-worker (service)
63
+ Suggestion: Add the owning team to the owners field
64
+ ```
65
+
66
+ ## Each data store has one writer
67
+
68
+ When several services write to the same [data store](/docs/development/guides/resources/data/introduction), none of them can change its schema without coordinating with the others. This rule groups services by the data stores in their [`writesTo`](/docs/development/guides/resources/data/add-read-write-relationships) and warns about any data store with more than one writer.
69
+
70
+ ```ts title="fitness/single-writer.fitness.ts"
71
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
72
+
73
+ export default defineFitnessFunction({
74
+ id: 'single-writer-per-data-store',
75
+ name: 'Each data store has one writer',
76
+ description: 'A data store written by several services is a shared database: none of them can change its schema safely.',
77
+ severity: 'warning',
78
+
79
+ async run({ catalog }) {
80
+ const services = await catalog.getServices({ latestOnly: true });
81
+
82
+ // Group the services by the data stores they write to
83
+ const writers = new Map<string, typeof services>();
84
+ for (const service of services) {
85
+ for (const store of service.writesTo ?? []) {
86
+ writers.set(store.id, [...(writers.get(store.id) ?? []), service]);
87
+ }
88
+ }
89
+
90
+ return [...writers]
91
+ .filter(([, services]) => services.length > 1)
92
+ .map(([storeId, services]) => ({
93
+ message: `${storeId} is written to by ${services.length} services`,
94
+ // The data store first, then every service that writes to it
95
+ resources: [
96
+ { type: 'container', id: storeId },
97
+ ...services.map((service) => ({ type: 'service', id: service.id, version: service.version })),
98
+ ],
99
+ suggestion: 'Give the data store one owning service, and have the others send it commands or events',
100
+ }));
101
+ },
102
+ });
103
+ ```
104
+
105
+ ```
106
+ ! Each data store has one writer 5 warnings
107
+ • review-database is written to by 2 services
108
+ Resources: review-database (container), review-moderation-worker (service), review-api (service)
109
+ Suggestion: Give the data store one owning service, and have the others send it commands or events
110
+ • payment-database is written to by 2 services
111
+ Resources: payment-database (container), payment-worker (service), payment-api (service)
112
+ Suggestion: Give the data store one owning service, and have the others send it commands or events
113
+ ...
114
+ ```
115
+
116
+ A worker and an API that share a database can be a deliberate choice. Start this rule as a `warning`, and change it to `error` once the shared data stores you've decided to keep are documented.
117
+
118
+ ## Services stay loosely coupled
119
+
120
+ A service that receives many messages usually has more than one reason to change. This rule warns when a service receives more messages than a limit you choose.
121
+
122
+ ```ts title="fitness/max-dependencies.fitness.ts"
123
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
124
+
125
+ const MAX_MESSAGES_RECEIVED = 8;
126
+
127
+ export default defineFitnessFunction({
128
+ id: 'max-messages-received',
129
+ name: `Services receive at most ${MAX_MESSAGES_RECEIVED} messages`,
130
+ severity: 'warning',
131
+
132
+ async run({ catalog }) {
133
+ const services = await catalog.getServices({ latestOnly: true });
134
+
135
+ return services
136
+ .filter((service) => (service.receives ?? []).length > MAX_MESSAGES_RECEIVED)
137
+ .map((service) => ({
138
+ message: `${service.name} receives ${service.receives!.length} messages`,
139
+ resources: [{ type: 'service', id: service.id, version: service.version }],
140
+ details: 'A service that reacts to many messages usually has more than one reason to change.',
141
+ suggestion: 'Look for a group of messages that could move to a service of its own',
142
+ }));
143
+ },
144
+ });
145
+ ```
146
+
147
+ To set a different limit for one service, put a copy of the rule in that service's `fitness/` folder and check `resource` instead of every service. See [resource fitness functions](./03-global-and-resource-functions.md#resource-fitness-functions) for an example.
148
+
149
+ ## No circular commands
150
+
151
+ Events often go back and forth between services, and that's fine: each service reacts to what happened. Commands are different. If service A tells B what to do, and B tells A what to do, the two can't be changed or deployed independently.
152
+
153
+ This rule builds a graph of which services send commands to which, from what each service `sends` and `receives`, and reports every loop it finds.
154
+
155
+ ```ts title="fitness/no-circular-commands.fitness.ts"
156
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
157
+
158
+ export default defineFitnessFunction({
159
+ id: 'no-circular-commands',
160
+ name: 'Services do not send commands in a circle',
161
+ description: 'If A commands B and B commands A, neither can be deployed or understood without the other.',
162
+
163
+ async run({ catalog }) {
164
+ const services = await catalog.getServices({ latestOnly: true });
165
+ const commands = new Set((await catalog.getCommands({ latestOnly: true })).map((command) => command.id));
166
+
167
+ // Who sends each command, and who receives it
168
+ const senders = new Map<string, string[]>();
169
+ for (const service of services) {
170
+ for (const message of service.sends ?? []) {
171
+ if (commands.has(message.id)) senders.set(message.id, [...(senders.get(message.id) ?? []), service.id]);
172
+ }
173
+ }
174
+
175
+ // An edge from each sender to the service that receives its command
176
+ const edges = new Map<string, Set<string>>();
177
+ for (const service of services) {
178
+ for (const message of service.receives ?? []) {
179
+ for (const sender of senders.get(message.id) ?? []) {
180
+ if (sender === service.id) continue;
181
+ edges.set(sender, (edges.get(sender) ?? new Set()).add(service.id));
182
+ }
183
+ }
184
+ }
185
+
186
+ // Depth-first search. Reaching a service that is already on the path means there's a cycle.
187
+ const cycles = new Map<string, string[]>();
188
+ const visit = (id: string, path: string[]) => {
189
+ if (path.includes(id)) {
190
+ const cycle = path.slice(path.indexOf(id));
191
+ // Report each cycle once, however it was reached
192
+ cycles.set([...cycle].sort().join(','), cycle);
193
+ return;
194
+ }
195
+ for (const next of edges.get(id) ?? []) visit(next, [...path, id]);
196
+ };
197
+ for (const id of edges.keys()) visit(id, []);
198
+
199
+ return [...cycles.values()].map((cycle) => ({
200
+ message: `Commands go round in a circle: ${[...cycle, cycle[0]].join(' → ')}`,
201
+ resources: cycle.map((id) => ({ type: 'service', id })),
202
+ suggestion: 'Replace one of the commands with an event, so one service reacts instead of being told',
203
+ }));
204
+ },
205
+ });
206
+ ```
207
+
208
+ ```
209
+ ✗ Services do not send commands in a circle 1 violation
210
+ • Commands go round in a circle: order-service → checkout-orchestrator → order-service
211
+ Resources: order-service (service), checkout-orchestrator (service)
212
+ Suggestion: Replace one of the commands with an event, so one service reacts instead of being told
213
+ ```
214
+
215
+ To check for loops through events and queries too, remove the `commands.has(...)` check. Expect more results: in an event-driven architecture, many of those loops are intended.
216
+
217
+ ## No deprecated messages
218
+
219
+ When you [deprecate a message](/docs/development/guides/resources/messages/versioning-and-lifecycle/deprecate-messages), the services still using it need to move off it. This rule finds every service that still sends or receives a deprecated event, command or query, and includes the deprecation message from the frontmatter in the result.
220
+
221
+ ```ts title="fitness/no-deprecated-messages.fitness.ts"
222
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
223
+
224
+ export default defineFitnessFunction({
225
+ id: 'no-deprecated-messages',
226
+ name: 'Services do not use deprecated messages',
227
+ severity: 'warning',
228
+
229
+ async run({ catalog }) {
230
+ // Every deprecated event, command and query, by id
231
+ const messages = [
232
+ ...(await catalog.getEvents({ latestOnly: true })).map((message) => ({ ...message, type: 'event' })),
233
+ ...(await catalog.getCommands({ latestOnly: true })).map((message) => ({ ...message, type: 'command' })),
234
+ ...(await catalog.getQueries({ latestOnly: true })).map((message) => ({ ...message, type: 'query' })),
235
+ ];
236
+ const deprecated = new Map(messages.filter((message) => message.deprecated).map((message) => [message.id, message]));
237
+
238
+ const services = await catalog.getServices({ latestOnly: true });
239
+ const violations = [];
240
+
241
+ for (const service of services) {
242
+ for (const pointer of [...(service.sends ?? []), ...(service.receives ?? [])]) {
243
+ const message = deprecated.get(pointer.id);
244
+ if (!message) continue;
245
+
246
+ violations.push({
247
+ message: `${service.name} still uses ${message.name}, which is deprecated`,
248
+ resources: [
249
+ { type: 'service', id: service.id, version: service.version },
250
+ { type: message.type, id: message.id, version: message.version },
251
+ ],
252
+ // Use the deprecation message from the frontmatter when there is one
253
+ details: typeof message.deprecated === 'object' ? message.deprecated.message : undefined,
254
+ });
255
+ }
256
+ }
257
+
258
+ return violations;
259
+ },
260
+ });
261
+ ```
262
+
263
+ ```
264
+ ! Services do not use deprecated messages 1 warning
265
+ • Order Service still uses Order Amended, which is deprecated
266
+ Resources: order-service (service), order-amended (event)
267
+ Use OrderUpdated instead
268
+ ```
269
+
270
+ ## Business-critical services are documented
271
+
272
+ Some services need more documentation than others. This rule finds services marked as business-critical with a badge, and checks each one has a repository, owners and an API specification.
273
+
274
+ ```ts title="fitness/critical-services.fitness.ts"
275
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
276
+
277
+ export default defineFitnessFunction({
278
+ id: 'critical-services-documented',
279
+ name: 'Business-critical services are fully documented',
280
+
281
+ async run({ catalog }) {
282
+ const services = await catalog.getServices({ latestOnly: true });
283
+
284
+ // Services are marked as critical with a badge in their frontmatter
285
+ const critical = services.filter((service) =>
286
+ service.badges?.some((badge) => badge.content === 'Business Critical')
287
+ );
288
+
289
+ const violations = [];
290
+ for (const service of critical) {
291
+ const resources = [{ type: 'service', id: service.id, version: service.version }];
292
+ if (!service.repository?.url) {
293
+ violations.push({ message: `${service.name} has no repository`, resources });
294
+ }
295
+ if (!service.owners?.length) {
296
+ violations.push({ message: `${service.name} has no owners`, resources });
297
+ }
298
+ if (!service.specifications) {
299
+ violations.push({ message: `${service.name} has no API specification`, resources });
300
+ }
301
+ }
302
+ return violations;
303
+ },
304
+ });
305
+ ```
306
+
307
+ The badge is one way to mark a service. You can use any frontmatter your catalog agrees on, for example a custom `x-tier: critical` property, and check `service['x-tier'] === 'critical'` instead.
308
+
309
+ ## Domain boundaries
310
+
311
+ A domain's services should only use the domain's own data stores. This rule lives in the domain's folder, so it's given the domain as `resource`, and the domain team owns it. The full example is on [Global and resource fitness functions](./03-global-and-resource-functions.md#resource-fitness-functions).
312
+
313
+ ## Share code between fitness functions
314
+
315
+ Only files ending in `.fitness.ts` are run, so you can keep shared code in other files in a `fitness/` folder and import it. Type the catalog with `ReadOnlyCatalog`:
316
+
317
+ ```ts title="fitness/helpers.ts"
318
+ import type { ReadOnlyCatalog } from '@eventcatalog/fitness';
319
+
320
+ /** The latest version of every event, command and query, with its type */
321
+ export const getMessages = async (catalog: ReadOnlyCatalog) => [
322
+ ...(await catalog.getEvents({ latestOnly: true })).map((message) => ({ ...message, type: 'event' as const })),
323
+ ...(await catalog.getCommands({ latestOnly: true })).map((message) => ({ ...message, type: 'command' as const })),
324
+ ...(await catalog.getQueries({ latestOnly: true })).map((message) => ({ ...message, type: 'query' as const })),
325
+ ];
326
+ ```
327
+
328
+ ```ts title="fitness/message-summaries.fitness.ts"
329
+ import { defineFitnessFunction } from '@eventcatalog/fitness';
330
+ import { getMessages } from './helpers';
331
+
332
+ export default defineFitnessFunction({
333
+ id: 'message-summaries',
334
+ name: 'Every message has a summary',
335
+ severity: 'warning',
336
+
337
+ async run({ catalog }) {
338
+ const messages = await getMessages(catalog);
339
+
340
+ return messages
341
+ .filter((message) => !message.summary?.trim())
342
+ .map((message) => ({
343
+ message: `${message.name} has no summary`,
344
+ resources: [{ type: message.type, id: message.id, version: message.version }],
345
+ }));
346
+ },
347
+ });
348
+ ```
349
+
350
+ ## Related
351
+
352
+ - [Create a fitness function](./02-create-fitness-function.md)
353
+ - [API reference](./05-api-reference.md): everything `run` is given and can return
354
+ - [Run in CI](./04-run-in-ci.md)
@@ -0,0 +1,11 @@
1
+ {
2
+ "label": "Fitness functions",
3
+ "position": 7,
4
+ "collapsible": true,
5
+ "collapsed": true,
6
+ "link": {
7
+ "type": "generated-index",
8
+ "slug": "/fitness-functions",
9
+ "description": "Write your architecture rules as TypeScript functions, run them against your catalog locally or in CI, and see which resources break them."
10
+ }
11
+ }
@@ -174,7 +174,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
174
174
  var import_picocolors = __toESM(require("picocolors"), 1);
175
175
 
176
176
  // package.json
177
- var version = "5.2.1";
177
+ var version = "5.4.1";
178
178
 
179
179
  // src/constants.ts
180
180
  var VERSION = version;
@@ -377,6 +377,14 @@ var CUSTOM_ROUTE_PATTERNS = {
377
377
  };
378
378
  var DEFAULT_IGNORES = ["**/versioned/**", "**/dist/**", "**/node_modules/**"];
379
379
  var CUSTOM_ROUTE_IGNORES = [...DEFAULT_IGNORES, "pages/**/_*/**", "pages/**/_*"];
380
+ async function countFitnessFunctions(projectDir) {
381
+ try {
382
+ const { discoverFitnessFiles } = await import("@eventcatalog/fitness");
383
+ return discoverFitnessFiles(projectDir).files.length;
384
+ } catch {
385
+ return void 0;
386
+ }
387
+ }
380
388
  async function countResources(projectDir) {
381
389
  const counts = {};
382
390
  for (const [type, patterns] of Object.entries({ ...RESOURCE_PATTERNS, ...CUSTOM_ROUTE_PATTERNS })) {
@@ -390,6 +398,8 @@ async function countResources(projectDir) {
390
398
  }
391
399
  counts[type] = total;
392
400
  }
401
+ const fitnessFunctions = await countFitnessFunctions(projectDir);
402
+ if (fitnessFunctions !== void 0) counts.fitnessFunctions = fitnessFunctions;
393
403
  return counts;
394
404
  }
395
405
  async function hashCatalogContent(projectDir) {
@@ -3622,6 +3632,20 @@ program.command("generate [siteDir]").description("Start the generator scripts."
3622
3632
  }
3623
3633
  await generate(dir);
3624
3634
  });
3635
+ var fitness = program.command("fitness").description("Run the architecture fitness functions in your catalog.");
3636
+ fitness.command("check").description("Run every *.fitness.ts function in the catalog, print the results and write them to a JSON report.").option("-o, --output <path>", "Where to write the report, relative to the catalog", ".eventcatalog/fitness/results.json").option("--format <format>", "Output format: text or json", "text").action(async (commandOptions) => {
3637
+ if (import_fs.default.existsSync(import_node_path23.default.join(dir, ".env"))) {
3638
+ import_dotenv.default.config({ path: import_node_path23.default.join(dir, ".env") });
3639
+ }
3640
+ const { runFitnessCheck } = await import("@eventcatalog/fitness");
3641
+ process.exitCode = await runFitnessCheck({
3642
+ dir,
3643
+ output: commandOptions.output,
3644
+ format: commandOptions.format,
3645
+ color: Boolean(process.stdout.isTTY) && !process.env.NO_COLOR
3646
+ });
3647
+ await new Promise((resolve2) => process.stdout.write("", () => resolve2()));
3648
+ });
3625
3649
  var reportFederationProgress = (event, verbose = false) => {
3626
3650
  switch (event.type) {
3627
3651
  case "configured":
@@ -7,30 +7,30 @@ import {
7
7
  import "./chunk-CA4U2JP7.js";
8
8
  import {
9
9
  log_build_default
10
- } from "./chunk-VEQLCFU6.js";
10
+ } from "./chunk-GQQFHJSY.js";
11
11
  import {
12
12
  getLicenseStatus,
13
13
  printLicenseStatus
14
14
  } from "./chunk-AD7CL772.js";
15
- import "./chunk-HVCPU4V3.js";
16
- import "./chunk-ZAZHAVKB.js";
15
+ import "./chunk-GAKU752C.js";
16
+ import "./chunk-YRZK7HKX.js";
17
17
  import "./chunk-CA6WCQ2T.js";
18
18
  import "./chunk-CPW6G62V.js";
19
19
  import {
20
20
  FederationConflictError,
21
21
  FederationDiagnosticError,
22
22
  federateCatalog
23
- } from "./chunk-NCEZDQSW.js";
24
- import "./chunk-R7Z5ALYI.js";
25
- import {
26
- getFederationDiagnosticCounts,
27
- getVisibleFederationDiagnostics
28
- } from "./chunk-JJPB6EOZ.js";
23
+ } from "./chunk-QJ7KKN2D.js";
29
24
  import "./chunk-WRNH5C3U.js";
30
25
  import "./chunk-5EPNFDT5.js";
31
26
  import "./chunk-A2RZR3U4.js";
32
27
  import "./chunk-SDZQJTJW.js";
33
28
  import "./chunk-352FGP6W.js";
29
+ import "./chunk-R7Z5ALYI.js";
30
+ import {
31
+ getFederationDiagnosticCounts,
32
+ getVisibleFederationDiagnostics
33
+ } from "./chunk-JJPB6EOZ.js";
34
34
  import {
35
35
  getProjectOutDir,
36
36
  isAuthEnabled,
@@ -39,10 +39,10 @@ import {
39
39
  } from "./chunk-NKNOXQ62.js";
40
40
  import {
41
41
  generate
42
- } from "./chunk-MESHMXLR.js";
42
+ } from "./chunk-WG276AIE.js";
43
43
  import {
44
44
  logger
45
- } from "./chunk-OVEXZEJJ.js";
45
+ } from "./chunk-VZNPE26F.js";
46
46
  import {
47
47
  resolve_catalog_dependencies_default
48
48
  } from "./chunk-PD7KTSYN.js";
@@ -79,7 +79,7 @@ import {
79
79
  } from "./chunk-LFQJXLHG.js";
80
80
  import {
81
81
  VERSION
82
- } from "./chunk-QSD6WMYE.js";
82
+ } from "./chunk-NSVRGATQ.js";
83
83
  import {
84
84
  getEventCatalogConfigFile,
85
85
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -1191,6 +1191,20 @@ program.command("generate [siteDir]").description("Start the generator scripts."
1191
1191
  }
1192
1192
  await generate(dir);
1193
1193
  });
1194
+ var fitness = program.command("fitness").description("Run the architecture fitness functions in your catalog.");
1195
+ fitness.command("check").description("Run every *.fitness.ts function in the catalog, print the results and write them to a JSON report.").option("-o, --output <path>", "Where to write the report, relative to the catalog", ".eventcatalog/fitness/results.json").option("--format <format>", "Output format: text or json", "text").action(async (commandOptions) => {
1196
+ if (fs3.existsSync(path2.join(dir, ".env"))) {
1197
+ dotenv.config({ path: path2.join(dir, ".env") });
1198
+ }
1199
+ const { runFitnessCheck } = await import("@eventcatalog/fitness");
1200
+ process.exitCode = await runFitnessCheck({
1201
+ dir,
1202
+ output: commandOptions.output,
1203
+ format: commandOptions.format,
1204
+ color: Boolean(process.stdout.isTTY) && !process.env.NO_COLOR
1205
+ });
1206
+ await new Promise((resolve) => process.stdout.write("", () => resolve()));
1207
+ });
1194
1208
  var reportFederationProgress = (event, verbose = false) => {
1195
1209
  switch (event.type) {
1196
1210
  case "configured":
@@ -2,14 +2,14 @@ import {
2
2
  FederationConflictError,
3
3
  FederationDiagnosticError,
4
4
  federateCatalog
5
- } from "../chunk-NCEZDQSW.js";
6
- import "../chunk-R7Z5ALYI.js";
7
- import "../chunk-JJPB6EOZ.js";
5
+ } from "../chunk-QJ7KKN2D.js";
8
6
  import "../chunk-WRNH5C3U.js";
9
7
  import "../chunk-5EPNFDT5.js";
10
8
  import "../chunk-A2RZR3U4.js";
11
9
  import "../chunk-SDZQJTJW.js";
12
10
  import "../chunk-352FGP6W.js";
11
+ import "../chunk-R7Z5ALYI.js";
12
+ import "../chunk-JJPB6EOZ.js";
13
13
  import "../chunk-4G7FSRPN.js";
14
14
  export {
15
15
  FederationConflictError,
package/dist/generate.cjs CHANGED
@@ -105,7 +105,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
105
105
  var import_picocolors = __toESM(require("picocolors"), 1);
106
106
 
107
107
  // package.json
108
- var version = "5.2.1";
108
+ var version = "5.4.1";
109
109
 
110
110
  // src/constants.ts
111
111
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-MESHMXLR.js";
4
- import "./chunk-OVEXZEJJ.js";
5
- import "./chunk-QSD6WMYE.js";
3
+ } from "./chunk-WG276AIE.js";
4
+ import "./chunk-VZNPE26F.js";
5
+ import "./chunk-NSVRGATQ.js";
6
6
  import "./chunk-4G7FSRPN.js";
7
7
  export {
8
8
  generate
@@ -0,0 +1,3 @@
1
+ export * from "@eventcatalog/fitness";
2
+ import * as dependency from "@eventcatalog/fitness";
3
+ export default dependency.default;
@@ -0,0 +1,3 @@
1
+ export * from "@modelcontextprotocol/client";
2
+ import * as dependency from "@modelcontextprotocol/client";
3
+ export default dependency.default;
@@ -0,0 +1,3 @@
1
+ export * from "@modelcontextprotocol/server";
2
+ import * as dependency from "@modelcontextprotocol/server";
3
+ export default dependency.default;
@@ -17,6 +17,7 @@
17
17
  "@auth/core/providers/okta": "@auth__core__providers__okta.mjs",
18
18
  "@auth/core/types": "@auth__core__types.mjs",
19
19
  "@eventcatalog/exporters": "@eventcatalog__exporters.mjs",
20
+ "@eventcatalog/fitness": "@eventcatalog__fitness.mjs",
20
21
  "@eventcatalog/license": "@eventcatalog__license.mjs",
21
22
  "@eventcatalog/linter": "@eventcatalog__linter.mjs",
22
23
  "@eventcatalog/sdk": "@eventcatalog__sdk.mjs",
@@ -44,13 +45,11 @@
44
45
  "@mcp-b/webmcp-polyfill": "@mcp-b__webmcp-polyfill.mjs",
45
46
  "@mermaid-js/layout-elk": "@mermaid-js__layout-elk.mjs",
46
47
  "@mermaid-js/layout-elk/dist/mermaid-layout-elk.core.mjs": "@mermaid-js__layout-elk__dist__mermaid-layout-elk.core.mjs.mjs",
48
+ "@modelcontextprotocol/client": "@modelcontextprotocol__client.mjs",
47
49
  "@modelcontextprotocol/ext-apps": "@modelcontextprotocol__ext-apps.mjs",
48
50
  "@modelcontextprotocol/ext-apps/react": "@modelcontextprotocol__ext-apps__react.mjs",
49
51
  "@modelcontextprotocol/ext-apps/server": "@modelcontextprotocol__ext-apps__server.mjs",
50
- "@modelcontextprotocol/sdk": "@modelcontextprotocol__sdk.mjs",
51
- "@modelcontextprotocol/sdk/server/mcp.js": "@modelcontextprotocol__sdk__server__mcp.js.mjs",
52
- "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js": "@modelcontextprotocol__sdk__server__webStandardStreamableHttp.js.mjs",
53
- "@modelcontextprotocol/sdk/types.js": "@modelcontextprotocol__sdk__types.js.mjs",
52
+ "@modelcontextprotocol/server": "@modelcontextprotocol__server.mjs",
54
53
  "@nanostores/react": "@nanostores__react.mjs",
55
54
  "@parcel/watcher": "@parcel__watcher.mjs",
56
55
  "@radix-ui/react-context-menu": "@radix-ui__react-context-menu.mjs",
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "5.2.1";
39
+ var version = "5.4.1";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-OVEXZEJJ.js";
4
- import "../chunk-QSD6WMYE.js";
3
+ } from "../chunk-VZNPE26F.js";
4
+ import "../chunk-NSVRGATQ.js";
5
5
  export {
6
6
  logger
7
7
  };