docusaurus-plugin-generate-schema-docs 1.8.3 → 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 (30) 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 +6 -0
  4. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +6 -0
  5. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +15 -0
  6. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +6 -0
  7. package/__tests__/components/PropertiesTable.test.js +66 -0
  8. package/__tests__/components/PropertyRow.test.js +85 -4
  9. package/__tests__/components/SchemaJsonViewer.test.js +118 -0
  10. package/__tests__/helpers/example-helper.test.js +12 -0
  11. package/__tests__/helpers/getConstraints.test.js +16 -0
  12. package/__tests__/helpers/processSchema.test.js +18 -0
  13. package/__tests__/helpers/schemaTraversal.test.js +110 -0
  14. package/__tests__/syncGtm.test.js +227 -3
  15. package/__tests__/validateSchemas.test.js +50 -0
  16. package/components/PropertiesTable.js +32 -2
  17. package/components/PropertyRow.js +29 -2
  18. package/components/SchemaJsonViewer.js +234 -131
  19. package/components/SchemaRows.css +40 -0
  20. package/components/SchemaViewer.js +11 -2
  21. package/helpers/example-helper.js +2 -2
  22. package/helpers/getConstraints.js +20 -0
  23. package/helpers/processSchema.js +32 -1
  24. package/helpers/schema-doc-template.js +4 -0
  25. package/helpers/schemaToExamples.js +29 -35
  26. package/helpers/schemaTraversal.cjs +148 -0
  27. package/package.json +1 -1
  28. package/scripts/sync-gtm.js +41 -28
  29. package/test-data/payloadContracts.js +35 -0
  30. package/validateSchemas.js +1 -1
@@ -26,6 +26,19 @@ const getContainerSymbol = (containerType) => {
26
26
  return '';
27
27
  };
28
28
 
