@eventcatalog/core 4.10.13 → 4.10.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/{chunk-SSSN5FXC.js → chunk-7LWOCX4R.js} +1 -1
  6. package/dist/{chunk-IIECZFXN.js → chunk-A4JRWRCV.js} +1 -1
  7. package/dist/{chunk-JHUICVBT.js → chunk-HBVTU6ZP.js} +1 -1
  8. package/dist/{chunk-PADMH2RJ.js → chunk-I6HUOX4R.js} +1 -1
  9. package/dist/{chunk-34RMFKFB.js → chunk-Q2ATWE7N.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/docs/api/02-config.md +60 -6
  13. package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +18 -11
  14. package/dist/docs/development/authentication/providers/03-setting-up-github.md +29 -0
  15. package/dist/docs/development/bring-your-own-documentation/custom-pages/02-adding-custom-docs.md +2 -2
  16. package/dist/docs/development/bring-your-own-documentation/custom-pages/03-components.md +17 -0
  17. package/dist/docs/development/components/07-resource-references.md +7 -1
  18. package/dist/docs/development/components/components/05-architecture-graph.md +65 -0
  19. package/dist/docs/development/components/components/{05-attachments.md → 06-attachments.md} +1 -1
  20. package/dist/docs/development/components/components/{06-channel-information.md → 07-channel-information.md} +1 -1
  21. package/dist/docs/development/components/components/{07-custom-properties.md → 08-custom-properties.md} +1 -1
  22. package/dist/docs/development/components/components/{08-design.md → 09-design.md} +1 -1
  23. package/dist/docs/development/components/components/{09-entitymap.md → 10-entitymap.md} +1 -1
  24. package/dist/docs/development/components/components/{10-flow.md → 11-flow.md} +1 -1
  25. package/dist/docs/development/components/components/{11-link.md → 12-link.md} +1 -1
  26. package/dist/docs/development/components/components/{12-mermaid-file-loader.md → 13-mermaid-file-loader.md} +13 -1
  27. package/dist/docs/development/components/components/{13-message-table.md → 14-message-table.md} +1 -1
  28. package/dist/docs/development/components/components/{14-nodegraph.md → 15-nodegraph.md} +1 -1
  29. package/dist/docs/development/components/components/{15-openapi.md → 16-openapi.md} +1 -1
  30. package/dist/docs/development/components/components/{16-prompt.md → 17-prompt.md} +1 -1
  31. package/dist/docs/development/components/components/{17-remote-schema.md → 18-remote-schema.md} +1 -1
  32. package/dist/docs/development/components/components/{18-resource-group-table.md → 19-resource-group-table.md} +1 -1
  33. package/dist/docs/development/components/components/{19-resource-link.md → 20-resource-link.md} +1 -1
  34. package/dist/docs/development/components/components/{20-schema.md → 21-schema.md} +1 -1
  35. package/dist/docs/development/components/components/{21-schema-viewer.md → 22-schema-viewer.md} +1 -1
  36. package/dist/docs/development/components/components/{22-steps.md → 23-steps.md} +1 -1
  37. package/dist/docs/development/components/components/{23-tabs.md → 24-tabs.md} +1 -1
  38. package/dist/docs/development/components/components/{24-tiles.md → 25-tiles.md} +1 -1
  39. package/dist/docs/development/components/components/{25-visibility.md → 26-visibility.md} +1 -1
  40. package/dist/docs/development/components/diagram-syntax/06-mermaid.md +45 -6
  41. package/dist/docs/development/customization/04-documentation-sidebar.md +3 -3
  42. package/dist/docs/development/customization/05-resource-sidebar.md +235 -0
  43. package/dist/docs/development/customization/09-embed-eventcatalog.md +179 -0
  44. package/dist/docs/development/customization/_category_.json +1 -1
  45. package/dist/docs/development/deployment/build-and-deploy.md +4 -0
  46. package/dist/docs/development/deployment/hosting-options.md +1 -1
  47. package/dist/docs/development/deployment/link-validation.md +105 -0
  48. package/dist/docs/development/developer-tools/eventcatalog-linter/00-overview.md +76 -0
  49. package/dist/docs/development/developer-tools/eventcatalog-linter/01-setup.md +144 -0
  50. package/dist/docs/development/developer-tools/eventcatalog-linter/_category_.json +10 -0
  51. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/01-configure-rules.md +130 -0
  52. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/02-run-in-ci.md +93 -0
  53. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/03-use-custom-frontmatter.md +81 -0
  54. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/04-reference-external-catalogs.md +51 -0
  55. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/05-fix-common-problems.md +120 -0
  56. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/_category_.json +12 -0
  57. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/01-cli.md +109 -0
  58. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/02-configuration.md +120 -0
  59. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/03-rules.md +287 -0
  60. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/04-versions.md +77 -0
  61. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/05-supported-resources.md +75 -0
  62. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/_category_.json +12 -0
  63. package/dist/docs/development/guides/97-architecture-graph.md +78 -0
  64. package/dist/docs/development/guides/domains/02-create-domain.md +1 -1
  65. package/dist/docs/development/guides/domains/03-ownership-and-language/02-adding-ubiquitous-language.md +29 -0
  66. package/dist/docs/development/guides/domains/09-reference.md +10 -0
  67. package/dist/docs/development/guides/resources/messages/04-message-channels/02-adding-channels.md +2 -2
  68. package/dist/docs/development/guides/resources/messages/04-message-channels/04-adding-messages-to-services.md +8 -0
  69. package/dist/docs/development/guides/resources/schemas/03-add-specifications-to-services/01-add-openapi-specifications.md +2 -2
  70. package/dist/docs/development/guides/resources/schemas/03-add-specifications-to-services/02-add-asyncapi-specifications.md +3 -3
  71. package/dist/docs/development/guides/systems/05-system-context-maps.md +2 -0
  72. package/dist/docs/development/license-keys/license-validation.md +7 -3
  73. package/dist/docs/development/license-keys/overview.md +7 -3
  74. package/dist/docs/development/upgrading/upgrading.md +3 -3
  75. package/dist/docs/development/upgrading/v2.md +3 -3
  76. package/dist/docs/federation/00-overview.md +158 -0
  77. package/dist/docs/federation/01-first-federation.md +199 -0
  78. package/dist/docs/federation/_category_.json +12 -0
  79. package/dist/docs/federation/explanation/_category_.json +12 -0
  80. package/dist/docs/federation/explanation/how-it-works.md +129 -0
  81. package/dist/docs/federation/explanation/lockfile-and-cache.md +134 -0
  82. package/dist/docs/federation/explanation/mvp-status-and-feedback.md +86 -0
  83. package/dist/docs/federation/explanation/ownership-and-references.md +134 -0
  84. package/dist/docs/federation/how-to/_category_.json +12 -0
  85. package/dist/docs/federation/how-to/configure-github-sources.md +156 -0
  86. package/dist/docs/federation/how-to/configure-validation-rules.md +127 -0
  87. package/dist/docs/federation/how-to/run-in-ci.md +138 -0
  88. package/dist/docs/federation/how-to/share-assets-and-components.md +125 -0
  89. package/dist/docs/federation/how-to/use-local-sources.md +138 -0
  90. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/00-introduction.md +11 -2
  91. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/01-installation.md +11 -3
  92. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/02-plugin-configuration.md +11 -2
  93. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/03-examples.md +11 -1
  94. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/04-configuration.md +11 -1
  95. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/05-setup-team-catalog.md +11 -2
  96. package/dist/docs/federation/legacy-federation/_category_.json +12 -0
  97. package/dist/docs/federation/reference/_category_.json +12 -0
  98. package/dist/docs/federation/reference/cli.md +122 -0
  99. package/dist/docs/federation/reference/configuration.md +192 -0
  100. package/dist/docs/federation/reference/diagnostic-rules.md +173 -0
  101. package/dist/docs/federation/reference/generated-output.md +118 -0
  102. package/dist/docs/federation/reference/supported-content.md +86 -0
  103. package/dist/docs/federation/reference/troubleshooting.md +208 -0
  104. package/dist/docs/plugins/01-intro.md +3 -3
  105. package/dist/docs/plugins/03-all-plugins.md +2 -2
  106. package/dist/docs/plugins/asyncapi/00-intro.md +4 -4
  107. package/dist/docs/plugins/asyncapi/02-plugin-configuration.md +4 -4
  108. package/dist/docs/plugins/backstage/00-overview.md +53 -0
  109. package/dist/docs/plugins/backstage/01-get-started.md +95 -0
  110. package/dist/docs/plugins/backstage/02-embed-entity-tabs.md +109 -0
  111. package/dist/docs/plugins/backstage/03-add-overview-cards.md +79 -0
  112. package/dist/docs/plugins/backstage/04-control-theme-and-size.md +54 -0
  113. package/dist/docs/plugins/backstage/05-migrate-to-annotations.md +49 -0
  114. package/dist/docs/plugins/backstage/06-configuration.md +85 -0
  115. package/dist/docs/plugins/backstage/07-components.md +129 -0
  116. package/dist/docs/plugins/backstage/08-how-embeds-work.md +52 -0
  117. package/dist/docs/plugins/backstage/_category_.json +4 -3
  118. package/dist/docs/plugins/eventbridge/00-intro.md +5 -5
  119. package/dist/docs/plugins/eventbridge/03-api.md +3 -3
  120. package/dist/docs/plugins/graphql/00-intro.md +2 -2
  121. package/dist/docs/plugins/graphql/02-plugin-configuration.md +2 -2
  122. package/dist/docs/plugins/hookdeck/01-intro.md +1 -1
  123. package/dist/docs/plugins/openapi/00-intro.md +1 -1
  124. package/dist/eventcatalog.cjs +1 -1
  125. package/dist/eventcatalog.js +5 -5
  126. package/dist/generate.cjs +1 -1
  127. package/dist/generate.js +3 -3
  128. package/dist/utils/cli-logger.cjs +1 -1
  129. package/dist/utils/cli-logger.js +2 -2
  130. package/eventcatalog/src/components/SchemaExplorer/AvroSchemaViewer.tsx +21 -19
  131. package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.test.tsx +114 -0
  132. package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.tsx +107 -86
  133. package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.test.tsx +91 -0
  134. package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.tsx +115 -33
  135. package/eventcatalog/src/components/SchemaExplorer/SchemaViewerHover.test.tsx +33 -0
  136. package/eventcatalog/src/components/SchemaExplorer/protobuf-validation.test.ts +84 -0
  137. package/eventcatalog/src/components/SchemaExplorer/protobuf-validation.ts +149 -0
  138. package/eventcatalog/src/utils/collections/glob-loader.ts +2 -2
  139. package/eventcatalog/src/utils/protobuf-schema.ts +211 -16
  140. package/package.json +8 -8
  141. package/dist/docs/cli/channels.md +0 -180
  142. package/dist/docs/cli/commands.md +0 -183
  143. package/dist/docs/cli/custom-docs.md +0 -78
  144. package/dist/docs/cli/data-products.md +0 -177
  145. package/dist/docs/cli/data-stores.md +0 -166
  146. package/dist/docs/cli/diagrams.md +0 -147
  147. package/dist/docs/cli/domains.md +0 -280
  148. package/dist/docs/cli/entities.md +0 -138
  149. package/dist/docs/cli/events.md +0 -186
  150. package/dist/docs/cli/export.md +0 -27
  151. package/dist/docs/cli/governance.md +0 -24
  152. package/dist/docs/cli/messages.md +0 -69
  153. package/dist/docs/cli/queries.md +0 -183
  154. package/dist/docs/cli/services.md +0 -266
  155. package/dist/docs/cli/snapshots.md +0 -44
  156. package/dist/docs/cli/teams.md +0 -75
  157. package/dist/docs/cli/users.md +0 -75
  158. package/dist/docs/cli/utilities.md +0 -43
  159. package/dist/docs/development/developer-tools/eventcatalog-linter.md +0 -732
  160. package/dist/docs/plugins/backstage/00-intro.md +0 -67
  161. package/dist/docs/plugins/backstage/01-installation.md +0 -242
  162. package/dist/docs/plugins/backstage/02-api.md +0 -51
  163. package/dist/docs/plugins/backstage/03-examples.md +0 -12
  164. package/dist/docs/plugins/eventcatalog-federation/_category_.json +0 -11
@@ -0,0 +1,84 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { ProtobufField } from '@utils/protobuf-schema';
3
+ import {
4
+ formatProtobufOptionValue,
5
+ formatProtovalidateRule,
6
+ getCustomFieldAnnotations,
7
+ getProtovalidateRules,
8
+ isProtovalidateRequired,
9
+ } from './protobuf-validation';
10
+
11
+ describe('protobuf validation helpers', () => {
12
+ it('flattens direct and aggregate Protovalidate options', () => {
13
+ const field: ProtobufField = {
14
+ name: 'status',
15
+ type: 'Status',
16
+ options: [
17
+ { name: '(buf.validate.field).required', value: true },
18
+ {
19
+ name: '(buf.validate.field).enum',
20
+ value: { defined_only: true, not_in: [0] },
21
+ },
22
+ ],
23
+ };
24
+
25
+ expect(getProtovalidateRules(field)).toEqual([
26
+ { path: 'required', value: true },
27
+ { path: 'enum.defined_only', value: true },
28
+ { path: 'enum.not_in', value: [0] },
29
+ ]);
30
+ expect(isProtovalidateRequired(field)).toBe(true);
31
+ });
32
+
33
+ it('ignores non-Protovalidate custom options', () => {
34
+ const field: ProtobufField = {
35
+ name: 'description',
36
+ type: 'string',
37
+ options: [{ name: '(datahub.v1.gdpr_rule).string', value: true }],
38
+ };
39
+
40
+ expect(getProtovalidateRules(field)).toEqual([]);
41
+ expect(isProtovalidateRequired(field)).toBe(false);
42
+ expect(getCustomFieldAnnotations(field)).toEqual([{ name: '(datahub.v1.gdpr_rule).string', value: true }]);
43
+ });
44
+
45
+ it('only treats extension options as custom annotations and formats aggregate values', () => {
46
+ const field: ProtobufField = {
47
+ name: 'description',
48
+ type: 'string',
49
+ options: [
50
+ { name: 'deprecated', value: true },
51
+ { name: '(buf.validate.field).string.min_len', value: 1 },
52
+ { name: '(datahub.v1.classification)', value: { level: 'personal', encrypted: true } },
53
+ ],
54
+ };
55
+
56
+ expect(getCustomFieldAnnotations(field)).toEqual([
57
+ {
58
+ name: '(datahub.v1.classification)',
59
+ value: { level: 'personal', encrypted: true },
60
+ },
61
+ ]);
62
+ expect(formatProtobufOptionValue(getCustomFieldAnnotations(field)[0].value)).toBe('{"level":"personal","encrypted":true}');
63
+ });
64
+
65
+ it('formats common rules for display', () => {
66
+ expect(formatProtovalidateRule({ path: 'string.email', value: true })).toEqual({ label: 'Format', value: 'Email' });
67
+ expect(formatProtovalidateRule({ path: 'uint32.gte', value: 1 })).toEqual({ label: 'Minimum', value: '1' });
68
+ expect(formatProtovalidateRule({ path: 'enum.defined_only', value: true })).toEqual({
69
+ label: 'Defined enum values only',
70
+ });
71
+ expect(formatProtovalidateRule({ path: 'enum.not_in', value: [0] })).toEqual({
72
+ label: 'Excluded values',
73
+ value: '0',
74
+ });
75
+ expect(formatProtovalidateRule({ path: 'repeated.items.string.min_len', value: 3 })).toEqual({
76
+ label: 'Item minimum length',
77
+ value: '3',
78
+ });
79
+ expect(formatProtovalidateRule({ path: 'map.values.string.max_len', value: 100 })).toEqual({
80
+ label: 'Value maximum length',
81
+ value: '100',
82
+ });
83
+ });
84
+ });
@@ -0,0 +1,149 @@
1
+ import type { ProtobufField, ProtobufOptionValue } from '@utils/protobuf-schema';
2
+
3
+ const PROTOVALIDATE_FIELD_OPTION = '(buf.validate.field)';
4
+
5
+ export interface ProtovalidateRule {
6
+ path: string;
7
+ value: ProtobufOptionValue;
8
+ }
9
+
10
+ export interface ProtovalidateRuleDisplay {
11
+ label: string;
12
+ value?: string;
13
+ }
14
+
15
+ export interface ProtobufCustomAnnotation {
16
+ name: string;
17
+ value: ProtobufOptionValue;
18
+ }
19
+
20
+ const isOptionObject = (value: ProtobufOptionValue): value is { [key: string]: ProtobufOptionValue } =>
21
+ typeof value === 'object' && !Array.isArray(value);
22
+
23
+ const flattenOptionValue = (path: string, value: ProtobufOptionValue): ProtovalidateRule[] => {
24
+ if (!isOptionObject(value)) return [{ path, value }];
25
+
26
+ return Object.entries(value).flatMap(([name, nestedValue]) => flattenOptionValue(path ? `${path}.${name}` : name, nestedValue));
27
+ };
28
+
29
+ export const getProtovalidateRules = (field: ProtobufField): ProtovalidateRule[] =>
30
+ (field.options ?? []).flatMap((option) => {
31
+ if (option.name !== PROTOVALIDATE_FIELD_OPTION && !option.name.startsWith(`${PROTOVALIDATE_FIELD_OPTION}.`)) return [];
32
+
33
+ const path = option.name.slice(PROTOVALIDATE_FIELD_OPTION.length).replace(/^\./, '');
34
+ return flattenOptionValue(path, option.value);
35
+ });
36
+
37
+ export const isProtovalidateRequired = (field: ProtobufField) =>
38
+ getProtovalidateRules(field).some((rule) => rule.path === 'required' && rule.value === true);
39
+
40
+ export const getCustomFieldAnnotations = (field: ProtobufField): ProtobufCustomAnnotation[] =>
41
+ (field.options ?? [])
42
+ .filter(
43
+ (option) =>
44
+ option.name.startsWith('(') &&
45
+ option.name !== PROTOVALIDATE_FIELD_OPTION &&
46
+ !option.name.startsWith(`${PROTOVALIDATE_FIELD_OPTION}.`)
47
+ )
48
+ .map(({ name, value }) => ({ name, value }));
49
+
50
+ export const formatProtobufOptionValue = (value: ProtobufOptionValue): string => {
51
+ if (Array.isArray(value)) return value.map(formatProtobufOptionValue).join(', ');
52
+ if (isOptionObject(value)) return JSON.stringify(value);
53
+ return String(value);
54
+ };
55
+
56
+ const humanize = (value: string) => value.replace(/_/g, ' ').replace(/\b\w/g, (character) => character.toUpperCase());
57
+
58
+ const FORMAT_NAMES: Record<string, string> = {
59
+ address: 'Address',
60
+ email: 'Email',
61
+ hostname: 'Hostname',
62
+ ip: 'IP',
63
+ ipv4: 'IPv4',
64
+ ipv6: 'IPv6',
65
+ protobuf_fqn: 'Protobuf FQN',
66
+ tuuid: 'TUUID',
67
+ uri: 'URI',
68
+ uri_ref: 'URI reference',
69
+ uuid: 'UUID',
70
+ };
71
+
72
+ const getRuleScope = (path: string) => {
73
+ if (path.includes('repeated.items')) return 'Item';
74
+ if (path.includes('map.keys')) return 'Key';
75
+ if (path.includes('map.values')) return 'Value';
76
+ return '';
77
+ };
78
+
79
+ const withScope = (scope: string, label: string) =>
80
+ scope ? `${scope} ${label.charAt(0).toLowerCase()}${label.slice(1)}` : label;
81
+
82
+ export const formatProtovalidateRule = ({ path, value }: ProtovalidateRule): ProtovalidateRuleDisplay => {
83
+ const name = path.split('.').pop() ?? path;
84
+ const scope = getRuleScope(path);
85
+ const formattedValue = formatProtobufOptionValue(value);
86
+
87
+ switch (name) {
88
+ case 'required':
89
+ return value === true ? { label: 'Required' } : { label: 'Required', value: formattedValue };
90
+ case 'const':
91
+ return { label: withScope(scope, 'Constant'), value: formattedValue };
92
+ case 'len':
93
+ return { label: withScope(scope, 'Length'), value: formattedValue };
94
+ case 'min_len':
95
+ return { label: withScope(scope, 'Minimum length'), value: formattedValue };
96
+ case 'max_len':
97
+ return { label: withScope(scope, 'Maximum length'), value: formattedValue };
98
+ case 'len_bytes':
99
+ return { label: withScope(scope, 'Byte length'), value: formattedValue };
100
+ case 'min_bytes':
101
+ return { label: withScope(scope, 'Minimum byte length'), value: formattedValue };
102
+ case 'max_bytes':
103
+ return { label: withScope(scope, 'Maximum byte length'), value: formattedValue };
104
+ case 'pattern':
105
+ return { label: withScope(scope, 'Match pattern'), value: formattedValue };
106
+ case 'gt':
107
+ return { label: withScope(scope, 'Greater than'), value: formattedValue };
108
+ case 'gte':
109
+ return { label: withScope(scope, 'Minimum'), value: formattedValue };
110
+ case 'lt':
111
+ return { label: withScope(scope, 'Less than'), value: formattedValue };
112
+ case 'lte':
113
+ return { label: withScope(scope, 'Maximum'), value: formattedValue };
114
+ case 'in':
115
+ return { label: withScope(scope, 'Allowed values'), value: formattedValue };
116
+ case 'not_in':
117
+ return { label: withScope(scope, 'Excluded values'), value: formattedValue };
118
+ case 'defined_only':
119
+ return value === true
120
+ ? { label: 'Defined enum values only' }
121
+ : { label: 'Defined enum values only', value: formattedValue };
122
+ case 'min_items':
123
+ return { label: 'Minimum items', value: formattedValue };
124
+ case 'max_items':
125
+ return { label: 'Maximum items', value: formattedValue };
126
+ case 'unique':
127
+ return value === true ? { label: 'Unique items' } : { label: 'Unique items', value: formattedValue };
128
+ case 'min_pairs':
129
+ return { label: 'Minimum map entries', value: formattedValue };
130
+ case 'max_pairs':
131
+ return { label: 'Maximum map entries', value: formattedValue };
132
+ case 'email':
133
+ case 'hostname':
134
+ case 'ip':
135
+ case 'ipv4':
136
+ case 'ipv6':
137
+ case 'uri':
138
+ case 'uri_ref':
139
+ case 'address':
140
+ case 'uuid':
141
+ case 'tuuid':
142
+ case 'protobuf_fqn':
143
+ return value === true
144
+ ? { label: withScope(scope, 'Format'), value: FORMAT_NAMES[name] }
145
+ : { label: withScope(scope, humanize(name)), value: formattedValue };
146
+ default:
147
+ return { label: withScope(scope, humanize(name)), value: formattedValue };
148
+ }
149
+ };
@@ -1,4 +1,4 @@
1
- import { glob } from 'astro/loaders';
1
+ import { glob, type Loader } from 'astro/loaders';
2
2
  import picomatch from 'picomatch';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
@@ -36,7 +36,7 @@ const matchesGlobPattern = (entry: string, patterns: string | string[]) => {
36
36
  return picomatch.isMatch(entry, positivePatterns) && !picomatch.isMatch(entry, negativePatterns);
37
37
  };
38
38
 
39
- export const globWithSafeWatcher = (options: GlobOptions) => {
39
+ export const globWithSafeWatcher = (options: GlobOptions): Loader => {
40
40
  const loader = glob(options);
41
41
 
42
42
  return {
@@ -17,6 +17,14 @@ export interface ProtobufField {
17
17
  map?: { keyType: string; valueType: string };
18
18
  oneof?: string;
19
19
  doc?: string;
20
+ options?: ProtobufOption[];
21
+ }
22
+
23
+ export type ProtobufOptionValue = string | number | boolean | ProtobufOptionValue[] | { [key: string]: ProtobufOptionValue };
24
+
25
+ export interface ProtobufOption {
26
+ name: string;
27
+ value: ProtobufOptionValue;
20
28
  }
21
29
 
22
30
  export interface ProtobufEnumValue {
@@ -60,6 +68,81 @@ const isIdentifierChar = (char: string) => /[A-Za-z0-9_.]/.test(char);
60
68
  const isNumberStart = (char: string) => /[0-9-]/.test(char);
61
69
  const isNumberChar = (char: string) => /[0-9a-fA-FxX.+-]/.test(char);
62
70
 
71
+ const UTF8_ENCODER = new TextEncoder();
72
+ const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true });
73
+
74
+ const SIMPLE_STRING_ESCAPES: Record<string, number> = {
75
+ a: 0x07,
76
+ b: 0x08,
77
+ f: 0x0c,
78
+ n: 0x0a,
79
+ r: 0x0d,
80
+ t: 0x09,
81
+ v: 0x0b,
82
+ '?': 0x3f,
83
+ '\\': 0x5c,
84
+ "'": 0x27,
85
+ '"': 0x22,
86
+ };
87
+
88
+ function decodeStringEscape(content: string, start: number, line: number): { bytes: number[]; nextIndex: number } {
89
+ const escapeType = content[start + 1];
90
+ if (!escapeType) throw new Error(`Unterminated string escape at line ${line}`);
91
+
92
+ if (escapeType in SIMPLE_STRING_ESCAPES) {
93
+ return { bytes: [SIMPLE_STRING_ESCAPES[escapeType]], nextIndex: start + 2 };
94
+ }
95
+
96
+ if (escapeType === 'x' || escapeType === 'X') {
97
+ const digits = content.slice(start + 2).match(/^[0-9a-fA-F]{1,2}/)?.[0];
98
+ if (!digits) throw new Error(`Invalid hexadecimal string escape at line ${line}`);
99
+ return { bytes: [Number.parseInt(digits, 16)], nextIndex: start + 2 + digits.length };
100
+ }
101
+
102
+ if (/[0-7]/.test(escapeType)) {
103
+ const digits = content.slice(start + 1).match(/^[0-7]{1,3}/)![0];
104
+ const byte = Number.parseInt(digits, 8);
105
+ if (byte > 0xff) throw new Error(`Octal string escape is out of byte range at line ${line}`);
106
+ return { bytes: [byte], nextIndex: start + 1 + digits.length };
107
+ }
108
+
109
+ if (escapeType === 'u' || escapeType === 'U') {
110
+ const digitCount = escapeType === 'u' ? 4 : 8;
111
+ const digits = content.slice(start + 2, start + 2 + digitCount);
112
+ if (digits.length !== digitCount || !/^[0-9a-fA-F]+$/.test(digits)) {
113
+ throw new Error(`Invalid Unicode string escape at line ${line}`);
114
+ }
115
+
116
+ const codePoint = Number.parseInt(digits, 16);
117
+ if (codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff)) {
118
+ throw new Error(`Unicode string escape is out of range at line ${line}`);
119
+ }
120
+ return { bytes: [...UTF8_ENCODER.encode(String.fromCodePoint(codePoint))], nextIndex: start + 2 + digitCount };
121
+ }
122
+
123
+ throw new Error(`Invalid string escape "\\${escapeType}" at line ${line}`);
124
+ }
125
+
126
+ function parseIntegerOptionValue(value: string): number | string | undefined {
127
+ const sign = value.startsWith('-') ? -1n : 1n;
128
+ const unsignedValue = value.startsWith('-') || value.startsWith('+') ? value.slice(1) : value;
129
+ let magnitude: bigint;
130
+
131
+ if (/^0[xX][0-9a-fA-F]+$/.test(unsignedValue)) {
132
+ magnitude = BigInt(unsignedValue);
133
+ } else if (/^0[0-7]*$/.test(unsignedValue)) {
134
+ magnitude = BigInt(`0o${unsignedValue.slice(1) || '0'}`);
135
+ } else if (/^[1-9][0-9]*$/.test(unsignedValue)) {
136
+ magnitude = BigInt(unsignedValue);
137
+ } else {
138
+ return undefined;
139
+ }
140
+
141
+ const parsedValue = sign * magnitude;
142
+ if (parsedValue < BigInt(Number.MIN_SAFE_INTEGER) || parsedValue > BigInt(Number.MAX_SAFE_INTEGER)) return value;
143
+ return Number(parsedValue);
144
+ }
145
+
63
146
  function tokenize(content: string): { tokens: Token[]; trailingComments: Map<number, string> } {
64
147
  const tokens: Token[] = [];
65
148
  const trailingComments = new Map<number, string>();
@@ -128,18 +211,31 @@ function tokenize(content: string): { tokens: Token[]; trailingComments: Map<num
128
211
  // String literal
129
212
  if (char === '"' || char === "'") {
130
213
  const quote = char;
131
- let value = '';
214
+ const bytes: number[] = [];
132
215
  i++;
216
+ const rawValueStart = i;
217
+ let plainTextStart = i;
133
218
  while (i < content.length && content[i] !== quote) {
134
219
  if (content[i] === '\\') {
135
- value += content[i + 1];
136
- i += 2;
220
+ bytes.push(...UTF8_ENCODER.encode(content.slice(plainTextStart, i)));
221
+ const decodedEscape = decodeStringEscape(content, i, line);
222
+ bytes.push(...decodedEscape.bytes);
223
+ i = decodedEscape.nextIndex;
224
+ plainTextStart = i;
137
225
  continue;
138
226
  }
139
227
  if (content[i] === '\n') line++;
140
- value += content[i];
141
228
  i++;
142
229
  }
230
+ bytes.push(...UTF8_ENCODER.encode(content.slice(plainTextStart, i)));
231
+ if (i >= content.length) throw new Error(`Unterminated string literal at line ${line}`);
232
+ const rawValue = content.slice(rawValueStart, i);
233
+ let value: string;
234
+ try {
235
+ value = UTF8_DECODER.decode(Uint8Array.from(bytes));
236
+ } catch {
237
+ value = rawValue;
238
+ }
143
239
  i++;
144
240
  pushToken({ value, line, isString: true });
145
241
  continue;
@@ -164,6 +260,18 @@ function tokenize(content: string): { tokens: Token[]; trailingComments: Map<num
164
260
  continue;
165
261
  }
166
262
 
263
+ // Signed symbolic floating-point values used by protobuf options (for example +inf).
264
+ if ((char === '+' || char === '-') && isIdentifierStart(content[i + 1] || '')) {
265
+ let value = char;
266
+ i++;
267
+ while (i < content.length && isIdentifierChar(content[i])) {
268
+ value += content[i];
269
+ i++;
270
+ }
271
+ pushToken({ value, line });
272
+ continue;
273
+ }
274
+
167
275
  if (isNumberStart(char)) {
168
276
  let value = content[i];
169
277
  i++;
@@ -232,16 +340,101 @@ class ProtobufParser {
232
340
  }
233
341
  }
234
342
 
235
- // Skip field options like [deprecated = true, (custom) = "value"]
236
- private skipFieldOptions() {
237
- if (this.peek()?.value !== '[') return;
238
- let depth = 0;
239
- while (this.pos < this.tokens.length) {
240
- const token = this.next();
241
- if (token.value === '[') depth++;
242
- if (token.value === ']') depth--;
243
- if (depth === 0) return;
343
+ private parseAggregateOptionKey(): string {
344
+ if (this.peek()?.value !== '[') return this.next().value;
345
+
346
+ this.next();
347
+ const nameParts: string[] = [];
348
+ while (this.peek() && this.peek()!.value !== ']') nameParts.push(this.next().value);
349
+ this.expect(']');
350
+ return `[${nameParts.join('')}]`;
351
+ }
352
+
353
+ private parseOptionValue(): ProtobufOptionValue {
354
+ const token = this.peek();
355
+ if (!token) throw new Error('Unexpected end of protobuf option');
356
+
357
+ if (token.value === '{' || token.value === '<') {
358
+ const openingToken = this.next().value;
359
+ const closingToken = openingToken === '{' ? '}' : '>';
360
+ const value: { [key: string]: ProtobufOptionValue } = {};
361
+
362
+ while (this.peek() && this.peek()!.value !== closingToken) {
363
+ if (this.peek()!.value === ',' || this.peek()!.value === ';') {
364
+ this.next();
365
+ continue;
366
+ }
367
+
368
+ const key = this.parseAggregateOptionKey();
369
+ if (this.peek()?.value === ':') this.next();
370
+ const entryValue = this.parseOptionValue();
371
+ const existingValue = value[key];
372
+ value[key] =
373
+ existingValue === undefined
374
+ ? entryValue
375
+ : Array.isArray(existingValue)
376
+ ? [...existingValue, entryValue]
377
+ : [existingValue, entryValue];
378
+ }
379
+
380
+ this.expect(closingToken);
381
+ return value;
244
382
  }
383
+
384
+ if (token.value === '[') {
385
+ this.next();
386
+ const values: ProtobufOptionValue[] = [];
387
+
388
+ while (this.peek() && this.peek()!.value !== ']') {
389
+ if (this.peek()!.value === ',' || this.peek()!.value === ';') {
390
+ this.next();
391
+ continue;
392
+ }
393
+ values.push(this.parseOptionValue());
394
+ }
395
+
396
+ this.expect(']');
397
+ return values;
398
+ }
399
+
400
+ const valueToken = this.next();
401
+ if (valueToken.isString) {
402
+ let value = valueToken.value;
403
+ while (this.peek()?.isString) value += this.next().value;
404
+ return value;
405
+ }
406
+ if (valueToken.value === 'true') return true;
407
+ if (valueToken.value === 'false') return false;
408
+
409
+ const integerValue = parseIntegerOptionValue(valueToken.value);
410
+ if (integerValue !== undefined) return integerValue;
411
+
412
+ const numericValue = Number(valueToken.value);
413
+ if (Number.isNaN(numericValue)) return valueToken.value;
414
+ if (Number.isInteger(numericValue) && !Number.isSafeInteger(numericValue)) return valueToken.value;
415
+ return numericValue;
416
+ }
417
+
418
+ private parseFieldOptions(): ProtobufOption[] {
419
+ if (this.peek()?.value !== '[') return [];
420
+
421
+ this.next();
422
+ const options: ProtobufOption[] = [];
423
+
424
+ while (this.peek() && this.peek()!.value !== ']') {
425
+ if (this.peek()!.value === ',') {
426
+ this.next();
427
+ continue;
428
+ }
429
+
430
+ const nameParts: string[] = [];
431
+ while (this.peek() && this.peek()!.value !== '=') nameParts.push(this.next().value);
432
+ this.expect('=');
433
+ options.push({ name: nameParts.join(''), value: this.parseOptionValue() });
434
+ }
435
+
436
+ this.expect(']');
437
+ return options;
245
438
  }
246
439
 
247
440
  private getTrailingDoc(line: number): string | undefined {
@@ -384,7 +577,7 @@ class ProtobufParser {
384
577
  const name = this.next().value;
385
578
  this.expect('=');
386
579
  const numberToken = this.next();
387
- this.skipFieldOptions();
580
+ const options = this.parseFieldOptions();
388
581
  const terminator = this.expect(';');
389
582
 
390
583
  const parsedNumber = Number.parseInt(numberToken.value, 10);
@@ -396,6 +589,7 @@ class ProtobufParser {
396
589
  label,
397
590
  oneof,
398
591
  doc: firstToken.doc || typeToken.doc || this.getTrailingDoc(terminator.line),
592
+ ...(options.length > 0 ? { options } : {}),
399
593
  };
400
594
  }
401
595
 
@@ -409,7 +603,7 @@ class ProtobufParser {
409
603
  const name = this.next().value;
410
604
  this.expect('=');
411
605
  const numberToken = this.next();
412
- this.skipFieldOptions();
606
+ const options = this.parseFieldOptions();
413
607
  const terminator = this.expect(';');
414
608
 
415
609
  const parsedNumber = Number.parseInt(numberToken.value, 10);
@@ -420,6 +614,7 @@ class ProtobufParser {
420
614
  map: { keyType, valueType },
421
615
  number: Number.isNaN(parsedNumber) ? undefined : parsedNumber,
422
616
  doc: keyword.doc || this.getTrailingDoc(terminator.line),
617
+ ...(options.length > 0 ? { options } : {}),
423
618
  };
424
619
  }
425
620
 
@@ -452,7 +647,7 @@ class ProtobufParser {
452
647
  const valueToken = this.next();
453
648
  this.expect('=');
454
649
  const numberToken = this.next();
455
- this.skipFieldOptions();
650
+ this.parseFieldOptions();
456
651
  const terminator = this.expect(';');
457
652
 
458
653
  const parsedNumber = Number.parseInt(numberToken.value, 10);
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "SEE LICENSE IN LICENSE",
9
9
  "type": "module",
10
- "version": "4.10.13",
10
+ "version": "4.10.15",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -32,10 +32,10 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@ai-sdk/react": "^3.0.17",
35
- "@astrojs/markdown-remark": "^7.2.4",
36
- "@astrojs/mdx": "^7.0.8",
37
- "@astrojs/node": "^11.1.4",
38
- "@astrojs/react": "^6.0.4",
35
+ "@astrojs/markdown-remark": "^7.3.1",
36
+ "@astrojs/mdx": "^8.0.1",
37
+ "@astrojs/node": "^11.1.6",
38
+ "@astrojs/react": "^6.0.6",
39
39
  "@astrojs/rss": "^4.0.19",
40
40
  "@asyncapi/avro-schema-parser": "3.0.24",
41
41
  "@asyncapi/parser": "^3.6.0",
@@ -61,7 +61,7 @@
61
61
  "@tanstack/react-table": "^8.17.3",
62
62
  "@xyflow/react": "^12.3.6",
63
63
  "ai": "^6.0.17",
64
- "astro": "7.2.8",
64
+ "astro": "7.3.3",
65
65
  "astro-expressive-code": "^0.44.0",
66
66
  "astro-seo": "^0.8.4",
67
67
  "auth-astro": "^4.2.0",
@@ -120,8 +120,8 @@
120
120
  "uuid": "^11.1.1",
121
121
  "zod": "^4.3.6",
122
122
  "@eventcatalog/linter": "1.1.19",
123
- "@eventcatalog/visualiser": "^4.1.4",
124
- "@eventcatalog/sdk": "2.29.0"
123
+ "@eventcatalog/sdk": "2.29.0",
124
+ "@eventcatalog/visualiser": "^4.1.4"
125
125
  },
126
126
  "devDependencies": {
127
127
  "@astrojs/check": "^0.9.10",