@eventcatalog/core 3.0.0-beta.2 → 3.0.0-beta.21

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 (103) hide show
  1. package/README.md +10 -0
  2. package/dist/__mocks__/astro-content.cjs +32 -0
  3. package/dist/__mocks__/astro-content.d.cts +13 -0
  4. package/dist/__mocks__/astro-content.d.ts +13 -0
  5. package/dist/__mocks__/astro-content.js +7 -0
  6. package/dist/analytics/analytics.cjs +1 -1
  7. package/dist/analytics/analytics.js +2 -2
  8. package/dist/analytics/log-build.cjs +1 -1
  9. package/dist/analytics/log-build.js +3 -3
  10. package/dist/{chunk-JSONCD7V.js → chunk-2FUEBPD3.js} +1 -1
  11. package/dist/{chunk-3W6JYTHP.js → chunk-HABY2LVH.js} +6 -2
  12. package/dist/{chunk-H4QHE5YZ.js → chunk-KQAMO3R4.js} +1 -1
  13. package/dist/chunk-Q6KRYWPV.js +44 -0
  14. package/dist/{chunk-PQL6O5YA.js → chunk-RRP2B7BL.js} +1 -1
  15. package/dist/constants.cjs +1 -1
  16. package/dist/constants.js +1 -1
  17. package/dist/eventcatalog.cjs +84 -65
  18. package/dist/eventcatalog.config.d.cts +4 -0
  19. package/dist/eventcatalog.config.d.ts +4 -0
  20. package/dist/eventcatalog.js +45 -57
  21. package/dist/generate.cjs +48 -2
  22. package/dist/generate.js +3 -1
  23. package/dist/utils/cli-logger.cjs +82 -0
  24. package/dist/utils/cli-logger.d.cts +10 -0
  25. package/dist/utils/cli-logger.d.ts +10 -0
  26. package/dist/utils/cli-logger.js +7 -0
  27. package/eventcatalog/astro.config.mjs +4 -1
  28. package/eventcatalog/integrations/ecstudio-watcher.mjs +1 -1
  29. package/eventcatalog/integrations/eventcatalog-features.ts +69 -0
  30. package/eventcatalog/public/icons/asyncapi-black.svg +2 -0
  31. package/eventcatalog/public/icons/graphql-black.svg +1 -0
  32. package/eventcatalog/public/icons/openapi-black.svg +1 -0
  33. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +821 -0
  34. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +24 -0
  35. package/eventcatalog/src/components/Grids/DomainGrid.tsx +1 -3
  36. package/eventcatalog/src/components/Grids/MessageGrid.tsx +8 -8
  37. package/eventcatalog/src/components/Header.astro +25 -5
  38. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +14 -3
  39. package/eventcatalog/src/components/Search/Search.astro +2 -2
  40. package/eventcatalog/src/components/Search/SearchModal.tsx +16 -7
  41. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +9 -2
  42. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/domain.ts +7 -6
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/service.ts +6 -3
  44. package/eventcatalog/src/components/SideNav/NestedSideBar/builders/shared.ts +1 -0
  45. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +23 -8
  46. package/eventcatalog/src/components/SideNav/NestedSideBar/sidebar-builder.ts +57 -11
  47. package/eventcatalog/src/content.config.ts +1 -10
  48. package/eventcatalog/src/enterprise/ai/chat-api.ts +262 -0
  49. package/eventcatalog/src/enterprise/auth/[...auth].ts +3 -0
  50. package/eventcatalog/src/enterprise/auth/login.astro +420 -0
  51. package/eventcatalog/src/enterprise/collections/index.ts +0 -1
  52. package/eventcatalog/src/layouts/Footer.astro +8 -5
  53. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +30 -19
  54. package/eventcatalog/src/pages/_index.astro +8 -9
  55. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +19 -3
  56. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +7 -7
  57. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  58. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -5
  59. package/eventcatalog/src/pages/docs/teams/[id].mdx.ts +36 -0
  60. package/eventcatalog/src/pages/docs/users/[id].mdx.ts +36 -0
  61. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +178 -0
  62. package/eventcatalog/src/pages/schemas/explorer/index.astro +5 -155
  63. package/eventcatalog/src/remark-plugins/directives.ts +30 -9
  64. package/eventcatalog/src/utils/collections/schemas.ts +31 -7
  65. package/eventcatalog/src/utils/feature.ts +8 -4
  66. package/eventcatalog/src/utils/resource-files.ts +86 -0
  67. package/package.json +12 -15
  68. package/default-files-for-collections/changelogs.md +0 -5
  69. package/default-files-for-collections/channels.md +0 -8
  70. package/default-files-for-collections/commands.md +0 -8
  71. package/default-files-for-collections/domains.md +0 -8
  72. package/default-files-for-collections/events.md +0 -8
  73. package/default-files-for-collections/flows.md +0 -11
  74. package/default-files-for-collections/queries.md +0 -8
  75. package/default-files-for-collections/services.md +0 -8
  76. package/default-files-for-collections/ubiquitousLanguages.md +0 -7
  77. package/eventcatalog/src/enterprise/collections/chat-prompts.ts +0 -32
  78. package/eventcatalog/src/enterprise/eventcatalog-chat/components/Chat.tsx +0 -60
  79. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatMessage.tsx +0 -414
  80. package/eventcatalog/src/enterprise/eventcatalog-chat/components/ChatSidebar.tsx +0 -169
  81. package/eventcatalog/src/enterprise/eventcatalog-chat/components/InputModal.tsx +0 -244
  82. package/eventcatalog/src/enterprise/eventcatalog-chat/components/MentionInput.tsx +0 -211
  83. package/eventcatalog/src/enterprise/eventcatalog-chat/components/WelcomePromptArea.tsx +0 -176
  84. package/eventcatalog/src/enterprise/eventcatalog-chat/components/default-prompts.ts +0 -93
  85. package/eventcatalog/src/enterprise/eventcatalog-chat/components/hooks/ChatProvider.tsx +0 -143
  86. package/eventcatalog/src/enterprise/eventcatalog-chat/components/windows/ChatWindow.server.tsx +0 -387
  87. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/api/chat.ts +0 -59
  88. package/eventcatalog/src/enterprise/eventcatalog-chat/pages/chat/index.astro +0 -104
  89. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/ai-provider.ts +0 -140
  90. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/anthropic.ts +0 -28
  91. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/google.ts +0 -41
  92. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/index.ts +0 -26
  93. package/eventcatalog/src/enterprise/eventcatalog-chat/providers/openai.ts +0 -61
  94. package/eventcatalog/src/enterprise/eventcatalog-chat/utils/chat-prompts.ts +0 -50
  95. package/eventcatalog/src/pages/auth/login.astro +0 -280
  96. package/eventcatalog/src/pages/chat/feature.astro +0 -179
  97. package/eventcatalog/src/pages/chat/index.astro +0 -10
  98. package/eventcatalog/src/pages/nav-index.json.ts +0 -30
  99. /package/eventcatalog/src/{pages → enterprise}/auth/error.astro +0 -0
  100. /package/eventcatalog/src/{middleware-auth.ts → enterprise/auth/middleware/middleware-auth.ts} +0 -0
  101. /package/eventcatalog/src/{middleware.ts → enterprise/auth/middleware/middleware.ts} +0 -0
  102. /package/eventcatalog/src/{pages/unauthorized/index.astro → enterprise/auth/unauthorized.astro} +0 -0
  103. /package/eventcatalog/src/{pages → enterprise}/plans/index.astro +0 -0
