@eventcatalog/core 3.5.2 → 3.6.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 (46) hide show
  1. package/README.md +1 -1
  2. package/dist/analytics/analytics.cjs +1 -1
  3. package/dist/analytics/analytics.js +2 -2
  4. package/dist/analytics/log-build.cjs +1 -1
  5. package/dist/analytics/log-build.js +3 -3
  6. package/dist/{chunk-YVX5C6L3.js → chunk-FCIJEGOL.js} +1 -1
  7. package/dist/{chunk-WO3AKJVB.js → chunk-N2VBSHPU.js} +1 -1
  8. package/dist/{chunk-OKWCSRLE.js → chunk-OFHFRJ42.js} +1 -1
  9. package/dist/{chunk-YOFNY2RC.js → chunk-SI6IEUYS.js} +1 -1
  10. package/dist/{chunk-YTZSPYJN.js → chunk-XRLZZXIS.js} +1 -1
  11. package/dist/constants.cjs +1 -1
  12. package/dist/constants.js +1 -1
  13. package/dist/eventcatalog.cjs +1 -1
  14. package/dist/eventcatalog.js +5 -5
  15. package/dist/generate.cjs +1 -1
  16. package/dist/generate.js +3 -3
  17. package/dist/utils/cli-logger.cjs +1 -1
  18. package/dist/utils/cli-logger.js +2 -2
  19. package/eventcatalog/astro.config.mjs +2 -1
  20. package/eventcatalog/src/components/EnvironmentDropdown.tsx +1 -1
  21. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +477 -0
  22. package/eventcatalog/src/components/MDX/components.tsx +2 -0
  23. package/eventcatalog/src/components/Search/SearchDataLoader.astro +23 -11
  24. package/eventcatalog/src/components/Search/SearchModal.tsx +17 -2
  25. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +12 -6
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -16
  27. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +816 -0
  28. package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +161 -0
  29. package/eventcatalog/src/components/Tables/Discover/columns.tsx +565 -0
  30. package/eventcatalog/src/components/Tables/Discover/index.ts +4 -0
  31. package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +1 -1
  32. package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +1 -1
  33. package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +1 -1
  34. package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +1 -1
  35. package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +54 -64
  36. package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +15 -30
  37. package/eventcatalog/src/enterprise/plans/index.astro +125 -98
  38. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +1 -1
  39. package/eventcatalog/src/pages/api/sidebar-data.json.ts +22 -0
  40. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
  41. package/eventcatalog/src/pages/discover/[type]/index.astro +360 -41
  42. package/eventcatalog/src/pages/docs/custom/feature.astro +45 -39
  43. package/eventcatalog/src/remark-plugins/resource-ref.ts +51 -0
  44. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -1
  45. package/eventcatalog/src/stores/sidebar-store/state.ts +25 -22
  46. package/package.json +3 -2
@@ -1,7 +1,21 @@
1
1
  ---
2
- import type { CollectionEntry } from 'astro:content';
3
- import type { CollectionTypes } from '@types';
4
- import DiscoverLayout, { type Props as DiscoverLayoutProps } from '@layouts/DiscoverLayout.astro';
2
+ import { QueueListIcon, RectangleGroupIcon, BoltIcon, ChatBubbleLeftIcon } from '@heroicons/react/24/outline';
3
+ import ServerIcon from '@heroicons/react/24/outline/ServerIcon';
4
+ import { MagnifyingGlassIcon } from '@heroicons/react/20/solid';
5
+ import { DatabaseIcon } from 'lucide-react';
6
+ import { getCommands } from '@utils/collections/commands';
7
+ import { getDomains, getDomainsForService } from '@utils/collections/domains';
8
+ import { getFlows } from '@utils/collections/flows';
9
+ import { getEvents } from '@utils/collections/events';
10
+ import { getServices } from '@utils/collections/services';
11
+ import { getQueries } from '@utils/collections/queries';
12
+ import { getContainers } from '@utils/collections/containers';
13
+ import { getUsers } from '@utils/collections/users';
14
+ import { getTeams } from '@utils/collections/teams';
15
+ import { buildUrl } from '@utils/url-builder';
16
+ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
17
+ import { DiscoverTable, type DiscoverTableData, type CollectionType } from '@components/Tables/Discover';
18
+ import config from '@config';
5
19
  import { Page } from './_index.data';
