@eventcatalog/core 4.9.0 → 4.10.0

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 (60) 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-XQHS36GO.js → chunk-5XURY2JV.js} +1 -1
  6. package/dist/{chunk-3CGSSMTK.js → chunk-BDFTJVGN.js} +1 -1
  7. package/dist/{chunk-HKVOSQTD.js → chunk-BQXEWQ5J.js} +1 -1
  8. package/dist/{chunk-MPHX5C7M.js → chunk-HFAQXAG3.js} +1 -1
  9. package/dist/{chunk-LVGQ23LR.js → chunk-IC6XSXJO.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.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/Badge.astro +3 -1
  19. package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +0 -5
  20. package/eventcatalog/src/components/MDX/Design/Design.astro +2 -0
  21. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -0
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +5 -7
  24. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -0
  25. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +121 -88
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +55 -7
  27. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +59 -15
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +53 -7
  29. package/eventcatalog/src/content.config.ts +70 -0
  30. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +19 -0
  31. package/eventcatalog/src/enterprise/fields/pages/fields.astro +2 -0
  32. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +1 -1
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +16 -15
  34. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +21 -17
  35. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -4
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +16 -8
  38. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +48 -65
  39. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +45 -18
  40. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -1
  41. package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -1
  42. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +5 -1
  43. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +2 -0
  44. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -0
  45. package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +96 -34
  46. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +167 -75
  47. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +135 -59
  48. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +144 -58
  49. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +306 -188
  50. package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +108 -39
  51. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +147 -63
  52. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +212 -121
  53. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +223 -127
  54. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +3 -23
  55. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +187 -97
  56. package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +678 -0
  57. package/eventcatalog/src/stores/sidebar-store/state.ts +66 -36
  58. package/eventcatalog/src/styles/tailwind.css +82 -0
  59. package/eventcatalog/src/utils/collections/resource-docs.ts +1 -0
  60. package/package.json +3 -3
@@ -10,16 +10,75 @@ import {
10
10
  buildAttachmentsSection,
11
11
  buildDiagramNavItems,
12
12
  buildResourceDocsSection,
13
+ buildArchitectureDecisionsSection,
13
14
  } from './shared';
14
15
  import { isVisualiserEnabled, isChangelogEnabled } from '@utils/feature';
15
16
  import { pluralizeMessageType } from '@utils/collections/messages';
16
17
  import { getSpecificationsForDomain, hasUbiquitousLanguageTermsWithSubdomainsInCollection } from '@utils/collections/domains';
17
18
  import { customIconFieldsForResource } from '@utils/icon';
19
+ import { resolveSidebarPages, toCustomSidebarContext, type SidebarSpec } from '../custom-sidebar';
18
20
 
19
21
  // Sort resolved collection entries A-Z by their display name (falling back to id).
20
22
  const byResourceName = (a: any, b: any) => (a.data?.name || a.data?.id || '').localeCompare(b.data?.name || b.data?.id || '');
21
23
 
22
- export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[], context: ResourceGroupContext): NavNode => {
24
+ /**
25
+ * Predefined sidebar sections for a domain, keyed by the token users reference from
26
+ * `sidebar.json` (e.g. `$quick-reference`). Each token is the kebab-cased title of the
27
+ * section as it appears in the default sidebar.
28
+ */
29
+ export type DomainSectionKey =
30
+ | 'quick-reference'
31
+ | 'documentation'
32
+ | 'architecture'
33
+ | 'diagrams'
34
+ | 'api-and-contracts'
35
+ | 'systems'
36
+ | 'subdomains'
37
+ | 'resources'
38
+ | 'services'
39
+ | 'flows'
40
+ | 'entities'
41
+ | 'domain-events'
42
+ | 'external-events'
43
+ | 'resource-groups'
44
+ | 'agents'
45
+ | 'external-integrations'
46
+ | 'data-products'
47
+ | 'decision-records'
48
+ | 'owners'
49
+ | 'code'
50
+ | 'attachments';
51
+
52
+ export type DomainSections = Record<DomainSectionKey, NavNode | NavNode[] | null>;
53
+
54
+ /**
55
+ * Order of sections in the default (generated) sidebar. `decision-records` is deliberately
56
+ * absent — in default mode it is inserted before Owners by `withArchitectureDecisionsSection`
57
+ * in state.ts, and only becomes a first-class token for custom sidebars.
58
+ */
59
+ export const DEFAULT_DOMAIN_SECTION_ORDER: DomainSectionKey[] = [
60
+ 'quick-reference',
61
+ 'documentation',
62
+ 'architecture',
63
+ 'diagrams',
64
+ 'api-and-contracts',
65
+ 'systems',
66
+ 'subdomains',
67
+ 'resources',
68
+ 'resource-groups',
69
+ 'agents',
70
+ 'external-integrations',
71
+ 'data-products',
72
+ 'owners',
73
+ 'code',
74
+ 'attachments',
75
+ ];
76
+
77
+ export const buildDomainSections = (
78
+ domain: CollectionEntry<'domains'>,
79
+ owners: any[],
80
+ context: ResourceGroupContext
81
+ ): DomainSections => {
23
82
  const agentsInDomain = domain.data.agents || [];
24
83
  const renderAgents = agentsInDomain.length > 0 && shouldRenderSideBarSection(domain, 'agents');
25
84
 
@@ -117,6 +176,251 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
117
176
  const graphQLSpecifications = specifications.filter((specification) => specification.type === 'graphql');
118
177
  const renderSpecifications = hasSpecifications && shouldRenderSideBarSection(domain, 'specifications');
119
178
 
179
+ // Resource subsections. These are built once and shared between the "Resources"
180
+ // umbrella (where they render as subtle subgroups) and their standalone tokens
181
+ // (`$services`, `$entities`, ...) for custom sidebars.
182
+ const servicesSection: NavNode | null = renderServices
183
+ ? {
184
+ type: 'group',
185
+ title: 'Services',
186
+ icon: 'Server',
187
+ pages: servicesInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
188
+ }
189
+ : null;
190
+
191
+ const flowsSection: NavNode | null = hasFlows
192
+ ? {
193
+ type: 'group',
194
+ title: 'Flows',
195
+ icon: 'Waypoints',
196
+ pages: domainFlows.map((flow) => `flow:${(flow as any).data.id}:${(flow as any).data.version}`),
197
+ }
198
+ : null;
199
+
200
+ const entitiesSection: NavNode | null = renderEntities
201
+ ? {
202
+ type: 'group',
203
+ title: 'Entities',
204
+ icon: 'Box',
205
+ pages: entitiesInDomain.map((entity) => ({
206
+ type: 'item',
207
+ title: (entity as any).data?.name || (entity as any).data.id,
208
+ href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
209
+ })),
210
+ }
211
+ : null;
212
+
213
+ const domainEventsSection: NavNode | null =
214
+ renderMessages && sendsMessages.length > 0
215
+ ? {
216
+ type: 'group',
217
+ title: 'Domain Events',
218
+ icon: 'Mail',
219
+ pages: sortedSendsMessages.map(
220
+ (message) => `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
221
+ ),
222
+ }
223
+ : null;
224
+
225
+ const externalEventsSection: NavNode | null =
226
+ renderMessages && receivesMessages.length > 0
227
+ ? {
228
+ type: 'group',
229
+ title: 'External Events',
230
+ icon: 'Mail',
231
+ pages: sortedReceivesMessages.map(
232
+ (receive) => `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
233
+ ),
234
+ }
235
+ : null;
236
+
237
+ const resourceSubsections = [servicesSection, flowsSection, entitiesSection, domainEventsSection, externalEventsSection].filter(
238
+ Boolean
239
+ ) as NavNode[];
240
+
241
+ return {
242
+ 'quick-reference': buildQuickReferenceSection(
243
+ [
244
+ { title: 'Overview', href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}`) },
245
+ hasResources && {
246
+ title: 'Domain Resources',
247
+ href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}/resources`),
248
+ },
249
+ renderUbiquitousLanguage && {
250
+ title: 'Ubiquitous Language',
251
+ href: buildUrl(`/docs/domains/${domain.data.id}/language`),
252
+ },
253
+ isChangelogEnabled() &&
254
+ shouldRenderSideBarSection(domain, 'changelog') && {
255
+ title: 'Changelog',
256
+ href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}/changelog`),
257
+ },
258
+ ].filter(Boolean) as { title: string; href: string }[]
259
+ ),
260
+ documentation: docsSection,
261
+ architecture: {
262
+ type: 'group',
263
+ title: 'Architecture',
264
+ icon: 'Workflow',
265
+ pages: [
266
+ {
267
+ type: 'item',
268
+ title: 'Overview',
269
+ href: buildUrl(`/architecture/domains/${domain.data.id}/${domain.data.version}`),
270
+ },
271
+ renderSystems &&
272
+ renderVisualiser &&
273
+ hasSystemContext && {
274
+ type: 'item',
275
+ title: 'System Diagram',
276
+ href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/systems-context`),
277
+ },
278
+ renderVisualiser &&
279
+ hasResourceDiagram && {
280
+ type: 'item',
281
+ title: 'Resource Diagram',
282
+ href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}`),
283
+ },
284
+ renderEntities &&
285
+ renderVisualiser && {
286
+ type: 'item',
287
+ title: 'Entity Diagram',
288
+ href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/entity-map`),
289
+ },
290
+ ].filter(Boolean) as ChildRef[],
291
+ },
292
+ diagrams: hasDiagrams
293
+ ? {
294
+ type: 'group',
295
+ title: 'Diagrams',
296
+ icon: 'FileImage',
297
+ pages: diagramNavItems,
298
+ }
299
+ : null,
300
+ 'api-and-contracts': renderSpecifications
301
+ ? {
302
+ type: 'group',
303
+ title: 'API & Contracts',
304
+ icon: 'FileCode',
305
+ pages: [
306
+ ...openAPISpecifications.map((specification) => ({
307
+ type: 'item',
308
+ title: specification.name,
309
+ leftIcon: '/icons/openapi-black.svg',
310
+ href: buildUrl(
311
+ `/docs/domains/${domain.data.id}/${domain.data.version}/spec/${specification.filenameWithoutExtension}`
312
+ ),
313
+ })),
314
+ ...asyncAPISpecifications.map((specification) => ({
315
+ type: 'item',
316
+ title: specification.name,
317
+ leftIcon: '/icons/asyncapi-black.svg',
318
+ href: buildUrl(
319
+ `/docs/domains/${domain.data.id}/${domain.data.version}/asyncapi/${specification.filenameWithoutExtension}`
320
+ ),
321
+ })),
322
+ ...graphQLSpecifications.map((specification) => ({
323
+ type: 'item',
324
+ title: specification.name,
325
+ leftIcon: '/icons/graphql-black.svg',
326
+ href: buildUrl(
327
+ `/docs/domains/${domain.data.id}/${domain.data.version}/graphql/${specification.filenameWithoutExtension}`
328
+ ),
329
+ })),
330
+ ],
331
+ }
332
+ : null,
333
+ systems: renderSystems
334
+ ? {
335
+ type: 'group',
336
+ title: 'Systems',
337
+ icon: 'Group',
338
+ pages: systemsInDomain.map((system) => `system:${(system as any).data.id}:${(system as any).data.version}`),
339
+ }
340
+ : null,
341
+ subdomains: renderSubDomains
342
+ ? {
343
+ type: 'group',
344
+ title: 'Subdomains',
345
+ icon: 'Boxes',
346
+ pages: subDomains.map((domain) => `domain:${(domain as any).data.id}:${(domain as any).data.version}`),
347
+ }
348
+ : null,
349
+ resources:
350
+ resourceSubsections.length > 0
351
+ ? {
352
+ type: 'group',
353
+ title: 'Resources',
354
+ icon: 'Boxes',
355
+ // Resource type subsections are ordered A-Z by their title, and the
356
+ // resources within each subsection are ordered A-Z by name (sorted above).
357
+ pages: resourceSubsections
358
+ .map((section) => ({ ...section, subtle: true }))
359
+ .sort((a, b) => a.title.localeCompare(b.title)) as ChildRef[],
360
+ }
361
+ : null,
362
+ services: servicesSection,
363
+ flows: flowsSection,
364
+ entities: entitiesSection,
365
+ 'domain-events': domainEventsSection,
366
+ 'external-events': externalEventsSection,
367
+ 'resource-groups': hasResourceGroups
368
+ ? (buildResourceGroupSections(resourceGroups, context).filter(Boolean) as NavNode[])
369
+ : null,
370
+ agents: renderAgents
371
+ ? {
372
+ type: 'group',
373
+ title: 'Agents In Domain',
374
+ icon: 'Bot',
375
+ pages: agentsInDomain.map((agent) => `agent:${(agent as any).data.id}:${(agent as any).data.version}`),
376
+ }
377
+ : null,
378
+ 'external-integrations': renderExternalSystems
379
+ ? {
380
+ type: 'group',
381
+ title: 'External Integrations',
382
+ icon: 'Globe',
383
+ pages: externalSystemsInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
384
+ }
385
+ : null,
386
+ 'data-products': renderDataProducts
387
+ ? {
388
+ type: 'group',
389
+ title: 'Data Products',
390
+ icon: 'Package',
391
+ pages: dataProductsInDomain.map(
392
+ (dataProduct) => `data-product:${(dataProduct as any).data.id}:${(dataProduct as any).data.version}`
393
+ ),
394
+ }
395
+ : null,
396
+ 'decision-records': shouldRenderSideBarSection(domain, 'architectureDecisions')
397
+ ? buildArchitectureDecisionsSection(domain, context.adrs || [])
398
+ : null,
399
+ owners: renderOwners ? buildOwnersSection(owners) : null,
400
+ code: renderRepository ? buildRepositorySection(domain.data.repository as { url: string; language: string }) : null,
401
+ attachments: hasAttachments ? buildAttachmentsSection(domain.data.attachments as any[]) : null,
402
+ };
403
+ };
404
+
405
+ export type BuildDomainNodeOptions = {
406
+ /** A parsed `sidebar.json` for this domain. When present it replaces the generated sidebar. */
407
+ sidebar?: SidebarSpec;
408
+ };
409
+
410
+ export const buildDomainNode = (
411
+ domain: CollectionEntry<'domains'>,
412
+ owners: any[],
413
+ context: ResourceGroupContext,
414
+ options: BuildDomainNodeOptions = {}
415
+ ): NavNode => {
416
+ const sections = buildDomainSections(domain, owners, context);
417
+
418
+ const pages = resolveSidebarPages(sections, DEFAULT_DOMAIN_SECTION_ORDER, {
419
+ sidebar: options.sidebar,
420
+ resource: { collection: 'domains', id: domain.data.id, version: domain.data.version, entry: domain },
421
+ context: toCustomSidebarContext(context),
422
+ });
423
+
120
424
  return {
121
425
  type: 'item',
122
426
  title: domain.data.name,
@@ -126,192 +430,6 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
126
430
  // 'Domains' section header (and Domain badge) already convey the type, so the
127
431
  // default Boxes glyph on every item is redundant.
128
432
  ...customIconFieldsForResource(domain.data),
129
- pages: [
130
- buildQuickReferenceSection(
131
- [
132
- { title: 'Overview', href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}`) },
133
- hasResources && {
134
- title: 'Domain Resources',
135
- href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}/resources`),
136
- },
137
- renderUbiquitousLanguage && {
138
- title: 'Ubiquitous Language',
139
- href: buildUrl(`/docs/domains/${domain.data.id}/language`),
140
- },
141
- isChangelogEnabled() &&
142
- shouldRenderSideBarSection(domain, 'changelog') && {
143
- title: 'Changelog',
144
- href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}/changelog`),
145
- },
146
- ].filter(Boolean) as { title: string; href: string }[]
147
- ),
148
- docsSection,
149
- {
150
- type: 'group',
151
- title: 'Architecture',
152
- icon: 'Workflow',
153
- pages: [
154
- {
155
- type: 'item',
156
- title: 'Overview',
157
- href: buildUrl(`/architecture/domains/${domain.data.id}/${domain.data.version}`),
158
- },
159
- renderSystems &&
160
- renderVisualiser &&
161
- hasSystemContext && {
162
- type: 'item',
163
- title: 'System Diagram',
164
- href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/systems-context`),
165
- },
166
- renderVisualiser &&
167
- hasResourceDiagram && {
168
- type: 'item',
169
- title: 'Resource Diagram',
170
- href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}`),
171
- },
172
- renderEntities &&
173
- renderVisualiser && {
174
- type: 'item',
175
- title: 'Entity Diagram',
176
- href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/entity-map`),
177
- },
178
- ].filter(Boolean) as ChildRef[],
179
- },
180
- hasDiagrams && {
181
- type: 'group',
182
- title: 'Diagrams',
183
- icon: 'FileImage',
184
- pages: diagramNavItems,
185
- },
186
- renderSpecifications && {
187
- type: 'group',
188
- title: 'API & Contracts',
189
- icon: 'FileCode',
190
- pages: [
191
- ...openAPISpecifications.map((specification) => ({
192
- type: 'item',
193
- title: specification.name,
194
- leftIcon: '/icons/openapi-black.svg',
195
- href: buildUrl(
196
- `/docs/domains/${domain.data.id}/${domain.data.version}/spec/${specification.filenameWithoutExtension}`
197
- ),
198
- })),
199
- ...asyncAPISpecifications.map((specification) => ({
200
- type: 'item',
201
- title: specification.name,
202
- leftIcon: '/icons/asyncapi-black.svg',
203
- href: buildUrl(
204
- `/docs/domains/${domain.data.id}/${domain.data.version}/asyncapi/${specification.filenameWithoutExtension}`
205
- ),
206
- })),
207
- ...graphQLSpecifications.map((specification) => ({
208
- type: 'item',
209
- title: specification.name,
210
- leftIcon: '/icons/graphql-black.svg',
211
- href: buildUrl(
212
- `/docs/domains/${domain.data.id}/${domain.data.version}/graphql/${specification.filenameWithoutExtension}`
213
- ),
214
- })),
215
- ],
216
- },
217
- renderSystems && {
218
- type: 'group',
219
- title: 'Systems',
220
- icon: 'Group',
221
- pages: systemsInDomain.map((system) => `system:${(system as any).data.id}:${(system as any).data.version}`),
222
- },
223
- renderSubDomains && {
224
- type: 'group',
225
- title: 'Subdomains',
226
- icon: 'Boxes',
227
- pages: subDomains.map((domain) => `domain:${(domain as any).data.id}:${(domain as any).data.version}`),
228
- },
229
- (renderServices ||
230
- hasFlows ||
231
- renderEntities ||
232
- (renderMessages && (sendsMessages.length > 0 || receivesMessages.length > 0))) && {
233
- type: 'group',
234
- title: 'Resources',
235
- icon: 'Boxes',
236
- // Resource type subsections are ordered A-Z by their title, and the
237
- // resources within each subsection are ordered A-Z by name (sorted above).
238
- pages: (
239
- [
240
- renderServices && {
241
- type: 'group',
242
- title: 'Services',
243
- subtle: true,
244
- icon: 'Server',
245
- pages: servicesInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
246
- },
247
- hasFlows && {
248
- type: 'group',
249
- title: 'Flows',
250
- subtle: true,
251
- icon: 'Waypoints',
252
- pages: domainFlows.map((flow) => `flow:${(flow as any).data.id}:${(flow as any).data.version}`),
253
- },
254
- renderEntities && {
255
- type: 'group',
256
- title: 'Entities',
257
- subtle: true,
258
- icon: 'Box',
259
- pages: entitiesInDomain.map((entity) => ({
260
- type: 'item',
261
- title: (entity as any).data?.name || (entity as any).data.id,
262
- href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
263
- })),
264
- },
265
- renderMessages &&
266
- sendsMessages.length > 0 && {
267
- type: 'group',
268
- title: 'Domain Events',
269
- subtle: true,
270
- icon: 'Mail',
271
- pages: sortedSendsMessages.map(
272
- (message) =>
273
- `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
274
- ),
275
- },
276
- renderMessages &&
277
- receivesMessages.length > 0 && {
278
- type: 'group',
279
- title: 'External Events',
280
- subtle: true,
281
- icon: 'Mail',
282
- pages: sortedReceivesMessages.map(
283
- (receive) =>
284
- `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
285
- ),
286
- },
287
- ].filter(Boolean) as NavNode[]
288
- ).sort((a, b) => a.title.localeCompare(b.title)) as ChildRef[],
289
- },
290
-
291
- ...(hasResourceGroups ? buildResourceGroupSections(resourceGroups, context) : []),
292
- renderAgents && {
293
- type: 'group',
294
- title: 'Agents In Domain',
295
- icon: 'Bot',
296
- pages: agentsInDomain.map((agent) => `agent:${(agent as any).data.id}:${(agent as any).data.version}`),
297
- },
298
- renderExternalSystems && {
299
- type: 'group',
300
- title: 'External Integrations',
301
- icon: 'Globe',
302
- pages: externalSystemsInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
303
- },
304
- renderDataProducts && {
305
- type: 'group',
306
- title: 'Data Products',
307
- icon: 'Package',
308
- pages: dataProductsInDomain.map(
309
- (dataProduct) => `data-product:${(dataProduct as any).data.id}:${(dataProduct as any).data.version}`
310
- ),
311
- },
312
- renderOwners && buildOwnersSection(owners),
313
- renderRepository && buildRepositorySection(domain.data.repository as { url: string; language: string }),
314
- hasAttachments && buildAttachmentsSection(domain.data.attachments as any[]),
315
- ].filter(Boolean) as ChildRef[],
433
+ pages,
316
434
  };