@@ -69,9 +69,21 @@ const pagefindAttributes =
69
69
  </VerticalSideBarLayout>
70
70
 
71
71
  <script is:inline src={js}></script>
72
- <script define:vars={{ schema: stringified, config }}>
73
- const root = document.getElementById('asyncapi');
74
- AsyncApiStandalone.hydrate({ schema, config }, root);
72
+ <script is:inline define:vars={{ schema: stringified, config }}>
73
+ function initAsyncApi() {
74
+ const root = document.getElementById('asyncapi');
75
+ if (!root || root.dataset.initialized) return;
76
+
77
+ if (typeof AsyncApiStandalone !== 'undefined') {
78
+ AsyncApiStandalone.render({ schema, config }, root);
79
+ root.dataset.initialized = 'true';
80
+ } else {
81
+ setTimeout(initAsyncApi, 50);
82
+ }
83
+ }
84
+
85
+ document.addEventListener('astro:page-load', initAsyncApi);
86
+ initAsyncApi();
75
87
  </script>
76
88
 
77
89
  <style>
@@ -82,6 +94,10 @@ const pagefindAttributes =
82
94
  }
83
95
  }
84
96
 
97
+ .burger-menu {
98
+ display: none;
99
+ }
100
+
85
101
  .aui-root .z-10 {
86
102
  z-index: 8;
87
103
  }