6
20
 
7
21
  export const prerender = Page.prerender;
@@ -9,12 +23,165 @@ export const getStaticPaths = Page.getStaticPaths;
9
23
 
10
24
  const { type, data } = await Page.getData(Astro);
11
25
 
12
- let title = `${type} (${data.length})`;
26
+ // Fetch all collections for tabs
27
+ const events = await getEvents();
28
+ const queries = await getQueries();
29
+ const commands = await getCommands();
30
+ const services = await getServices();
31
+ const domains = await getDomains({ getAllVersions: false });
32
+ const flows = await getFlows();
33
+ const containers = await getContainers();
34
+ const users = await getUsers();
35
+ const teams = await getTeams();
13
36
 
14
- if (type === 'containers') {
15
- title = `Data (${data.length})`;
16
- }
37
+ // Create lookup maps for users and teams
38
+ const userMap = new Map(users.map((u) => [u.data.id, u]));
39
+ const teamMap = new Map(teams.map((t) => [t.data.id, t]));
40
+
41
+ // Type configuration for property options
42
+ const typeConfig: Record<
43
+ string,
44
+ {
45
+ label: string;
46
+ propertyOptions: Array<{ id: string; label: string }>;
47
+ }
48
+ > = {
49
+ events: {
50
+ label: 'Events',
51
+ propertyOptions: [
52
+ { id: 'hasProducers', label: 'Has Producers' },
53
+ { id: 'hasConsumers', label: 'Has Consumers' },
54
+ { id: 'hasOwners', label: 'Has Owners' },
55
+ { id: 'isDeprecated', label: 'Is Deprecated' },
56
+ ],
57
+ },
58
+ commands: {
59
+ label: 'Commands',
60
+ propertyOptions: [
61
+ { id: 'hasProducers', label: 'Has Producers' },
62
+ { id: 'hasConsumers', label: 'Has Consumers' },
63
+ { id: 'hasOwners', label: 'Has Owners' },
64
+ { id: 'isDeprecated', label: 'Is Deprecated' },
65
+ ],
66
+ },
67
+ queries: {
68
+ label: 'Queries',
69
+ propertyOptions: [
70
+ { id: 'hasProducers', label: 'Has Producers' },
71
+ { id: 'hasConsumers', label: 'Has Consumers' },
72
+ { id: 'hasOwners', label: 'Has Owners' },
73
+ { id: 'isDeprecated', label: 'Is Deprecated' },
74
+ ],
75
+ },
76
+ domains: {
77
+ label: 'Domains',
78
+ propertyOptions: [
79
+ { id: 'hasServices', label: 'Has Services' },
80
+ { id: 'hasOwners', label: 'Has Owners' },
81
+ { id: 'isSubdomain', label: 'Is Subdomain' },
82
+ { id: 'isDeprecated', label: 'Is Deprecated' },
83
+ ],
84
+ },
85
+ services: {
86
+ label: 'Services',
87
+ propertyOptions: [
88
+ { id: 'hasSpecifications', label: 'Has Specifications' },
89
+ { id: 'hasOwners', label: 'Has Owners' },
90
+ { id: 'hasRepository', label: 'Has Repository' },
91
+ { id: 'hasDataDependencies', label: 'Has Data Dependencies' },
92
+ { id: 'isDeprecated', label: 'Is Deprecated' },
93
+ ],
94
+ },
95
+ flows: {
96
+ label: 'Flows',
97
+ propertyOptions: [
98
+ { id: 'hasOwners', label: 'Has Owners' },
99
+ { id: 'isDeprecated', label: 'Is Deprecated' },
100
+ ],
101
+ },
102
+ containers: {
103
+ label: 'Data',
104
+ propertyOptions: [
105
+ { id: 'hasOwners', label: 'Has Owners' },
106
+ { id: 'hasWriters', label: 'Has Writers' },
107
+ { id: 'hasReaders', label: 'Has Readers' },
108
+ { id: 'isDeprecated', label: 'Is Deprecated' },
109
+ ],
110
+ },
111
+ };
112
+
113
+ const currentTypeConfig = typeConfig[type] || typeConfig.events;
114
+
115
+ // @ts-ignore
116
+ const tableConfiguration = config[type as keyof typeof config]?.tableConfiguration ?? { columns: {} };
17
117
 
