@eventcatalog/core 4.10.10 → 4.10.12

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 (37) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-Y6VX2CGL.js → chunk-7URQ754Q.js} +1 -1
  6. package/dist/{chunk-PR77FB54.js → chunk-A53VCEEA.js} +1 -1
  7. package/dist/{chunk-K45ABG6N.js → chunk-S4LLGOO4.js} +1 -1
  8. package/dist/{chunk-U6R6BFJ5.js → chunk-VFSEVDCQ.js} +1 -1
  9. package/dist/{chunk-QXANW76J.js → chunk-Y5XU3YLT.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.config.d.cts +9 -0
  14. package/dist/eventcatalog.config.d.ts +9 -0
  15. package/dist/eventcatalog.js +5 -5
  16. package/dist/generate.cjs +1 -1
  17. package/dist/generate.js +3 -3
  18. package/dist/utils/cli-logger.cjs +1 -1
  19. package/dist/utils/cli-logger.js +2 -2
  20. package/eventcatalog/astro.config.mjs +2 -0
  21. package/eventcatalog/src/components/Grids/DomainGrid.tsx +20 -12
  22. package/eventcatalog/src/components/Grids/message-link.spec.ts +59 -0
  23. package/eventcatalog/src/components/Grids/message-link.ts +38 -0
  24. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +22 -40
  25. package/eventcatalog/src/content.config.ts +1 -1
  26. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +88 -0
  27. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +19 -8
  28. package/eventcatalog/src/plugins/link-validation.ts +42 -0
  29. package/eventcatalog/src/utils/collections/domains.ts +1 -80
  30. package/eventcatalog/src/utils/collections/glob-loader.spec.ts +69 -2
  31. package/eventcatalog/src/utils/collections/glob-loader.ts +5 -3
  32. package/eventcatalog/src/utils/collections/hydrate-services.ts +51 -0
  33. package/eventcatalog/src/utils/collections/schema-loader.ts +1 -1
  34. package/eventcatalog/src/utils/collections/systems.ts +17 -7
  35. package/eventcatalog/src/utils/link-validation.ts +224 -0
  36. package/eventcatalog/src/utils/resource-reference-links.ts +29 -0
  37. package/package.json +4 -3
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(analytics_exports);
36
36
  var import_os = __toESM(require("os"), 1);
37
37
 
38
38
  // package.json
39
- var version = "4.10.10";
39
+ var version = "4.10.12";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-QXANW76J.js";
4
- import "../chunk-PR77FB54.js";
3
+ } from "../chunk-Y5XU3YLT.js";
4
+ import "../chunk-A53VCEEA.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -140,7 +140,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
140
140
  var import_os = __toESM(require("os"), 1);
141
141
 
142
142
  // package.json
143
- var version = "4.10.10";
143
+ var version = "4.10.12";
144
144
 
145
145
  // src/constants.ts
146
146
  var VERSION = version;
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-K45ABG6N.js";
4
- import "../chunk-QXANW76J.js";
3
+ } from "../chunk-S4LLGOO4.js";
4
+ import "../chunk-Y5XU3YLT.js";
5
5
  import "../chunk-ZAZHAVKB.js";
6
- import "../chunk-PR77FB54.js";
6
+ import "../chunk-A53VCEEA.js";
7
7
  import "../chunk-6QENHZZP.js";
8
8
  export {
9
9
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-U6R6BFJ5.js";
3
+ } from "./chunk-VFSEVDCQ.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "4.10.10";
2
+ var version = "4.10.12";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-QXANW76J.js";
3
+ } from "./chunk-Y5XU3YLT.js";
4
4
  import {
5
5
  countResources,
6
6
  hashCatalogContent,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-PR77FB54.js";
3
+ } from "./chunk-A53VCEEA.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-PR77FB54.js";
3
+ } from "./chunk-A53VCEEA.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import os from "os";
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "4.10.10";
28
+ var version = "4.10.12";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-PR77FB54.js";
3
+ } from "./chunk-A53VCEEA.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -144,7 +144,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
144
144
  var import_picocolors = __toESM(require("picocolors"), 1);
145
145
 
146
146
  // package.json
147
- var version = "4.10.10";
147
+ var version = "4.10.12";
148
148
 
149
149
  // src/constants.ts
150
150
  var VERSION = version;
