@eventcatalog/core 2.0.12 → 2.0.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @eventcatalog/core
2
2
 
3
+ ## 2.0.13
4
+
5
+ ### Patch Changes
6
+
7
+ - 1e4c805: feat(core): Adding <SchemaViewer /> component
8
+
3
9
  ## 2.0.12
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eventcatalog/core",
3
3
  "type": "module",
4
- "version": "2.0.12",
4
+ "version": "2.0.13",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -21,7 +21,7 @@
21
21
  "astro": "astro",
22
22
  "scripts:hydrate-content": "node scripts/catalog-to-astro-content-directory.js",
23
23
  "start:catalog": "node scripts/start-catalog-locally.js",
24
- "verify-build:catalog": "rimraf packages/eventcatalog/dist && npm run build:cd",
24
+ "verify-build:catalog": "rimraf dist && npm run build:cd",
25
25
  "changeset": "changeset",
26
26
  "release": "changeset publish",
27
27
  "format": "prettier --config .prettierrc --write \"**/*.{js,jsx,ts,tsx,json}\"",
@@ -36,6 +36,8 @@
36
36
  "@changesets/cli": "^2.27.5",
37
37
  "@headlessui/react": "^2.0.3",
38
38
  "@heroicons/react": "^2.1.3",
39
+ "@stoplight/json-schema-viewer": "^4.7.0",
40
+ "@stoplight/mosaic": "^1.53.2",
39
41
  "@tailwindcss/typography": "^0.5.13",
40
42
  "@tanstack/react-table": "^8.17.3",
41
43
  "@types/dagre": "^0.7.52",
