docusaurus-plugin-generate-schema-docs 1.8.2 → 1.8.4

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 (43) hide show
  1. package/README.md +2 -0
  2. package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
  3. package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +21 -3
  4. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +26 -4
  5. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +45 -6
  6. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +16 -2
  7. package/__tests__/components/ConditionalRows.test.js +28 -0
  8. package/__tests__/components/FoldableRows.test.js +31 -290
  9. package/__tests__/components/PropertiesTable.test.js +66 -0
  10. package/__tests__/components/PropertyRow.test.js +297 -0
  11. package/__tests__/components/SchemaJsonViewer.test.js +194 -10
  12. package/__tests__/components/SchemaRows.test.js +62 -12
  13. package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
  14. package/__tests__/generateEventDocs.test.js +3 -0
  15. package/__tests__/helpers/example-helper.test.js +12 -0
  16. package/__tests__/helpers/getConstraints.test.js +16 -0
  17. package/__tests__/helpers/processSchema.test.js +18 -0
  18. package/__tests__/helpers/schemaToTableData.test.js +112 -0
  19. package/__tests__/helpers/schemaTraversal.test.js +110 -0
  20. package/__tests__/syncGtm.test.js +227 -3
  21. package/__tests__/validateSchemas.test.js +50 -0
  22. package/components/ConditionalRows.js +6 -3
  23. package/components/FoldableRows.js +9 -3
  24. package/components/PropertiesTable.js +34 -3
  25. package/components/PropertyRow.js +118 -6
  26. package/components/SchemaJsonViewer.js +324 -4
  27. package/components/SchemaRows.css +138 -7
  28. package/components/SchemaRows.js +11 -1
  29. package/components/SchemaViewer.js +11 -2
  30. package/generateEventDocs.js +87 -1
  31. package/helpers/choice-index-template.js +6 -2
  32. package/helpers/example-helper.js +2 -2
  33. package/helpers/file-system.js +28 -0
  34. package/helpers/getConstraints.js +20 -0
  35. package/helpers/processSchema.js +32 -1
  36. package/helpers/schema-doc-template.js +11 -1
  37. package/helpers/schemaToExamples.js +29 -35
  38. package/helpers/schemaToTableData.js +68 -7
  39. package/helpers/schemaTraversal.cjs +148 -0
  40. package/package.json +1 -1
  41. package/scripts/sync-gtm.js +41 -28
  42. package/test-data/payloadContracts.js +35 -0
  43. package/validateSchemas.js +1 -1
@@ -1,4 +1,4 @@
1
- .required-row {
1
+ .property-cell--required {
2
2
  background-color: rgba(var(--ifm-color-danger-rgb), 0.05);
3
3
  }
4
4
 
@@ -26,6 +26,49 @@
26
26
  margin-bottom: 1em;
27
27
  }
28
28
 
29
+ .schema-json-viewer__controls {
30
+ margin: 0.5rem 0;
31
+ }
32
+
33
+ .schema-json-viewer__link {
34
+ appearance: none;
35
+ background: none;
36
+ border: none;
37
+ color: var(--ifm-link-color);
38
+ cursor: pointer;
39
+ font: inherit;
40
+ padding: 0;
41
+ text-decoration: none;
42
+ }
43
+
44
+ .schema-json-viewer__link:hover {
45
+ color: var(--ifm-link-hover-color);
46
+ text-decoration: underline;
47
+ }
48
+
49
+ .schema-json-viewer__ref-link {
50
+ text-decoration: underline;
51
+ text-decoration-thickness: 1.5px;
52
+ text-underline-offset: 0.14em;
53
+ }
54
+
55
+ .schema-json-viewer__ref-link:hover {
56
+ color: var(--ifm-link-hover-color) !important;
57
+ text-decoration-thickness: 2px;
58
+ }
59
+
60
+ .schema-json-viewer__keyword {
61
+ font-weight: 600;
62
+ }
63
+
64
+ .schema-json-viewer__keyword--meta {
65
+ color: var(--ifm-color-info-light);
66
+ }
67
+
68
+ .schema-json-viewer__keyword--structural {
69
+ color: var(--ifm-color-success-light);
70
+ }
71
+
29
72
  /* --- Property Name and Container Symbol Styles --- */