@@ -73,16 +73,16 @@ const logList = await Promise.all(logListPromise);
73
73
 
74
74
  const getBadge = () => {
75
75
  if (props.collection === 'services') {
76
- return { backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' };
76
+ return { backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-600' };
77
77
  }
78
78
  if (props.collection === 'events') {
79
- return { backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-400' };
79
+ return { backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-600' };
80
80
  }
81
81
  if (props.collection === 'commands') {
82
- return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' };
82
+ return { backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-600' };
83
83
  }
84
84
  if (props.collection === 'queries') {
85
- return { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-blue-400' };
85
+ return { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-green-600' };
86
86
  }
87
87
  if (props.collection === 'domains') {
88
88
  return {
@@ -90,14 +90,14 @@ const getBadge = () => {
90
90
  textColor: 'yellow',
91
91
  content: 'Domain',
92
92
  icon: RectangleGroupIcon,
93
- class: 'text-yellow-400',
93
+ class: 'text-yellow-600',
94
94
  };
95
95
  }
96
96
  if (props.collection === 'containers') {
97
- return { backgroundColor: 'blue', textColor: 'blue', content: 'Container', icon: DatabaseIcon, class: 'text-blue-400' };
97
+ return { backgroundColor: 'blue', textColor: 'blue', content: 'Container', icon: DatabaseIcon, class: 'text-blue-600' };
98
98
  }
99
99
  if (props.collection === 'flows') {
100
- return { backgroundColor: 'teal', textColor: 'teal', content: 'Flow', icon: QueueListIcon, class: 'text-teal-400' };
100
+ return { backgroundColor: 'teal', textColor: 'teal', content: 'Flow', icon: QueueListIcon, class: 'text-teal-600' };
101
101
  }
102
102
  };
103
103
 
@@ -36,7 +36,7 @@ if (isRemote) {
36
36
  const pageTitle = `${collection} | ${data.name} | GraphQL Schema`.replace(/^\w/, (c) => c.toUpperCase());
37
37
 
38
38
  const getServiceBadge = () => {
39
- return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' }];
39
+ return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-600' }];
40
40
  };
41
41
 
42
42
  const getGraphQLBadge = () => {
@@ -71,17 +71,17 @@ const getContentBadges = () =>
71
71
 
72
72
  const getBadge = () => {
73
73
  if (props.collection === 'services') {
74
- return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-400' }];
74
+ return [{ backgroundColor: 'pink', textColor: 'pink', content: 'Service', icon: ServerIcon, class: 'text-pink-600' }];
75
75
  }
76
76
  if (props.collection === 'events') {
77
- return [{ backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-400' }];
77
+ return [{ backgroundColor: 'orange', textColor: 'orange', content: 'Event', icon: BoltIcon, class: 'text-orange-600' }];
78
78
  }
79
79
  if (props.collection === 'commands') {
80
- return [{ backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-400' }];
80
+ return [{ backgroundColor: 'blue', textColor: 'blue', content: 'Command', icon: ChatBubbleLeftIcon, class: 'text-blue-600' }];
81
81
  }
82
82
  if (props.collection === 'queries') {
83
83
  return [
84
- { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-green-400' },
84
+ { backgroundColor: 'green', textColor: 'green', content: 'Query', icon: MagnifyingGlassIcon, class: 'text-green-600' },
85
85
  ];
86
86
  }
87
87
  if (props.collection === 'domains') {
@@ -91,7 +91,7 @@ const getBadge = () => {
91
91
  textColor: 'yellow',
92
92
  content: 'Domain',
93
93
  icon: RectangleGroupIcon,
94
- class: 'text-yellow-400',
94
+ class: 'text-yellow-600',
95
95
  },
96
96
  ];
97
97
  }
@@ -0,0 +1,36 @@
1
+ // This file exposes the markdown for EventCatalog in the Url
2
+ // For example http://localhost:3000/docs/teams/full-stack loads the Page and http://localhost:3000/docs/teams/full-stack.mdx loads the markdown
3
+ // This is used for the LLMs to load the markdown for the given item (llms.txt);
4
+
5
+ import type { APIRoute } from 'astro';
6
+ import { getCollection } from 'astro:content';
7
+ import config from '@config';
8
+ import fs from 'fs';
9
+
10
+ const teams = await getCollection('teams');
11
+
12
+ export async function getStaticPaths() {
13
+ // Just return empty array if LLMs are not enabled
14
+ if (!config.llmsTxt?.enabled) {
15
+ return [];
16
+ }
17
+
18
+ return teams.map((team) => ({
19
+ params: { type: 'teams', id: team.data.id },
20
+ props: { content: team },
21
+ }));
22
+ }
23
+
24
+ export const GET: APIRoute = async ({ params, props }) => {
25
+ // Just return empty array if LLMs are not enabled
26
+ if (!config.llmsTxt?.enabled) {
27
+ return new Response('llms.txt is not enabled for this Catalog.', { status: 404 });
28
+ }
29
+
30
+ if (props?.content?.filePath) {
31
+ const file = fs.readFileSync(props.content.filePath, 'utf8');
32
+ return new Response(file, { status: 200 });
33
+ }
34
+
35
+ return new Response('Not found', { status: 404 });
36
+ };
@@ -0,0 +1,36 @@
1
+ // This file exposes the markdown for EventCatalog in the Url
2
+ // For example http://localhost:3000/docs/users/dboyne loads the Page and http://localhost:3000/docs/users/dboyne.mdx loads the markdown
3
+ // This is used for the LLMs to load the markdown for the given item (llms.txt);
4
+
5
+ import type { APIRoute } from 'astro';
6
+ import { getCollection } from 'astro:content';
7
+ import config from '@config';
8
+ import fs from 'fs';
9
+
10
+ const users = await getCollection('users');
11
+
12
+ export async function getStaticPaths() {
13
+ // Just return empty array if LLMs are not enabled
14
+ if (!config.llmsTxt?.enabled) {
15
+ return [];
16
+ }
17
+
18
+ return users.map((user) => ({
19
+ params: { type: 'users', id: user.data.id },
20
+ props: { content: user },
21
+ }));
22
+ }
23
+
24
+ export const GET: APIRoute = async ({ params, props }) => {
25
+ // Just return empty array if LLMs are not enabled
26
+ if (!config.llmsTxt?.enabled) {
27
+ return new Response('llms.txt is not enabled for this Catalog.', { status: 404 });
28
+ }
29
+
30
+ if (props?.content?.filePath) {
31
+ const file = fs.readFileSync(props.content?.filePath, 'utf8');
32
+ return new Response(file, { status: 200 });
33
+ }
34
+
35
+ return new Response('Not found', { status: 404 });
36
+ };
@@ -0,0 +1,178 @@
1
+ import { isSSR } from '@utils/feature';
2
+ import { HybridPage } from '@utils/page-loaders/hybrid-page';
3
+ import { getEvents } from '@utils/collections/events';
4
+ import { getCommands } from '@utils/collections/commands';
5
+ import { getQueries } from '@utils/collections/queries';
6
+ import { getServices, getSpecificationsForService } from '@utils/collections/services';
7
+ import { getOwner } from '@utils/collections/owners';
8
+ import { buildUrl } from '@utils/url-builder';
9
+ import { resourceFileExists, readResourceFile } from '@utils/resource-files';
10
+ import path from 'path';
11
+
12
+ // Helper function to enrich owners with full details
13
+ async function enrichOwners(ownersRaw: any[]) {
14
+ if (!ownersRaw || ownersRaw.length === 0) return [];
15
+
16
+ const owners = await Promise.all(ownersRaw.map(getOwner));
17
+ const filteredOwners = owners.filter((o) => o !== undefined);
18
+
19
+ return filteredOwners.map((o) => ({
20
+ id: o.data.id,
21
+ name: o.data.name,
22
+ type: o.collection,
23
+ href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
24
+ }));
25
+ }
26
+
27
+ async function fetchAllSchemas() {
28
+ // Fetch all messages
29
+ const events = await getEvents({ getAllVersions: true });
30
+ const commands = await getCommands({ getAllVersions: true });
31
+ const queries = await getQueries({ getAllVersions: true });
32
+
33
+ // Fetch all services
34
+ const services = await getServices({ getAllVersions: true });
35
+
36
+ // Combine all messages
37
+ const allMessages = [...events, ...commands, ...queries];
38
+
39
+ // Filter messages with schemas and read schema content - only keep essential data
40
+ const messagesWithSchemas = await Promise.all(
41
+ allMessages
42
+ .filter((message) => message.data.schemaPath)
43
+ .filter((message) => resourceFileExists(message, message.data.schemaPath ?? ''))
44
+ .map(async (message) => {
45
+ try {
46
+ const schemaPath = message.data.schemaPath ?? '';
47
+ const schemaContent = readResourceFile(message, schemaPath) ?? '';
48
+ const schemaExtension = path.extname(schemaPath).slice(1);
49
+ const enrichedOwners = await enrichOwners(message.data.owners || []);
50
+
51
+ return {
52
+ collection: message.collection,
53
+ data: {
54
+ id: message.data.id,
55
+ name: message.data.name,
56
+ version: message.data.version,
57
+ summary: message.data.summary,
58
+ schemaPath: message.data.schemaPath,
59
+ producers: message.data.producers || [],
60
+ consumers: message.data.consumers || [],
61
+ owners: enrichedOwners,
62
+ },
63
+ schemaContent,
64
+ schemaExtension,
65
+ };
66
+ } catch (error) {
67
+ console.error(`Error reading schema for ${message.data.id}:`, error);
68
+ const enrichedOwners = await enrichOwners(message.data.owners || []);
69
+ return {
70
+ collection: message.collection,
71
+ data: {
72
+ id: message.data.id,
73
+ name: message.data.name,
74
+ version: message.data.version,
75
+ summary: message.data.summary,
76
+ schemaPath: message.data.schemaPath,
77
+ producers: message.data.producers || [],
78
+ consumers: message.data.consumers || [],
79
+ owners: enrichedOwners,
80
+ },
81
+ schemaContent: '',
82
+ schemaExtension: 'json',
83
+ };
84
+ }
85
+ })
86
+ );
87
+
88
+ // Filter services with specifications and read spec content - only keep essential data
89
+ const servicesWithSpecs = await Promise.all(
90
+ services.map(async (service) => {
91
+ try {
92
+ const specifications = getSpecificationsForService(service);
93
+
94
+ if (specifications.length === 0) {
95
+ return null;
96
+ }
97
+
98
+ return await Promise.all(
99
+ specifications.map(async (spec) => {
100
+ if (!resourceFileExists(service, spec.path)) {
101
+ return null;
102
+ }
103
+
104
+ const schemaContent = readResourceFile(service, spec.path) ?? '';
105
+ const schemaExtension = spec.type;
106
+ const enrichedOwners = await enrichOwners(service.data.owners || []);
107
+
108
+ return {
109
+ collection: 'services',
110
+ data: {
111
+ id: `${service.data.id}`,
112
+ name: `${service.data.name} - ${spec.name}`,
113
+ version: service.data.version,
114
+ summary: service.data.summary,
115
+ schemaPath: spec.path,
116
+ owners: enrichedOwners,
117
+ },
118
+ schemaContent,
119
+ schemaExtension,
120
+ specType: spec.type,
121
+ specName: spec.name,
122
+ specFilenameWithoutExtension: spec.filenameWithoutExtension,
123
+ };
124
+ })
125
+ );
126
+ } catch (error) {
127
+ console.error(`Error reading specifications for service ${service.data.id}:`, error);
128
+ return null;
129
+ }
130
+ })
131
+ );
132
+
133
+ // Flatten and filter out null values
134
+ const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
135
+
136
+ return [...messagesWithSchemas, ...flatServicesWithSpecs];
137
+ }
138
+
139
+ export class Page extends HybridPage {
140
+ static get prerender(): boolean {
141
+ return !isSSR();
142
+ }
143
+
144
+ static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
145
+ if (isSSR()) {
146
+ return [];
147
+ }
148
+
149
+ const allSchemas = await fetchAllSchemas();
150
+
151
+ return [
152
+ {
153
+ params: {},
154
+ props: {
155
+ schemas: allSchemas,
156
+ },
157
+ },
158
+ ];
159
+ }
160
+
161
+ protected static async fetchData(_params: any) {
162
+ const allSchemas = await fetchAllSchemas();
163
+ return {
164
+ schemas: allSchemas,
165
+ };
166
+ }
167
+
168
+ protected static hasValidProps(props: any): boolean {
169
+ return props && props.schemas !== undefined;
170
+ }
171
+
172
+ protected static createNotFoundResponse(): Response {
173
+ return new Response(null, {
174
+ status: 404,
175
+ statusText: 'Schema explorer not found',
176
+ });
177
+ }
178
+ }
@@ -1,163 +1,13 @@
1
1
  ---
2
2
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
3
- import { getEvents } from '@utils/collections/events';
4
- import { getCommands } from '@utils/collections/commands';
5
- import { getQueries } from '@utils/collections/queries';
6
- import { getServices, getSpecificationsForService } from '@utils/collections/services';
7
3
  import SchemaExplorer from '@components/SchemaExplorer/SchemaExplorer';
8
4
  import { isEventCatalogScaleEnabled } from '@utils/feature';
9
- import { getOwner } from '@utils/collections/owners';
10
- import { buildUrl } from '@utils/url-builder';
11
- import fs from 'fs';
12
- import path from 'path';
5
+ import { Page } from './_index.data';
13
6
 
14
- // Fetch all messages
15
- const events = await getEvents({ getAllVersions: true });
16
- const commands = await getCommands({ getAllVersions: true });
17
- const queries = await getQueries({ getAllVersions: true });
7
+ export const prerender = Page.prerender;
8
+ export const getStaticPaths = Page.getStaticPaths;
18
9
 
19
- // Fetch all services
20
- const services = await getServices({ getAllVersions: true });
21
-
22
- // Combine all messages
23
- const allMessages = [...events, ...commands, ...queries];
24
-
25
- // Helper function to enrich owners with full details
26
- async function enrichOwners(ownersRaw: any[]) {
27
- if (!ownersRaw || ownersRaw.length === 0) return [];
28
-
29
- const owners = await Promise.all(ownersRaw.map(getOwner));
30
- const filteredOwners = owners.filter((o) => o !== undefined);
31
-
32
- return filteredOwners.map((o) => ({
33
- id: o.data.id,
34
- name: o.data.name,
35
- type: o.collection,
36
- href: buildUrl(`/docs/${o.collection}/${o.data.id}`),
37
- }));
38
- }
39
-
40
- // Filter messages with schemas and read schema content - only keep essential data
41
- const messagesWithSchemas = await Promise.all(
42
- allMessages
43
- .filter((message) => message.data.schemaPath)
44
- // Make sure the file exists
45
- .filter((message) => fs.existsSync(path.join(path.dirname(message.filePath ?? ''), message.data.schemaPath ?? '')))
46
- .map(async (message) => {
47
- try {
48
- // Get the schema file path
49
- const schemaPath = message.data.schemaPath;
50
- const fullSchemaPath = path.join(path.dirname(message.filePath ?? ''), schemaPath ?? '');
51
-
52
- // Read the schema content
53
- let schemaContent = '';
54
- if (fs.existsSync(fullSchemaPath)) {
55
- schemaContent = fs.readFileSync(fullSchemaPath, 'utf-8');
56
- }
57
-
58
- // Get schema file extension
59
- const schemaExtension = path.extname(schemaPath ?? '').slice(1);
60
-
61
- // Enrich owners with full details
62
- const enrichedOwners = await enrichOwners(message.data.owners || []);
63
-
64
- // Only return essential data - strip out markdown, full data objects, etc.
65
- return {
66
- collection: message.collection,
67
- data: {
68
- id: message.data.id,
69
- name: message.data.name,
70
- version: message.data.version,
71
- summary: message.data.summary,
72
- schemaPath: message.data.schemaPath,
73
- producers: message.data.producers || [],
74
- consumers: message.data.consumers || [],
75
- owners: enrichedOwners,
76
- },
77
- schemaContent,
78
- schemaExtension,
79
- };
80
- } catch (error) {
81
- console.error(`Error reading schema for ${message.data.id}:`, error);
82
- const enrichedOwners = await enrichOwners(message.data.owners || []);
83
- return {
84
- collection: message.collection,
85
- data: {
86
- id: message.data.id,
87
- name: message.data.name,
88
- version: message.data.version,
89
- summary: message.data.summary,
90
- schemaPath: message.data.schemaPath,
91
- producers: message.data.producers || [],
92
- consumers: message.data.consumers || [],
93
- owners: enrichedOwners,
94
- },
95
- schemaContent: '',
96
- schemaExtension: 'json',
97
- };
98
- }
99
- })
100
- );
101
-
102
- // Filter services with specifications and read spec content - only keep essential data
103
- const servicesWithSpecs = await Promise.all(
104
- services.map(async (service) => {
105
- try {
106
- const specifications = getSpecificationsForService(service);
107
-
108
- // Only include services that have specifications
109
- if (specifications.length === 0) {
110
- return null;
111
- }
112
-
113
- // Process each specification file for this service
114
- return await Promise.all(
115
- specifications.map(async (spec) => {
116
- const specPath = path.join(path.dirname(service.filePath ?? ''), spec.path);
117
-
118
- // Only include if the spec file exists
119
- if (!fs.existsSync(specPath)) {
120
- return null;
121
- }
122
-
123
- const schemaContent = fs.readFileSync(specPath, 'utf-8');
124
- // Use spec type (openapi, asyncapi) as the extension for proper labeling
125
- const schemaExtension = spec.type;
126
-
127
- // Enrich owners with full details
128
- const enrichedOwners = await enrichOwners(service.data.owners || []);
129
-
130
- // Only return essential data - strip out markdown, sends/receives, entities, etc.
131
- return {
132
- collection: 'services',
133
- data: {
134
- id: `${service.data.id}`,
135
- name: `${service.data.name} - ${spec.name}`,
136
- version: service.data.version,
137
- summary: service.data.summary,
138
- schemaPath: spec.path,
139
- owners: enrichedOwners,
140
- },
141
- schemaContent,
142
- schemaExtension,
143
- specType: spec.type,
144
- specName: spec.name,
145
- specFilenameWithoutExtension: spec.filenameWithoutExtension,
146
- };
147
- })
148
- );
149
- } catch (error) {
150
- console.error(`Error reading specifications for service ${service.data.id}:`, error);
151
- return null;
152
- }
153
- })
154
- );
155
-
156
- // Flatten and filter out null values
157
- const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
158
-
159
- // Combine messages and services
160
- const allSchemas = [...messagesWithSchemas, ...flatServicesWithSpecs];
10
+ const { schemas } = await Page.getData(Astro);
161
11
 
162
12
  const apiAccessEnabled = isEventCatalogScaleEnabled();
163
13
  ---
@@ -167,7 +17,7 @@ const apiAccessEnabled = isEventCatalogScaleEnabled();
167
17
  <div class="flex docs-layout w-full h-full">
168
18
  <div class="w-full lg:mr-2 pr-8 py-6 flex flex-col h-full">
169
19
  <div class="w-full !max-w-none h-full flex flex-col overflow-hidden">
170
- <SchemaExplorer client:load schemas={allSchemas as any} apiAccessEnabled={apiAccessEnabled} />
20
+ <SchemaExplorer client:load schemas={schemas as any} apiAccessEnabled={apiAccessEnabled} />
171
21
  </div>
172
22
  </div>
173
23
  </div>
@@ -30,7 +30,33 @@ export function remarkDirectives() {
30
30
  class: `rounded-lg p-4 my-4 ${blockTypes[node.name as keyof typeof blockTypes] || ''}`,
31
31
  };
32
32
 
33
- // Create header div that will contain icon and type
33
+ // Check if there's a custom title (label) provided via :::note[Custom Title]
34
+ // In remark-directive, the label is stored in node.children as a paragraph node
35
+ // with data.directiveLabel = true
36
+ let titleChildren;
37
+ let contentChildren;
38
+
39
+ const firstChild = node.children && node.children.length > 0 ? node.children[0] : null;
40
+ const hasCustomTitle = firstChild && firstChild.data?.directiveLabel === true;
41
+
42
+ if (hasCustomTitle && firstChild) {
43
+ // Custom title was provided in the label - it contains markdown parsed as inline content
44
+ titleChildren = firstChild.children || [
45
+ { type: 'text', value: node.name.charAt(0).toUpperCase() + node.name.slice(1) },
46
+ ];
47
+ contentChildren = node.children.slice(1);
48
+ } else {
49
+ // No custom title, use default based on directive name
50
+ titleChildren = [
51
+ {
52
+ type: 'text',
53
+ value: node.name.charAt(0).toUpperCase() + node.name.slice(1),
54
+ },
55
+ ];
56
+ contentChildren = node.children;
57
+ }
58
+
59
+ // Create header div that will contain icon and title
34
60
  const headerNode = {
35
61
  type: 'element',
36
62
  data: {
@@ -70,7 +96,7 @@ export function remarkDirectives() {
70
96
  },
71
97
  ],
72
98
  },
73
- // Type label
99
+ // Title (with support for markdown)
74
100
  {
75
101
  type: 'element',
76
102
  data: {
@@ -79,12 +105,7 @@ export function remarkDirectives() {
79
105
  class: '',
80
106
  },
81
107
  },
82
- children: [
83
- {
84
- type: 'text',
85
- value: node.name.charAt(0).toUpperCase() + node.name.slice(1),
86
- },
87
- ],
108
+ children: titleChildren,
88
109
  },
89
110
  ],
90
111
  };
@@ -98,7 +119,7 @@ export function remarkDirectives() {
98
119
  class: 'prose prose-md w-full !max-w-none ',
99
120
  },
100
121
  },
101
- children: node.children,
122
+ children: contentChildren,
102
123
  };
103
124
 
104
125
  // Replace node's children with header and content
@@ -2,6 +2,7 @@ import type { CollectionEntry } from 'astro:content';
2
2
  import type { PageTypes } from '@types';
3
3
  import path from 'path';
4
4
  import { buildUrl } from '@utils/url-builder';
5
+ import { getAbsoluteFilePathForAstroFile } from '@utils/files';
5
6
 
6
7
  export type Schema = {
7
8
  url: string;
@@ -13,12 +14,23 @@ export const getSchemaURL = (resource: CollectionEntry<PageTypes>) => {
13
14
  const publicPath = resource?.catalog?.publicPath;
14
15
  const schemaFilePath = resource?.data?.schemaPath;
15
16
 
16
- if (!publicPath || !schemaFilePath) {
17
+ // No schema file path, return an empty string
18
+ if (!schemaFilePath) {
19
+ return;
20
+ }
21
+
22
+ if (!publicPath) {
23
+ // Then we try and get the absolute file path from the resource
24
+ const absoluteFilePath = getAbsoluteFilePathForAstroFile(resource.filePath ?? '', schemaFilePath ?? '');
25
+ if (absoluteFilePath) {
26
+ return absoluteFilePath;
27
+ }
28
+ // Can't find the schema file, return an empty string
17
29
  return;
18
30
  }
19
31
 
20
32
  // new URL
21
- return path.join(publicPath, schemaFilePath);
33
+ return path.join(publicPath, schemaFilePath ?? '');
22
34
  };
23
35
 
24
36
  export const getSchemaFormatFromURL = (url: string) => {
@@ -43,17 +55,29 @@ export const getSchemasFromResource = (resource: CollectionEntry<PageTypes>): Sc
43
55
  ? specifications.find((spec) => spec.type === 'openapi')?.path
44
56
  : specifications?.openapiPath;
45
57
  // @ts-ignore
46
- const publicPath = resource?.catalog?.publicPath;
58
+ let publicPath = resource?.catalog?.publicPath;
47
59
  const schemas = [];
48
60
 
49
61
  if (asyncapiPath) {
50
- const asyncapiUrl = path.join(publicPath, asyncapiPath);
51
- schemas.push({ url: buildUrl(asyncapiUrl), format: 'asyncapi' });
62
+ if (!publicPath) {
63
+ // We try and get the absoulate file path from the resource
64
+ const absoluteFilePath = getAbsoluteFilePathForAstroFile(resource.filePath ?? '', asyncapiPath ?? '');
65
+ schemas.push({ url: buildUrl(absoluteFilePath), format: 'asyncapi' });
66
+ } else {
67
+ // The resource has the public path, so we can use it to build the URL
68
+ schemas.push({ url: buildUrl(path.join(publicPath, asyncapiPath)), format: 'asyncapi' });
69
+ }
52
70
  }
53
71
 
54
72
  if (openapiPath) {
55
- const openapiUrl = path.join(publicPath, openapiPath);
56
- schemas.push({ url: buildUrl(openapiUrl), format: 'openapi' });
73
+ if (!publicPath) {
74
+ // We try and get the absoulate file path from the resource
75
+ const absoluteFilePath = getAbsoluteFilePathForAstroFile(resource.filePath ?? '', openapiPath ?? '');
76
+ schemas.push({ url: buildUrl(absoluteFilePath), format: 'openapi' });
77
+ } else {
78
+ // The resource has the public path, so we can use it to build the URL
79
+ schemas.push({ url: buildUrl(path.join(publicPath, openapiPath)), format: 'openapi' });
80
+ }
57
81
  }
58
82
 
59
83
  return schemas;