317
435
  };
@@ -1,7 +1,8 @@
1
1
  import type { CollectionEntry } from 'astro:content';
2
2
  import { buildUrl } from '@utils/url-builder';
3
- import type { NavNode, ChildRef, ResourceGroupContext } from './shared';
3
+ import type { NavNode, ResourceGroupContext } from './shared';
4
4
  import {
5
+ buildArchitectureDecisionsSection,
5
6
  buildAttachmentsSection,
6
7
  buildOwnersSection,
7
8
  buildQuickReferenceSection,
@@ -10,8 +11,45 @@ import {
10
11
  } from './shared';
11
12
  import { isChangelogEnabled, isVisualiserEnabled } from '@utils/feature';
12
13
  import { iconFieldsForResource } from '@utils/icon';
14
+ import { resolveSidebarPages, toCustomSidebarContext, type SidebarSpec } from '../custom-sidebar';
13
15
 
14
- export const buildEntityNode = (entity: CollectionEntry<'entities'>, owners: any[], context: ResourceGroupContext): NavNode => {
16
+ /**
17
+ * Predefined sidebar sections for an entity, keyed by the token users reference from
18
+ * `sidebar.json` (e.g. `$quick-reference`). Each token is the kebab-cased title of the
19
+ * section as it appears in the default sidebar.
20
+ */
21
+ export type EntitySectionKey =
22
+ | 'quick-reference'
23
+ | 'documentation'
24
+ | 'architecture'
25
+ | 'domains'
26
+ | 'services'
27
+ | 'decision-records'
28
+ | 'owners'
29
+ | 'attachments';
30
+
31
+ export type EntitySections = Record<EntitySectionKey, NavNode | NavNode[] | null>;
32
+
33
+ /**
34
+ * Order of sections in the default (generated) sidebar. `decision-records` is deliberately
35
+ * absent — in default mode it is inserted before Owners by `withArchitectureDecisionsSection`
36
+ * in state.ts, and only becomes a first-class token for custom sidebars.
37
+ */
38
+ export const DEFAULT_ENTITY_SECTION_ORDER: EntitySectionKey[] = [
39
+ 'quick-reference',
40
+ 'documentation',
41
+ 'architecture',
42
+ 'domains',
43
+ 'services',
44
+ 'owners',
45
+ 'attachments',
46
+ ];
47
+
48
+ export const buildEntitySections = (
49
+ entity: CollectionEntry<'entities'>,
50
+ owners: any[],
51
+ context: ResourceGroupContext
52
+ ): EntitySections => {
15
53
  const domains = entity.data.domains || [];
16
54
  const services = entity.data.services || [];
17
55
 
@@ -40,48 +78,79 @@ export const buildEntityNode = (entity: CollectionEntry<'entities'>, owners: any
40
78
  context.resourceDocCategories
41
79
  );
42
80
 
81
+ return {
82
+ 'quick-reference': buildQuickReferenceSection(
83
+ [
84
+ { title: 'Overview', href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}`) },
85
+ isChangelogEnabled() &&
86
+ shouldRenderSideBarSection(entity, 'changelog') && {
87
+ title: 'Changelog',
88
+ href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}/changelog`),
89
+ },
90
+ ].filter(Boolean) as { title: string; href: string }[]
91
+ ),
92
+ documentation: docsSection,
93
+ architecture: renderArchitecture
94
+ ? {
95
+ type: 'group',
96
+ title: 'Architecture',
97
+ icon: 'Workflow',
98
+ pages: entityMapTargets.map((target) => ({
99
+ type: 'item',
100
+ title: entityMapTargets.length === 1 ? 'Entity Map' : `${target.label} Entity Map`,
101
+ href: target.href,
102
+ })),
103
+ }
104
+ : null,
105
+ domains: renderDomains
106
+ ? {
107
+ type: 'group',
108
+ title: 'Domains',
109
+ icon: 'Boxes',
110
+ pages: domains.map((domain: any) => `domain:${domain.data.id}:${domain.data.version}`),
111
+ }
112
+ : null,
113
+ services: renderServices
114
+ ? {
115
+ type: 'group',
116
+ title: 'Services',
117
+ icon: 'Server',
118
+ pages: services.map((service: any) => `service:${service.data.id}:${service.data.version}`),
119
+ }
120
+ : null,
121
+ 'decision-records': shouldRenderSideBarSection(entity, 'architectureDecisions')
122
+ ? buildArchitectureDecisionsSection(entity, context.adrs || [])
123
+ : null,
124
+ owners: renderOwners ? buildOwnersSection(owners) : null,
125
+ attachments: hasAttachments ? buildAttachmentsSection(entity.data.attachments as any[]) : null,
126
+ };
127
+ };
128
+
129
+ export type BuildEntityNodeOptions = {
130
+ /** A parsed `sidebar.json` for this entity. When present it replaces the generated sidebar. */
131
+ sidebar?: SidebarSpec;
132
+ };
133
+
134
+ export const buildEntityNode = (
135
+ entity: CollectionEntry<'entities'>,
136
+ owners: any[],
137
+ context: ResourceGroupContext,
138
+ options: BuildEntityNodeOptions = {}
139
+ ): NavNode => {
140
+ const sections = buildEntitySections(entity, owners, context);
141
+
142
+ const pages = resolveSidebarPages(sections, DEFAULT_ENTITY_SECTION_ORDER, {
143
+ sidebar: options.sidebar,
144
+ resource: { collection: 'entities', id: entity.data.id, version: entity.data.version, entry: entity },
145
+ context: toCustomSidebarContext(context),
146
+ });
147
+
43
148
  return {
44
149
  type: 'item',
45
150
  title: entity.data.name,
46
151
  badge: 'Entity',
47
152
  summary: entity.data.summary,
48
153
  ...iconFieldsForResource(entity.data, 'Box'),
49
- pages: [
50
- buildQuickReferenceSection(
51
- [
52
- { title: 'Overview', href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}`) },
53
- isChangelogEnabled() &&
54
- shouldRenderSideBarSection(entity, 'changelog') && {
55
- title: 'Changelog',
56
- href: buildUrl(`/docs/entities/${entity.data.id}/${entity.data.version}/changelog`),
57
- },
58
- ].filter(Boolean) as { title: string; href: string }[]
59
- ),
60
- docsSection,
61
- renderArchitecture && {
62
- type: 'group',
63
- title: 'Architecture',
64
- icon: 'Workflow',
65
- pages: entityMapTargets.map((target) => ({
66
- type: 'item',
67
- title: entityMapTargets.length === 1 ? 'Entity Map' : `${target.label} Entity Map`,
68
- href: target.href,
69
- })),
70
- },
71
- renderDomains && {
72
- type: 'group',
73
- title: 'Domains',
74
- icon: 'Boxes',
75
- pages: domains.map((domain: any) => `domain:${domain.data.id}:${domain.data.version}`),
76
- },
77
- renderServices && {
78
- type: 'group',
79
- title: 'Services',
80
- icon: 'Server',
81
- pages: services.map((service: any) => `service:${service.data.id}:${service.data.version}`),
82
- },
83
- renderOwners && buildOwnersSection(owners),
84
- hasAttachments && buildAttachmentsSection(entity.data.attachments as any[]),
85
- ].filter(Boolean) as ChildRef[],
154
+ pages,
86
155
  };
87
156
  };