30
73
 
31
74
  .property-name {
@@ -34,6 +77,78 @@
34
77
  gap: 4px;
35
78
  }
36
79
 
80
+ .property-name--keyword {
81
+ align-items: center;
82
+ }
83
+
84
+ .property-keyword-wrapper {
85
+ position: relative;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ flex-wrap: wrap;
89
+ gap: 0.35rem;
90
+ }
91
+
92
+ .property-keyword-stack {
93
+ display: inline-flex;
94
+ flex-direction: column;
95
+ align-items: flex-start;
96
+ gap: 0.2rem;
97
+ }
98
+
99
+ .property-keyword {
100
+ font-size: 0.95em;
101
+ font-weight: 600;
102
+ }
103
+
104
+ .property-keyword-badge {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ padding: 0.1rem 0.4rem;
108
+ border-radius: 999px;
109
+ border: 1px solid var(--ifm-table-border-color);
110
+ background-color: var(--ifm-color-emphasis-100);
111
+ color: var(--ifm-color-emphasis-800);
112
+ font-size: 0.68rem;
113
+ font-weight: 700;
114
+ letter-spacing: 0.02em;
115
+ text-transform: uppercase;
116
+ white-space: nowrap;
117
+ }
118
+
119
+ .property-keyword-pattern {
120
+ font-size: 0.85em;
121
+ color: var(--ifm-color-emphasis-700);
122
+ }
123
+
124
+ .property-keyword-tooltip {
125
+ position: absolute;
126
+ left: 0;
127
+ bottom: calc(100% + 0.4rem);
128
+ z-index: 3;
129
+ min-width: 14rem;
130
+ max-width: 18rem;
131
+ padding: 0.5rem 0.625rem;
132
+ border-radius: 0.375rem;
133
+ background: var(--ifm-background-surface-color);
134
+ border: 1px solid var(--ifm-table-border-color);
135
+ box-shadow: var(--ifm-global-shadow-lw);
136
+ color: var(--ifm-font-color-base);
137
+ font-size: 0.8rem;
138
+ line-height: 1.35;
139
+ opacity: 0;
140
+ pointer-events: none;
141
+ transform: translateY(0.125rem);
142
+ transition:
143
+ opacity 120ms ease,
144
+ transform 120ms ease;
145
+ }
146
+
147
+ .property-keyword-wrapper:hover .property-keyword-tooltip {
148
+ opacity: 1;
149
+ transform: translateY(0);
150
+ }
151
+
37
152
  .container-symbol {
38
153
  font-family: var(--ifm-font-family-monospace);
39
154
  font-size: 0.9em;
@@ -67,6 +182,10 @@
67
182
  border-left: none;
68
183
  }
69
184
 
185
+ .schema-table td[colspan='5'] {
186
+ border-left: none;
187
+ }
188
+
70
189
  /*
71
190
  * Constraint-only continuation rows render a single cell; in those rows that
72
191
  * cell is also :first-child, but it still needs the separator before the
@@ -85,6 +204,18 @@
85
204
  box-shadow: inset 0 1px 0 var(--ifm-table-border-color);
86
205
  }
87
206
 
207
+ .schema-table tbody tr.schema-row--zebra-even {
208
+ background-color: var(--ifm-table-stripe-background);
209
+ }
210
+
211
+ .schema-table tbody tr.schema-row--zebra-odd {
212
+ background-color: transparent;
213
+ }
214
+
215
+ .schema-table tbody tr.schema-row--control {
216
+ background-color: transparent;
217
+ }
218
+
88
219
  /* --- Organigram Connector Line Styles --- */
89
220
 
90
221
  /*
@@ -108,10 +239,12 @@ td.has-children {
108
239
  td[class*='level-']::before {
109
240
  content: '';
110
241
  position: absolute;
111
- top: 0;
112
- bottom: 0;
242
+ top: -1px;
243
+ bottom: -1px;
113
244
  width: 0;
114
245
  border-left: 1px solid var(--ifm-table-border-color);
246
+ z-index: 1;
247
+ pointer-events: none;
115
248
  }
116
249
 
117
250
  /* Last items: stop vertical line at middle */