29
+ const formatPropertyType = (value) => {
30
+ if (Array.isArray(value)) {
31
+ return value.join(', ');
32
+ }
33
+ if (typeof value === 'string') {
34
+ return value;
35
+ }
36
+ if (value === undefined || value === null) {
37
+ return '';
38
+ }
39
+ return JSON.stringify(value);
40
+ };
41
+
29
42
  const KEYWORD_HELP_TEXT = {
30
43
  additionalProperties:
31
44
  'Controls properties not listed in properties and not matched by patternProperties.',
@@ -33,6 +46,8 @@ const KEYWORD_HELP_TEXT = {
33
46
  'Applies the subschema to property names that match the given regular expression.',
34
47
  };
35
48
 
49
+ const SCHEMA_KEYWORD_BADGE_TEXT = 'Schema constraint';
50
+
36
51
  function splitKeywordLabel(name) {
37
52
  const match = /^patternProperties (\/.+\/)$/.exec(name);
38
53
  if (!match) {
@@ -45,6 +60,15 @@ function splitKeywordLabel(name) {
45
60
  };
46
61
  }
47
62
 
63
+ function buildKeywordHelpId(name, rowPath) {
64
+ if (!rowPath || rowPath.length === 0) {
65
+ return `schema-keyword-help-${name}`;
66
+ }
67
+
68
+ const normalizedPath = rowPath.join('-').replace(/[^a-zA-Z0-9_-]/g, '_');
69
+ return `schema-keyword-help-${normalizedPath}`;
70
+ }
71
+
48
72
  /**
49
73
  * Renders a single property row in the schema table.
50
74
  * All data is passed in via the `row` prop, which comes from `tableData`.
@@ -145,7 +169,7 @@ export default function PropertyRow({
145
169
  : name;
146
170
  const keywordHelpText = KEYWORD_HELP_TEXT[keywordHelpKey];
147
171
  const keywordHelpId = keywordHelpText
148
- ? `schema-keyword-help-${name}`
172
+ ? buildKeywordHelpId(name, row.path)
149
173
  : undefined;
150
174
  const zebraClassName =
151
175
  stripeIndex === undefined
@@ -208,6 +232,9 @@ export default function PropertyRow({
208
232
  {name}
209
233
  </code>
210
234
  )}
235
+ <span className="property-keyword-badge">
236
+ {SCHEMA_KEYWORD_BADGE_TEXT}
237
+ </span>
211
238
  <span
212
239
  id={keywordHelpId}
213
240
  className="property-keyword-tooltip"
@@ -222,7 +249,7 @@ export default function PropertyRow({
222
249
  </span>
223
250
  </td>
224
251
  <td rowSpan={rowSpan}>
225
- <code>{propertyType}</code>
252
+ <code>{formatPropertyType(propertyType)}</code>
226
253
  </td>
227
254
 
228
255
  {/* The first constraint cell */}
@@ -1,14 +1,43 @@
1
- import React, { Fragment, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import Link from '@docusaurus/Link';
3
+ import { usePrismTheme } from '@docusaurus/theme-common';
4
+ import { Highlight } from 'prism-react-renderer';
3
5
 
4
- function isPlainObject(value) {
5
- return (
6
- value !== null &&
7
- typeof value === 'object' &&
8
- !Array.isArray(value) &&
9
- Object.getPrototypeOf(value) === Object.prototype
10
- );
11
- }
6
+ const SCHEMA_META_KEYS = [
7
+ '$schema',
8
+ '$id',
9
+ '$anchor',
10
+ '$dynamicAnchor',
11
+ '$comment',
12
+ '$vocabulary',
13
+ ];
14
+
15
+ const SCHEMA_STRUCTURAL_KEYS = new Set([
16
+ '$ref',
17
+ '$defs',
18
+ 'properties',
19
+ 'required',
20
+ 'allOf',
21
+ 'anyOf',
22
+ 'oneOf',
23
+ 'if',
24
+ 'then',
25
+ 'else',
26
+ 'not',
27
+ 'items',
28
+ 'prefixItems',
29
+ 'contains',
30
+ 'dependentSchemas',
31
+ 'patternProperties',
32
+ 'additionalProperties',
33
+ ]);
34
+
35
+ const SCHEMA_NAME_MAP_KEYS = new Set([
36
+ 'properties',
37
+ 'patternProperties',
38
+ '$defs',
39
+ 'dependentSchemas',
40
+ ]);
12
41
 
13
42
  function isExternalRef(value) {
14
43
  return typeof value === 'string' && /^https?:\/\//.test(value);
@@ -51,137 +80,189 @@ function resolveLocalRef(currentPath, refValue) {
51
80
  return normalizePathSegments(`${dirname(currentPath)}/${refValue}`);
52
81
  }
53
82
 
54
- function JsonIndent({ depth }) {
55
- return <span>{' '.repeat(depth)}</span>;
83
+ function getSchemaKeywordClassName(key, parentKey) {
84
+ if (parentKey && SCHEMA_NAME_MAP_KEYS.has(parentKey)) {
85
+ return '';
86
+ }
87
+
88
+ if (SCHEMA_META_KEYS.includes(key)) {
89
+ return 'schema-json-viewer__keyword schema-json-viewer__keyword--meta';
90
+ }
91
+
92
+ if (SCHEMA_STRUCTURAL_KEYS.has(key)) {
93
+ return 'schema-json-viewer__keyword schema-json-viewer__keyword--structural';
94
+ }
95
+
96
+ return '';
56
97
  }
57
98
 
58
- function JsonPrimitive({
59
- value,
60
- propertyKey,
61
- currentPath,
62
- schemaSources,
63
- onNavigate,
64
- }) {
65
- const interactiveRefClassName = 'schema-json-viewer__link';
66
- const quotedValue = `${JSON.stringify(value)}`;
67
-
68
- if (typeof value === 'string') {
69
- if (propertyKey === '$ref') {
70
- if (isExternalRef(value)) {
71
- return (
72
- <Link
73
- className={interactiveRefClassName}
74
- href={value}
75
- target="_blank"
76
- rel="noreferrer"
77
- >
78
- {quotedValue}
79
- </Link>
80
- );
81
- }
82
-
83
- const resolvedRef = resolveLocalRef(currentPath, value);
84
- if (resolvedRef && schemaSources?.[resolvedRef]) {
85
- return (
86
- <button
87
- type="button"
88
- className={interactiveRefClassName}
89
- onClick={() => onNavigate(resolvedRef)}
90
- >
91
- {quotedValue}
92
- </button>
93
- );
94
- }
99
+ function joinClassNames(...classNames) {
100
+ return classNames.filter(Boolean).join(' ');
101
+ }
102
+
103
+ function createParserState() {
104
+ return {
105
+ stack: [],
106
+ };
107
+ }
108
+
109
+ function beginNestedValue(state, tokenContent) {
110
+ const currentContext = state.stack[state.stack.length - 1];
111
+ let parentKey = null;
112
+
113
+ if (currentContext?.type === 'object' && currentContext.afterColon) {
114
+ parentKey = currentContext.currentKey;
115
+ currentContext.currentKey = null;
116
+ currentContext.afterColon = false;
117
+ }
118
+
119
+ state.stack.push({
120
+ type: tokenContent === '{' ? 'object' : 'array',
121
+ parentKey,
122
+ currentKey: null,
123
+ afterColon: false,
124
+ });
125
+ }
126
+
127
+ function classifyRenderedToken(state, token) {
128
+ const currentContext = state.stack[state.stack.length - 1];
129
+ const tokenTypes = new Set(token.types);
130
+ const content = token.content;
131
+ const semantic = {};
132
+
133
+ if (!content) {
134
+ return semantic;
135
+ }
136
+
137
+ if (tokenTypes.has('property')) {
138
+ const key = JSON.parse(content);
139
+ semantic.propertyKey = key;
140
+ semantic.parentKey = currentContext?.parentKey ?? null;
141
+
142
+ if (currentContext?.type === 'object') {
143
+ currentContext.currentKey = key;
144
+ currentContext.afterColon = false;
95
145
  }
96
146
 
97
- return <span className="token string">{quotedValue}</span>;
147
+ return semantic;
148
+ }
149
+
150
+ if (content === ':') {
151
+ if (
152
+ currentContext?.type === 'object' &&
153
+ currentContext.currentKey !== null
154
+ ) {
155
+ currentContext.afterColon = true;
156
+ }
157
+ return semantic;
98
158
  }
99
159
 
100
- if (typeof value === 'number') {
101
- return <span className="token number">{value}</span>;
160
+ if (content === '{' || content === '[') {
161
+ beginNestedValue(state, content);
162
+ return semantic;
102
163
  }
103
164
 
104
- if (typeof value === 'boolean') {
105
- return <span className="token boolean">{String(value)}</span>;
165
+ if (content === '}' || content === ']') {
166
+ state.stack.pop();
167
+ return semantic;
106
168
  }
107
169
 
108
- return <span className="token null keyword">null</span>;
170
+ if (content === ',') {
171
+ if (currentContext?.type === 'object') {
172
+ currentContext.currentKey = null;
173
+ currentContext.afterColon = false;
174
+ }
175
+ return semantic;
176
+ }
177
+
178
+ if (currentContext?.type === 'object' && currentContext.afterColon) {
179
+ if (tokenTypes.has('string') && content.trim().startsWith('"')) {
180
+ semantic.valueKey = currentContext.currentKey;
181
+ semantic.stringValue = JSON.parse(content);
182
+ currentContext.currentKey = null;
183
+ currentContext.afterColon = false;
184
+ return semantic;
185
+ }
186
+
187
+ if (
188
+ tokenTypes.has('number') ||
189
+ tokenTypes.has('boolean') ||
190
+ (tokenTypes.has('keyword') && content === 'null')
191
+ ) {
192
+ currentContext.currentKey = null;
193
+ currentContext.afterColon = false;
194
+ }
195
+ }
196
+
197
+ return semantic;
109
198
  }
110
199
 
111
- function JsonNode({
112
- value,
113
- depth,
200
+ function renderToken({
201
+ token,
202
+ tokenIndex,
203
+ getTokenProps,
204
+ semantic,
114
205
  currentPath,
115
206
  schemaSources,
116
207
  onNavigate,
117
- propertyKey = null,
118
208
  }) {
119
- if (Array.isArray(value)) {
120
- return (
121
- <>
122
- <span className="token punctuation">[</span>
123
- {value.length > 0 && <br />}
124
- {value.map((item, index) => (
125
- <Fragment key={`${depth}-${index}`}>
126
- <JsonIndent depth={depth + 1} />
127
- <JsonNode
128
- value={item}
129
- depth={depth + 1}
130
- currentPath={currentPath}
131
- schemaSources={schemaSources}
132
- onNavigate={onNavigate}
133
- />
134
- {index < value.length - 1 && (
135
- <span className="token punctuation">,</span>
136
- )}
137
- <br />
138
- </Fragment>
139
- ))}
140
- {value.length > 0 && <JsonIndent depth={depth} />}
141
- <span className="token punctuation">]</span>
142
- </>
143
- );
144
- }
209
+ const tokenProps = getTokenProps({ token, key: tokenIndex });
210
+ const propertyKeyClassName = semantic.propertyKey
211
+ ? getSchemaKeywordClassName(semantic.propertyKey, semantic.parentKey)
212
+ : '';
213
+ const className = joinClassNames(tokenProps.className, propertyKeyClassName);
214
+
215
+ if (
216
+ semantic.valueKey === '$ref' &&
217
+ typeof semantic.stringValue === 'string'
218
+ ) {
219
+ if (isExternalRef(semantic.stringValue)) {
220
+ return (
221
+ <Link
222
+ key={tokenIndex}
223
+ className={joinClassNames(
224
+ className,
225
+ 'schema-json-viewer__link',
226
+ 'schema-json-viewer__ref-link',
227
+ )}
228
+ style={tokenProps.style}
229
+ href={semantic.stringValue}
230
+ target="_blank"
231
+ rel="noreferrer"
232
+ >
233
+ {token.content}
234
+ </Link>
235
+ );
236
+ }
145
237
 
146
- if (isPlainObject(value)) {
147
- const entries = Object.entries(value);
148
- return (
149
- <>
150
- <span className="token punctuation">{'{'}</span>
151
- {entries.length > 0 && <br />}
152
- {entries.map(([key, child], index) => (
153
- <Fragment key={`${depth}-${key}`}>
154
- <JsonIndent depth={depth + 1} />
155
- <span className="token property">{JSON.stringify(key)}</span>
156
- <span className="token punctuation">: </span>
157
- <JsonNode
158
- value={child}
159
- depth={depth + 1}
160
- currentPath={currentPath}
161
- schemaSources={schemaSources}
162
- onNavigate={onNavigate}
163
- propertyKey={key}
164
- />
165
- {index < entries.length - 1 && (
166
- <span className="token punctuation">,</span>
167
- )}
168
- <br />
169
- </Fragment>
170
- ))}
171
- {entries.length > 0 && <JsonIndent depth={depth} />}
172
- <span className="token punctuation">{'}'}</span>
173
- </>
174
- );
238
+ const resolvedRef = resolveLocalRef(currentPath, semantic.stringValue);
239
+ if (resolvedRef && schemaSources?.[resolvedRef]) {
240
+ return (
241
+ <button
242
+ key={tokenIndex}
243
+ type="button"
244
+ className={joinClassNames(
245
+ className,
246
+ 'schema-json-viewer__link',
247
+ 'schema-json-viewer__ref-link',
248
+ )}
249
+ style={tokenProps.style}
250
+ onClick={() => onNavigate(resolvedRef)}
251
+ >
252
+ {token.content}
253
+ </button>
254
+ );
255
+ }
175
256
  }
176
257
 
177
258
  return (
178
- <JsonPrimitive
179
- value={value}
180
- propertyKey={propertyKey}
181
- currentPath={currentPath}
182
- schemaSources={schemaSources}
183
- onNavigate={onNavigate}
184
- />
259
+ <span
260
+ key={tokenIndex}
261
+ className={className || tokenProps.className}
262
+ style={tokenProps.style}
263
+ >
264
+ {token.content}
265
+ </span>
185
266
  );
186
267
  }
187
268
 
@@ -190,6 +271,7 @@ export default function SchemaJsonViewer({
190
271
  sourcePath = null,
191
272
  schemaSources = null,
192
273
  }) {
274
+ const prismTheme = usePrismTheme();
193
275
  const resolvedSchemaSources =
194
276
  schemaSources || (sourcePath ? { [sourcePath]: schema } : {});
195
277
  const rootPath = sourcePath;
@@ -197,6 +279,7 @@ export default function SchemaJsonViewer({
197
279
 
198
280
  const currentSchema =
199
281
  (currentPath && resolvedSchemaSources?.[currentPath]) || schema;
282
+ const formattedSchema = JSON.stringify(currentSchema, null, 2);
200
283
 
201
284
  return (
202
285
  <details className="schema-json-viewer">
@@ -212,17 +295,37 @@ export default function SchemaJsonViewer({
212
295
  </button>
213
296
  </div>
214
297
  ) : null}
215
- <pre data-language="json">
216
- <code className="language-json">
217
- <JsonNode
218
- value={currentSchema}
219
- depth={0}
220
- currentPath={currentPath}
221
- schemaSources={resolvedSchemaSources}
222
- onNavigate={setCurrentPath}
223
- />
224
- </code>
225
- </pre>
298
+ <Highlight code={formattedSchema} language="json" theme={prismTheme}>
299
+ {({ className, style, tokens, getLineProps, getTokenProps }) => {
300
+ const parserState = createParserState();
301
+
302
+ return (
303
+ <pre className={className} style={style} data-language="json">
304
+ <code className="language-json">
305
+ {tokens.map((line, lineIndex) => (
306
+ <span
307
+ key={lineIndex}
308
+ {...getLineProps({ line, key: lineIndex })}
309
+ >
310
+ {line.map((token, tokenIndex) =>
311
+ renderToken({
312
+ token,
313
+ tokenIndex,
314
+ getTokenProps,
315
+ semantic: classifyRenderedToken(parserState, token),
316
+ currentPath,
317
+ schemaSources: resolvedSchemaSources,
318
+ onNavigate: setCurrentPath,
319
+ }),
320
+ )}
321
+ {'\n'}
322
+ </span>
323
+ ))}
324
+ </code>
325
+ </pre>
326
+ );
327
+ }}
328
+ </Highlight>
226
329
  </details>
227
330
  );
228
331
  }
@@ -46,6 +46,29 @@
46
46
  text-decoration: underline;
47
47
  }
48
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
+
49
72
  /* --- Property Name and Container Symbol Styles --- */
50
73
 
51
74
  .property-name {
@@ -62,6 +85,8 @@
62
85
  position: relative;
63
86
  display: inline-flex;
64
87
  align-items: center;
88
+ flex-wrap: wrap;
89
+ gap: 0.35rem;
65
90
  }
66
91
 
67
92
  .property-keyword-stack {
@@ -76,6 +101,21 @@
76
101
  font-weight: 600;
77
102
  }
78
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
+
79
119
  .property-keyword-pattern {
80
120
  font-size: 0.85em;
81
121
  color: var(--ifm-color-emphasis-700);
@@ -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
  }
@@ -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
  }
@@ -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
  }