118
+ const tabs = [
119
+ {
120
+ label: `Domains (${domains.length})`,
121
+ href: buildUrl('/discover/domains'),
122
+ isActive: type === 'domains',
123
+ icon: RectangleGroupIcon,
124
+ activeColor: 'yellow',
125
+ enabled: domains.length > 0,
126
+ visible: domains.length > 0,
127
+ },
128
+ {
129
+ label: `Services (${services.length})`,
130
+ href: buildUrl('/discover/services'),
131
+ isActive: type === 'services',
132
+ icon: ServerIcon,
133
+ activeColor: 'pink',
134
+ enabled: services.length > 0,
135
+ visible: services.length > 0,
136
+ },
137
+ {
138
+ label: `Data (${containers.length})`,
139
+ href: buildUrl('/discover/containers'),
140
+ isActive: type === 'containers',
141
+ icon: DatabaseIcon,
142
+ activeColor: 'blue',
143
+ enabled: containers.length > 0,
144
+ visible: containers.length > 0,
145
+ },
146
+ {
147
+ label: `Events (${events.length})`,
148
+ href: buildUrl('/discover/events'),
149
+ isActive: type === 'events',
150
+ icon: BoltIcon,
151
+ activeColor: 'orange',
152
+ enabled: events.length > 0,
153
+ visible: events.length > 0,
154
+ },
155
+ {
156
+ label: `Queries (${queries.length})`,
157
+ href: buildUrl('/discover/queries'),
158
+ isActive: type === 'queries',
159
+ icon: MagnifyingGlassIcon,
160
+ activeColor: 'green',
161
+ enabled: queries.length > 0,
162
+ visible: queries.length > 0,
163
+ },
164
+ {
165
+ label: `Commands (${commands.length})`,
166
+ href: buildUrl('/discover/commands'),
167
+ isActive: type === 'commands',
168
+ icon: ChatBubbleLeftIcon,
169
+ activeColor: 'blue',
170
+ enabled: commands.length > 0,
171
+ visible: commands.length > 0,
172
+ },
173
+ {
174
+ label: `Flows (${flows.length})`,
175
+ href: buildUrl('/discover/flows'),
176
+ isActive: type === 'flows',
177
+ icon: QueueListIcon,
178
+ activeColor: 'teal',
179
+ enabled: flows.length > 0,
180
+ visible: flows.length > 0,
181
+ },
182
+ ];
183
+
184
+ // Map data to match the expected structure for the table
18
185
  function mapToItem(i: any) {
19
186
  return {
20
187
  collection: i.collection,
@@ -25,39 +192,191 @@ function mapToItem(i: any) {
25
192
  },
26
193
  };
27
194
  }