@@ -130,6 +263,8 @@ td[class*='level-']::after {
130
263
  height: 0;
131
264
  width: 0.75rem;
132
265
  border-bottom: 1px solid var(--ifm-table-border-color);
266
+ z-index: 1;
267
+ pointer-events: none;
133
268
  }
134
269
 
135
270
  /* --- Level-based positioning --- */
@@ -172,10 +307,6 @@ td.level-6::after {
172
307
 
173
308
  /* --- Choice Row Styles --- */
174
309
 
175
- .choice-row {
176
- background-color: var(--ifm-table-stripe-background);
177
- }
178
-
179
310
  .choice-row:hover {
180
311
  background-color: var(--ifm-hover-overlay);
181
312
  }
@@ -12,7 +12,11 @@ import ConditionalRows from './ConditionalRows';
12
12
  * @param {Array} props.tableData - Flat array of row objects
13
13
  * @param {Array} [props.bracketEnds] - Bracket descriptors that end on the last row
14
14
  */
15
- export default function SchemaRows({ tableData, bracketEnds }) {
15
+ export default function SchemaRows({
16
+ tableData,
17
+ bracketEnds,
18
+ stripeState = { current: 0 },
19
+ }) {
16
20
  if (!tableData) {
17
21
  return null;
18
22
  }
@@ -20,12 +24,15 @@ export default function SchemaRows({ tableData, bracketEnds }) {
20
24
  return tableData.map((row, index) => {
21
25
  const key = row.path.join('.');
22
26
  const isLast = index === tableData.length - 1;
27
+ const stripeIndex = stripeState.current++;
23
28
 
24
29
  if (row.type === 'choice') {
25
30
  return (
26
31
  <FoldableRows
27
32
  key={key}
28
33
  row={row}
34
+ stripeIndex={stripeIndex}
35
+ stripeState={stripeState}
29
36
  bracketEnds={isLast ? bracketEnds : undefined}
30
37
  />
31
38
  );
@@ -36,6 +43,8 @@ export default function SchemaRows({ tableData, bracketEnds }) {
36
43
  <ConditionalRows
37
44
  key={key}
38
45
  row={row}
46
+ stripeIndex={stripeIndex}
47
+ stripeState={stripeState}
39
48
  bracketEnds={isLast ? bracketEnds : undefined}
40
49
  />
41
50
  );
@@ -46,6 +55,7 @@ export default function SchemaRows({ tableData, bracketEnds }) {
46
55
  <PropertyRow
47
56
  key={key}
48
57
  row={row}
58
+ stripeIndex={stripeIndex}
49
59
  isLastInGroup={row.isLastInGroup}
50
60
  bracketEnds={isLast ? bracketEnds : undefined}
51
61
  />
@@ -3,7 +3,16 @@ import Heading from '@theme/Heading';
3
3
  import ExampleDataLayer from './ExampleDataLayer';
4
4
  import PropertiesTable from './PropertiesTable';
5
5
 
6
- export default function SchemaViewer({ schema, dataLayerName }) {
6
+ export default function SchemaViewer({
7
+ schema,
8
+ sourceSchema,
9
+ sourcePath,
10
+ schemaSources,
11
+ dataLayerName,
12
+ }) {
13
+ const resolvedSourceSchema =
14
+ (sourcePath && schemaSources?.[sourcePath]) || sourceSchema || schema;
15
+
7
16
  const hasOneOfAnyOf =
8
17
  schema.oneOf ||
9
18
  schema.anyOf ||
@@ -21,7 +30,7 @@ export default function SchemaViewer({ schema, dataLayerName }) {
21
30
  <ExampleDataLayer schema={schema} dataLayerName={dataLayerName} />
22
31
 
23
32
  <Heading as="h2">Event Properties</Heading>
24
- <PropertiesTable schema={schema} />
33
+ <PropertiesTable schema={schema} sourceSchema={resolvedSourceSchema} />
25
34
  </div>
26
35
  );
27
36
  }
@@ -1,7 +1,12 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import { getPathsForVersion } from './helpers/path-helpers.js';
4
- import { readSchemas, writeDoc, createDir } from './helpers/file-system.js';
4
+ import {
5
+ readSchemas,
6
+ readSchemaSources,
7
+ writeDoc,
8
+ createDir,
9
+ } from './helpers/file-system.js';
5
10
  import { processOneOfSchema, slugify } from './helpers/schema-processing.js';
6
11
  import SchemaDocTemplate from './helpers/schema-doc-template.js';
7
12
  import ChoiceIndexTemplate from './helpers/choice-index-template.js';
@@ -37,6 +42,64 @@ function resolvePartial({
37
42
  };
38
43
  }
39
44
 
45
+ function isPlainObject(value) {
46
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
47
+ }
48
+
49
+ function toSchemaSourceKey(schemaDir, filePath) {
50
+ return path.relative(schemaDir, filePath).split(path.sep).join('/');
51
+ }
52
+
53
+ function resolveLocalSchemaRef(currentPath, ref) {
54
+ if (!ref || ref.startsWith('#') || /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(ref)) {
55
+ return null;
56
+ }
57
+
58
+ const [fileRef] = ref.split('#');
59
+ return path
60
+ .normalize(path.join(path.dirname(currentPath), fileRef))
61
+ .split(path.sep)
62
+ .join('/');
63
+ }
64
+
65
+ function collectReachableSchemaSources(sourcePath, allSchemaSources) {
66
+ if (!sourcePath || !allSchemaSources[sourcePath]) return {};
67
+
68
+ const visited = new Set();
69
+ const reachableSources = {};
70
+
71
+ function walkSchema(currentPath) {
72
+ if (visited.has(currentPath) || !allSchemaSources[currentPath]) return;
73
+ visited.add(currentPath);
74
+
75
+ const schema = allSchemaSources[currentPath];
76
+ reachableSources[currentPath] = schema;
77
+
78
+ function walkNode(node) {
79
+ if (Array.isArray(node)) {
80
+ node.forEach(walkNode);
81
+ return;
82
+ }
83
+
84
+ if (!isPlainObject(node)) return;
85
+
86
+ if (typeof node.$ref === 'string') {
87
+ const resolvedRefPath = resolveLocalSchemaRef(currentPath, node.$ref);
88
+ if (resolvedRefPath) {
89
+ walkSchema(resolvedRefPath);
90
+ }
91
+ }
92
+
93
+ Object.values(node).forEach(walkNode);
94
+ }
95
+
96
+ walkNode(schema);
97
+ }
98
+
99
+ walkSchema(sourcePath);
100
+ return reachableSources;
101
+ }
102
+
40
103
  async function collectLeafEventNames(schema, filePath, eventNames) {
41
104
  if (!schema.oneOf) {
42
105
  eventNames.push(path.basename(filePath, '.json'));
@@ -86,6 +149,8 @@ async function generateAndWriteDoc(
86
149
  alreadyMergedSchema = null,
87
150
  editFilePath = null,
88
151
  partialNameConflicts = new Set(),
152
+ schemaSources = {},
153
+ schemaDir,
89
154
  ) {
90
155
  const { organizationName, projectName, siteDir, dataLayerName, version } =
91
156
  options;
@@ -134,6 +199,11 @@ async function generateAndWriteDoc(
134
199
  topPartialComponent: top.component,
135
200
  bottomPartialComponent: bottom.component,
136
201
  dataLayerName,
202
+ sourcePath: toSchemaSourceKey(schemaDir, editFilePath || filePath),
203
+ schemaSources: collectReachableSchemaSources(
204
+ toSchemaSourceKey(schemaDir, editFilePath || filePath),
205
+ schemaSources,
206
+ ),
137
207
  });
138
208
 
139
209
  const outputFilename = path.basename(filePath).replace('.json', '.mdx');
@@ -147,6 +217,8 @@ async function generateOneOfDocs(
147
217
  outputDir,
148
218
  options,
149
219
  partialNameConflicts,
220
+ schemaSources,
221
+ schemaDir,
150
222
  ) {
151
223
  const { organizationName, projectName, siteDir } = options;
152
224
  const editUrl = buildEditUrl(
@@ -165,6 +237,11 @@ async function generateOneOfDocs(
165
237
  schema,
166
238
  processedOptions: processed,
167
239
  editUrl,
240
+ sourcePath: toSchemaSourceKey(schemaDir, filePath),
241
+ schemaSources: collectReachableSchemaSources(
242
+ toSchemaSourceKey(schemaDir, filePath),
243
+ schemaSources,
244
+ ),
168
245
  });
169
246
  writeDoc(eventOutputDir, 'index.mdx', indexPageContent);
170
247
 
@@ -183,6 +260,8 @@ async function generateOneOfDocs(
183
260
  eventOutputDir,
184
261
  options,
185
262
  partialNameConflicts,
263
+ schemaSources,
264
+ schemaDir,
186
265
  );
187
266
  } else {
188
267
  await generateAndWriteDoc(
@@ -194,6 +273,8 @@ async function generateOneOfDocs(
194
273
  processedSchema,
195
274
  sourceFilePath || filePath,
196
275
  partialNameConflicts,
276
+ schemaSources,
277
+ schemaDir,
197
278
  );
198
279
  }
199
280
  }
@@ -205,6 +286,7 @@ export default async function generateEventDocs(options) {
205
286
 
206
287
  createDir(outputDir);
207
288
  const schemas = readSchemas(schemaDir);
289
+ const schemaSources = readSchemaSources(schemaDir);
208
290
  const partialNameConflicts = await getPartialNameConflicts(schemas);
209
291
 
210
292
  console.log(`🚀 Generating documentation for ${schemas.length} schemas...`);
@@ -229,6 +311,8 @@ export default async function generateEventDocs(options) {
229
311
  outputDir,
230
312
  options,
231
313
  partialNameConflicts,
314
+ schemaSources,
315
+ schemaDir,
232
316
  );
233
317
  } else {
234
318
  await generateAndWriteDoc(
@@ -240,6 +324,8 @@ export default async function generateEventDocs(options) {
240
324
  null,
241
325
  null,
242
326
  partialNameConflicts,
327
+ schemaSources,
328
+ schemaDir,
243
329
  );
244
330
  }
245
331
  }
@@ -1,5 +1,5 @@
1
1
  export default function ChoiceIndexTemplate(data) {
2
- const { schema, processedOptions, editUrl } = data;
2
+ const { schema, processedOptions, editUrl, sourcePath, schemaSources } = data;
3
3
 
4
4
  return `---
5
5
  title: ${schema.title}
@@ -18,6 +18,10 @@ ${processedOptions
18
18
  .map((option) => `- [${option.schema.title}](./${option.slug})`)
19
19
  .join('\n')}
20
20
 
21
- <SchemaJsonViewer schema={${JSON.stringify(schema)}} />
21
+ <SchemaJsonViewer
22
+ schema={${JSON.stringify(schema)}}
23
+ sourcePath={${JSON.stringify(sourcePath)}}
24
+ schemaSources={${JSON.stringify(schemaSources)}}
25
+ />
22
26
  `;
23
27
  }
@@ -5,7 +5,7 @@ export function getSingleExampleValue(propSchema) {
5
5
  if (propSchema.examples?.length > 0) {
6
6
  return propSchema.examples[0];
7
7
  }
8
- if (propSchema.example) {
8
+ if (Object.prototype.hasOwnProperty.call(propSchema, 'example')) {
9
9
  return propSchema.example;
10
10
  }
11
11
  if (Object.prototype.hasOwnProperty.call(propSchema, 'default')) {
@@ -25,7 +25,7 @@ export function getExamples(propSchema) {
25
25
  examples.push(...propSchema.examples);
26
26
  }
27
27
 
28
- if (propSchema.example) {
28
+ if (Object.prototype.hasOwnProperty.call(propSchema, 'example')) {
29
29
  if (!examples.includes(propSchema.example)) {
30
30
  examples.push(propSchema.example);
31
31
  }
@@ -23,6 +23,34 @@ export function readSchemas(directory) {
23
23
  });
24
24
  }
25
25
 
26
+ export function readSchemaSources(directory) {
27
+ const schemaSources = {};
28
+
29
+ function walk(currentDirectory) {
30
+ const entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
31
+
32
+ entries.forEach((entry) => {
33
+ const entryPath = path.join(currentDirectory, entry.name);
34
+ if (entry.isDirectory()) {
35
+ walk(entryPath);
36
+ return;
37
+ }
38
+
39
+ if (!entry.name.endsWith('.json')) return;
40
+ const relativePath = path
41
+ .relative(directory, entryPath)
42
+ .split(path.sep)
43
+ .join('/');
44
+ schemaSources[relativePath] = JSON.parse(
45
+ fs.readFileSync(entryPath, 'utf-8'),
46
+ );
47
+ });
48
+ }
49
+
50
+ walk(directory);
51
+ return schemaSources;
52
+ }
53
+
26
54
  export function writeDoc(outputDir, fileName, content) {
27
55
  fs.writeFileSync(path.join(outputDir, fileName), content);
28
56
  console.log(
@@ -1,4 +1,23 @@
1
1
  // A list of JSON Schema keywords that have simple key-value constraints.
2
+ function formatInlineConstraintValue(value) {
3
+ if (Array.isArray(value)) {
4
+ return `[${value.map((item) => formatInlineConstraintValue(item)).join(', ')}]`;
5
+ }
6
+
7
+ if (value && typeof value === 'object') {
8
+ const entries = Object.entries(value).map(
9
+ ([key, nested]) => `${key}: ${formatInlineConstraintValue(nested)}`,
10
+ );
11
+ return `{ ${entries.join(', ')} }`;
12
+ }
13
+
14
+ return JSON.stringify(value);
15
+ }
16
+
17
+ function formatNotConstraint(val) {
18
+ return `not: ${formatInlineConstraintValue(val)}`;
19
+ }
20
+
2
21
  const constraintHandlers = {
3
22
  // Simple key-value constraints
4
23
  minLength: (val) => `minLength: ${val}`,
@@ -32,6 +51,7 @@ const constraintHandlers = {
32
51
  contains: (val) => `contains: ${JSON.stringify(val)}`,
33
52
  enum: (val) => `enum: [${val.join(', ')}]`,
34
53
  const: (val) => `const: ${JSON.stringify(val)}`,
54
+ not: (val) => formatNotConstraint(val),
35
55
  };
36
56
 
37
57
  export const getConstraints = (prop, isReq) => {
@@ -3,6 +3,37 @@ import fs from 'fs';
3
3
  import { resolveConstraintSchemaPath } from './constraintSchemaPaths.js';
4
4
  import { mergeSchema } from './mergeSchema.js';
5
5
 
6
+ function unwrapRedundantNotAnyOf(node) {
7
+ if (!node || typeof node !== 'object') {
8
+ return node;
9
+ }
10
+
11
+ if (Array.isArray(node)) {
12
+ return node.map(unwrapRedundantNotAnyOf);
13
+ }
14
+
15
+ const normalized = {};
16
+ for (const [key, value] of Object.entries(node)) {
17
+ normalized[key] = unwrapRedundantNotAnyOf(value);
18
+ }
19
+
20
+ if (normalized.not && typeof normalized.not === 'object') {
21
+ let candidate = normalized.not;
22
+ while (
23
+ candidate &&
24
+ typeof candidate === 'object' &&
25
+ !Array.isArray(candidate) &&
26
+ Array.isArray(candidate.anyOf) &&
27
+ candidate.anyOf.length === 1
28
+ ) {
29
+ candidate = candidate.anyOf[0];
30
+ }
31
+ normalized.not = candidate;
32
+ }
33
+
34
+ return normalized;
35
+ }
36
+
6
37
  /**
7
38
  * Processes a JSON schema file by bundling external references,
8
39
  * dereferencing internal references, and merging allOf properties.
@@ -41,5 +72,5 @@ export default async function processSchema(filePath) {
41
72
  // Then merge allOf properties
42
73
  const mergedSchema = mergeSchema(dereferencedSchema);
43
74
 
44
- return mergedSchema;
75
+ return unwrapRedundantNotAnyOf(mergedSchema);
45
76
  }
@@ -9,7 +9,10 @@ export default function MdxTemplate(data) {
9
9
  topPartialComponent,
10
10
  bottomPartialComponent,
11
11
  dataLayerName,
12
+ sourcePath,
13
+ schemaSources,
12
14
  } = data;
15
+ const sourceSchema = schemaSources?.[sourcePath] || schema;
13
16
 
14
17
  return `---
15
18
  title: ${schema.title}
@@ -31,9 +34,16 @@ ${topPartialComponent}
31
34
 
32
35
  <SchemaViewer
33
36
  schema={${JSON.stringify(mergedSchema)}}
37
+ sourceSchema={${JSON.stringify(sourceSchema)}}
38
+ sourcePath={${JSON.stringify(sourcePath)}}
39
+ schemaSources={${JSON.stringify(schemaSources)}}
34
40
  ${dataLayerName ? ` dataLayerName={'${dataLayerName}'}` : ''}
35
41
  />
36
- <SchemaJsonViewer schema={${JSON.stringify(schema)}} />
42
+ <SchemaJsonViewer
43
+ schema={${JSON.stringify(schema)}}
44
+ sourcePath={${JSON.stringify(sourcePath)}}
45
+ schemaSources={${JSON.stringify(schemaSources)}}
46
+ />
37
47
 
38
48
  ${bottomPartialComponent}
39
49
  `;
@@ -1,44 +1,31 @@
1
1
  import buildExampleFromSchema from './buildExampleFromSchema';
2
2
  import { mergeSchema } from './mergeSchema.js';
3
+ import traversalHelpers from './schemaTraversal.cjs';
3
4
 
4
- const findChoicePoints = (subSchema, path = []) => {
5
- if (!subSchema) {
6
- return [];
7
- }
5
+ const { visitSchemaNodes } = traversalHelpers;
8
6
 
9
- const choiceType = subSchema.oneOf
10
- ? 'oneOf'
11
- : subSchema.anyOf
12
- ? 'anyOf'
13
- : null;
14
- const currentChoice = choiceType ? [{ path, schema: subSchema }] : [];
7
+ const findChoicePoints = (schema) => {
8
+ const choicePoints = [];
15
9
 
16
- const nestedChoices = subSchema.properties
17
- ? Object.entries(subSchema.properties).flatMap(([key, propSchema]) =>
18
- findChoicePoints(propSchema, [...path, 'properties', key]),
19
- )
20
- : [];
10
+ visitSchemaNodes(schema, (subSchema, context) => {
11
+ if (subSchema.oneOf || subSchema.anyOf) {
12
+ choicePoints.push({ path: context.path, schema: subSchema });
13
+ }
14
+ });
21
15
 
22
- return [...currentChoice, ...nestedChoices];
16
+ return choicePoints;
23
17
  };
24
18
 
25
- const findConditionalPoints = (subSchema, path = []) => {
26
- if (!subSchema) {
27
- return [];
28
- }
29
-
30
- const currentConditional =
31
- subSchema.if && (subSchema.then || subSchema.else)
32
- ? [{ path, schema: subSchema }]
33
- : [];
19
+ const findConditionalPoints = (schema) => {
20
+ const conditionalPoints = [];
34
21
 
35
- const nestedConditionals = subSchema.properties
36
- ? Object.entries(subSchema.properties).flatMap(([key, propSchema]) =>
37
- findConditionalPoints(propSchema, [...path, 'properties', key]),
38
- )
39
- : [];
22
+ visitSchemaNodes(schema, (subSchema, context) => {
23
+ if (subSchema.if && (subSchema.then || subSchema.else)) {
24
+ conditionalPoints.push({ path: context.path, schema: subSchema });
25
+ }
26
+ });
40
27
 
41
- return [...currentConditional, ...nestedConditionals];
28
+ return conditionalPoints;
42
29
  };
43
30
 
44
31
  const generateExampleForChoice = (rootSchema, path, option) => {
@@ -151,10 +138,17 @@ export function schemaToExamples(rootSchema) {
151
138
 
152
139
  if (choicePoints.length === 0 && conditionalPoints.length === 0) {
153
140
  const example = buildExampleFromSchema(rootSchema);
154
- if (example && Object.keys(example).length > 0) {
155
- return [
156
- { property: 'default', options: [{ title: 'Example', example }] },
157
- ];
141
+ if (typeof example !== 'undefined') {
142
+ const shouldIncludeObjectExample =
143
+ typeof example !== 'object' ||
144
+ example === null ||
145
+ Object.keys(example).length > 0;
146
+
147
+ if (shouldIncludeObjectExample) {
148
+ return [
149
+ { property: 'default', options: [{ title: 'Example', example }] },
150
+ ];
151
+ }
158
152
  }
159
153
  return [];
160
154
  }