@eventcatalog/core 3.7.0 → 3.7.2

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.
@@ -37,7 +37,7 @@ var import_axios = __toESM(require("axios"), 1);
37
37
  var import_os = __toESM(require("os"), 1);
38
38
 
39
39
  // package.json
40
- var version = "3.7.0";
40
+ var version = "3.7.2";
41
41
 
42
42
  // src/constants.ts
43
43
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "../chunk-6DINZHVK.js";
4
- import "../chunk-WFIM5UXN.js";
3
+ } from "../chunk-GQZVIS3Z.js";
4
+ import "../chunk-WAX3S32H.js";
5
5
  export {
6
6
  raiseEvent
7
7
  };
@@ -106,7 +106,7 @@ var import_axios = __toESM(require("axios"), 1);
106
106
  var import_os = __toESM(require("os"), 1);
107
107
 
108
108
  // package.json
109
- var version = "3.7.0";
109
+ var version = "3.7.2";
110
110
 
111
111
  // src/constants.ts
112
112
  var VERSION = version;
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  log_build_default
3
- } from "../chunk-2CIYDOFG.js";
4
- import "../chunk-6DINZHVK.js";
5
- import "../chunk-WFIM5UXN.js";
3
+ } from "../chunk-M7EPRGHR.js";
4
+ import "../chunk-GQZVIS3Z.js";
5
+ import "../chunk-WAX3S32H.js";
6
6
  import "../chunk-UPONRQSN.js";
7
7
  export {
8
8
  log_build_default as default
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WFIM5UXN.js";
3
+ } from "./chunk-WAX3S32H.js";
4
4
 
5
5
  // src/utils/cli-logger.ts
6
6
  import pc from "picocolors";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WFIM5UXN.js";
3
+ } from "./chunk-WAX3S32H.js";
4
4
 
5
5
  // src/analytics/analytics.js
6
6
  import axios from "axios";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  raiseEvent
3
- } from "./chunk-6DINZHVK.js";
3
+ } from "./chunk-GQZVIS3Z.js";
4
4
  import {
5
5
  getEventCatalogConfigFile,
6
6
  verifyRequiredFieldsAreInCatalogConfigFile
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-TWL75YK3.js";
3
+ } from "./chunk-7CTNGTBB.js";
4
4
  import {
5
5
  cleanup,
6
6
  getEventCatalogConfigFile
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "3.7.0";
2
+ var version = "3.7.2";
3
3
 
4
4
  // src/constants.ts
5
5
  var VERSION = version;
@@ -25,7 +25,7 @@ __export(constants_exports, {
25
25
  module.exports = __toCommonJS(constants_exports);
26
26
 
27
27
  // package.json
28
- var version = "3.7.0";
28
+ var version = "3.7.2";
29
29
 
30
30
  // src/constants.ts
31
31
  var VERSION = version;
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-WFIM5UXN.js";
3
+ } from "./chunk-WAX3S32H.js";
4
4
  export {
5
5
  VERSION
6
6
  };
@@ -109,7 +109,7 @@ var verifyRequiredFieldsAreInCatalogConfigFile = async (projectDirectory) => {
109
109
  var import_picocolors = __toESM(require("picocolors"), 1);
110
110
 
111
111
  // package.json
112
- var version = "3.7.0";
112
+ var version = "3.7.2";
113
113
 
114
114
  // src/constants.ts
115
115
  var VERSION = version;
@@ -6,8 +6,8 @@ import {
6
6
  } from "./chunk-PLNJC7NZ.js";
7
7
  import {
8
8
  log_build_default
9
- } from "./chunk-2CIYDOFG.js";
10
- import "./chunk-6DINZHVK.js";
9
+ } from "./chunk-M7EPRGHR.js";
10
+ import "./chunk-GQZVIS3Z.js";
11
11
  import {
12
12
  runMigrations
13
13
  } from "./chunk-BH3JMNAV.js";
@@ -21,13 +21,13 @@ import {
21
21
  } from "./chunk-5VBIXL6C.js";
22
22
  import {
23
23
  generate
24
- } from "./chunk-VA6OWAKW.js";
24
+ } from "./chunk-O6SRHGZ7.js";
25
25
  import {
26
26
  logger
27
- } from "./chunk-TWL75YK3.js";
27
+ } from "./chunk-7CTNGTBB.js";
28
28
  import {
29
29
  VERSION
30
- } from "./chunk-WFIM5UXN.js";
30
+ } from "./chunk-WAX3S32H.js";
31
31
  import "./chunk-UPONRQSN.js";