195
+
196
+ // Helper to map owner references with type detection
197
+ function mapOwner(o: any) {
198
+ if (!o) return null;
199
+ const id = typeof o === 'string' ? o : o.data?.id || o.id || o;
200
+
201
+ // Look up in users first, then teams
202
+ const user = userMap.get(id);
203
+ if (user) {
204
+ return { id, name: user.data.name || id, type: 'user' as const };
205
+ }
206
+
207
+ const team = teamMap.get(id);
208
+ if (team) {
209
+ return { id, name: team.data.name || id, type: 'team' as const };
210
+ }
211
+
212
+ // Fallback if not found in either
213
+ const name = typeof o === 'string' ? o : o.data?.name || o.name || id;
214
+ return { id, name, type: 'user' as const };
215
+ }
216
+
217
+ // Helper to check if service has specifications
218
+ function hasSpecifications(service: any): boolean {
219
+ const specs = service.data?.specifications;
220
+ if (!specs) return false;
221
+ if (Array.isArray(specs)) return specs.length > 0;
222
+ return !!(specs.openapiPath || specs.asyncapiPath || specs.graphqlPath);
223
+ }
224
+
225
+ // Build a Set of subdomain IDs (domains that are nested within other domains)
226
+ const allSubdomainIds = new Set(
227
+ domains.flatMap((d: any) => (d.data?.domains || []).map((sd: any) => sd.data?.id || sd.id)).filter(Boolean)
228
+ );
229
+
230
+ // For services, enrich with domain information
231
+ const enrichedData =
232
+ type === 'services'
233
+ ? await Promise.all(
234
+ data.map(async (service: any) => {
235
+ const serviceDomains = await getDomainsForService(service);
236
+ return {
237
+ ...service,
238
+ enrichedDomains: serviceDomains.map((d: any) => ({
239
+ id: d.data.id,
240
+ name: d.data.name,
241
+ version: d.data.version,
242
+ })),
243
+ };
244
+ })
245
+ )
246
+ : data;
247
+
248
+ const tableData = enrichedData.map((d: any) => ({
249
+ collection: d.collection,
250
+ owners: (d.data?.owners || []).map(mapOwner).filter(Boolean),
251
+ hasOwners: (d.data?.owners || []).length > 0,
252
+ hasServices: type === 'domains' ? (d.data?.services || []).length > 0 : false,
253
+ isSubdomain: type === 'domains' ? allSubdomainIds.has(d.data.id) : false,
254
+ // Service-specific properties
255
+ domains: type === 'services' ? d.enrichedDomains : undefined,
256
+ hasSpecifications: type === 'services' ? hasSpecifications(d) : false,
257
+ hasRepository: type === 'services' ? !!d.data?.repository?.url : false,
258
+ hasDataDependencies: type === 'services' ? (d.data?.writesTo || []).length > 0 || (d.data?.readsFrom || []).length > 0 : false,
259
+ isDeprecated: d.data?.deprecated === true || (typeof d.data?.deprecated === 'object' && d.data?.deprecated !== null),
260
+ data: {
261
+ id: d.data.id,
262
+ name: d.data.name,
263
+ summary: d.data?.summary,
264
+ version: d.data.version,
265
+ latestVersion: d.data?.latestVersion,
266
+ draft: d.data?.draft,
267
+ badges: d.data?.badges,
268
+ producers: d.data?.producers?.map(mapToItem) ?? [],
269
+ consumers: d.data?.consumers?.map(mapToItem) ?? [],
270
+ receives: d.data?.receives?.map(mapToItem) ?? [],
271
+ sends: d.data?.sends?.map(mapToItem) ?? [],
272
+ services: d.data?.services?.map(mapToItem) ?? [],
273
+ servicesThatWriteToContainer: d.data?.servicesThatWriteToContainer?.map(mapToItem) ?? [],
274
+ servicesThatReadFromContainer: d.data?.servicesThatReadFromContainer?.map(mapToItem) ?? [],
275
+ },
276
+ }));
277
+
278
+ // Get unique owners from all items
279
+ const uniqueOwners = Array.from(new Map(tableData.flatMap((d: any) => d.owners || []).map((o: any) => [o.id, o])).values()).sort(
280
+ (a: any, b: any) => a.name.localeCompare(b.name)
281
+ );
282
+
283
+ // Get unique producers (services) for message types
284
+ // Check for duplicate names and add version if needed
285
+ const servicesByName = new Map<string, typeof services>();
286
+ services.forEach((s) => {
287
+ const name = s.data.name;
288
+ if (!servicesByName.has(name)) servicesByName.set(name, []);
289
+ servicesByName.get(name)!.push(s);
290
+ });
291
+
292
+ const uniqueProducers = services
293
+ .map((s) => {
294
+ const hasDuplicateName = (servicesByName.get(s.data.name)?.length ?? 0) > 1;
295
+ const isLatest = s.data.version === s.data.latestVersion;
296
+ const versionLabel = isLatest ? 'latest' : `v${s.data.version}`;
297
+ return {
298
+ id: s.data.id,
299
+ name: hasDuplicateName ? `${s.data.name} (${versionLabel})` : s.data.name,
300
+ };
301
+ })
302
+ .sort((a, b) => a.name.localeCompare(b.name));
303
+
304
+ // Show producers/consumers filter only for events, commands, queries
305
+ const showProducersFilter = ['events', 'commands', 'queries'].includes(type);
306
+ const showConsumersFilter = ['events', 'commands', 'queries'].includes(type);
307
+
308
+ // Consumers are the same services list
309
+ const uniqueConsumers = uniqueProducers;
310
+
311
+ // Get unique domains for the services filter
312
+ const uniqueDomains = domains.map((d) => ({
313
+ id: d.data.id,
314
+ name: d.data.name,
315
+ version: d.data.version,
316
+ }));
317
+
318
+ // Show domains filter only for services
319
+ const showDomainsFilter = type === 'services';
320
+
321
+ const title = `${currentTypeConfig.label} (${data.length})`;
28
322
  ---
