@eventcatalog/core 3.13.0 → 3.14.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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/catalog-to-astro-content-directory.cjs +1 -1
- package/dist/{chunk-53JVQT34.js → chunk-2OZIUQR5.js} +1 -1
- package/dist/{chunk-33AANWUG.js → chunk-BWF7CUGM.js} +1 -1
- package/dist/{chunk-I3U7AKYR.js → chunk-KZCWZ5PZ.js} +1 -1
- package/dist/{chunk-BX3YBDRJ.js → chunk-Q23QNBZG.js} +1 -1
- package/dist/{chunk-6OL426GP.js → chunk-SJ5OPJEM.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +66 -4
- package/dist/eventcatalog.config.d.cts +2 -1
- package/dist/eventcatalog.config.d.ts +2 -1
- package/dist/eventcatalog.js +70 -7
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/integrations/eventcatalog-features.ts +9 -0
- package/eventcatalog/src/components/Header.astro +5 -5
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +14 -11
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraphPortal.tsx +1 -1
- package/eventcatalog/src/components/MDX/RemoteFile.astro +5 -16
- package/eventcatalog/src/components/Search/Search.astro +1 -1
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -13
- package/eventcatalog/src/components/Search/SearchModal.tsx +101 -39
- package/eventcatalog/src/components/ThemeToggle.tsx +11 -3
- package/eventcatalog/src/content.config.ts +12 -11
- package/eventcatalog/src/enterprise/api/catalog.ts +22 -0
- package/eventcatalog/src/pages/api/search-index.json.ts +46 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +51 -5
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_OpenAPI.tsx +26 -21
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +4 -4
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +7 -2
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +7 -2
- package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +5 -0
- package/eventcatalog/src/pages/visualiser/context-map/index.astro +2 -2
- package/eventcatalog/src/pages/visualiser/designs/[id]/_index.data.ts +4 -4
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +6 -2
- package/eventcatalog/src/pages/visualiser/domain-integrations/index.astro +2 -2
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +7 -2
- package/eventcatalog/src/utils/collections/util.ts +2 -0
- package/eventcatalog/src/utils/eventcatalog-config/catalog.ts +3 -18
- package/eventcatalog/src/utils/feature.ts +2 -0
- package/eventcatalog/src/utils/remote-file.ts +15 -0
- package/eventcatalog/src/utils/remote-spec.ts +45 -0
- package/package.json +5 -5
- package/eventcatalog/src/pages/api/catalog.ts +0 -34
|
@@ -24,8 +24,6 @@ import {
|
|
|
24
24
|
} from '@heroicons/react/24/outline';
|
|
25
25
|
import { StarIcon as StarIconSolid, CircleStackIcon } from '@heroicons/react/24/solid';
|
|
26
26
|
import { useStore } from '@nanostores/react';
|
|
27
|
-
import { sidebarStore } from '../../stores/sidebar-store';
|
|
28
|
-
import type { NavNode } from '../../stores/sidebar-store/state';
|
|
29
27
|
import { favoritesStore, toggleFavorite as toggleFavoriteAction } from '../../stores/favorites-store';
|
|
30
28
|
import { buildUrl } from '@utils/url-builder';
|
|
31
29
|
|
|
@@ -99,11 +97,48 @@ const getUrlForItem = (node: any, key: string) => {
|
|
|
99
97
|
return buildUrl(`/docs/${pluralType}/${id}/${version}`);
|
|
100
98
|
};
|
|
101
99
|
|
|
100
|
+
interface SearchNode {
|
|
101
|
+
key: string;
|
|
102
|
+
title: string;
|
|
103
|
+
badge?: string;
|
|
104
|
+
summary?: string;
|
|
105
|
+
href?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface SearchNodeCompact {
|
|
109
|
+
k: string;
|
|
110
|
+
t: string;
|
|
111
|
+
b?: string;
|
|
112
|
+
s?: string;
|
|
113
|
+
h?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface SearchIndexPayload {
|
|
117
|
+
i?: SearchNodeCompact[];
|
|
118
|
+
items?: SearchNode[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const normalizeSearchIndexPayload = (payload: SearchIndexPayload): SearchNode[] => {
|
|
122
|
+
if (payload.i) {
|
|
123
|
+
return payload.i.map((item) => ({
|
|
124
|
+
key: item.k,
|
|
125
|
+
title: item.t,
|
|
126
|
+
badge: item.b,
|
|
127
|
+
summary: item.s,
|
|
128
|
+
href: item.h,
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return payload.items || [];
|
|
133
|
+
};
|
|
134
|
+
|
|
102
135
|
export default function SearchModal() {
|
|
103
136
|
const [query, setQuery] = useState('');
|
|
104
137
|
const [open, setOpen] = useState(false);
|
|
105
138
|
const [activeFilter, setActiveFilter] = useState('all');
|
|
106
|
-
const
|
|
139
|
+
const [searchNodes, setSearchNodes] = useState<SearchNode[]>([]);
|
|
140
|
+
const [isLoadingSearchIndex, setIsLoadingSearchIndex] = useState(false);
|
|
141
|
+
const [searchIndexLoadError, setSearchIndexLoadError] = useState<string | null>(null);
|
|
107
142
|
const favorites = useStore(favoritesStore);
|
|
108
143
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
109
144
|
|
|
@@ -122,6 +157,34 @@ export default function SearchModal() {
|
|
|
122
157
|
return () => window.removeEventListener('searchModalToggle', handleModalToggle as EventListener);
|
|
123
158
|
}, []);
|
|
124
159
|
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (!open || searchNodes.length > 0 || isLoadingSearchIndex) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setIsLoadingSearchIndex(true);
|
|
166
|
+
setSearchIndexLoadError(null);
|
|
167
|
+
|
|
168
|
+
const apiUrl = buildUrl('/api/search-index.json', true);
|
|
169
|
+
|
|
170
|
+
fetch(apiUrl)
|
|
171
|
+
.then((response) => {
|
|
172
|
+
if (!response.ok) {
|
|
173
|
+
throw new Error(`Failed to fetch search index: ${response.status}`);
|
|
174
|
+
}
|
|
175
|
+
return response.json() as Promise<SearchIndexPayload>;
|
|
176
|
+
})
|
|
177
|
+
.then((payload) => {
|
|
178
|
+
setSearchNodes(normalizeSearchIndexPayload(payload));
|
|
179
|
+
})
|
|
180
|
+
.catch((error) => {
|
|
181
|
+
setSearchIndexLoadError(error instanceof Error ? error.message : 'Unable to load search index');
|
|
182
|
+
})
|
|
183
|
+
.finally(() => {
|
|
184
|
+
setIsLoadingSearchIndex(false);
|
|
185
|
+
});
|
|
186
|
+
}, [open, searchNodes.length, isLoadingSearchIndex]);
|
|
187
|
+
|
|
125
188
|
const closeModal = () => {
|
|
126
189
|
if ((window as any).searchModalState) {
|
|
127
190
|
(window as any).searchModalState.close();
|
|
@@ -131,30 +194,22 @@ export default function SearchModal() {
|
|
|
131
194
|
};
|
|
132
195
|
|
|
133
196
|
const items = useMemo(() => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const allItems = Object.entries(data.nodes)
|
|
138
|
-
.map(([key, node]) => {
|
|
139
|
-
// Skip reference entries (string values that point to other keys)
|
|
140
|
-
if (typeof node === 'string') return null;
|
|
141
|
-
|
|
142
|
-
const url = getUrlForItem(node, key);
|
|
197
|
+
return searchNodes
|
|
198
|
+
.map((node) => {
|
|
199
|
+
const url = getUrlForItem(node as any, node.key);
|
|
143
200
|
if (!url) return null;
|
|
144
201
|
|
|
145
202
|
return {
|
|
146
|
-
id: url,
|
|
203
|
+
id: url,
|
|
147
204
|
name: node.title,
|
|
148
|
-
url
|
|
205
|
+
url,
|
|
149
206
|
type: node.badge || 'Page',
|
|
150
|
-
key: key,
|
|
207
|
+
key: node.key,
|
|
151
208
|
rawNode: node,
|
|
152
209
|
};
|
|
153
210
|
})
|
|
154
211
|
.filter((item): item is NonNullable<typeof item> => item !== null);
|
|
155
|
-
|
|
156
|
-
return allItems;
|
|
157
|
-
}, [data]);
|
|
212
|
+
}, [searchNodes]);
|
|
158
213
|
|
|
159
214
|
// Get searchable items (items that match the query but not filtered by type yet)
|
|
160
215
|
const searchableItems = useMemo(() => {
|
|
@@ -236,17 +291,9 @@ export default function SearchModal() {
|
|
|
236
291
|
});
|
|
237
292
|
};
|
|
238
293
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const node = data.nodes[key];
|
|
243
|
-
if (!node) return null;
|
|
244
|
-
// If it's a string reference, follow it
|
|
245
|
-
if (typeof node === 'string') {
|
|
246
|
-
return data.nodes[node] as NavNode | undefined;
|
|
247
|
-
}
|
|
248
|
-
return node;
|
|
249
|
-
};
|
|
294
|
+
const searchNodeLookup = useMemo(() => {
|
|
295
|
+
return new Map(searchNodes.map((node) => [node.key, node]));
|
|
296
|
+
}, [searchNodes]);
|
|
250
297
|
|
|
251
298
|
const filteredItems = useMemo(() => {
|
|
252
299
|
if (query === '') {
|
|
@@ -255,18 +302,17 @@ export default function SearchModal() {
|
|
|
255
302
|
return favorites
|
|
256
303
|
.slice(0, 5)
|
|
257
304
|
.map((fav) => {
|
|
258
|
-
const node =
|
|
259
|
-
|
|
260
|
-
const url = getUrlForItem(node, fav.nodeKey);
|
|
305
|
+
const node = searchNodeLookup.get(fav.nodeKey);
|
|
306
|
+
const url = node ? getUrlForItem(node as any, fav.nodeKey) : fav.href;
|
|
261
307
|
if (!url) return null;
|
|
262
308
|
|
|
263
309
|
return {
|
|
264
310
|
id: url,
|
|
265
311
|
name: fav.title,
|
|
266
|
-
url
|
|
267
|
-
type: fav.badge || 'Page',
|
|
312
|
+
url,
|
|
313
|
+
type: fav.badge || node?.badge || 'Page',
|
|
268
314
|
key: fav.nodeKey,
|
|
269
|
-
rawNode: node,
|
|
315
|
+
rawNode: node || { title: fav.title, badge: fav.badge, summary: undefined },
|
|
270
316
|
isFavorite: true,
|
|
271
317
|
};
|
|
272
318
|
})
|
|
@@ -290,7 +336,7 @@ export default function SearchModal() {
|
|
|
290
336
|
}
|
|
291
337
|
|
|
292
338
|
return result.slice(0, 50); // Limit results for performance
|
|
293
|
-
}, [searchableItems, query, activeFilter, favorites,
|
|
339
|
+
}, [searchableItems, query, activeFilter, favorites, searchNodeLookup]);
|
|
294
340
|
|
|
295
341
|
return (
|
|
296
342
|
<Transition.Root
|
|
@@ -368,7 +414,23 @@ export default function SearchModal() {
|
|
|
368
414
|
))}
|
|
369
415
|
</div>
|
|
370
416
|
|
|
371
|
-
{
|
|
417
|
+
{isLoadingSearchIndex && (
|
|
418
|
+
<div className="py-10 px-6 text-center text-sm sm:px-14">
|
|
419
|
+
<MagnifyingGlassIcon className="mx-auto h-6 w-6 text-[rgb(var(--ec-icon-color))] animate-pulse" />
|
|
420
|
+
<p className="mt-4 font-semibold text-[rgb(var(--ec-page-text))]">Loading search index…</p>
|
|
421
|
+
<p className="mt-2 text-[rgb(var(--ec-page-text-muted))]">Preparing resources for search.</p>
|
|
422
|
+
</div>
|
|
423
|
+
)}
|
|
424
|
+
|
|
425
|
+
{searchIndexLoadError && !isLoadingSearchIndex && (
|
|
426
|
+
<div className="py-10 px-6 text-center text-sm sm:px-14">
|
|
427
|
+
<ExclamationCircleIcon className="mx-auto h-6 w-6 text-red-500" />
|
|
428
|
+
<p className="mt-4 font-semibold text-[rgb(var(--ec-page-text))]">Search unavailable</p>
|
|
429
|
+
<p className="mt-2 text-[rgb(var(--ec-page-text-muted))]">{searchIndexLoadError}</p>
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
|
|
433
|
+
{!isLoadingSearchIndex && !searchIndexLoadError && filteredItems.length > 0 && (
|
|
372
434
|
<>
|
|
373
435
|
{query === '' && favorites.length > 0 && (
|
|
374
436
|
<div className="px-6 pt-3 pb-2">
|
|
@@ -462,7 +524,7 @@ export default function SearchModal() {
|
|
|
462
524
|
</>
|
|
463
525
|
)}
|
|
464
526
|
|
|
465
|
-
{query !== '' && filteredItems.length === 0 && (
|
|
527
|
+
{!isLoadingSearchIndex && !searchIndexLoadError && query !== '' && filteredItems.length === 0 && (
|
|
466
528
|
<div className="py-14 px-6 text-center text-sm sm:px-14">
|
|
467
529
|
<ExclamationCircleIcon
|
|
468
530
|
type="outline"
|
|
@@ -476,7 +538,7 @@ export default function SearchModal() {
|
|
|
476
538
|
</div>
|
|
477
539
|
)}
|
|
478
540
|
|
|
479
|
-
{query === '' && filteredItems.length === 0 && (
|
|
541
|
+
{!isLoadingSearchIndex && !searchIndexLoadError && query === '' && filteredItems.length === 0 && (
|
|
480
542
|
<div className="py-14 px-6 text-center text-sm sm:px-14">
|
|
481
543
|
<MagnifyingGlassIcon className="mx-auto h-6 w-6 text-[rgb(var(--ec-icon-color))]" />
|
|
482
544
|
<p className="mt-4 font-semibold text-[rgb(var(--ec-page-text))]">Search for anything</p>
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { useStore } from '@nanostores/react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
2
3
|
import { Moon, Sun } from 'lucide-react';
|
|
3
4
|
import { themeStore, toggleTheme } from '@stores/theme-store';
|
|
4
5
|
|
|
5
6
|
export default function ThemeToggle() {
|
|
6
7
|
const theme = useStore(themeStore);
|
|
8
|
+
const [mounted, setMounted] = useState(false);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setMounted(true);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const label = mounted ? `Switch to ${theme === 'light' ? 'dark' : 'light'} mode` : 'Toggle theme';
|
|
7
15
|
|
|
8
16
|
return (
|
|
9
17
|
<button
|
|
10
18
|
onClick={toggleTheme}
|
|
11
19
|
className="p-2 rounded-md text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-icon-hover))] hover:bg-[rgb(var(--ec-dropdown-hover))] transition-colors"
|
|
12
|
-
aria-label={
|
|
13
|
-
title={
|
|
20
|
+
aria-label={label}
|
|
21
|
+
title={label}
|
|
14
22
|
>
|
|
15
|
-
{theme === 'light' ? <Moon className="w-5 h-5" /> : <Sun className="w-5 h-5" />}
|
|
23
|
+
{!mounted ? <Moon className="w-5 h-5" /> : theme === 'light' ? <Moon className="w-5 h-5" /> : <Sun className="w-5 h-5" />}
|
|
16
24
|
</button>
|
|
17
25
|
);
|
|
18
26
|
}
|
|
@@ -31,7 +31,7 @@ export const projectDirBase = (() => {
|
|
|
31
31
|
|
|
32
32
|
const pages = defineCollection({
|
|
33
33
|
loader: glob({
|
|
34
|
-
pattern: ['**/pages/*.(md|mdx)'],
|
|
34
|
+
pattern: ['**/pages/*.(md|mdx)', '!**/dist/**'],
|
|
35
35
|
base: projectDirBase,
|
|
36
36
|
}),
|
|
37
37
|
schema: z
|
|
@@ -90,7 +90,7 @@ const resourcePointer = z.object({
|
|
|
90
90
|
|
|
91
91
|
const changelogs = defineCollection({
|
|
92
92
|
loader: glob({
|
|
93
|
-
pattern: ['**/changelog.(md|mdx)'],
|
|
93
|
+
pattern: ['**/changelog.(md|mdx)', '!**/dist/**'],
|
|
94
94
|
base: projectDirBase,
|
|
95
95
|
}),
|
|
96
96
|
schema: z.object({
|
|
@@ -147,6 +147,7 @@ const baseSchema = z.object({
|
|
|
147
147
|
type: z.enum(['openapi', 'asyncapi', 'graphql']),
|
|
148
148
|
path: z.string(),
|
|
149
149
|
name: z.string().optional(),
|
|
150
|
+
headers: z.record(z.string()).optional(),
|
|
150
151
|
})
|
|
151
152
|
),
|
|
152
153
|
])
|
|
@@ -228,7 +229,7 @@ const flowStep = z
|
|
|
228
229
|
|
|
229
230
|
const flows = defineCollection({
|
|
230
231
|
loader: glob({
|
|
231
|
-
pattern: ['**/flows/*/index.(md|mdx)', '**/flows/*/versioned/*/index.(md|mdx)'],
|
|
232
|
+
pattern: ['**/flows/*/index.(md|mdx)', '**/flows/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
232
233
|
base: projectDirBase,
|
|
233
234
|
generateId: ({ data }) => {
|
|
234
235
|
return `${data.id}-${data.version}`;
|
|
@@ -316,7 +317,7 @@ const messageDetailsPanelPropertySchema = z.object({
|
|
|
316
317
|
|
|
317
318
|
const events = defineCollection({
|
|
318
319
|
loader: glob({
|
|
319
|
-
pattern: ['**/events/*/index.(md|mdx)', '**/events/*/versioned/*/index.(md|mdx)'],
|
|
320
|
+
pattern: ['**/events/*/index.(md|mdx)', '**/events/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
320
321
|
base: projectDirBase,
|
|
321
322
|
generateId: ({ data, ...rest }) => {
|
|
322
323
|
return `${data.id}-${data.version}`;
|
|
@@ -336,7 +337,7 @@ const events = defineCollection({
|
|
|
336
337
|
|
|
337
338
|
const commands = defineCollection({
|
|
338
339
|
loader: glob({
|
|
339
|
-
pattern: ['**/commands/*/index.(md|mdx)', '**/commands/*/versioned/*/index.(md|mdx)'],
|
|
340
|
+
pattern: ['**/commands/*/index.(md|mdx)', '**/commands/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
340
341
|
base: projectDirBase,
|
|
341
342
|
generateId: ({ data }) => {
|
|
342
343
|
return `${data.id}-${data.version}`;
|
|
@@ -356,7 +357,7 @@ const commands = defineCollection({
|
|
|
356
357
|
|
|
357
358
|
const queries = defineCollection({
|
|
358
359
|
loader: glob({
|
|
359
|
-
pattern: ['**/queries/*/index.(md|mdx)', '**/queries/*/versioned/*/index.(md|mdx)'],
|
|
360
|
+
pattern: ['**/queries/*/index.(md|mdx)', '**/queries/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
360
361
|
base: projectDirBase,
|
|
361
362
|
generateId: ({ data }) => {
|
|
362
363
|
return `${data.id}-${data.version}`;
|
|
@@ -388,7 +389,7 @@ const dataProductOutputPointer = z.object({
|
|
|
388
389
|
|
|
389
390
|
const dataProducts = defineCollection({
|
|
390
391
|
loader: glob({
|
|
391
|
-
pattern: ['**/data-products/*/index.(md|mdx)', '**/data-products/*/versioned/*/index.(md|mdx)'],
|
|
392
|
+
pattern: ['**/data-products/*/index.(md|mdx)', '**/data-products/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
392
393
|
base: projectDirBase,
|
|
393
394
|
generateId: ({ data }) => {
|
|
394
395
|
return `${data.id}-${data.version}`;
|
|
@@ -465,7 +466,7 @@ const dataClassificationEnum = z.enum(['public', 'internal', 'confidential', 're
|
|
|
465
466
|
|
|
466
467
|
const containers = defineCollection({
|
|
467
468
|
loader: glob({
|
|
468
|
-
pattern: ['**/containers/**/index.(md|mdx)', '**/containers/**/versioned/*/index.(md|mdx)'],
|
|
469
|
+
pattern: ['**/containers/**/index.(md|mdx)', '**/containers/**/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
469
470
|
base: projectDirBase,
|
|
470
471
|
generateId: ({ data }) => {
|
|
471
472
|
return `${data.id}-${data.version}`;
|
|
@@ -556,7 +557,7 @@ const domains = defineCollection({
|
|
|
556
557
|
|
|
557
558
|
const channels = defineCollection({
|
|
558
559
|
loader: glob({
|
|
559
|
-
pattern: ['**/channels/**/index.(md|mdx)', '**/channels/**/versioned/*/index.(md|mdx)'],
|
|
560
|
+
pattern: ['**/channels/**/index.(md|mdx)', '**/channels/**/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
560
561
|
base: projectDirBase,
|
|
561
562
|
generateId: ({ data }) => {
|
|
562
563
|
return `${data.id}-${data.version}`;
|
|
@@ -623,7 +624,7 @@ const ubiquitousLanguages = defineCollection({
|
|
|
623
624
|
|
|
624
625
|
const entities = defineCollection({
|
|
625
626
|
loader: glob({
|
|
626
|
-
pattern: ['**/entities/*/index.(md|mdx)', '**/entities/*/versioned/*/index.(md|mdx)'],
|
|
627
|
+
pattern: ['**/entities/*/index.(md|mdx)', '**/entities/*/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
627
628
|
base: projectDirBase,
|
|
628
629
|
generateId: ({ data, ...rest }) => {
|
|
629
630
|
return `${data.id}-${data.version}`;
|
|
@@ -740,7 +741,7 @@ const designs = defineCollection({
|
|
|
740
741
|
|
|
741
742
|
const diagrams = defineCollection({
|
|
742
743
|
loader: glob({
|
|
743
|
-
pattern: ['**/diagrams/**/index.(md|mdx)', '**/diagrams/**/versioned/*/index.(md|mdx)'],
|
|
744
|
+
pattern: ['**/diagrams/**/index.(md|mdx)', '**/diagrams/**/versioned/*/index.(md|mdx)', '!**/dist/**'],
|
|
744
745
|
base: projectDirBase,
|
|
745
746
|
generateId: ({ data }) => `${data.id}-${data.version}`,
|
|
746
747
|
}),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import utils from '@eventcatalog/sdk';
|
|
3
|
+
import { isSSR } from '@utils/feature';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Route that dumps the whole catalog as JSON (without markdown)
|
|
7
|
+
* Experimental API
|
|
8
|
+
*
|
|
9
|
+
* This route is injected only when `api.fullCatalogAPIEnabled` is true.
|
|
10
|
+
*/
|
|
11
|
+
export const GET: APIRoute = async () => {
|
|
12
|
+
const { dumpCatalog } = utils(process.env.PROJECT_DIR || '');
|
|
13
|
+
const catalog = await dumpCatalog({ includeMarkdown: false });
|
|
14
|
+
|
|
15
|
+
return new Response(JSON.stringify(catalog), {
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const prerender = !isSSR();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { APIRoute } from 'astro';
|
|
2
|
+
import { getNestedSideBarData } from '@stores/sidebar-store/state';
|
|
3
|
+
|
|
4
|
+
const isDev = import.meta.env.DEV;
|
|
5
|
+
|
|
6
|
+
interface SearchIndexItemCompact {
|
|
7
|
+
k: string;
|
|
8
|
+
t: string;
|
|
9
|
+
b?: string;
|
|
10
|
+
s?: string;
|
|
11
|
+
h?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SKIPPED_PREFIXES = ['list:'];
|
|
15
|
+
|
|
16
|
+
export const GET: APIRoute = async () => {
|
|
17
|
+
const sidebarData = await getNestedSideBarData();
|
|
18
|
+
|
|
19
|
+
const items = Object.entries(sidebarData.nodes).reduce<SearchIndexItemCompact[]>((acc, [key, node]) => {
|
|
20
|
+
if (typeof node === 'string') return acc;
|
|
21
|
+
if (SKIPPED_PREFIXES.some((prefix) => key.startsWith(prefix))) return acc;
|
|
22
|
+
if (!node.title) return acc;
|
|
23
|
+
|
|
24
|
+
acc.push({
|
|
25
|
+
k: key,
|
|
26
|
+
t: node.title,
|
|
27
|
+
...(node.badge ? { b: node.badge } : {}),
|
|
28
|
+
...(node.summary ? { s: node.summary } : {}),
|
|
29
|
+
...(node.href ? { h: node.href } : {}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return acc;
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return new Response(JSON.stringify({ i: items }), {
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Cache-Control': isDev
|
|
39
|
+
? 'no-cache, no-store, must-revalidate'
|
|
40
|
+
: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400',
|
|
41
|
+
Vary: 'Accept-Encoding',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const prerender = !isDev;
|
|
@@ -5,6 +5,8 @@ import { Parser } from '@asyncapi/parser';
|
|
|
5
5
|
import { AvroSchemaParser } from '@asyncapi/avro-schema-parser';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
|
|
8
|
+
import { isSameOrigin, resolveHeaders } from '@utils/remote-spec';
|
|
9
|
+
|
|
8
10
|
import '@asyncapi/react-component/styles/default.min.css';
|
|
9
11
|
import js from '@asyncapi/react-component/browser/standalone/without-parser.js?url';
|
|
10
12
|
import { AsyncApiComponentWP, type ConfigInterface } from '@asyncapi/react-component';
|
|
@@ -19,7 +21,7 @@ export const prerender = Page.prerender;
|
|
|
19
21
|
export const getStaticPaths = Page.getStaticPaths;
|
|
20
22
|
|
|
21
23
|
// Get data
|
|
22
|
-
const { collection, data, filePath, filename, path: relativeSpecPath } = await Page.getData(Astro);
|
|
24
|
+
const { collection, data, filePath, filename, path: relativeSpecPath, headers = {} } = await Page.getData(Astro);
|
|
23
25
|
|
|
24
26
|
const fileName = filename || 'asyncapi.yaml';
|
|
25
27
|
const pathToSpec = getAbsoluteFilePathForAstroFile(filePath, fileName);
|
|
@@ -27,18 +29,62 @@ const fileExists = fs.existsSync(pathToSpec);
|
|
|
27
29
|
const isRemote = relativeSpecPath.includes('https://');
|
|
28
30
|
let content = '';
|
|
29
31
|
|
|
32
|
+
const allowAnyEnvInSpecHeaders = Config?.asyncAPI?.allowAnyEnvInSpecHeaders ?? false;
|
|
33
|
+
const resolvedHeaders = resolveHeaders(headers, { allowAnyEnvInHeaders: allowAnyEnvInSpecHeaders });
|
|
34
|
+
|
|
30
35
|
if (fileExists && !isRemote) {
|
|
31
36
|
content = fs.readFileSync(pathToSpec, 'utf8');
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
if (isRemote) {
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const response = await fetch(relativeSpecPath, { headers: resolvedHeaders });
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`Failed to fetch AsyncAPI spec: ${response.status} ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
content = await response.text();
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
// AsyncAPI parser will
|
|
49
|
+
// AsyncAPI parser will parse schemas for users, they can turn this off.
|
|
40
50
|
const parseSchemas = Config?.asyncAPI?.renderParsedSchemas ?? true;
|
|
41
|
-
const
|
|
51
|
+
const parserOptions: NonNullable<Parameters<Parser['parse']>[1]> = {
|
|
52
|
+
parseSchemas,
|
|
53
|
+
source: isRemote ? relativeSpecPath : pathToSpec,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (isRemote && Object.keys(resolvedHeaders).length > 0) {
|
|
57
|
+
// NOTE: AsyncAPI parser exposes resolver wiring via __unstable.
|
|
58
|
+
// Keep this isolated so parser upgrades can adjust in one place.
|
|
59
|
+
const resolverWithHeaders = {
|
|
60
|
+
canRead: true,
|
|
61
|
+
read: async (uri: { toString: () => string }) => {
|
|
62
|
+
const targetUrl = uri.toString();
|
|
63
|
+
const shouldForwardHeaders = isSameOrigin(relativeSpecPath, targetUrl);
|
|
64
|
+
|
|
65
|
+
const response = await fetch(targetUrl, {
|
|
66
|
+
headers: shouldForwardHeaders ? resolvedHeaders : undefined,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`Failed to fetch reference: ${response.status} ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return response.text();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
parserOptions.__unstable = {
|
|
78
|
+
resolver: {
|
|
79
|
+
resolvers: [
|
|
80
|
+
{ schema: 'https', ...resolverWithHeaders },
|
|
81
|
+
{ schema: 'http', ...resolverWithHeaders },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parsed = await new Parser({ schemaParsers: [AvroSchemaParser()] }).parse(content, parserOptions);
|
|
42
88
|
const stringified = parsed.document?.json();
|
|
43
89
|
const config: ConfigInterface = { show: { sidebar: true, errors: true } };
|
|
44
90
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { ApiReferenceReact } from '@scalar/api-reference-react';
|
|
1
|
+
import { useState, useEffect, lazy, Suspense } from 'react';
|
|
3
2
|
import '@scalar/api-reference-react/style.css';
|
|
4
3
|
import './_styles.css';
|
|
5
4
|
|
|
5
|
+
const ApiReferenceReact = lazy(() =>
|
|
6
|
+
import('@scalar/api-reference-react').then((module) => ({ default: module.ApiReferenceReact }))
|
|
7
|
+
);
|
|
8
|
+
|
|
6
9
|
const OpenAPISpec = ({ spec }: { spec: string }) => {
|
|
7
10
|
const [loaded, setLoaded] = useState(false);
|
|
8
11
|
const [isDarkMode, setIsDarkMode] = useState(() => {
|
|
@@ -31,25 +34,27 @@ const OpenAPISpec = ({ spec }: { spec: string }) => {
|
|
|
31
34
|
return (
|
|
32
35
|
<div>
|
|
33
36
|
{!loaded && <div>Loading...</div>}
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
37
|
+
<Suspense fallback={<div>Loading OpenAPI reference...</div>}>
|
|
38
|
+
<ApiReferenceReact
|
|
39
|
+
key={isDarkMode ? 'dark' : 'light'}
|
|
40
|
+
configuration={{
|
|
41
|
+
spec: {
|
|
42
|
+
content: spec,
|
|
43
|
+
},
|
|
44
|
+
theme: 'fastify',
|
|
45
|
+
hideClientButton: true,
|
|
46
|
+
onLoaded: () => {
|
|
47
|
+
setLoaded(true);
|
|
48
|
+
},
|
|
49
|
+
forceDarkModeState: isDarkMode ? 'dark' : 'light',
|
|
50
|
+
darkMode: isDarkMode,
|
|
51
|
+
defaultOpenAllTags: true,
|
|
52
|
+
hideDarkModeToggle: true,
|
|
53
|
+
searchHotKey: 'p',
|
|
54
|
+
showSidebar: true,
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
</Suspense>
|
|
53
58
|
</div>
|
|
54
59
|
);
|
|
55
60
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HybridPage } from '@utils/page-loaders/hybrid-page';
|
|
2
|
-
import { isAuthEnabled } from '@utils/feature';
|
|
2
|
+
import { isAuthEnabled, isVisualiserEnabled } from '@utils/feature';
|
|
3
3
|
import { getServices, type Service } from '@utils/collections/services';
|
|
4
4
|
|
|
5
5
|
const serviceHasData = (service: Service) => {
|
|
@@ -8,7 +8,7 @@ const serviceHasData = (service: Service) => {
|
|
|
8
8
|
|
|
9
9
|
export class Page extends HybridPage {
|
|
10
10
|
static async getStaticPaths(): Promise<Array<{ params: any; props: any }>> {
|
|
11
|
-
if (isAuthEnabled()) {
|
|
11
|
+
if (isAuthEnabled() || !isVisualiserEnabled()) {
|
|
12
12
|
return [];
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -33,7 +33,7 @@ export class Page extends HybridPage {
|
|
|
33
33
|
protected static async fetchData(params: any) {
|
|
34
34
|
const { id, version } = params;
|
|
35
35
|
|
|
36
|
-
if (!id || !version) {
|
|
36
|
+
if (!id || !version || !isVisualiserEnabled()) {
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -58,7 +58,7 @@ export class Page extends HybridPage {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
static get clientAuthScript(): string {
|
|
61
|
-
if (!isAuthEnabled()) {
|
|
61
|
+
if (!isAuthEnabled() || !isVisualiserEnabled()) {
|
|
62
62
|
return '';
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -19,8 +19,13 @@ const {
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
<VisualiserLayout title={`Visualiser | ${props.data.name} (${props.collection})`} description={props.data.summary}>
|
|
22
|
-
<div class="
|
|
23
|
-
<div
|
|
22
|
+
<div class="m-4">
|
|
23
|
+
<div
|
|
24
|
+
class="h-[calc(100vh-130px)] w-full relative border border-[rgb(var(--ec-page-border))] rounded-md"
|
|
25
|
+
id={`${id}-portal`}
|
|
26
|
+
transition:animate="fade"
|
|
27
|
+
>
|
|
28
|
+
</div>
|
|
24
29
|
<NodeGraph
|
|
25
30
|
id={id}
|
|
26
31
|
collection={`${collection}-containers`}
|
|
@@ -20,8 +20,13 @@ const {
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
<VisualiserLayout title={`Visualiser | ${props.data.name} (${props.collection})`} description={props.data.summary}>
|
|
23
|
-
<div class="
|
|
24
|
-
<div
|
|
23
|
+
<div class="m-4">
|
|
24
|
+
<div
|
|
25
|
+
class="h-[calc(100vh-130px)] w-full relative border border-[rgb(var(--ec-page-border))] rounded-md"
|
|
26
|
+
id={`${id}-portal`}
|
|
27
|
+
transition:animate="fade"
|
|
28
|
+
>
|
|
29
|
+
</div>
|
|
25
30
|
<NodeGraph
|
|
26
31
|
id={id}
|
|
27
32
|
collection={collection}
|
|
@@ -8,8 +8,13 @@ import { getDomains } from '@utils/collections/domains';
|
|
|
8
8
|
import { getContainers } from '@utils/collections/containers';
|
|
9
9
|
import type { CollectionEntry } from 'astro:content';
|
|
10
10
|
import type { CollectionTypes } from '@types';
|
|
11
|
+
import { isVisualiserEnabled } from '@utils/feature';
|
|
11
12
|
|
|
12
13
|
export async function getStaticPaths() {
|
|
14
|
+
if (!isVisualiserEnabled()) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
const [events, commands, services, domains, containers] = await Promise.all([
|
|
14
19
|
getEvents(),
|
|
15
20
|
getCommands(),
|
|
@@ -5,9 +5,9 @@ import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<VerticalSideBarLayout title={`Visualiser | Bounded context map`}>
|
|
8
|
-
<div class="
|
|
8
|
+
<div class="m-4">
|
|
9
9
|
<div
|
|
10
|
-
class="h-[calc(100vh-130px)] w-full relative border border-
|
|
10
|
+
class="h-[calc(100vh-130px)] w-full relative border border-[rgb(var(--ec-page-border))] rounded-md"
|
|
11
11
|
id={`domain-context-map-portal`}
|
|
12
12
|
transition:animate="fade"
|
|
13
13
|
>
|