@@ -242,6 +242,15 @@ interface Config {
242
242
  host?: string | boolean;
243
243
  trailingSlash?: boolean;
244
244
  output?: 'server' | 'static';
245
+ /** Validate internal links and anchors after static builds. Set false to disable. */
246
+ linkValidation?: false | {
247
+ /** @default 'warn' */
248
+ onBrokenLinks?: 'warn' | 'error' | 'ignore';
249
+ /** @default 'warn' */
250
+ onBrokenAnchors?: 'warn' | 'error' | 'ignore';
251
+ /** Glob patterns for destination URL paths, relative to the catalog base (e.g. /api/**). */
252
+ ignore?: string[];
253
+ };
245
254
  server?: {
246
255
  allowedHosts?: string[] | true;
247
256
  };
@@ -242,6 +242,15 @@ interface Config {
242
242
  host?: string | boolean;
243
243
  trailingSlash?: boolean;
244
244
  output?: 'server' | 'static';
245
+ /** Validate internal links and anchors after static builds. Set false to disable. */
246
+ linkValidation?: false | {
247
+ /** @default 'warn' */
248
+ onBrokenLinks?: 'warn' | 'error' | 'ignore';
249
+ /** @default 'warn' */
250
+ onBrokenAnchors?: 'warn' | 'error' | 'ignore';
251
+ /** Glob patterns for destination URL paths, relative to the catalog base (e.g. /api/**). */
252
+ ignore?: string[];
253
+ };
245
254
  server?: {
246
255
  allowedHosts?: string[] | true;
247
256
  };
@@ -4,8 +4,8 @@ import {
4
4
  import "./chunk-CA4U2JP7.js";
5
5
  import {
6
6
  log_build_default
7
- } from "./chunk-K45ABG6N.js";
8
- import "./chunk-QXANW76J.js";
7
+ } from "./chunk-S4LLGOO4.js";
8
+ import "./chunk-Y5XU3YLT.js";
9
9
  import "./chunk-ZAZHAVKB.js";
10
10
  import {
11
11
  FederationConflictError,
@@ -31,10 +31,10 @@ import {
31
31
  } from "./chunk-B7HCX5HM.js";
32
32
  import {
33
33
  generate
34
- } from "./chunk-Y6VX2CGL.js";
34
+ } from "./chunk-7URQ754Q.js";
35
35
  import {
36
36
  logger
37
- } from "./chunk-U6R6BFJ5.js";
37
+ } from "./chunk-VFSEVDCQ.js";
38
38
  import {
39
39
  resolve_catalog_dependencies_default
40
40
  } from "./chunk-LHR4G2UO.js";
@@ -54,7 +54,7 @@ import {
54
54
  import "./chunk-W5JQON7Z.js";
55
55
  import {
56
56
  VERSION
57
- } from "./chunk-PR77FB54.js";
57
+ } from "./chunk-A53VCEEA.js";
58
58
  import {
59
59
  linkCoreNodeModules,
60
60
  resolveInstalledCoreNodeModules
package/dist/generate.cjs CHANGED
@@ -108,7 +108,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
108
108
  var import_picocolors = __toESM(require("picocolors"), 1);
109
109
 
110
110
  // package.json
111
- var version = "4.10.10";
111
+ var version = "4.10.12";
112
112
 
113
113
  // src/constants.ts
114
114
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-Y6VX2CGL.js";
4
- import "./chunk-U6R6BFJ5.js";
5
- import "./chunk-PR77FB54.js";
3
+ } from "./chunk-7URQ754Q.js";
4
+ import "./chunk-VFSEVDCQ.js";
5
+ import "./chunk-A53VCEEA.js";
6
6
  import "./chunk-6QENHZZP.js";
7
7
  export {
8
8
  generate
@@ -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 = "4.10.10";
39
+ var version = "4.10.12";
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-U6R6BFJ5.js";
4
- import "../chunk-PR77FB54.js";
3
+ } from "../chunk-VFSEVDCQ.js";
4
+ import "../chunk-A53VCEEA.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -17,6 +17,7 @@ import rehypeAutolinkHeadings from 'rehype-autolink-headings';
17
17
  import { eventCatalogLikeC4 } from './src/plugins/likec4';
18
18
  import { loadAstroCompressIntegration } from './src/plugins/astro-compress';
19
19
  import { astroTrailingSlashEndpointFix } from './src/plugins/astro-trailing-slash-endpoint-fix';
20
+ import { linkValidation } from './src/plugins/link-validation';
20
21
 
21
22
  import rehypeExpressiveCode from 'rehype-expressive-code';
22
23
 
@@ -111,6 +112,7 @@ export default defineConfig({
111
112
  effectiveOutput !== 'server' && compress && (await loadAstroCompressIntegration(projectDirectory)),
112
113
  ecstudioWatcher(),
113
114
  eventCatalogIntegration(),
115
+ linkValidation(config.linkValidation),
114
116
  ].filter(Boolean),
115
117
  vite: {
116
118
  plugins: [
@@ -17,6 +17,7 @@ import {
17
17
  import { buildUrl } from '@utils/url-builder';
18
18
  import { BoxIcon, Group as GroupIcon } from 'lucide-react';
19
19
  import { getSpecUrl, getSpecIcon, getSpecLabel, getServiceSpecifications } from './specification-utils';
20
+ import { getMessageLinkProps } from './message-link';
20
21
 
21
22
  // ============================================
22
23
  // Types
@@ -64,12 +65,8 @@ export const EntityBadge = memo(({ entity }: { entity: any }) => {
64
65
  });
65
66
 
66
67
  const MessageLink = memo(({ message }: { message: any }) => {
67
- const data = message?.data || message;
68
- const collection = message?.collection || 'events';
69
- const { Icon, color } = getMessageIcon(collection);
70
- const id = data?.id || message?.id;
71
- const name = data?.name || data?.id || id;
72
- const version = data?.version || message?.data?.version || 'latest';
68
+ const { collection, name, version, href } = getMessageLinkProps(message);
69
+ const { Icon, color } = getMessageIcon(collection || '');
73
70
 
74
71
  const iconStyles: Record<string, string> = {
75
72
  orange: 'text-orange-500',
@@ -78,14 +75,25 @@ const MessageLink = memo(({ message }: { message: any }) => {
78
75
  gray: 'text-gray-500',
79
76
  };
80
77
 
81
- return (
82
- <a
83
- href={buildUrl(`/docs/${collection}/${id}/${version}`)}
84
- className="flex items-center gap-2 py-1.5 text-sm text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] transition-colors group"
85
- >
78
+ const content = (
79
+ <>
86
80
  <Icon className={`h-4 w-4 flex-shrink-0 ${iconStyles[color]}`} />
87
- <span className="group-hover:underline">{name}</span>
81
+ <span className={href ? 'group-hover:underline' : undefined}>{name}</span>
88
82
  <span className="text-xs text-[rgb(var(--ec-icon-color))]">v{version}</span>
83
+ </>
84
+ );
85
+
86
+ const className =
87
+ 'flex items-center gap-2 py-1.5 text-sm text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] transition-colors group';
88
+
89
+ // Missing collection means an unhydrated pointer — do not guess `/docs/events/...`.
90
+ if (!href) {
91
+ return <span className={className}>{content}</span>;
92
+ }
93
+
94
+ return (
95
+ <a href={buildUrl(href)} className={className}>
96
+ {content}
89
97
  </a>
90
98
  );
91
99
  });
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getMessageLinkProps } from './message-link';
3
+
4
+ describe('getMessageLinkProps', () => {
5
+ it('builds a command docs url and prefers the resource name over the id', () => {
6
+ expect(
7
+ getMessageLinkProps({
8
+ collection: 'commands',
9
+ data: { id: 'order-create', name: 'Create Order', version: '0.0.1' },
10
+ })
11
+ ).toEqual({
12
+ collection: 'commands',
13
+ id: 'order-create',
14
+ name: 'Create Order',
15
+ version: '0.0.1',
16
+ href: '/docs/commands/order-create/0.0.1',
17
+ });
18
+ });
19
+
20
+ it('builds a query docs url from a hydrated collection entry', () => {
21
+ expect(
22
+ getMessageLinkProps({
23
+ collection: 'queries',
24
+ data: { id: 'get-stock-level', name: 'Get Stock Level', version: '1.0.0' },
25
+ })
26
+ ).toEqual({
27
+ collection: 'queries',
28
+ id: 'get-stock-level',
29
+ name: 'Get Stock Level',
30
+ version: '1.0.0',
31
+ href: '/docs/queries/get-stock-level/1.0.0',
32
+ });
33
+ });
34
+
35
+ it('does not default a missing collection to events', () => {
36
+ expect(getMessageLinkProps({ id: 'order-create', version: '0.0.1' })).toEqual({
37
+ collection: undefined,
38
+ id: 'order-create',
39
+ name: 'order-create',
40
+ version: '0.0.1',
41
+ href: undefined,
42
+ });
43
+ });
44
+
45
+ it('ignores unknown collection values instead of linking to events', () => {
46
+ expect(
47
+ getMessageLinkProps({
48
+ collection: 'services',
49
+ data: { id: 'order-service', name: 'Order Service', version: '1.0.0' },
50
+ })
51
+ ).toEqual({
52
+ collection: undefined,
53
+ id: 'order-service',
54
+ name: 'Order Service',
55
+ version: '1.0.0',
56
+ href: undefined,
57
+ });
58
+ });
59
+ });
@@ -0,0 +1,38 @@
1
+ import type { CollectionMessageTypes } from '@types';
2
+
3
+ export const MESSAGE_COLLECTIONS: readonly CollectionMessageTypes[] = ['events', 'commands', 'queries'];
4
+
5
+ export function isMessageCollection(value: unknown): value is CollectionMessageTypes {
6
+ return value === 'events' || value === 'commands' || value === 'queries';
7
+ }
8
+
9
+ export interface MessageLinkProps {
10
+ collection?: CollectionMessageTypes;
11
+ id?: string;
12
+ name?: string;
13
+ version: string;
14
+ href?: string;
15
+ }
16
+
17
+ /**
18
+ * Resolve a DomainGrid/SystemGrid receives/sends item into a docs link.
19
+ *
20
+ * Hydrated collection entries carry `collection` + `data.name`. Bare `{id, version}`
21
+ * pointers do not — never guess `events` for those, or commands/queries get the wrong URL.
22
+ */
23
+ export function getMessageLinkProps(message: any): MessageLinkProps {
24
+ const data = message?.data ?? message;
25
+ const collectionCandidate = message?.collection ?? data?.collection;
26
+ const collection = isMessageCollection(collectionCandidate) ? collectionCandidate : undefined;
27
+ const id = data?.id ?? message?.id;
28
+ const name = data?.name || message?.name || id;
29
+ const version = data?.version ?? message?.version ?? 'latest';
30
+
31
+ return {
32
+ collection,
33
+ id,
34
+ name,
35
+ version,
36
+ href: collection && id ? `/docs/${collection}/${id}/${version}` : undefined,
37
+ };
38
+ }
@@ -16,6 +16,12 @@ import { getCollection } from 'astro:content';
16
16
  import { getServiceSpecifications, getSpecUrl, getSpecLabel } from '@components/Grids/specification-utils';
17
17
  import { getResourceReferenceStyle } from '@utils/resource-reference-colors';
18
18
  import { isIconPath, resolveIconUrl } from '@utils/icon';
19
+ import {
20
+ getResourceReferenceUrl,
21
+ isVersionedReference,
22
+ resolveMessageReference,
23
+ resolveOwnerReference,
24
+ } from '@utils/resource-reference-links';
19
25
 
20
26
  interface Props {
21
27
  type:
@@ -157,18 +163,16 @@ try {
157
163
  }
158
164
 
159
165
  const resourcesCollection = (await getCollection(collection as any)) as { data: { id: string; version: string } }[];
160
- const resources = getItemsFromCollectionByIdAndSemverOrLatest(resourcesCollection, resourceId, version);
166
+ const resources = isVersionedReference(collection)
167
+ ? getItemsFromCollectionByIdAndSemverOrLatest(resourcesCollection, resourceId, version)
168
+ : resourcesCollection.filter((item) => item.data.id === resourceId);
161
169
 
162
170
  if (resources.length === 0) {
163
171
  throw new Error(`Resource not found: ${resourceId}`);
164
172
  }
165
173
 
166
174
  resource = resources[0];
167
- // Diagrams use /diagrams/ path, other resources use /docs/{collection}/
168
- href =
169
- type === 'diagram'
170
- ? buildUrl(`/diagrams/${resourceId}/${resource.data.version}`)
171
- : buildUrl(`/docs/${collection}/${resourceId}/${resource.data.version}`);
175
+ href = getResourceReferenceUrl(collection, resourceId, resource.data.version);
172
176
  }
173
177
  } catch (error) {
174
178
  hasError = true;
@@ -180,7 +184,7 @@ const maxSummaryLength = 120;
180
184
  const summary = resource?.data?.summary || '';
181
185
  const truncatedSummary = summary.length > maxSummaryLength ? summary.slice(0, maxSummaryLength) + '...' : summary;
182
186
 
183
- const isVersionedResource = type !== 'doc';
187
+ const isVersionedResource = type !== 'doc' && isVersionedReference(collection);
184
188
 
185
189
  // Only these types have visualizers
186
190
  const hasVisualizer = ['agent', 'domain', 'service', 'event', 'query', 'command', 'container', 'system'].includes(type);
@@ -191,7 +195,7 @@ const isDeprecated = deprecation?.isMarkedAsDeprecated || false;
191
195
  const resourceIconUrl = isIconPath(resource?.data?.styles?.icon) ? resolveIconUrl(resource.data.styles.icon) : null;
192
196
 
193
197
  // Get owners (first 2)
194
- const owners = resource?.data?.owners?.slice(0, 2) || [];
198
+ const owners = await Promise.all((resource?.data?.owners?.slice(0, 2) || []).map(resolveOwnerReference));
195
199
 
196
200
  // Check if message type has a schema
197
201
  const isMessageType = ['event', 'command', 'query'].includes(type);
@@ -207,39 +211,16 @@ const receives = resource?.data?.receives || [];
207
211
  const isService = type === 'service' || type === 'agent';
208
212
  const supportsSpecifications = type === 'service';
209
213
 
210
- // Helper to resolve message version and collection - use specified version or fetch from collection
211
- const resolveMessage = async (msg: any): Promise<{ version: string | null; collection: string | null }> => {
212
- // If version is specified and not "latest", use it (assume event as default collection)
213
- if (msg.version && msg.version !== 'latest') {
214
- return { version: msg.version, collection: 'events' };
215
- }
216
-
217
- // Try to find the message in events, commands, or queries collections
218
- const collections = ['events', 'commands', 'queries'];
219
- for (const col of collections) {
220
- try {
221
- const items = (await getCollection(col as any)) as { data: { id: string; version: string } }[];
222
- const found = getItemsFromCollectionByIdAndSemverOrLatest(items, msg.id);
223
- if (found.length > 0 && found[0].data.version && found[0].data.version !== 'latest') {
224
- return { version: found[0].data.version, collection: col };
225
- }
226
- } catch (e) {
227
- // Collection might not exist or item not found, continue
228
- }
229
- }
230
- return { version: null, collection: null };
231
- };
232
-
233
214
  // Resolve versions and collections for messages to show
234
215
  const sendsWithVersions = await Promise.all(
235
216
  sends.slice(0, maxMessages).map(async (msg: any) => {
236
- const resolved = await resolveMessage(msg);
217
+ const resolved = await resolveMessageReference(msg);
237
218
  return { ...msg, resolvedVersion: resolved.version, resolvedCollection: resolved.collection };
238
219
  })
239
220
  );
240
221
  const receivesWithVersions = await Promise.all(
241
222
  receives.slice(0, maxMessages).map(async (msg: any) => {
242
- const resolved = await resolveMessage(msg);
223
+ const resolved = await resolveMessageReference(msg);
243
224
  return { ...msg, resolvedVersion: resolved.version, resolvedCollection: resolved.collection };
244
225
  })
245
226
  );
@@ -422,15 +403,16 @@ const tooltipId = `ref-tooltip-${Math.random().toString(36).slice(2, 9)}`;
422
403
  <span class="text-[rgb(var(--ec-page-text-muted))]">Owner</span>
423
404
  <span class="font-mono text-[rgb(var(--ec-page-text))]">
424
405
  {owners.map((o: any, idx: number) => {
425
- const ownerId = typeof o === 'string' ? o : o.id;
406
+ const ownerId = o.id;
426
407
  return (
427
408
  <>
428
- <a
429
- href={buildUrl(`/docs/users/${ownerId}`)}
430
- class="hover:underline hover:text-[rgb(var(--ec-accent))]"
431
- >
432
- {ownerId}
433
- </a>
409
+ {o.href ? (
410
+ <a href={o.href} class="hover:underline hover:text-[rgb(var(--ec-accent))]">
411
+ {ownerId}
412
+ </a>
413
+ ) : (
414
+ <span>{ownerId}</span>
415
+ )}
434
416
  {idx < owners.length - 1 && ', '}
435
417
  </>
436
418
  );
@@ -1107,7 +1107,7 @@ const teams = defineCollection({
1107
1107
 
1108
1108
  const designs = defineCollection({
1109
1109
  loader: async () => {
1110
- const data = await globPackage('**/**/*.ecstudio', { cwd: projectDirBase, ignore: ['dist/**'] });
1110
+ const data = await globPackage('**/**/*.ecstudio', { cwd: projectDirBase, ignore: ['dist/**', '**/node_modules/**'] });
1111
1111
  // File all the files in the designs folder
1112
1112
  // Limit 3 designs community edition?
1113
1113
  const files = data.reduce<{ id: string; name: string }[]>((acc, filePath) => {
@@ -0,0 +1,88 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { loadArchitectureItems, Page } from './_index.data';
3
+
4
+ const collectionMocks = vi.hoisted(() => ({
5
+ getDomains: vi.fn(),
6
+ getServices: vi.fn(),
7
+ getSystems: vi.fn(),
8
+ }));
9
+
10
+ vi.mock('@utils/feature', () => ({
11
+ isSSR: () => false,
12
+ }));
13
+
14
+ vi.mock('@utils/collections/domains', () => ({ getDomains: collectionMocks.getDomains }));
15
+ vi.mock('@utils/collections/systems', () => ({ getSystems: collectionMocks.getSystems }));
16
+ vi.mock('@utils/page-loaders/page-data-loader', () => ({
17
+ pageDataLoader: {
18
+ services: (...args: unknown[]) => collectionMocks.getServices(...args),
19
+ domains: (...args: unknown[]) => collectionMocks.getDomains(...args),
20
+ systems: (...args: unknown[]) => collectionMocks.getSystems(...args),
21
+ },
22
+ }));
23
+
24
+ const domain = {
25
+ collection: 'domains',
26
+ data: { id: 'ordering', name: 'Ordering', version: '1.0.0' },
27
+ };
28
+
29
+ const service = {
30
+ collection: 'services',
31
+ data: { id: 'order-service', name: 'Order Service', version: '1.0.0' },
32
+ };
33
+
34
+ const system = {
35
+ collection: 'systems',
36
+ data: { id: 'order-management-system', name: 'Order Management', version: '1.0.0' },
37
+ };
38
+
39
+ describe('architecture page data', () => {
40
+ beforeEach(() => {
41
+ collectionMocks.getDomains.mockReset().mockResolvedValue([domain]);
42
+ collectionMocks.getServices.mockReset().mockResolvedValue([service]);
43
+ collectionMocks.getSystems.mockReset().mockResolvedValue([system]);
44
+ });
45
+
46
+ it('hydrates domain and system services for static architecture paths', async () => {
47
+ const paths = await Page.getStaticPaths();
48
+
49
+ expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
50
+ expect(collectionMocks.getSystems).toHaveBeenCalledWith({ enrichServices: true });
51
+ expect(paths).toEqual(
52
+ expect.arrayContaining([
53
+ expect.objectContaining({
54
+ params: { type: 'domains', id: 'ordering', version: '1.0.0' },
55
+ }),
56
+ expect.objectContaining({
57
+ params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
58
+ }),
59
+ expect.objectContaining({
60
+ params: { type: 'services', id: 'order-service', version: '1.0.0' },
61
+ }),
62
+ ])
63
+ );
64
+ });
65
+
66
+ it('hydrates domain services when architecture pages are fetched in SSR', async () => {
67
+ await Page.getData({
68
+ props: {},
69
+ params: { type: 'domains', id: 'ordering', version: '1.0.0' },
70
+ } as any);
71
+
72
+ expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
73
+ });
74
+
75
+ it('hydrates system services when architecture pages are fetched in SSR', async () => {
76
+ await Page.getData({
77
+ props: {},
78
+ params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
79
+ } as any);
80
+
81
+ expect(collectionMocks.getSystems).toHaveBeenCalledWith({ enrichServices: true });
82
+ });
83
+
84
+ it('loads domains with enrichServices so DomainGrid command links keep their collection', async () => {
85
+ await loadArchitectureItems('domains');
86
+ expect(collectionMocks.getDomains).toHaveBeenCalledWith({ enrichServices: true });
87
+ });
88
+ });