29
323
 
30
- <DiscoverLayout
31
- title={title}
32
- subtitle={`Find, filter and search for any ${type} in your system.`}
33
- data={data.map(
34
- (d: CollectionEntry<CollectionTypes>) =>
35
- ({
36
- collection: d.collection,
37
- data: {
38
- id: d.data.id,
39
- name: d.data.name,
40
- summary: d.data?.summary,
41
- version: d.data.version,
42
- latestVersion: d.data?.latestVersion,
43
- draft: d.data?.draft,
44
- badges: d.data?.badges,
45
- // @ts-ignore
46
- consumers: d.data?.consumers?.map(mapToItem) ?? [],
47
- // @ts-ignore
48
- producers: d.data?.producers?.map(mapToItem) ?? [],
49
- // @ts-ignore
50
- receives: d.data?.receives?.map(mapToItem) ?? [],
51
- // @ts-ignore
52
- sends: d.data?.sends?.map(mapToItem) ?? [],
53
- // @ts-ignore
54
- services: d.data?.services?.map(mapToItem) ?? [],
55
- // @ts-ignore
56
- servicesThatWriteToContainer: d.data?.servicesThatWriteToContainer?.map(mapToItem) ?? [],
57
- // @ts-ignore
58
- servicesThatReadFromContainer: d.data?.servicesThatReadFromContainer?.map(mapToItem) ?? [],
59
- },
60
- }) as DiscoverLayoutProps<typeof type>['data'][0]
61
- )}
62
- type={type}
63
- />
324
+ <VerticalSideBarLayout title={`Explore | ${title}`} showNestedSideBar={false}>
325
+ <main class="ml-0 bg-[rgb(var(--ec-page-bg))] min-h-content">
326
+ <div id="discover-collection-tabs">
327
+ <div class="hidden sm:block">
328
+ <div class="border-b border-[rgb(var(--ec-page-border))]">
329
+ <nav class="flex space-x-8 -mb-0.5 pl-6" aria-label="Tabs">
330
+ {
331
+ tabs
332
+ .filter((tab) => tab.visible)
333
+ .map((tab) => (
334
+ <a
335
+ href={tab.href}
336
+ class:list={[
337
+ 'group inline-flex items-center py-4 px-1 text-sm font-light text-[rgb(var(--ec-page-text))]',
338
+ tab.isActive && 'border-b-[2px] border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-accent))]',
339
+ !tab.isActive && 'opacity-70 hover:opacity-100',
340
+ !tab.enabled && 'disabled',
341
+ ]}
342
+ aria-current="page"
343
+ >
344
+ <tab.icon
345
+ className={`w-6 h-6 -ml-0.5 mr-2 ${tab.isActive ? 'text-[rgb(var(--ec-accent))]' : 'text-[rgb(var(--ec-icon-color))]'}`}
346
+ />
347
+ <span>{tab.label}</span>
348
+ </a>
349
+ ))
350
+ }
351
+ </nav>
352
+ </div>
353
+ </div>
354
+ </div>
355
+
356
+ <div class="py-4 px-6 md:pr-10">
357
+ <DiscoverTable
358
+ data={tableData as DiscoverTableData[]}
359
+ collectionType={type as CollectionType}
360
+ collectionLabel={currentTypeConfig.label}
361
+ domains={uniqueDomains}
362
+ owners={uniqueOwners as Array<{ id: string; name: string; type?: 'user' | 'team' }>}
363
+ producers={uniqueProducers}
364
+ consumers={uniqueConsumers}
365
+ propertyOptions={currentTypeConfig.propertyOptions}
366
+ tableConfiguration={tableConfiguration}
367
+ showDomainsFilter={showDomainsFilter}
368
+ showProducersFilter={showProducersFilter}
369
+ showConsumersFilter={showConsumersFilter}
370
+ client:only="react"
371
+ />
372
+ </div>
373
+ </main>
374
+ </VerticalSideBarLayout>
375
+
376
+ <style>
377
+ a.disabled {
378
+ pointer-events: none;
379
+ cursor: default;
380
+ opacity: 0.25;
381
+ }
382
+ </style>
@@ -4,19 +4,21 @@ import { BookOpenIcon, FileText } from 'lucide-react';
4
4
  ---
