@eventcatalog/core 2.38.0 → 2.39.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/{chunk-2WN6WIV6.js → chunk-7TGRVLSE.js} +1 -1
- package/dist/{chunk-XJ2PHUZU.js → chunk-PG6VTEEI.js} +1 -1
- package/dist/{chunk-QTN3KJEE.js → chunk-YFIADR2I.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +3 -3
- package/eventcatalog/src/components/Lists/SpecificationsList.astro +31 -19
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +30 -1
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.tsx +32 -2
- package/eventcatalog/src/components/MDX/NodeGraph/Nodes/Service.tsx +53 -27
- package/eventcatalog/src/components/SideNav/ListViewSideBar/components/SpecificationList.tsx +61 -0
- package/eventcatalog/src/components/SideNav/ListViewSideBar/index.tsx +117 -116
- package/eventcatalog/src/components/SideNav/ListViewSideBar/types.ts +6 -3
- package/eventcatalog/src/components/SideNav/ListViewSideBar/utils.ts +2 -2
- package/eventcatalog/src/content.config.ts +13 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/{index.astro → [filename].astro} +28 -16
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/{index.astro → [filename].astro} +29 -15
- package/eventcatalog/src/utils/collections/services.ts +29 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log_build_default
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-YFIADR2I.js";
|
|
4
|
+
import "../chunk-7TGRVLSE.js";
|
|
5
|
+
import "../chunk-PG6VTEEI.js";
|
|
6
6
|
import "../chunk-E7TXTI7G.js";
|
|
7
7
|
export {
|
|
8
8
|
log_build_default as default
|
package/dist/constants.cjs
CHANGED
package/dist/constants.js
CHANGED
package/dist/eventcatalog.cjs
CHANGED
package/dist/eventcatalog.js
CHANGED
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
} from "./chunk-DCLTVJDP.js";
|
|
7
7
|
import {
|
|
8
8
|
log_build_default
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-YFIADR2I.js";
|
|
10
|
+
import "./chunk-7TGRVLSE.js";
|
|
11
11
|
import {
|
|
12
12
|
catalogToAstro,
|
|
13
13
|
checkAndConvertMdToMdx
|
|
14
14
|
} from "./chunk-SLEMYHTU.js";
|
|
15
15
|
import {
|
|
16
16
|
VERSION
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-PG6VTEEI.js";
|
|
18
18
|
import {
|
|
19
19
|
isBackstagePluginEnabled,
|
|
20
20
|
isEventCatalogScaleEnabled,
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import type { CollectionTypes } from '@types';
|
|
3
3
|
import { buildUrl } from '@utils/url-builder';
|
|
4
4
|
import type { CollectionEntry } from 'astro:content';
|
|
5
|
-
|
|
5
|
+
import { getSpecificationsForService } from '@utils/collections/services';
|
|
6
|
+
import type { Service } from '@utils/collections/services';
|
|
6
7
|
interface Props {
|
|
7
8
|
collectionItem: CollectionEntry<CollectionTypes>;
|
|
8
9
|
}
|
|
@@ -11,30 +12,41 @@ const { collectionItem } = Astro.props;
|
|
|
11
12
|
|
|
12
13
|
const specVersions = collectionItem.data.specifications || {};
|
|
13
14
|
const numberOfSpecifications = Object.keys(specVersions).length;
|
|
15
|
+
|
|
16
|
+
const specs = getSpecificationsForService(collectionItem as Service);
|
|
17
|
+
|
|
18
|
+
const openAPISpecifications = specs.filter((spec) => spec.type === 'openapi');
|
|
19
|
+
const asyncAPISpecifications = specs.filter((spec) => spec.type === 'asyncapi');
|
|
14
20
|
---
|
|
15
21
|
|
|
16
22
|
<div class="space-y-2 mb-8">
|
|
17
23
|
<span class="text-sm text-black group-data-[hover]:text-black/80 capitalize">Specifications ({numberOfSpecifications})</span>
|
|
18
24
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
openAPISpecifications.length > 0 &&
|
|
26
|
+
openAPISpecifications.map((spec) => (
|
|
27
|
+
<a
|
|
28
|
+
href={buildUrl(
|
|
29
|
+
`/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.version}/spec/${spec.filenameWithoutExtension}`
|
|
30
|
+
)}
|
|
31
|
+
class="px-1 text-sm font-light flex items-center space-x-1 hover:underline hover:text-primary rounded-md hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white"
|
|
32
|
+
>
|
|
33
|
+
<img src={buildUrl('/icons/openapi.svg', true)} class="h-4 w-4" />
|
|
34
|
+
<span>{spec.name}</span>
|
|
35
|
+
</a>
|
|
36
|
+
))
|
|
28
37
|
}
|
|
29
38
|
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
asyncAPISpecifications.length > 0 &&
|
|
40
|
+
asyncAPISpecifications.map((spec) => (
|
|
41
|
+
<a
|
|
42
|
+
href={buildUrl(
|
|
43
|
+
`/docs/${collectionItem.collection}/${collectionItem.data.id}/${collectionItem.data.version}/asyncapi/${spec.filenameWithoutExtension}`
|
|
44
|
+
)}
|
|
45
|
+
class="px-1 text-sm font-light flex items-center space-x-1 hover:underline rounded-md hover:bg-gradient-to-l hover:from-purple-500 hover:to-purple-700 hover:text-white"
|
|
46
|
+
>
|
|
47
|
+
<img src={buildUrl('/icons/asyncapi.svg', true)} class="h-4 w-4" />
|
|
48
|
+
<span>{spec.name}</span>
|
|
49
|
+
</a>
|
|
50
|
+
))
|
|
39
51
|
}
|
|
40
52
|
</div>
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
getNodesAndEdgesForDomainContextMap,
|
|
12
12
|
} from '@utils/node-graphs/domains-node-graph';
|
|
13
13
|
import { getNodesAndEdges as getNodesAndEdgesForFlows } from '@utils/node-graphs/flows-node-graph';
|
|
14
|
-
|
|
14
|
+
import { buildUrl } from '@utils/url-builder';
|
|
15
|
+
import { getVersionFromCollection } from '@utils/collections/versions';
|
|
16
|
+
import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
|
|
15
17
|
interface Props {
|
|
16
18
|
id: string;
|
|
17
19
|
collection: string;
|
|
@@ -40,6 +42,8 @@ const getNodesAndEdgesFunctions = {
|
|
|
40
42
|
flows: getNodesAndEdgesForFlows,
|
|
41
43
|
};
|
|
42
44
|
|
|
45
|
+
let links: { label: string; url: string }[] = [];
|
|
46
|
+
|
|
43
47
|
if (collection in getNodesAndEdgesFunctions) {
|
|
44
48
|
const { nodes: fetchedNodes, edges: fetchedEdges } = await getNodesAndEdgesFunctions[
|
|
45
49
|
collection as keyof typeof getNodesAndEdgesFunctions
|
|
@@ -51,6 +55,30 @@ if (collection in getNodesAndEdgesFunctions) {
|
|
|
51
55
|
|
|
52
56
|
nodes = fetchedNodes;
|
|
53
57
|
edges = fetchedEdges;
|
|
58
|
+
|
|
59
|
+
if (mode === 'full') {
|
|
60
|
+
// Try and get the list of versions for the rendered item
|
|
61
|
+
try {
|
|
62
|
+
const allItems = await pageDataLoader[collection as keyof typeof pageDataLoader]();
|
|
63
|
+
const versions = getVersionFromCollection(allItems, id, version);
|
|
64
|
+
|
|
65
|
+
const item = versions[0];
|
|
66
|
+
const listOfVersions = item.data.versions || [];
|
|
67
|
+
|
|
68
|
+
// Order by version
|
|
69
|
+
listOfVersions.sort((a, b) => b.localeCompare(a));
|
|
70
|
+
|
|
71
|
+
if (listOfVersions.length > 1) {
|
|
72
|
+
links = listOfVersions.map((version) => ({
|
|
73
|
+
label: `${item.data.name} v${version}`,
|
|
74
|
+
url: buildUrl(`/visualiser/${collection}/${id}/${version}`),
|
|
75
|
+
selected: version === version,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
links = [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
54
82
|
}
|
|
55
83
|
|
|
56
84
|
if (collection === 'domain-context-map') {
|
|
@@ -71,6 +99,7 @@ if (collection === 'domain-context-map') {
|
|
|
71
99
|
linkTo={linkTo}
|
|
72
100
|
client:only="react"
|
|
73
101
|
linksToVisualiser={linksToVisualiser}
|
|
102
|
+
links={links}
|
|
74
103
|
/>
|
|
75
104
|
</div>
|
|
76
105
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useReactFlow,
|
|
15
15
|
} from '@xyflow/react';
|
|
16
16
|
import '@xyflow/react/dist/style.css';
|
|
17
|
-
|
|
17
|
+
import { HistoryIcon } from 'lucide-react';
|
|
18
18
|
// Nodes and edges
|
|
19
19
|
import ServiceNode from './Nodes/Service';
|
|
20
20
|
import FlowNode from './Nodes/Flow';
|
|
@@ -46,6 +46,7 @@ interface Props {
|
|
|
46
46
|
linkTo: 'docs' | 'visualiser';
|
|
47
47
|
includeKey?: boolean;
|
|
48
48
|
linksToVisualiser?: boolean;
|
|
49
|
+
links?: { label: string; url: string }[];
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const getVisualiserUrlForCollection = (collectionItem: CollectionEntry<CollectionTypes>) => {
|
|
@@ -60,6 +61,7 @@ const NodeGraphBuilder = ({
|
|
|
60
61
|
linkTo = 'docs',
|
|
61
62
|
includeKey = true,
|
|
62
63
|
linksToVisualiser = false,
|
|
64
|
+
links = [],
|
|
63
65
|
}: Props) => {
|
|
64
66
|
const nodeTypes = useMemo(
|
|
65
67
|
() => ({
|
|
@@ -317,8 +319,33 @@ const NodeGraphBuilder = ({
|
|
|
317
319
|
{title}
|
|
318
320
|
</span>
|
|
319
321
|
)}
|
|
320
|
-
<div className="flex justify-end ">
|
|
322
|
+
<div className="flex justify-end space-x-2">
|
|
321
323
|
<DownloadButton filename={title} addPadding={false} />
|
|
324
|
+
{/* // Dropdown for links */}
|
|
325
|
+
{links.length > 0 && (
|
|
326
|
+
<div className="relative flex items-center -mt-1">
|
|
327
|
+
<span className="absolute left-2 pointer-events-none flex items-center h-full">
|
|
328
|
+
<HistoryIcon className="h-4 w-4 text-gray-600" />
|
|
329
|
+
</span>
|
|
330
|
+
<select
|
|
331
|
+
value={links.find((link) => window.location.href.includes(link.url))?.url || links[0].url}
|
|
332
|
+
onChange={(e) => navigate(e.target.value)}
|
|
333
|
+
className="appearance-none pl-7 pr-6 py-0 text-[14px] bg-white rounded-md border border-gray-200 hover:bg-gray-100/50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
|
|
334
|
+
style={{ minWidth: 120, height: '26px' }}
|
|
335
|
+
>
|
|
336
|
+
{links.map((link) => (
|
|
337
|
+
<option key={link.url} value={link.url}>
|
|
338
|
+
{link.label}
|
|
339
|
+
</option>
|
|
340
|
+
))}
|
|
341
|
+
</select>
|
|
342
|
+
<span className="absolute right-2 pointer-events-none">
|
|
343
|
+
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
|
344
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
|
345
|
+
</svg>
|
|
346
|
+
</span>
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
322
349
|
</div>
|
|
323
350
|
</div>
|
|
324
351
|
</Panel>
|
|
@@ -409,6 +436,7 @@ interface NodeGraphProps {
|
|
|
409
436
|
includeKey?: boolean;
|
|
410
437
|
footerLabel?: string;
|
|
411
438
|
linksToVisualiser?: boolean;
|
|
439
|
+
links?: { label: string; url: string }[];
|
|
412
440
|
}
|
|
413
441
|
|
|
414
442
|
const NodeGraph = ({
|
|
@@ -422,6 +450,7 @@ const NodeGraph = ({
|
|
|
422
450
|
includeKey = true,
|
|
423
451
|
footerLabel,
|
|
424
452
|
linksToVisualiser = false,
|
|
453
|
+
links = [],
|
|
425
454
|
}: NodeGraphProps) => {
|
|
426
455
|
const [elem, setElem] = useState(null);
|
|
427
456
|
const [showFooter, setShowFooter] = useState(true);
|
|
@@ -452,6 +481,7 @@ const NodeGraph = ({
|
|
|
452
481
|
linkTo={linkTo}
|
|
453
482
|
includeKey={includeKey}
|
|
454
483
|
linksToVisualiser={linksToVisualiser}
|
|
484
|
+
links={links}
|
|
455
485
|
/>
|
|
456
486
|
|
|
457
487
|
{showFooter && (
|
|
@@ -29,8 +29,32 @@ export default function ServiceNode({ data, sourcePosition, targetPosition }: an
|
|
|
29
29
|
const nodeLabel = label || service?.data?.sidebar?.badge || 'Service';
|
|
30
30
|
const fontSize = nodeLabel.length > 10 ? '7px' : '9px';
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
let asyncApiFiles = Array.isArray(specifications) ? specifications?.filter((spec) => spec.type === 'asyncapi') : [];
|
|
33
|
+
let openApiFiles = Array.isArray(specifications) ? specifications?.filter((spec) => spec.type === 'openapi') : [];
|
|
34
|
+
|
|
35
|
+
if (!Array.isArray(specifications) && specifications?.asyncapiPath) {
|
|
36
|
+
asyncApiFiles.push({ path: specifications.asyncapiPath, type: 'asyncapi', name: 'AsyncAPI' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!Array.isArray(specifications) && specifications?.openapiPath) {
|
|
40
|
+
openApiFiles.push({ path: specifications.openapiPath, type: 'openapi', name: 'OpenAPI' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add filename on asyncApiFiles and openApiFiles
|
|
44
|
+
asyncApiFiles = asyncApiFiles.map((file) => {
|
|
45
|
+
return {
|
|
46
|
+
...file,
|
|
47
|
+
filename: file.path.split('/').pop()?.split('.').shift(),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
openApiFiles = openApiFiles.map((file) => {
|
|
51
|
+
return {
|
|
52
|
+
...file,
|
|
53
|
+
filename: file.path.split('/').pop()?.split('.').shift(),
|
|
54
|
+
name: file.name,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
34
58
|
const repositoryUrl = repository?.url;
|
|
35
59
|
|
|
36
60
|
return (
|
|
@@ -95,31 +119,33 @@ export default function ServiceNode({ data, sourcePosition, targetPosition }: an
|
|
|
95
119
|
<a href={buildUrl(`/docs/services/${id}/${version}`)}>Read documentation</a>
|
|
96
120
|
</ContextMenu.Item>
|
|
97
121
|
<ContextMenu.Separator className="h-[1px] bg-gray-200 m-1" />
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
{asyncApiFiles.length > 0 &&
|
|
123
|
+
asyncApiFiles.map((file) => (
|
|
124
|
+
<ContextMenu.Item asChild key={file.path}>
|
|
125
|
+
<a
|
|
126
|
+
href={buildUrl(`/docs/services/${id}/${version}/asyncapi/${file.filename}`)}
|
|
127
|
+
className="text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center"
|
|
128
|
+
target="_blank"
|
|
129
|
+
rel="noopener noreferrer"
|
|
130
|
+
>
|
|
131
|
+
View AsyncAPI specification {file.name ? `(${file.name})` : ''}
|
|
132
|
+
</a>
|
|
133
|
+
</ContextMenu.Item>
|
|
134
|
+
))}
|
|
135
|
+
{openApiFiles.length > 0 &&
|
|
136
|
+
openApiFiles.map((file) => (
|
|
137
|
+
<ContextMenu.Item asChild key={file.path}>
|
|
138
|
+
<a
|
|
139
|
+
href={buildUrl(`/docs/services/${id}/${version}/spec/${file.filename}`)}
|
|
140
|
+
className="text-sm px-2 py-1.5 outline-none cursor-pointer hover:bg-orange-100 rounded-sm flex items-center"
|
|
141
|
+
target="_blank"
|
|
142
|
+
rel="noopener noreferrer"
|
|
143
|
+
>
|
|
144
|
+
View OpenAPI specification {file.name ? `(${file.name})` : ''}
|
|
145
|
+
</a>
|
|
146
|
+
</ContextMenu.Item>
|
|
147
|
+
))}
|
|
148
|
+
{asyncApiFiles.length > 0 && openApiFiles.length > 0 && <ContextMenu.Separator className="h-[1px] bg-gray-200 m-1" />}
|
|
123
149
|
{repositoryUrl && (
|
|
124
150
|
<>
|
|
125
151
|
<ContextMenu.Item asChild>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { buildUrl } from '@utils/url-builder';
|
|
3
|
+
import type { ServiceItem } from '../types';
|
|
4
|
+
|
|
5
|
+
interface SpecificationListProps {
|
|
6
|
+
specifications: {
|
|
7
|
+
type: string;
|
|
8
|
+
path: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
filename?: string;
|
|
11
|
+
filenameWithoutExtension?: string;
|
|
12
|
+
}[];
|
|
13
|
+
id: string;
|
|
14
|
+
version: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SpecificationList: React.FC<SpecificationListProps> = ({ specifications, id, version }) => {
|
|
18
|
+
const asyncAPISpecifications = specifications.filter((spec) => spec.type === 'asyncapi');
|
|
19
|
+
const openAPISpecifications = specifications.filter((spec) => spec.type === 'openapi');
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ul className="space-y-0.5 border-l border-gray-200/80 ml-[9px] pl-4">
|
|
23
|
+
{asyncAPISpecifications &&
|
|
24
|
+
asyncAPISpecifications.length > 0 &&
|
|
25
|
+
asyncAPISpecifications.map((spec) => (
|
|
26
|
+
<a
|
|
27
|
+
key={`${spec.name}-asyncapi`}
|
|
28
|
+
href={buildUrl(`/docs/services/${id}/${version}/asyncapi/${spec.filenameWithoutExtension}`)}
|
|
29
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md justify-between ${
|
|
30
|
+
window.location.href.includes(`docs/services/${id}/${version}/asyncapi`) ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
31
|
+
}`}
|
|
32
|
+
>
|
|
33
|
+
<span className="truncate flex items-center gap-1">{spec.name}</span>
|
|
34
|
+
<span className="text-purple-600 ml-2 text-[10px] uppercase font-medium bg-gray-50 px-4 py-0.5 rounded">
|
|
35
|
+
<img src={buildUrl('/icons/asyncapi.svg', true)} className="w-4 h-4" alt="AsyncAPI" />
|
|
36
|
+
</span>
|
|
37
|
+
</a>
|
|
38
|
+
))}
|
|
39
|
+
{openAPISpecifications &&
|
|
40
|
+
openAPISpecifications.length > 0 &&
|
|
41
|
+
openAPISpecifications.map((spec) => (
|
|
42
|
+
<a
|
|
43
|
+
key={`${spec.name}-openapi`}
|
|
44
|
+
href={buildUrl(`/docs/services/${id}/${version}/spec/${spec.filenameWithoutExtension}`)}
|
|
45
|
+
className={`items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md flex justify-between ${
|
|
46
|
+
window.location.href.includes(`docs/services/${id}/${version}/spec/${spec.filenameWithoutExtension}`)
|
|
47
|
+
? 'bg-purple-100'
|
|
48
|
+
: 'hover:bg-purple-100'
|
|
49
|
+
}`}
|
|
50
|
+
>
|
|
51
|
+
<span className="truncate flex items-center gap-1">{spec.name}</span>
|
|
52
|
+
<span className="text-green-600 ml-2 text-[10px] uppercase font-medium bg-gray-50 px-4 py-0.5 rounded">
|
|
53
|
+
<img src={buildUrl('/icons/openapi.svg', true)} className="w-4 h-4" alt="OpenAPI" />
|
|
54
|
+
</span>
|
|
55
|
+
</a>
|
|
56
|
+
))}
|
|
57
|
+
</ul>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default SpecificationList;
|
|
@@ -3,6 +3,7 @@ import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
|
|
3
3
|
import { buildUrl, buildUrlWithParams } from '@utils/url-builder';
|
|
4
4
|
import CollapsibleGroup from './components/CollapsibleGroup';
|
|
5
5
|
import MessageList from './components/MessageList';
|
|
6
|
+
import SpecificationsList from './components/SpecificationList';
|
|
6
7
|
import type { MessageItem, ServiceItem, ListViewSideBarProps } from './types';
|
|
7
8
|
const STORAGE_KEY = 'EventCatalog:catalogSidebarCollapsedGroups';
|
|
8
9
|
const DEBOUNCE_DELAY = 300; // 300ms debounce delay
|
|
@@ -51,130 +52,128 @@ const ServiceItem = React.memo(
|
|
|
51
52
|
collapsedGroups: { [key: string]: boolean };
|
|
52
53
|
toggleGroupCollapse: (group: string) => void;
|
|
53
54
|
isVisualizer: boolean;
|
|
54
|
-
}) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
>
|
|
71
|
-
<div className="space-y-0.5 border-gray-200/80 border-l pl-3 ml-[9px] mt-1">
|
|
72
|
-
<a
|
|
73
|
-
href={`${item.href}`}
|
|
74
|
-
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
75
|
-
decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
76
|
-
}`}
|
|
77
|
-
>
|
|
78
|
-
<span className="truncate">Overview</span>
|
|
79
|
-
</a>
|
|
80
|
-
<a
|
|
81
|
-
href={buildUrlWithParams('/architecture/docs/messages', {
|
|
82
|
-
serviceName: item.name,
|
|
83
|
-
serviceId: item.id,
|
|
84
|
-
})}
|
|
85
|
-
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
86
|
-
window.location.href.includes(`serviceId=${item.id}`) ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
87
|
-
}`}
|
|
88
|
-
>
|
|
89
|
-
<span className="truncate flex items-center gap-1">Architecture</span>
|
|
90
|
-
</a>
|
|
91
|
-
{item.specifications && item.specifications.asyncapiPath && (
|
|
92
|
-
<a
|
|
93
|
-
href={buildUrl(`/docs/services/${item.id}/${item.version}/asyncapi`)}
|
|
94
|
-
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md flex justify-between ${
|
|
95
|
-
window.location.href.includes(`docs/services/${item.id}/${item.version}/asyncapi`)
|
|
96
|
-
? 'bg-purple-100'
|
|
97
|
-
: 'hover:bg-purple-100'
|
|
98
|
-
}`}
|
|
55
|
+
}) => {
|
|
56
|
+
const asyncAPISpecifications = item.specifications?.filter((spec) => spec.type === 'asyncapi');
|
|
57
|
+
const openAPISpecifications = item.specifications?.filter((spec) => spec.type === 'openapi');
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<CollapsibleGroup
|
|
61
|
+
isCollapsed={collapsedGroups[item.href]}
|
|
62
|
+
onToggle={() => toggleGroupCollapse(item.href)}
|
|
63
|
+
title={
|
|
64
|
+
<button
|
|
65
|
+
onClick={(e) => {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
toggleGroupCollapse(item.href);
|
|
68
|
+
}}
|
|
69
|
+
className="flex justify-between items-center pl-2 w-full text-xs"
|
|
99
70
|
>
|
|
100
|
-
<span className="truncate
|
|
101
|
-
<span className="text-purple-600 ml-2 text-[10px]
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{item.specifications && item.specifications.openapiPath && (
|
|
71
|
+
<span className="truncate text-xs font-bold">{item.label}</span>
|
|
72
|
+
<span className="text-purple-600 ml-2 text-[10px] font-medium bg-purple-50 px-2 py-0.5 rounded">SERVICE</span>
|
|
73
|
+
</button>
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
<div className="space-y-0.5 border-gray-200/80 border-l pl-3 ml-[9px] mt-1">
|
|
107
77
|
<a
|
|
108
|
-
href={
|
|
109
|
-
className={`items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md
|
|
110
|
-
|
|
111
|
-
? 'bg-purple-100'
|
|
112
|
-
: 'hover:bg-purple-100'
|
|
78
|
+
href={`${item.href}`}
|
|
79
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
80
|
+
decodedCurrentPath === item.href ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
113
81
|
}`}
|
|
114
82
|
>
|
|
115
|
-
<span className="truncate
|
|
116
|
-
<span className="text-green-600 ml-2 text-[10px] uppercase font-medium bg-gray-50 px-4 py-0.5 rounded">
|
|
117
|
-
<img src={buildUrl('/icons/openapi.svg', true)} className="w-4 h-4" />
|
|
118
|
-
</span>
|
|
83
|
+
<span className="truncate">Overview</span>
|
|
119
84
|
</a>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}}
|
|
130
|
-
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
85
|
+
{!isVisualizer && (
|
|
86
|
+
<a
|
|
87
|
+
href={buildUrlWithParams('/architecture/docs/messages', {
|
|
88
|
+
serviceName: item.name,
|
|
89
|
+
serviceId: item.id,
|
|
90
|
+
})}
|
|
91
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
92
|
+
window.location.href.includes(`serviceId=${item.id}`) ? 'bg-purple-100' : 'hover:bg-purple-100'
|
|
93
|
+
}`}
|
|
131
94
|
>
|
|
132
|
-
|
|
133
|
-
</
|
|
134
|
-
}
|
|
135
|
-
>
|
|
136
|
-
<MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} />
|
|
137
|
-
</CollapsibleGroup>
|
|
95
|
+
<span className="truncate flex items-center gap-1">Architecture</span>
|
|
96
|
+
</a>
|
|
97
|
+
)}
|
|
138
98
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
99
|
+
{!isVisualizer && item.specifications && item.specifications.length > 0 && (
|
|
100
|
+
<CollapsibleGroup
|
|
101
|
+
isCollapsed={collapsedGroups[`${item.href}-specifications`]}
|
|
102
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-specifications`)}
|
|
103
|
+
title={
|
|
104
|
+
<button
|
|
105
|
+
onClick={(e) => {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
toggleGroupCollapse(`${item.href}-specifications`);
|
|
108
|
+
}}
|
|
109
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
110
|
+
>
|
|
111
|
+
Specifications ({item.specifications?.length})
|
|
112
|
+
</button>
|
|
113
|
+
}
|
|
149
114
|
>
|
|
150
|
-
|
|
151
|
-
</
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
<MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} />
|
|
155
|
-
</CollapsibleGroup>
|
|
156
|
-
{!isVisualizer && item.entities.length > 0 && (
|
|
115
|
+
<SpecificationsList specifications={item.specifications} id={item.id} version={item.version} />
|
|
116
|
+
</CollapsibleGroup>
|
|
117
|
+
)}
|
|
118
|
+
|
|
157
119
|
<CollapsibleGroup
|
|
158
|
-
isCollapsed={collapsedGroups[`${item.href}-
|
|
159
|
-
onToggle={() => toggleGroupCollapse(`${item.href}-
|
|
120
|
+
isCollapsed={collapsedGroups[`${item.href}-receives`]}
|
|
121
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-receives`)}
|
|
160
122
|
title={
|
|
161
123
|
<button
|
|
162
124
|
onClick={(e) => {
|
|
163
125
|
e.stopPropagation();
|
|
164
|
-
toggleGroupCollapse(`${item.href}-
|
|
126
|
+
toggleGroupCollapse(`${item.href}-receives`);
|
|
165
127
|
}}
|
|
166
128
|
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
167
129
|
>
|
|
168
|
-
|
|
130
|
+
Receives messages ({item.receives.length})
|
|
169
131
|
</button>
|
|
170
132
|
}
|
|
171
133
|
>
|
|
172
|
-
<MessageList messages={item.
|
|
134
|
+
<MessageList messages={item.receives} decodedCurrentPath={decodedCurrentPath} />
|
|
173
135
|
</CollapsibleGroup>
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
136
|
+
|
|
137
|
+
<CollapsibleGroup
|
|
138
|
+
isCollapsed={collapsedGroups[`${item.href}-sends`]}
|
|
139
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-sends`)}
|
|
140
|
+
title={
|
|
141
|
+
<button
|
|
142
|
+
onClick={(e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
toggleGroupCollapse(`${item.href}-sends`);
|
|
145
|
+
}}
|
|
146
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
147
|
+
>
|
|
148
|
+
Sends messages ({item.sends.length})
|
|
149
|
+
</button>
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<MessageList messages={item.sends} decodedCurrentPath={decodedCurrentPath} />
|
|
153
|
+
</CollapsibleGroup>
|
|
154
|
+
{!isVisualizer && item.entities.length > 0 && (
|
|
155
|
+
<CollapsibleGroup
|
|
156
|
+
isCollapsed={collapsedGroups[`${item.href}-entities`]}
|
|
157
|
+
onToggle={() => toggleGroupCollapse(`${item.href}-entities`)}
|
|
158
|
+
title={
|
|
159
|
+
<button
|
|
160
|
+
onClick={(e) => {
|
|
161
|
+
e.stopPropagation();
|
|
162
|
+
toggleGroupCollapse(`${item.href}-entities`);
|
|
163
|
+
}}
|
|
164
|
+
className="truncate underline ml-2 text-xs mb-1 py-1"
|
|
165
|
+
>
|
|
166
|
+
Entities ({item.entities.length})
|
|
167
|
+
</button>
|
|
168
|
+
}
|
|
169
|
+
>
|
|
170
|
+
<MessageList messages={item.entities} decodedCurrentPath={decodedCurrentPath} />
|
|
171
|
+
</CollapsibleGroup>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
</CollapsibleGroup>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
178
177
|
);
|
|
179
178
|
|
|
180
179
|
const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPath }) => {
|
|
@@ -354,18 +353,20 @@ const ListViewSideBar: React.FC<ListViewSideBarProps> = ({ resources, currentPat
|
|
|
354
353
|
>
|
|
355
354
|
<span className="truncate">Overview</span>
|
|
356
355
|
</a>
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
356
|
+
{!isVisualizer && (
|
|
357
|
+
<a
|
|
358
|
+
href={buildUrlWithParams('/architecture/docs/services', {
|
|
359
|
+
serviceIds: item.services.map((service: any) => service.data.id).join(','),
|
|
360
|
+
domainId: item.id,
|
|
361
|
+
domainName: item.name,
|
|
362
|
+
})}
|
|
363
|
+
className={`flex items-center px-2 py-1.5 text-xs text-gray-600 hover:bg-purple-100 rounded-md ${
|
|
364
|
+
window.location.href.includes(`domainId=${item.id}`) ? 'bg-purple-100 ' : 'hover:bg-purple-100'
|
|
365
|
+
}`}
|
|
366
|
+
>
|
|
367
|
+
<span className="truncate">Architecture</span>
|
|
368
|
+
</a>
|
|
369
|
+
)}
|
|
369
370
|
{!isVisualizer && (
|
|
370
371
|
<a
|
|
371
372
|
href={buildUrl(`/docs/domains/${item.id}/language`)}
|
|
@@ -27,9 +27,12 @@ export interface ServiceItem {
|
|
|
27
27
|
receives: MessageItem[];
|
|
28
28
|
entities: EntityItem[];
|
|
29
29
|
specifications?: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
type: string;
|
|
31
|
+
path: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
filename?: string;
|
|
34
|
+
filenameWithoutExtension?: string;
|
|
35
|
+
}[];
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
interface DomainItem {
|
|
@@ -3,7 +3,7 @@ import { buildUrl } from '@utils/url-builder';
|
|
|
3
3
|
import { getChannels } from '@utils/channels';
|
|
4
4
|
import { getDomains } from '@utils/collections/domains';
|
|
5
5
|
import { getFlows } from '@utils/collections/flows';
|
|
6
|
-
import { getServices } from '@utils/collections/services';
|
|
6
|
+
import { getServices, getSpecificationsForService } from '@utils/collections/services';
|
|
7
7
|
import { getCommands } from '@utils/commands';
|
|
8
8
|
import { getEvents } from '@utils/events';
|
|
9
9
|
import { getQueries } from '@utils/queries';
|
|
@@ -76,7 +76,7 @@ export async function getResourcesForNavigation({ currentPath }: { currentPath:
|
|
|
76
76
|
sends: sendsWithHref,
|
|
77
77
|
receives: receivesWithHref,
|
|
78
78
|
entities: entitiesWithHref,
|
|
79
|
-
specifications: isCollectionService ? item
|
|
79
|
+
specifications: isCollectionService ? getSpecificationsForService(item) : null,
|
|
80
80
|
sidebar: item.data?.sidebar,
|
|
81
81
|
};
|
|
82
82
|
|
|
@@ -89,10 +89,19 @@ const baseSchema = z.object({
|
|
|
89
89
|
})
|
|
90
90
|
.optional(),
|
|
91
91
|
specifications: z
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
.union([
|
|
93
|
+
z.object({
|
|
94
|
+
openapiPath: z.string().optional(),
|
|
95
|
+
asyncapiPath: z.string().optional(),
|
|
96
|
+
}),
|
|
97
|
+
z.array(
|
|
98
|
+
z.object({
|
|
99
|
+
type: z.enum(['openapi', 'asyncapi']),
|
|
100
|
+
path: z.string(),
|
|
101
|
+
name: z.string().optional(),
|
|
102
|
+
})
|
|
103
|
+
),
|
|
104
|
+
])
|
|
96
105
|
.optional(),
|
|
97
106
|
hidden: z.boolean().optional(),
|
|
98
107
|
resourceGroups: z
|
package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/{index.astro → [filename].astro}
RENAMED
|
@@ -5,7 +5,7 @@ import { renderToString } from 'react-dom/server';
|
|
|
5
5
|
import { Parser } from '@asyncapi/parser';
|
|
6
6
|
import { AvroSchemaParser } from '@asyncapi/avro-schema-parser';
|
|
7
7
|
import fs from 'fs';
|
|
8
|
-
|
|
8
|
+
import { getSpecificationsForService } from '@utils/collections/services';
|
|
9
9
|
import type { CollectionTypes, PageTypes } from '@types';
|
|
10
10
|
|
|
11
11
|
import '@asyncapi/react-component/styles/default.min.css';
|
|
@@ -20,27 +20,39 @@ export async function getStaticPaths() {
|
|
|
20
20
|
const itemTypes: PageTypes[] = ['events', 'commands', 'queries', 'services', 'domains'];
|
|
21
21
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
23
|
+
const hasSpecifications = (item: CollectionEntry<CollectionTypes>) => {
|
|
24
|
+
const specifications = getSpecificationsForService(item);
|
|
25
|
+
// Ensure there is at least one 'asyncapi' specification
|
|
26
|
+
return specifications && specifications.some((spec) => spec.type === 'asyncapi');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const filteredItems = allItems.map((items) => items.filter(hasSpecifications));
|
|
25
30
|
|
|
26
31
|
return filteredItems.flatMap((items, index) =>
|
|
27
|
-
items.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
items.flatMap((item) => {
|
|
33
|
+
const asyncApiSpecifications = getSpecificationsForService(item).filter((spec) => spec.type === 'asyncapi');
|
|
34
|
+
|
|
35
|
+
return asyncApiSpecifications.map((spec) => ({
|
|
36
|
+
params: {
|
|
37
|
+
type: itemTypes[index],
|
|
38
|
+
id: item.data.id,
|
|
39
|
+
version: item.data.version,
|
|
40
|
+
filename: spec.filenameWithoutExtension || spec.type,
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
type: itemTypes[index],
|
|
44
|
+
filenameWithoutExtension: spec.filenameWithoutExtension || spec.type,
|
|
45
|
+
filename: spec.filename || spec.type,
|
|
46
|
+
...item,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
})
|
|
38
50
|
);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
// @ts-ignore
|
|
42
|
-
const { collection, catalog, data, filePath } = Astro.props;
|
|
43
|
-
const fileName =
|
|
54
|
+
const { collection, catalog, data, filePath, filename } = Astro.props;
|
|
55
|
+
const fileName = filename || 'asyncapi.yaml';
|
|
44
56
|
const directory = path.dirname(filePath || '');
|
|
45
57
|
const pathToSpec = path.join(directory, fileName);
|
|
46
58
|
const fileExists = fs.existsSync(pathToSpec);
|
package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/{index.astro → [filename].astro}
RENAMED
|
@@ -9,6 +9,7 @@ import { DocumentMinusIcon } from '@heroicons/react/24/outline';
|
|
|
9
9
|
import { buildUrl } from '@utils/url-builder';
|
|
10
10
|
import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
|
|
11
11
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
12
|
+
import { getSpecificationsForService } from '@utils/collections/services';
|
|
12
13
|
import './_styles.css';
|
|
13
14
|
|
|
14
15
|
export async function getStaticPaths() {
|
|
@@ -16,27 +17,40 @@ export async function getStaticPaths() {
|
|
|
16
17
|
|
|
17
18
|
const allItems = await Promise.all(itemTypes.map((type) => pageDataLoader[type]()));
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
-
|
|
20
|
+
const hasSpecifications = (item: CollectionEntry<CollectionTypes>) => {
|
|
21
|
+
const specifications = getSpecificationsForService(item);
|
|
22
|
+
// Ensure there is at least one 'openapi' specification
|
|
23
|
+
return specifications && specifications.some((spec) => spec.type === 'openapi');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const filteredItems = allItems.map((items) => items.filter(hasSpecifications));
|
|
21
27
|
|
|
22
28
|
return filteredItems.flatMap((items, index) =>
|
|
23
|
-
items.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
items.flatMap((item) => {
|
|
30
|
+
// Filter for openapi specifications only
|
|
31
|
+
const openApiSpecifications = getSpecificationsForService(item).filter((spec) => spec.type === 'openapi');
|
|
32
|
+
|
|
33
|
+
return openApiSpecifications.map((spec) => ({
|
|
34
|
+
params: {
|
|
35
|
+
type: itemTypes[index],
|
|
36
|
+
id: item.data.id,
|
|
37
|
+
version: item.data.version,
|
|
38
|
+
filename: spec.filenameWithoutExtension || spec.type,
|
|
39
|
+
},
|
|
40
|
+
props: {
|
|
41
|
+
type: itemTypes[index],
|
|
42
|
+
filenameWithoutExtension: spec.filenameWithoutExtension || spec.type,
|
|
43
|
+
filename: spec.filename || spec.type,
|
|
44
|
+
...item,
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
})
|
|
34
48
|
);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
// @ts-ignore
|
|
38
|
-
const { collection, data, catalog, filePath } = Astro.props;
|
|
39
|
-
const fileName =
|
|
52
|
+
const { collection, data, catalog, filePath, filename } = Astro.props;
|
|
53
|
+
const fileName = filename || 'openapi.yml';
|
|
40
54
|
|
|
41
55
|
const directory = path.dirname(filePath || '');
|
|
42
56
|
const pathToSpec = path.join(directory, fileName);
|
|
@@ -3,7 +3,7 @@ import { getCollection } from 'astro:content';
|
|
|
3
3
|
import type { CollectionEntry } from 'astro:content';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import semver from 'semver';
|
|
6
|
-
|
|
6
|
+
import type { CollectionTypes } from '@types';
|
|
7
7
|
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
|
|
8
8
|
|
|
9
9
|
export type Service = CollectionEntry<'services'>;
|
|
@@ -129,3 +129,31 @@ export const getConsumersOfMessage = (services: Service[], message: CollectionEn
|
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
};
|
|
132
|
+
|
|
133
|
+
export const getSpecificationsForService = (service: CollectionEntry<CollectionTypes>) => {
|
|
134
|
+
const specifications = Array.isArray(service.data.specifications) ? service.data.specifications : [];
|
|
135
|
+
|
|
136
|
+
if (service.data.specifications && !Array.isArray(service.data.specifications)) {
|
|
137
|
+
if (service.data.specifications.asyncapiPath) {
|
|
138
|
+
specifications.push({
|
|
139
|
+
type: 'asyncapi',
|
|
140
|
+
path: service.data.specifications.asyncapiPath,
|
|
141
|
+
name: 'AsyncAPI',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (service.data.specifications.openapiPath) {
|
|
145
|
+
specifications.push({
|
|
146
|
+
type: 'openapi',
|
|
147
|
+
path: service.data.specifications.openapiPath,
|
|
148
|
+
name: 'OpenAPI',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return specifications.map((spec) => ({
|
|
154
|
+
...spec,
|
|
155
|
+
name: spec.name || (spec.type === 'asyncapi' ? 'AsyncAPI' : 'OpenAPI'),
|
|
156
|
+
filename: path.basename(spec.path),
|
|
157
|
+
filenameWithoutExtension: path.basename(spec.path, path.extname(spec.path)),
|
|
158
|
+
}));
|
|
159
|
+
};
|