@@ -25,8 +25,8 @@ export default function Admonition({ children, type = 'info', className = '' }:
25
25
  <div className={`bg-${color}-50 border-l-4 border-${color}-400 my-4 ${className}`}>
26
26
  <div className="flex">
27
27
  <div className="ml-3 py-2 text-sm flex items-center">
28
- <Icon className={`inline-block mr-2 h-5 w-5 text-${color}-400`} aria-hidden="true" />
29
- {children}
28
+ <Icon className={`block mr-2 h-7 w-7 text-${color}-400`} aria-hidden="true" />
29
+ <div>{children}</div>
30
30
  </div>
31
31
  </div>
32
32
  </div>
@@ -0,0 +1,94 @@
1
+ ---
2
+ const { catalog, id } = Astro.props;
3
+ import fs from 'node:fs/promises';
4
+ import { existsSync } from 'fs';
5
+ import path from 'path';
6
+ import SchemaViewerClient from './SchemaViewer'
7
+ import Admonition from '../Admonition';
8
+ let schemas = [];
9
+
10
+ function findSchemaViewers(document: string) {
11
+ // Define regex pattern to match <SchemaViewer ... />
12
+ const pattern = /<SchemaViewer\s+([^>]*)\/>/g;
13
+
14
+ // Find all matches of the pattern
15
+ const matches = [...document.matchAll(pattern)];
16
+
17
+ // Extract the properties of each SchemaViewer
18
+ const schemaViewers = matches.map(match => {
19
+ const propsString = match[1];
20
+ const props = {};
21
+
22
+ // Use regex to extract key-value pairs from propsString
23
+ const propsPattern = /(\w+)=["']([^"']+)["']/g;
24
+ let propMatch;
25
+ while ((propMatch = propsPattern.exec(propsString)) !== null) {
26
+ const key = propMatch[1];
27
+ const value = propMatch[2];
28
+ // @ts-ignore
29
+ props[key] = value;
30
+ }
31
+
32
+ return props;
33
+ });
34
+
35
+ return schemaViewers;
36
+ }
37
+
38
+ try {
39
+ const file = await fs.readFile(catalog.absoluteFilePath, 'utf-8');
40
+ const schemaViewers = findSchemaViewers(file);
41
+
42
+ // Loop around all the possible SchemaViewers in the file.
43
+ const getAllComponents = schemaViewers.map(async (schemaViewerProps: any) => {
44
+
45
+ const schemaPath = path.join(catalog.filePath, schemaViewerProps.file)
46
+ const exists = existsSync(schemaPath);
47
+ let schema;
48
+
49
+ if(exists) {
50
+ // Load the schema for the component
51
+ schema = await fs.readFile(schemaPath, 'utf-8');
52
+ schema = JSON.parse(schema);
53
+ }
54
+
55
+ return {
56
+ id: schemaViewerProps.id || id,
57
+ exists,
58
+ schema,
59
+ schemaPath,
60
+ ...schemaViewerProps
61
+ }
62
+ });
63
+
64
+ schemas = await Promise.all(getAllComponents);
65
+
66
+ } catch (error) {
67
+ console.log('Failed to process schemas')
68
+ console.log(error);
69
+ }
70
+
71
+ ---
72
+ <section class="space-y-4">
73
+ {schemas.length > 0 &&
74
+ schemas.map((schema) => (
75
+ <div>
76
+
77
+ {schema.exists &&
78
+ <SchemaViewerClient {...schema} client:load />
79
+ }
80
+
81
+ {/* User has tried to load the schema, but it was not found on file system */}
82
+ {!schema.exists && (
83
+ <Admonition type="warning">
84
+ <>
85
+ <span class="block font-bold">{`<SchemaViewer/>`} failed to load</span>
86
+ <span class="block">Tried to load schema from {schema.schemaPath}, but no schema can be found</span>
87
+ </>
88
+ </Admonition>
89
+ )}
90
+
91
+ </div>
92
+ ))
93
+ }
94
+ </section>
@@ -0,0 +1,9 @@
1
+ /* Custom styling for the schema viewer */
2
+ .schemaViewer :global(.svg-inline--fa) {
3
+ width: 1.25em;
4
+ height: 1em;
5
+ }
6
+
7
+ .schemaViewer p, span {
8
+ color: rgb(111, 111, 111) !important;
9
+ }
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ // @ts-ignore
4
+ import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
5
+ import styles from './SchemaViewer.module.css';
6
+
7
+ import '@stoplight/mosaic/styles.css';
8
+ import { createPortal } from 'react-dom';
9
+
10
+ type Props = {
11
+ id: string;
12
+ file: string;
13
+ renderRootTreeLines?: boolean;
14
+ hideExamples?: boolean;
15
+ defaultExpandedDepth?: number;
16
+ maxHeight?: string;
17
+ schema: any;
18
+ catalog: any;
19
+ title?: string;
20
+ };
21
+
22
+ const SchemaViewer = ({
23
+ id,
24
+ maxHeight = '500',
25
+ renderRootTreeLines = true,
26
+ hideExamples = false,
27
+ defaultExpandedDepth = 3,
28
+ title,
29
+ schema,
30
+ }: Props) => {
31
+ const [elem, setElem] = useState(null);
32
+ useEffect(() => {
33
+ // @ts-ignore
34
+ setElem(document.getElementById(`${id}-SchemaViewer-portal`));
35
+ }, []);
36
+
37
+ if (!elem) return null;
38
+
39
+ return (
40
+ <div>
41
+ {createPortal(
42
+ <section className="not-prose space-y-2 ">
43
+ {title && <h2 className="text-3xl font-bold">{title}</h2>}
44
+ <div className="border border-gray-100 p-2 schemaViewer">
45
+ <JsonSchemaViewer
46
+ schema={schema}
47
+ emptyText="No schema defined"
48
+ maxHeight={parseInt(maxHeight, 10)}
49
+ defaultExpandedDepth={defaultExpandedDepth}
50
+ renderRootTreeLines={renderRootTreeLines}
51
+ hideExamples={hideExamples}
52
+ className={styles.schemaViewer}
53
+ />
54
+ </div>
55
+ </section>,
56
+ elem
57
+ )}
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export default SchemaViewer;
@@ -0,0 +1,5 @@
1
+ const SchemaViewerPortal = (props: any) => {
2
+ return <div id={`${props.id}-SchemaViewer-portal`} />;
3
+ };
4
+
5
+ export default SchemaViewerPortal;
@@ -1,23 +1,25 @@
1
- // Astro components
2
-
3
1
  // React components
4
2
  import Schema from '@components/MDX/Schema';
5
3
  import File from '@components/MDX/File';
6
4
  import Accordion from '@components/MDX/Accordion/Accordion.astro';
7
5
  import AccordionGroup from '@components/MDX/Accordion/AccordionGroup.astro';
8
6
  import Admonition from '@components/MDX/Admonition';
9
- import NodeGraphPortal from '@components/MDX/NodeGraph/NodeGraphPortal';
10
7
  import OpenAPI from '@components/MDX/OpenAPI/OpenAPI';
11
8
 
9
+ // Portals: required for server/client components
10
+ import NodeGraphPortal from '@components/MDX/NodeGraph/NodeGraphPortal';
11
+ import SchemaViewerPortal from '@components/MDX/SchemaViewer/SchemaViewerPortal';
12
+
12
13
  const components = (props: any) => {
13
14
  return {
14
- AccordionGroup,
15
15
  Accordion,
16
- OpenAPI: (mdxProp: any) => <OpenAPI {...mdxProp} {...props} />,
16
+ AccordionGroup,
17
17
  Admonition: (mdxProp: any) => <Admonition {...mdxProp} {...props} />,
18
18
  File: (mdxProp: any) => File({ ...props, ...mdxProp }),
19
- Schema: (mdxProp: any) => Schema({ ...props, ...mdxProp }),
20
19
  NodeGraph: (mdxProp: any) => NodeGraphPortal({ ...props.data, ...mdxProp }),
20
+ SchemaViewer: (mdxProp: any) => SchemaViewerPortal({ ...props.data, ...mdxProp }),
21
+ OpenAPI: (mdxProp: any) => <OpenAPI {...mdxProp} {...props} />,
22
+ Schema: (mdxProp: any) => Schema({ ...props, ...mdxProp }),
21
23
  };
22
24
  };
23
25
 
@@ -16,6 +16,7 @@ import { EnvelopeIcon, PencilIcon, RectangleGroupIcon, ServerIcon } from '@heroi
16
16
  import { getDomains } from '@utils/domains/domains';
17
17
  import DomainSideBar from '@components/SideBars/DomainSideBar.astro';
18
18
  import type { CollectionTypes } from '@types';
19
+ import SchemaViewer from '@components/MDX/SchemaViewer/SchemaViewer.astro';
19
20
 
20
21
  export async function getStaticPaths() {
21
22
  const events = await getEvents();
@@ -67,6 +68,7 @@ const badges = [
67
68
  getBadge(),
68
69
  ...contentBadges
69
70
  ];
71
+
70
72
  ---
71
73
 
72
74
  <DocsLayout title={pageTitle} description={props.data.summary}>
@@ -90,13 +92,6 @@ const badges = [
90
92
  )
91
93
  }
92
94
  </div>
93
-
94
- <!-- <div class="hidden md:block opacity-50 md:pr-10">
95
- {props.collection === 'services' && <ServerIcon className="w-20 h-20 text-pink-500" />}
96
- {props.collection === 'events' && <EnvelopeIcon className="w-20 h-20 text-orange-500" />}
97
- {props.collection === 'commands' && <EnvelopeIcon className="w-20 h-20 text-blue-500" />}
98
- {props.collection === 'domains' && <RectangleGroupIcon className="w-20 h-20 text-yellow-500" />}
99
- </div> -->
100
95
  </div>
101
96
 
102
97
  <div>
@@ -131,9 +126,11 @@ const badges = [
131
126
  </div>
132
127
 
133
128
  <div class="prose prose-md py-4 w-full">
134
- <Content components={components(props)} />
129
+ <Content components={components(props)} />
135
130
  </div>
136
131
  <div>
132
+ <!-- @ts-ignore -->
133
+ <SchemaViewer id={props.data.id} catalog={props.catalog} />
137
134
  <NodeGraph id={props.data.id} collection={props.collection} version={props.data.version} mode="simple" href={{ label: 'Open in Visualiser', url: `/visualiser/${props.collection}/${props.data.id}/${props.data.version}`}} />
138
135
  </div>
139
136
  <footer class="py-4 space-y-8 divide-y divide-gray-200/60">
@@ -177,7 +174,16 @@ const badges = [
177
174
 
178
175
  </style>
179
176
 
177
+
178
+ <script define:vars={{ props }}>
179
+ // Fix to pass information to componets that are client side only
180
+ // and require catalog information
181
+ window.eventcatalog = {};
182
+ window.eventcatalog[`${props.collection}-${props.data.id}`] = props.catalog;
183
+ </script>
184
+
180
185
  <script>
186
+
181
187
  /**
182
188
  * @params {HTMLCollectionOf<HTMLElement>} graphs
183
189
  */
@@ -1,7 +1,9 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
3
  import path from 'path';
4
- import { getVersionForCollectionItem, getVersions } from './collections/util';
4
+ import { getVersionForCollectionItem } from './collections/util';
5
+
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
5
7
 
6
8
  type Command = CollectionEntry<'commands'> & {
7
9
  catalog: {
@@ -51,6 +53,7 @@ export const getCommands = async ({ getAllVersions = true }: Props = {}): Promis
51
53
  },
52
54
  catalog: {
53
55
  path: path.join(command.collection, command.id.replace('/index.mdx', '')),
56
+ absoluteFilePath: path.join(PROJECT_DIR, command.collection, command.id.replace('/index.mdx', '/index.md')),
54
57
  filePath: path.join(process.cwd(), 'src', 'catalog-files', command.collection, command.id.replace('/index.mdx', '')),
55
58
  publicPath: path.join('/generated', command.collection, command.id.replace('/index.mdx', '')),
56
59
  type: 'command',
@@ -3,6 +3,8 @@ import { getCollection } from 'astro:content';
3
3
  import type { CollectionEntry } from 'astro:content';
4
4
  import path from 'path';
5
5
 
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
7
+
6
8
  export type Domain = CollectionEntry<'domains'>;
7
9
 
8
10
  interface Props {
@@ -41,6 +43,7 @@ export const getDomains = async ({ getAllVersions = true }: Props = {}): Promise
41
43
  },
42
44
  catalog: {
43
45
  path: path.join(domain.collection, domain.id.replace('/index.mdx', '')),
46
+ absoluteFilePath: path.join(PROJECT_DIR, domain.collection, domain.id.replace('/index.mdx', '/index.md')),
44
47
  filePath: path.join(process.cwd(), 'src', 'catalog-files', domain.collection, domain.id.replace('/index.mdx', '')),
45
48
  publicPath: path.join('/generated', domain.collection, domain.id.replace('/index.mdx', '')),
46
49
  type: 'service',
@@ -1,7 +1,9 @@
1
1
  import { getCollection } from 'astro:content';
2
2
  import type { CollectionEntry } from 'astro:content';
3
3
  import path from 'path';
4
- import { getVersionForCollectionItem, getVersions } from './collections/util';
4
+ import { getVersionForCollectionItem } from './collections/util';
5
+
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
5
7
 
6
8
  type Event = CollectionEntry<'events'> & {
7
9
  catalog: {
@@ -53,6 +55,7 @@ export const getEvents = async ({ getAllVersions = true }: Props = {}): Promise<
53
55
  },
54
56
  catalog: {
55
57
  path: path.join(event.collection, event.id.replace('/index.mdx', '')),
58
+ absoluteFilePath: path.join(PROJECT_DIR, event.collection, event.id.replace('/index.mdx', '/index.md')),
56
59
  filePath: path.join(process.cwd(), 'src', 'catalog-files', event.collection, event.id.replace('/index.mdx', '')),
57
60
  publicPath: path.join('/generated', event.collection, event.id.replace('/index.mdx', '')),
58
61
  type: 'event',
@@ -2,10 +2,8 @@ import { getVersionForCollectionItem, getVersions } from '@utils/collections/uti
2
2
  import { getCollection } from 'astro:content';
3
3
  import type { CollectionEntry } from 'astro:content';
4
4
  import path from 'path';
5
- // import { getEvents } from './events';
6
- // import { getCommands } from './commands';
7
5
 
8
- // import type { Service as ServiceInterface } from '../types/index'
6
+ const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
9
7
 
10
8
  export type Service = CollectionEntry<'services'>;
11
9
 
@@ -79,6 +77,7 @@ export const getServices = async ({ getAllVersions = true }: Props = {}): Promis
79
77
  },
80
78
  catalog: {
81
79
  path: path.join(service.collection, service.id.replace('/index.mdx', '')),
80
+ absoluteFilePath: path.join(PROJECT_DIR, service.collection, service.id.replace('/index.mdx', '/index.md')),
82
81
  filePath: path.join(process.cwd(), 'src', 'catalog-files', service.collection, service.id.replace('/index.mdx', '')),
83
82
  publicPath: path.join('/generated', service.collection, service.id.replace('/index.mdx', '')),
84
83
  type: 'service',