5
5
 
6
6
  <VerticalSideBarLayout title="Custom Documentation" showNestedSideBar={false}>
7
- <div class="min-h-[calc(100vh-60px)] bg-white">
7
+ <div class="min-h-[calc(100vh-60px)] bg-[rgb(var(--ec-page-bg))]">
8
8
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
9
9
  {/* Hero Section */}
10
10
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center mb-16">
11
11
  <div>
12
- <div class="inline-flex items-center px-4 py-2 rounded-full bg-blue-100 text-blue-700 font-medium text-sm mb-6">
12
+ <div
13
+ class="inline-flex items-center px-4 py-2 rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 font-medium text-sm mb-6"
14
+ >
13
15
  <FileText className="w-4 h-4 mr-2" />
14
16
  New: Bring your documentation into EventCatalog
15
17
  </div>
16
- <h1 class="text-4xl font-bold text-gray-900 tracking-tight mb-4">
18
+ <h1 class="text-4xl font-bold text-[rgb(var(--ec-page-text))] tracking-tight mb-4">
17
19
  Document Everything. Share Knowledge. Build Better.
18
20
  </h1>
19
- <p class="text-xl text-gray-600 mb-8">
21
+ <p class="text-xl text-[rgb(var(--ec-page-text-muted))] mb-8">
20
22
  Add your own documentation to EventCatalog — from ADRs and system guides to runbooks and onboarding material. Connect
21
23
  your knowledge to your architecture.
22
24
  </p>
@@ -37,14 +39,14 @@ import { BookOpenIcon, FileText } from 'lucide-react';
37
39
  <a
38
40
  href="https://www.eventcatalog.cloud"
39
41
  target="_blank"
40
- class="inline-flex items-center justify-center px-6 py-3 border border-gray-300 text-base font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 transition-colors duration-150"
42
+ class="inline-flex items-center justify-center px-6 py-3 border border-[rgb(var(--ec-page-border))] text-base font-medium rounded-lg text-[rgb(var(--ec-page-text))] bg-[rgb(var(--ec-card-bg))] hover:bg-[rgb(var(--ec-content-hover))] transition-colors duration-150"
41
43
  >
42
44
  Try for free
43
45
  </a>
44
46
  </div>
45
- <p class="text-sm text-gray-500">
47
+ <p class="text-sm text-[rgb(var(--ec-page-text-muted))]">
46
48
  Available with EventCatalog Starter or Scale plans
47
- <a href="https://www.eventcatalog.dev/pricing" class="text-blue-600 font-medium block"
49
+ <a href="https://www.eventcatalog.dev/pricing" class="text-blue-600 dark:text-blue-400 font-medium block"
48
50
  >Try free for 14 days, no credit card required</a