32
32
 
33
33
  // src/eventcatalog.ts
package/dist/generate.cjs CHANGED
@@ -73,7 +73,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
73
73
  var import_picocolors = __toESM(require("picocolors"), 1);
74
74
 
75
75
  // package.json
76
- var version = "3.7.0";
76
+ var version = "3.7.2";
77
77
 
78
78
  // src/constants.ts
79
79
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-VA6OWAKW.js";
4
- import "./chunk-TWL75YK3.js";
5
- import "./chunk-WFIM5UXN.js";
3
+ } from "./chunk-O6SRHGZ7.js";
4
+ import "./chunk-7CTNGTBB.js";
5
+ import "./chunk-WAX3S32H.js";
6
6
  import "./chunk-UPONRQSN.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "3.7.0";
39
+ var version = "3.7.2";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-TWL75YK3.js";
4
- import "../chunk-WFIM5UXN.js";
3
+ } from "../chunk-7CTNGTBB.js";
4
+ import "../chunk-WAX3S32H.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -157,32 +157,32 @@ function processSchema(schema: any, rootSchema?: any): any {
157
157
  return mergeAllOfSchemas({ ...schema, processSchema: (s: any) => processSchema(s, root) });
158
158
  }
159
159
 
160
- if (schema.oneOf) {
161
- const processedVariants = schema.oneOf.map((variant: any) => {
160
+ if (schema.oneOf || schema.anyOf) {
161
+ const variantsArray = schema.oneOf || schema.anyOf;
162
+ const variantType = schema.oneOf ? 'oneOf' : 'anyOf';
163
+
164
+ const processedVariants = variantsArray.map((variant: any, index: number) => {
162
165
  const processedVariant = processSchema(variant, root);
163
166
  return {
164
- title: processedVariant.title || variant.title || 'Unnamed Variant',
167
+ title: processedVariant.title || variant.title || processedVariant.$id || `Option ${index + 1}`,
168
+ description: processedVariant.description || variant.description,
165
169
  required: processedVariant.required || variant.required || [],
166
170
  properties: processedVariant.properties || {},
171
+ type: processedVariant.type || variant.type || 'object',
167
172
  ...processedVariant,
168
173
  };
169
174
  });
170
175
 
171
- const allProperties: Record<string, any> = {};
172
- processedVariants.forEach((variant: any) => {
173
- if (variant.properties) {
174
- Object.assign(allProperties, variant.properties);
175
- }
176
- });
177
-
178
176
  return {
179
177
  ...schema,
180
178
  type: schema.type || 'object',
181
- properties: {
182
- ...(schema.properties || {}),
183
- ...allProperties,
184
- },
179
+ properties: schema.properties
180
+ ? Object.fromEntries(
181
+ Object.entries(schema.properties).map(([key, prop]: [string, any]) => [key, processSchema(prop, root)])
182
+ )
183
+ : {},
185
184
  variants: processedVariants,
185
+ variantType,
186
186
  };
187
187
  }
188
188
 
@@ -203,9 +203,66 @@ function processSchema(schema: any, rootSchema?: any): any {
203
203
  return schema;
204
204
  }
205
205
 
206
+ // NestedVariantSelector component for handling variants within array items
207
+ const NestedVariantSelector = ({
208
+ variants,
209
+ variantType,
210
+ level,
211
+ expand,
212
+ }: {
213
+ variants: any[];
214
+ variantType?: string;
215
+ level: number;
216
+ expand: boolean;
217
+ }) => {
218
+ const [selectedIndex, setSelectedIndex] = useState(0);
219
+ const selectedVariant = variants[selectedIndex];
220
+
221
+ return (
222
+ <div className="mt-2">
223
+ <div className="p-2 bg-[rgb(var(--ec-accent-subtle,var(--ec-content-hover)))] border border-[rgb(var(--ec-page-border))] rounded-md">
224
+ <div className="flex flex-col sm:flex-row sm:items-center gap-2">
225
+ <span className="text-xs font-medium text-[rgb(var(--ec-page-text))]">
226
+ {variantType === 'anyOf' ? 'Any of' : 'One of'} {variants.length} options:
227
+ </span>
228
+ <select
229
+ value={selectedIndex}
230
+ onChange={(e) => setSelectedIndex(parseInt(e.target.value))}
231
+ className="form-select text-xs border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] rounded-md shadow-sm focus:border-[rgb(var(--ec-accent))] focus:ring focus:ring-[rgb(var(--ec-accent)/0.2)] py-1"
232
+ >
233
+ {variants.map((variant: any, index: number) => (
234
+ <option key={index} value={index}>
235
+ {variant.title}
236
+ </option>
237
+ ))}
238
+ </select>
239
+ </div>
240
+ {selectedVariant?.description && (
241
+ <p className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">{selectedVariant.description}</p>
242
+ )}
243
+ </div>
244
+ {selectedVariant?.properties && (
245
+ <div className="mt-2">
246
+ {Object.entries(selectedVariant.properties).map(([propName, propDetails]: [string, any]) => (
247
+ <SchemaProperty
248
+ key={`${selectedIndex}-${propName}`}
249
+ name={propName}
250
+ details={propDetails}
251
+ isRequired={selectedVariant.required?.includes(propName) ?? false}
252
+ level={level}
253
+ expand={expand}
254
+ />
255
+ ))}
256
+ </div>
257
+ )}
258
+ </div>
259
+ );
260
+ };
261
+
206
262
  // SchemaProperty component
207
263
  const SchemaProperty = ({ name, details, isRequired, level, isListItem = false, expand }: SchemaPropertyProps) => {
208
264
  const [isExpanded, setIsExpanded] = useState(expand);
265
+ const [selectedVariantIndex, setSelectedVariantIndex] = useState(0);
209
266
  const contentId = useRef(`prop-${name}-${level}-${Math.random().toString(36).substring(2, 7)}`).current;
210
267
 
211
268
  useEffect(() => {
@@ -219,8 +276,11 @@ const SchemaProperty = ({ name, details, isRequired, level, isListItem = false,
219
276
  ((details.items.type === 'object' && details.items.properties) ||
220
277
  details.items.allOf ||
221
278
  details.items.oneOf ||
279
+ details.items.anyOf ||
280
+ details.items.variants ||
222
281
  details.items.$ref);
223
- const isCollapsible = hasNestedProperties || hasArrayItemProperties;
282
+ const hasVariants = details.variants && details.variants.length > 0;
283
+ const isCollapsible = hasNestedProperties || hasArrayItemProperties || hasVariants;
224
284
 
225
285
  const indentationClass = `pl-${level * 3}`;
226
286
 
@@ -247,7 +307,7 @@ const SchemaProperty = ({ name, details, isRequired, level, isListItem = false,
247
307
  <div>
248
308
  <span className="font-semibold text-[rgb(var(--ec-page-text))] text-sm">{name}</span>
249
309
  <span className="ml-1.5 text-[rgb(var(--ec-accent))] font-mono text-xs">
250
- {details.type}
310
+ {hasVariants ? (details.variantType === 'anyOf' ? 'anyOf' : 'oneOf') : details.type}
251
311
  {details.type === 'array' && details.items?.type ? `[${details.items.type}]` : ''}
252
312
  {details.format ? `<${details.format}>` : ''}
253
313
  {details._refPath && (
@@ -325,9 +385,53 @@ const SchemaProperty = ({ name, details, isRequired, level, isListItem = false,
325
385
  )}
326
386
  </div>
327
387
 
328
- {(hasNestedProperties || hasArrayItems) && (
388
+ {(hasNestedProperties || hasArrayItems || hasVariants) && (
329
389
  <div id={contentId} className={`nested-content mt-1 ${isCollapsible && !isExpanded ? 'hidden' : ''}`}>
390
+ {hasVariants && (
391
+ <div className="mt-2 mb-2 p-2 bg-[rgb(var(--ec-accent-subtle,var(--ec-content-hover)))] border border-[rgb(var(--ec-page-border))] rounded-md">
392
+ <div className="flex flex-col sm:flex-row sm:items-center gap-2">
393
+ <span className="text-xs font-medium text-[rgb(var(--ec-page-text))]">
394
+ {details.variantType === 'anyOf' ? 'Any of' : 'One of'} {details.variants.length} options:
395
+ </span>
396
+ <select
397
+ value={selectedVariantIndex}
398
+ onChange={(e) => setSelectedVariantIndex(parseInt(e.target.value))}
399
+ className="form-select text-xs border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] rounded-md shadow-sm focus:border-[rgb(var(--ec-accent))] focus:ring focus:ring-[rgb(var(--ec-accent)/0.2)] py-1"
400
+ >
401
+ {details.variants.map((variant: any, index: number) => (
402
+ <option key={index} value={index}>
403
+ {variant.title}
404
+ </option>
405
+ ))}
406
+ </select>
407
+ </div>
408
+ {details.variants[selectedVariantIndex]?.description && (
409
+ <p className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">
410
+ {details.variants[selectedVariantIndex].description}
411
+ </p>
412
+ )}
413
+ </div>
414
+ )}
415
+
416
+ {hasVariants && details.variants[selectedVariantIndex]?.properties && (
417
+ <div className="mt-1">
418
+ {Object.entries(details.variants[selectedVariantIndex].properties).map(
419
+ ([nestedName, nestedDetails]: [string, any]) => (
420
+ <SchemaProperty
421
+ key={`${selectedVariantIndex}-${nestedName}`}
422
+ name={nestedName}
423
+ details={nestedDetails}
424
+ isRequired={details.variants[selectedVariantIndex]?.required?.includes(nestedName) ?? false}
425
+ level={level + 1}
426
+ expand={expand}
427
+ />
428
+ )
429
+ )}
430
+ </div>
431
+ )}
432
+
330
433
  {hasNestedProperties &&
434
+ !hasVariants &&
331
435
  details.properties &&
332
436
  Object.entries(details.properties).map(([nestedName, nestedDetails]: [string, any]) => (
333
437
  <SchemaProperty
@@ -355,11 +459,21 @@ const SchemaProperty = ({ name, details, isRequired, level, isListItem = false,
355
459
  expand={expand}
356
460
  />
357
461
  ))}
358
- {(details.items.allOf || details.items.oneOf || details.items.$ref) && !details.items.properties && (
359
- <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">
360
- Complex array item schema detected. The properties should be processed by the parent SchemaViewer.
361
- </div>
462
+ {details.items.variants && details.items.variants.length > 0 && (
463
+ <NestedVariantSelector
464
+ variants={details.items.variants}
465
+ variantType={details.items.variantType}
466
+ level={level + 1}
467
+ expand={expand}
468
+ />
362
469
  )}
470
+ {(details.items.allOf || details.items.oneOf || details.items.anyOf || details.items.$ref) &&
471
+ !details.items.properties &&
472
+ !details.items.variants && (
473
+ <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">
474
+ Complex array item schema detected. The properties should be processed by the parent SchemaViewer.
475
+ </div>
476
+ )}
363
477
  </div>
364
478
  )}
365
479
  </div>
@@ -421,7 +535,7 @@ export default function JSONSchemaViewer({
421
535
  return { displaySchema: display, isRootArray: isArray };
422
536
  }, [processedSchema]);
423
537
 
424
- const { description, properties, required = [], variants } = displaySchema;
538
+ const { description, properties, required = [], variants, variantType } = displaySchema;
425
539
  const totalProperties = useMemo(() => countProperties(displaySchema), [displaySchema]);
426
540
 
427
541
  // Search functionality
@@ -667,14 +781,16 @@ export default function JSONSchemaViewer({
667
781
  )}
668
782
  {description && <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mb-5">{description}</p>}
669
783
 
670
- {variants && (
671
- <div className="mb-4">
672
- <div className="flex items-center space-x-2">
673
- <span className="text-sm text-[rgb(var(--ec-page-text-muted))]">(one of)</span>
784
+ {variants && variants.length > 0 && (
785
+ <div className="mb-4 p-3 bg-[rgb(var(--ec-accent-subtle,var(--ec-content-hover)))] border border-[rgb(var(--ec-page-border))] rounded-md">
786
+ <div className="flex flex-col sm:flex-row sm:items-center gap-2">
787
+ <span className="text-sm font-medium text-[rgb(var(--ec-page-text))]">
788
+ {variantType === 'anyOf' ? 'Any of' : 'One of'} {variants.length} options:
789
+ </span>
674
790
  <select
675
791
  value={selectedVariantIndex}
676
792
  onChange={(e) => setSelectedVariantIndex(parseInt(e.target.value))}
677
- className="form-select text-sm border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] rounded-md shadow-sm focus:border-[rgb(var(--ec-accent))] focus:ring focus:ring-[rgb(var(--ec-accent)/0.2)]"
793
+ className="form-select text-sm border-[rgb(var(--ec-input-border))] bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] rounded-md shadow-sm focus:border-[rgb(var(--ec-accent))] focus:ring focus:ring-[rgb(var(--ec-accent)/0.2)] flex-1 sm:flex-initial"
678
794
  >
679
795
  {variants.map((variant: any, index: number) => (
680
796
  <option key={index} value={index}>
@@ -683,27 +799,41 @@ export default function JSONSchemaViewer({
683
799
  ))}
684
800
  </select>
685
801
  </div>
802
+ {variants[selectedVariantIndex]?.description && (
803
+ <p className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-2">{variants[selectedVariantIndex].description}</p>
804
+ )}
686
805
  </div>
687
806
  )}
688
807
 
689
- {properties ? (
690
- <div ref={propertiesContainerRef}>
691
- {Object.entries(properties).map(([name, details]: [string, any]) => (
692
- <SchemaProperty
693
- key={name}
694
- name={name}
695
- details={details}
696
- isRequired={
697
- variants ? variants[selectedVariantIndex]?.required?.includes(name) || false : required.includes(name)
698
- }
699
- level={0}
700
- expand={expandAll}
701
- />
702
- ))}
703
- </div>
704
- ) : !isRootArray ? (
705
- <p className="text-[rgb(var(--ec-page-text-muted))] text-sm">Schema does not contain any properties.</p>
706
- ) : (
808
+ {(() => {
809
+ // Determine which properties to display
810
+ const propsToDisplay =
811
+ variants && variants.length > 0 && variants[selectedVariantIndex]?.properties
812
+ ? { ...properties, ...variants[selectedVariantIndex].properties }
813
+ : properties;
814
+ const requiredProps = variants && variants.length > 0 ? variants[selectedVariantIndex]?.required || [] : required;
815
+
816
+ if (propsToDisplay && Object.keys(propsToDisplay).length > 0) {
817
+ return (
818
+ <div ref={propertiesContainerRef}>
819
+ {Object.entries(propsToDisplay).map(([name, details]: [string, any]) => (
820
+ <SchemaProperty
821
+ key={`${selectedVariantIndex}-${name}`}
822
+ name={name}
823
+ details={details}
824
+ isRequired={requiredProps.includes(name)}
825
+ level={0}
826
+ expand={expandAll}
827
+ />
828
+ ))}
829
+ </div>
830
+ );
831
+ } else if (!isRootArray) {
832
+ return <p className="text-[rgb(var(--ec-page-text-muted))] text-sm">Schema does not contain any properties.</p>;
833
+ }
834
+ return null;
835
+ })()}
836
+ {!properties && !variants && isRootArray && (
707
837
  <div className="text-center py-8">
708
838
  <div className="text-[rgb(var(--ec-page-text-muted))] text-sm">
709
839
  <p>
@@ -4,6 +4,7 @@ import { getEvents } from '@utils/collections/events';
4
4
  import { getCommands } from '@utils/collections/commands';
5
5
  import { getQueries } from '@utils/collections/queries';
6
6
  import { getServices, getSpecificationsForService } from '@utils/collections/services';
7
+ import { getDomains, getSpecificationsForDomain } from '@utils/collections/domains';
7
8
  import { getOwner } from '@utils/collections/owners';
8
9
  import { buildUrl } from '@utils/url-builder';
9
10
  import { resourceFileExists, readResourceFile } from '@utils/resource-files';
@@ -133,7 +134,58 @@ async function fetchAllSchemas() {
133
134
  // Flatten and filter out null values
134
135
  const flatServicesWithSpecs = servicesWithSpecs.flat().filter((service) => service !== null);
135
136
 
136
- return [...messagesWithSchemas, ...flatServicesWithSpecs];
137
+ // Fetch all domains
138
+ const domains = await getDomains({ getAllVersions: true });
139
+
140
+ // Filter domains with specifications and read spec content - only keep essential data
141
+ const domainsWithSpecs = await Promise.all(
142
+ domains.map(async (domain) => {
143
+ try {
144
+ const specifications = getSpecificationsForDomain(domain);
145
+
146
+ if (specifications.length === 0) {
147
+ return null;
148
+ }
149
+
150
+ return await Promise.all(
151
+ specifications.map(async (spec) => {
152
+ if (!resourceFileExists(domain, spec.path)) {
153
+ return null;
154
+ }
155
+
156
+ const schemaContent = readResourceFile(domain, spec.path) ?? '';
157
+ const schemaExtension = spec.type;
158
+ const enrichedOwners = await enrichOwners(domain.data.owners || []);
159
+
160
+ return {
161
+ collection: 'domains',
162
+ data: {
163
+ id: `${domain.data.id}`,
164
+ name: `${domain.data.name} - ${spec.name}`,
165
+ version: domain.data.version,
166
+ summary: domain.data.summary,
167
+ schemaPath: spec.path,
168
+ owners: enrichedOwners,
169
+ },
170
+ schemaContent,
171
+ schemaExtension,
172
+ specType: spec.type,
173
+ specName: spec.name,
174
+ specFilenameWithoutExtension: spec.filenameWithoutExtension,
175
+ };
176
+ })
177
+ );
178
+ } catch (error) {
179
+ console.error(`Error reading specifications for domain ${domain.data.id}:`, error);
180
+ return null;
181
+ }
182
+ })
183
+ );
184
+
185
+ // Flatten and filter out null values for domains
186
+ const flatDomainsWithSpecs = domainsWithSpecs.flat().filter((domain) => domain !== null);
187
+
188
+ return [...messagesWithSchemas, ...flatServicesWithSpecs, ...flatDomainsWithSpecs];
137
189
  }
138
190
 
139
191
  export class Page extends HybridPage {
@@ -12,6 +12,7 @@ import {
12
12
  } from './shared';
13
13
  import { isVisualiserEnabled } from '@utils/feature';
14
14
  import { pluralizeMessageType } from '@utils/collections/messages';
15
+ import { getSpecificationsForDomain } from '@utils/collections/domains';
15
16
 
16
17
  export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[], context: ResourceGroupContext): NavNode => {
17
18
  const servicesInDomain = domain.data.services || [];
@@ -48,6 +49,14 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
48
49
  const diagramNavItems = buildDiagramNavItems(domainDiagrams, context.diagrams);
49
50
  const hasDiagrams = diagramNavItems.length > 0;
50
51
 
52
+ // Specifications
53
+ const specifications = getSpecificationsForDomain(domain);
54
+ const hasSpecifications = specifications.length > 0;
55
+ const openAPISpecifications = specifications.filter((specification) => specification.type === 'openapi');
56
+ const asyncAPISpecifications = specifications.filter((specification) => specification.type === 'asyncapi');
57
+ const graphQLSpecifications = specifications.filter((specification) => specification.type === 'graphql');
58
+ const renderSpecifications = hasSpecifications && shouldRenderSideBarSection(domain, 'specifications');
59
+
51
60
  return {
52
61
  type: 'item',
53
62
  title: domain.data.name,
@@ -87,6 +96,37 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
87
96
  icon: 'FileImage',
88
97
  pages: diagramNavItems,
89
98
  },
99
+ renderSpecifications && {
100
+ type: 'group',
101
+ title: 'API & Contracts',
102
+ icon: 'FileCode',
103
+ pages: [
104
+ ...openAPISpecifications.map((specification) => ({
105
+ type: 'item',
106
+ title: specification.name,
107
+ leftIcon: '/icons/openapi-black.svg',
108
+ href: buildUrl(
109
+ `/docs/domains/${domain.data.id}/${domain.data.version}/spec/${specification.filenameWithoutExtension}`
110
+ ),
111
+ })),
112
+ ...asyncAPISpecifications.map((specification) => ({
113
+ type: 'item',
114
+ title: specification.name,
115
+ leftIcon: '/icons/asyncapi-black.svg',
116
+ href: buildUrl(
117
+ `/docs/domains/${domain.data.id}/${domain.data.version}/asyncapi/${specification.filenameWithoutExtension}`
118
+ ),
119
+ })),
120
+ ...graphQLSpecifications.map((specification) => ({
121
+ type: 'item',
122
+ title: specification.name,
123
+ leftIcon: '/icons/graphql-black.svg',
124
+ href: buildUrl(
125
+ `/docs/domains/${domain.data.id}/${domain.data.version}/graphql/${specification.filenameWithoutExtension}`
126
+ ),
127
+ })),
128
+ ],
129
+ },
90
130
  renderSubDomains && {
91
131
  type: 'group',
92
132
  title: 'Subdomains',
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import type { CollectionMessageTypes } from '@types';
5
5
  import type { Service } from './types';
6
6
  import utils from '@eventcatalog/sdk';
7
- import { createVersionedMap, findInMap } from '@utils/collections/util';
7
+ import { createVersionedMap, findInMap, processSpecifications } from '@utils/collections/util';
8
8
 
9
9
  const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
10
10
  const CACHE_ENABLED = process.env.DISABLE_EVENTCATALOG_CACHE !== 'true';
@@ -383,3 +383,7 @@ export const getDomainsForService = async (service: Service): Promise<Domain[]>
383
383
  export const domainHasEntities = (domain: Domain): boolean => {
384
384
  return (domain.data.entities && domain.data.entities.length > 0) || false;
385
385
  };
386
+
387
+ export const getSpecificationsForDomain = (domain: Domain) => {
388
+ return processSpecifications(domain.data.specifications as any);
389
+ };
@@ -6,7 +6,7 @@ import type { CollectionMessageTypes, CollectionTypes } from '@types';
6
6
  const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd();
7
7
  import utils, { type Domain } from '@eventcatalog/sdk';
8
8
  import { getDomains, getDomainsForService } from './domains';
9
- import { createVersionedMap, findInMap } from '@utils/collections/util';
9
+ import { createVersionedMap, findInMap, processSpecifications } from '@utils/collections/util';
10
10
 
11
11
  export type Service = CollectionEntry<'services'>;
12
12
 
@@ -174,31 +174,7 @@ export const getConsumersOfMessage = (services: Service[], message: CollectionEn
174
174
  };
175
175
 
176
176
  export const getSpecificationsForService = (service: CollectionEntry<CollectionTypes>) => {
177
- const specifications = Array.isArray(service.data.specifications) ? service.data.specifications : [];
178
-
179
- if (service.data.specifications && !Array.isArray(service.data.specifications)) {
180
- if (service.data.specifications.asyncapiPath) {
181
- specifications.push({
182
- type: 'asyncapi',
183
- path: service.data.specifications.asyncapiPath,
184
- name: 'AsyncAPI',
185
- });
186
- }
187
- if (service.data.specifications.openapiPath) {
188
- specifications.push({
189
- type: 'openapi',
190
- path: service.data.specifications.openapiPath,
191
- name: 'OpenAPI',
192
- });
193
- }
194
- }
195
-
196
- return specifications.map((spec) => ({
197
- ...spec,
198
- name: spec.name || (spec.type === 'asyncapi' ? 'AsyncAPI' : 'OpenAPI'),
199
- filename: path.basename(spec.path),
200
- filenameWithoutExtension: path.basename(spec.path, path.extname(spec.path)),
201
- }));
177
+ return processSpecifications(service.data.specifications as any);
202
178
  };
203
179
  // Get services for channel
204
180
  export const getProducersAndConsumersForChannel = async (channel: CollectionEntry<'channels'>) => {
@@ -1,6 +1,70 @@
1
1
  import type { CollectionTypes } from '@types';
2
2
  import type { CollectionEntry } from 'astro:content';
3
3
  import semver, { coerce, compare, eq, satisfies as satisfiesRange } from 'semver';
4
+ import path from 'node:path';
5
+
6
+ // --- SPECIFICATION HELPERS ---
7
+
8
+ export type SpecificationType = 'asyncapi' | 'openapi' | 'graphql';
9
+
10
+ export interface SpecificationInput {
11
+ type: SpecificationType;
12
+ path: string;
13
+ name?: string;
14
+ }
15
+
16
+ export interface ProcessedSpecification {
17
+ type: SpecificationType;
18
+ path: string;
19
+ name: string;
20
+ filename: string;
21
+ filenameWithoutExtension: string;
22
+ }
23
+
24
+ export const getDefaultSpecificationName = (type: string): string => {
25
+ switch (type) {
26
+ case 'asyncapi':
27
+ return 'AsyncAPI';
28
+ case 'openapi':
29
+ return 'OpenAPI';
30
+ case 'graphql':
31
+ return 'GraphQL';
32
+ default:
33
+ return 'Specification';
34
+ }
35
+ };
36
+
37
+ interface LegacySpecificationFormat {
38
+ asyncapiPath?: string;
39
+ openapiPath?: string;
40
+ graphqlPath?: string;
41
+ }
42
+
43
+ export const processSpecifications = (
44
+ specifications: SpecificationInput[] | LegacySpecificationFormat | undefined
45
+ ): ProcessedSpecification[] => {
46
+ const specs: SpecificationInput[] = Array.isArray(specifications) ? [...specifications] : [];
47
+
48
+ // Handle legacy object format
49
+ if (specifications && !Array.isArray(specifications)) {
50
+ if (specifications.asyncapiPath) {
51
+ specs.push({ type: 'asyncapi', path: specifications.asyncapiPath, name: 'AsyncAPI' });
52
+ }
53
+ if (specifications.openapiPath) {
54
+ specs.push({ type: 'openapi', path: specifications.openapiPath, name: 'OpenAPI' });
55
+ }
56
+ if (specifications.graphqlPath) {
57
+ specs.push({ type: 'graphql', path: specifications.graphqlPath, name: 'GraphQL' });
58
+ }
59
+ }
60
+
61
+ return specs.map((spec) => ({
62
+ ...spec,
63
+ name: spec.name || getDefaultSpecificationName(spec.type),
64
+ filename: path.basename(spec.path),
65
+ filenameWithoutExtension: path.basename(spec.path, path.extname(spec.path)),
66
+ }));
67
+ };
4
68
 
5
69
  export const getPreviousVersion = (version: string, versions: string[]) => {
6
70
  const index = versions.indexOf(version);
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/event-catalog/eventcatalog.git"
7
7
  },
8
8
  "type": "module",
9
- "version": "3.7.0",
9
+ "version": "3.7.2",
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
@@ -23,9 +23,9 @@
23
23
  "@ai-sdk/react": "^3.0.17",
24
24
  "@astrojs/markdown-remark": "^6.3.10",
25
25
  "@astrojs/mdx": "^4.3.13",
26
- "@astrojs/node": "^9.5.1",
26
+ "@astrojs/node": "^9.5.2",
27
27
  "@astrojs/react": "^4.4.2",
28
- "@astrojs/rss": "^4.0.14",
28
+ "@astrojs/rss": "^4.0.15",
29
29
  "@astrojs/tailwind": "^6.0.2",
30
30
  "@asyncapi/avro-schema-parser": "3.0.24",
31
31
  "@asyncapi/parser": "3.4.0",
@@ -55,7 +55,7 @@
55
55
  "@tanstack/react-table": "^8.17.3",
56
56
  "@xyflow/react": "^12.3.6",
57
57
  "ai": "^6.0.17",
58
- "astro": "^5.16.8",
58
+ "astro": "^5.16.10",
59
59
  "astro-compress": "^2.3.8",
60
60
  "astro-expressive-code": "^0.41.3",
61
61
  "astro-seo": "^0.8.4",