49
51
  >
50
52
  </p>
@@ -62,7 +64,7 @@ import { BookOpenIcon, FileText } from 'lucide-react';
62
64
  <img
63
65
  src="/images/custom-docs-placeholder.png"
64
66
  alt="Custom Documentation Preview"
65
- class="rounded-xl shadow-xl border border-gray-200"
67
+ class="rounded-xl shadow-xl border border-[rgb(var(--ec-page-border))]"
66
68
  />
67
69
  </div>
68
70
  </div>
@@ -70,75 +72,79 @@ import { BookOpenIcon, FileText } from 'lucide-react';
70
72
 
71
73
  {/* Features Section */}
72
74
  <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
73
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
74
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
75
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
75
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
76
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
77
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
76
78
  <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"></path>
77
79
  </svg>
78
80
  </div>
79
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Centralized Knowledge</h3>
80
- <p class="text-gray-600">
81
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">Centralized Knowledge</h3>
82
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
81
83
  Keep architecture decisions, system guides, and runbooks in one place — easy to access, easy to trust.
82
84
  </p>
83
85
  </div>
84
86
 
85
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
86
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
87
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
87
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
88
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
89
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
88
90
  <path
89
91
  d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"
90
92
  ></path>
91
93
  </svg>
92
94
  </div>
93
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Rich Formatting</h3>
94
- <p class="text-gray-600">
95
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">Rich Formatting</h3>
96
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
95
97
  Use Markdown, diagrams, code blocks, and EventCatalog components to create structured, useful documentation.
96
98
  </p>
97
99
  </div>
98
100
 
99
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
100
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
101
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
101
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
102
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
103
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
102
104
  <path d="M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM6 4h5v8l-2.5-1.5L6 12V4z"
103
105
  ></path>
104
106
  </svg>
105
107
  </div>
106
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Version Control</h3>
107
- <p class="text-gray-600">Track changes and ensure your documentation grows alongside your system.</p>
108
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">Version Control</h3>
109
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
110
+ Track changes and ensure your documentation grows alongside your system.
111
+ </p>
108
112
  </div>
109
113
 
110
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
111
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
112
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
114
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
115
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
116
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
113
117
  <path
114
118
  d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
115
119
  ></path>
116
120
  </svg>
117
121
  </div>
118
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Documentation Ownership</h3>
119
- <p class="text-gray-600">Assign and track document owners, making it easy to find the right person in seconds.</p>
122
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">Documentation Ownership</h3>
123
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
124
+ Assign and track document owners, making it easy to find the right person in seconds.
125
+ </p>
120
126
  </div>
121
127
 
122
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
123
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
124
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
128
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
129
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
130
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
125
131
  <path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z"></path>
126
132
  </svg>
127
133
  </div>
128
- <h3 class="text-lg font-semibold text-gray-900 mb-2">Customizable Sidebars</h3>
129
- <p class="text-gray-600">
134
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">Customizable Sidebars</h3>
135
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
130
136
  Auto-generated and fully customizable navigation sidebars to organize your documentation perfectly.
131
137
  </p>
132
138
  </div>
133
139
 
134
- <div class="bg-white rounded-xl p-6 shadow-sm border border-gray-200">
135
- <div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
136
- <svg class="w-6 h-6 text-blue-600" viewBox="0 0 24 24" fill="currentColor">
140
+ <div class="bg-[rgb(var(--ec-card-bg))] rounded-xl p-6 shadow-sm border border-[rgb(var(--ec-page-border))]">
141
+ <div class="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center mb-4">
142
+ <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" viewBox="0 0 24 24" fill="currentColor">
137
143
  <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"></path>
138
144
  </svg>
139
145
  </div>
140
- <h3 class="text-lg font-semibold text-gray-900 mb-2">EventCatalog Chat</h3>
141
- <p class="text-gray-600">
146
+ <h3 class="text-lg font-semibold text-[rgb(var(--ec-page-text))] mb-2">EventCatalog Chat</h3>
147
+ <p class="text-[rgb(var(--ec-page-text-muted))]">
142
148
  Interact with your documentation using AI-powered chat to find answers quickly and efficiently.
143
149
  </p>
144
150
  </div>
@@ -149,7 +155,7 @@ import { BookOpenIcon, FileText } from 'lucide-react';
149
155
  <a
150
156
  href="https://www.eventcatalog.dev/docs/development/guides/customize-sidebars/application-sidebar"
151
157
  target="_blank"
152
- class="text-sm text-gray-400 hover:text-gray-500 transition-colors duration-150"
158
+ class="text-sm text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))] transition-colors duration-150"
153
159
  >
154
160
  Not ready for custom documentation? You can hide this feature in settings
155
161
  </a>
@@ -0,0 +1,51 @@
1
+ import { findAndReplace } from 'mdast-util-find-and-replace';
2
+
3
+ /**
4
+ * Remark plugin that transforms [[type|Name]] or [[Name]] syntax into ResourceRef MDX components.
5
+ *
6
+ * Supported patterns:
7
+ * - [[entity|Order]] -> <ResourceRef type="entity">Order</ResourceRef>
8
+ * - [[service|OrderService@1.0.0]] -> <ResourceRef type="service" version="1.0.0">OrderService</ResourceRef>
9
+ * - [[diagram|target-architecture]] -> <ResourceRef type="diagram">target-architecture</ResourceRef>
10
+ * - [[Order]] -> <ResourceRef type="entity">Order</ResourceRef> (defaults to entity)
11
+ * - [[Order@0.0.1]] -> <ResourceRef type="entity" version="0.0.1">Order</ResourceRef>
12
+ */
13
+ export function remarkResourceRef() {
14
+ return function (tree: any) {
15
+ // First pass: match [[type|Name]] or [[type|Name@version]] pattern
16
+ findAndReplace(tree, [
17
+ /\[\[([a-z]+)\|([\w-]+)(?:@([\d.]+))?\]\]/g,
18
+ // @ts-ignore: Types are complex but it works
19
+ function (_match: string, type: string, resourceId: string, version?: string) {
20
+ const attributes: any[] = [{ type: 'mdxJsxAttribute', name: 'type', value: type }];
21
+ if (version) {
22
+ attributes.push({ type: 'mdxJsxAttribute', name: 'version', value: version });
23
+ }
24
+ return {
25
+ type: 'mdxJsxTextElement',
26
+ name: 'ResourceRef',
27
+ attributes,
28
+ children: [{ type: 'text', value: resourceId }],
29
+ };
30
+ },
31
+ ]);
32
+
33
+ // Second pass: match [[Name]] or [[Name@version]] pattern (defaults to entity)
34
+ findAndReplace(tree, [
35
+ /\[\[([\w-]+)(?:@([\d.]+))?\]\]/g,
36
+ // @ts-ignore: Types are complex but it works
37
+ function (_match: string, resourceId: string, version?: string) {
38
+ const attributes: any[] = [{ type: 'mdxJsxAttribute', name: 'type', value: 'entity' }];
39
+ if (version) {
40
+ attributes.push({ type: 'mdxJsxAttribute', name: 'version', value: version });
41
+ }
42
+ return {
43
+ type: 'mdxJsxTextElement',
44
+ name: 'ResourceRef',
45
+ attributes,
46
+ children: [{ type: 'text', value: resourceId }],
47
+ };
48
+ },
49
+ ]);
50
+ };
51
+ }
@@ -31,7 +31,7 @@ export type NavNode = {
31
31
  */
32
32
  export type NavigationData = {
33
33
  roots: ChildRef[]; // What to show at top level
34
- nodes: Record<string, NavNode>; // Flat map of all nodes by key
34
+ nodes: Record<string, NavNode | string>; // Flat map of nodes by key, strings are references to other keys (e.g., unversioned aliases)
35
35
  };
36
36
 
37
37
  export const uniqueBy = <T>(array: T[], key: keyof T): T[] => {