docusaurus-plugin-generate-schema-docs 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/__tests__/ExampleDataLayer.test.js +92 -0
- package/__tests__/__fixtures__/static/schemas/add-to-cart-event.json +44 -0
- package/__tests__/__fixtures__/static/schemas/choice-event.json +72 -0
- package/__tests__/__fixtures__/static/schemas/components/dataLayer.json +56 -0
- package/__tests__/__fixtures__/static/schemas/components/product.json +125 -0
- package/__tests__/__fixtures__/static/schemas/nested/child-event.json +10 -0
- package/__tests__/__fixtures__/static/schemas/nested/grandchild-a.json +9 -0
- package/__tests__/__fixtures__/static/schemas/nested/grandchild-b.json +9 -0
- package/__tests__/__fixtures__/static/schemas/nested/parent-event.json +7 -0
- package/__tests__/__fixtures__/static/schemas/root-any-of-event.json +34 -0
- package/__tests__/__fixtures__/static/schemas/root-choice-event.json +36 -0
- package/__tests__/__fixtures__/validateSchemas/circular-schema.json +8 -0
- package/__tests__/__fixtures__/validateSchemas/components/referenced.json +10 -0
- package/__tests__/__fixtures__/validateSchemas/invalid-example-schema.json +8 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-missing-ref.json +8 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-ref.json +8 -0
- package/__tests__/__fixtures__/validateSchemas/no-example-schema.json +12 -0
- package/__tests__/__fixtures__/validateSchemas/schema-A.json +7 -0
- package/__tests__/__fixtures__/validateSchemas/schema-B.json +7 -0
- package/__tests__/__fixtures__/validateSchemas/valid-schema.json +8 -0
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/add-to-cart-event.json +44 -0
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/dataLayer.json +56 -0
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/product.json +125 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/add-to-cart-event.json +44 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/components/dataLayer.json +56 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/components/product.json +125 -0
- package/__tests__/__fixtures_versioned__/versions.json +1 -0
- package/__tests__/__snapshots__/ExampleDataLayer.test.js.snap +117 -0
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +92 -0
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +151 -0
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +53 -0
- package/__tests__/components/FoldableRows.test.js +330 -0
- package/__tests__/components/PropertiesTable.test.js +41 -0
- package/__tests__/components/PropertyRow.test.js +487 -0
- package/__tests__/components/SchemaJsonViewer.test.js +36 -0
- package/__tests__/components/SchemaRows.test.js +110 -0
- package/__tests__/components/SchemaViewer.test.js +44 -0
- package/__tests__/components/TableHeader.test.js +20 -0
- package/__tests__/generateEventDocs.nested.test.js +80 -0
- package/__tests__/generateEventDocs.test.js +90 -0
- package/__tests__/generateEventDocs.versioned.test.js +69 -0
- package/__tests__/helpers/buildExampleFromSchema.test.js +188 -0
- package/__tests__/helpers/file-system.test.js +44 -0
- package/__tests__/helpers/getConstraints.test.js +58 -0
- package/__tests__/helpers/loadSchema.test.js +20 -0
- package/__tests__/helpers/path-helpers.test.js +34 -0
- package/__tests__/helpers/processSchema.test.js +56 -0
- package/__tests__/helpers/schema-processing.test.js +82 -0
- package/__tests__/helpers/schemaToExamples.test.js +56 -0
- package/__tests__/helpers/schemaToTableData.filtering.test.js +65 -0
- package/__tests__/helpers/schemaToTableData.hierarchicalLines.test.js +539 -0
- package/__tests__/helpers/schemaToTableData.test.js +222 -0
- package/__tests__/helpers/update-schema-ids.test.js +107 -0
- package/__tests__/update-schema-ids.test.js +39 -0
- package/__tests__/validateSchemas.test.js +137 -0
- package/components/ExampleDataLayer.js +60 -27
- package/components/FoldableRows.js +164 -0
- package/components/PropertiesTable.js +12 -14
- package/components/PropertyRow.js +183 -0
- package/components/SchemaJsonViewer.js +8 -7
- package/components/SchemaRows.css +250 -0
- package/components/SchemaRows.js +24 -69
- package/components/SchemaViewer.js +21 -13
- package/components/TableHeader.js +15 -0
- package/generateEventDocs.js +141 -60
- package/helpers/buildExampleFromSchema.js +59 -73
- package/helpers/choice-index-template.js +22 -0
- package/helpers/file-system.js +32 -0
- package/helpers/getConstraints.js +52 -0
- package/helpers/loadSchema.js +11 -0
- package/helpers/path-helpers.js +22 -0
- package/helpers/processSchema.js +32 -0
- package/helpers/schema-doc-template.js +36 -0
- package/helpers/schema-processing.js +75 -0
- package/helpers/schemaToExamples.js +99 -0
- package/helpers/schemaToTableData.js +311 -0
- package/helpers/update-schema-ids.js +47 -0
- package/index.js +146 -47
- package/package.json +6 -3
- package/validateSchemas.js +56 -70
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import $RefParser from '@apidevtools/json-schema-ref-parser';
|
|
2
|
+
import mergeJsonSchema from 'json-schema-merge-allof';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Processes a JSON schema file by bundling external references,
|
|
6
|
+
* dereferencing internal references, and merging allOf properties.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} filePath Path to the JSON schema file.
|
|
9
|
+
* @returns {Promise<object>} The processed (merged) schema.
|
|
10
|
+
*/
|
|
11
|
+
export default async function processSchema(filePath) {
|
|
12
|
+
// 1. Bundle all external references into a single, self-contained schema
|
|
13
|
+
const bundledSchema = await $RefParser.bundle(filePath, {
|
|
14
|
+
mutateInputSchema: false,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// 2. Dereference the bundled schema to resolve internal refs for allOf merging
|
|
18
|
+
const dereferencedSchema = await $RefParser.dereference(bundledSchema, {
|
|
19
|
+
dereference: {
|
|
20
|
+
circular: 'ignore', // Keep recursive parts as $refs
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Then merge allOf properties
|
|
25
|
+
const mergedSchema = mergeJsonSchema(dereferencedSchema, {
|
|
26
|
+
resolvers: {
|
|
27
|
+
defaultResolver: mergeJsonSchema.options.resolvers.title,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return mergedSchema;
|
|
32
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export default function MdxTemplate(data) {
|
|
2
|
+
const {
|
|
3
|
+
schema,
|
|
4
|
+
mergedSchema,
|
|
5
|
+
editUrl,
|
|
6
|
+
file,
|
|
7
|
+
topPartialImport,
|
|
8
|
+
bottomPartialImport,
|
|
9
|
+
topPartialComponent,
|
|
10
|
+
bottomPartialComponent,
|
|
11
|
+
} = data;
|
|
12
|
+
|
|
13
|
+
return `---
|
|
14
|
+
title: ${schema.title}
|
|
15
|
+
description: ${JSON.stringify(schema.description)}
|
|
16
|
+
sidebar_label: ${schema.title}
|
|
17
|
+
custom_edit_url: ${editUrl}
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
import SchemaViewer from '@theme/SchemaViewer';
|
|
21
|
+
import SchemaJsonViewer from '@theme/SchemaJsonViewer';
|
|
22
|
+
${topPartialImport}
|
|
23
|
+
${bottomPartialImport}
|
|
24
|
+
|
|
25
|
+
# ${schema.title}
|
|
26
|
+
|
|
27
|
+
${schema.description}
|
|
28
|
+
|
|
29
|
+
${topPartialComponent}
|
|
30
|
+
|
|
31
|
+
<SchemaViewer schema={${JSON.stringify(mergedSchema)}} />
|
|
32
|
+
<SchemaJsonViewer schema={${JSON.stringify(schema)}} />
|
|
33
|
+
|
|
34
|
+
${bottomPartialComponent}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import processSchema from './processSchema.js';
|
|
3
|
+
|
|
4
|
+
export function slugify(text) {
|
|
5
|
+
if (!text) {
|
|
6
|
+
return 'option';
|
|
7
|
+
}
|
|
8
|
+
return text
|
|
9
|
+
.toString()
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/\s+/g, '-') // Replace spaces with -
|
|
12
|
+
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
|
13
|
+
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
|
14
|
+
.replace(/^-+/, '') // Trim - from start of text
|
|
15
|
+
.replace(/-+$/, ''); // Trim - from end of text
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function fixRefs(obj, basePath) {
|
|
19
|
+
for (const key in obj) {
|
|
20
|
+
if (
|
|
21
|
+
key === '$ref' &&
|
|
22
|
+
typeof obj[key] === 'string' &&
|
|
23
|
+
!obj[key].startsWith('#') &&
|
|
24
|
+
!obj[key].startsWith('http')
|
|
25
|
+
) {
|
|
26
|
+
obj[key] = path.resolve(basePath, obj[key]);
|
|
27
|
+
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
28
|
+
fixRefs(obj[key], basePath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function processOneOfSchema(schema, filePath) {
|
|
34
|
+
const processedSchemas = [];
|
|
35
|
+
const choiceType = schema.oneOf ? 'oneOf' : null;
|
|
36
|
+
|
|
37
|
+
if (choiceType) {
|
|
38
|
+
const parentWithoutChoice = { ...schema };
|
|
39
|
+
delete parentWithoutChoice[choiceType];
|
|
40
|
+
|
|
41
|
+
for (const option of schema[choiceType]) {
|
|
42
|
+
let resolvedOption = option;
|
|
43
|
+
if (option.$ref && !option.$ref.startsWith('#')) {
|
|
44
|
+
const refPath = path.resolve(path.dirname(filePath), option.$ref);
|
|
45
|
+
resolvedOption = await processSchema(refPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Merge the parent schema with the resolved option schema
|
|
49
|
+
const newSchema = { ...parentWithoutChoice, ...resolvedOption };
|
|
50
|
+
newSchema.properties = {
|
|
51
|
+
...parentWithoutChoice.properties,
|
|
52
|
+
...resolvedOption.properties,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let slug;
|
|
56
|
+
const hadId = resolvedOption.$id && resolvedOption.$id.endsWith('.json');
|
|
57
|
+
if (hadId) {
|
|
58
|
+
slug = path.basename(resolvedOption.$id, '.json');
|
|
59
|
+
} else {
|
|
60
|
+
slug = slugify(newSchema.title);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!hadId) {
|
|
64
|
+
newSchema.$id = `${schema.$id}#${slug}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
processedSchemas.push({
|
|
68
|
+
slug,
|
|
69
|
+
schema: newSchema,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return processedSchemas;
|
|
75
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import buildExampleFromSchema from './buildExampleFromSchema';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is the main helper that generates a list of complete example objects,
|
|
5
|
+
* grouped by the property that contains the choice.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} rootSchema The complete schema for the event.
|
|
8
|
+
* @returns {object[]} An array of group objects.
|
|
9
|
+
*/
|
|
10
|
+
export function schemaToExamples(rootSchema) {
|
|
11
|
+
const choicePoints = [];
|
|
12
|
+
|
|
13
|
+
// 1. Find all choice points in the schema recursively
|
|
14
|
+
function findChoices(subSchema, path = []) {
|
|
15
|
+
if (!subSchema) return;
|
|
16
|
+
|
|
17
|
+
// First, check for choices at the current schema level
|
|
18
|
+
const choiceType = subSchema.oneOf
|
|
19
|
+
? 'oneOf'
|
|
20
|
+
: subSchema.anyOf
|
|
21
|
+
? 'anyOf'
|
|
22
|
+
: null;
|
|
23
|
+
if (choiceType) {
|
|
24
|
+
choicePoints.push({ path, schema: subSchema });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Then, recurse into any nested properties
|
|
28
|
+
if (subSchema.properties) {
|
|
29
|
+
for (const [key, propSchema] of Object.entries(subSchema.properties)) {
|
|
30
|
+
findChoices(propSchema, [...path, 'properties', key]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
findChoices(rootSchema);
|
|
36
|
+
|
|
37
|
+
// 2. If no choices are found, generate a single default example
|
|
38
|
+
if (choicePoints.length === 0) {
|
|
39
|
+
const example = buildExampleFromSchema(rootSchema);
|
|
40
|
+
if (example && Object.keys(example).length > 0) {
|
|
41
|
+
// Return in the same group structure for consistency
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
property: 'default',
|
|
45
|
+
options: [{ title: 'Example', example }],
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Map each found choice point to a "group" of examples
|
|
53
|
+
return choicePoints.map(({ path, schema }) => {
|
|
54
|
+
const choiceType = schema.oneOf ? 'oneOf' : 'anyOf';
|
|
55
|
+
const choices = schema[choiceType];
|
|
56
|
+
const propertyName = path.length > 0 ? path[path.length - 1] : 'root';
|
|
57
|
+
|
|
58
|
+
// For each option within the choice, generate a complete example
|
|
59
|
+
const options = choices.map((option) => {
|
|
60
|
+
// Create a deep copy of the root schema to modify for this specific example
|
|
61
|
+
const schemaVariant = JSON.parse(JSON.stringify(rootSchema));
|
|
62
|
+
|
|
63
|
+
// If path is empty, the choice is at the root of the schema.
|
|
64
|
+
if (path.length === 0) {
|
|
65
|
+
// Merge the chosen option's properties into the root properties
|
|
66
|
+
schemaVariant.properties = {
|
|
67
|
+
...schemaVariant.properties,
|
|
68
|
+
...option.properties,
|
|
69
|
+
};
|
|
70
|
+
// Remove the choice block from the root
|
|
71
|
+
delete schemaVariant.oneOf;
|
|
72
|
+
delete schemaVariant.anyOf;
|
|
73
|
+
} else {
|
|
74
|
+
// The choice is nested. Find the parent of the choice block in the copied schema.
|
|
75
|
+
let parentOfChoice = schemaVariant;
|
|
76
|
+
for (let i = 0; i < path.length; i++) {
|
|
77
|
+
parentOfChoice = parentOfChoice[path[i]];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Delete the choice keywords and merge the selected option.
|
|
81
|
+
// This preserves other properties on the parent (like `type` and `description`).
|
|
82
|
+
delete parentOfChoice.oneOf;
|
|
83
|
+
delete parentOfChoice.anyOf;
|
|
84
|
+
Object.assign(parentOfChoice, option);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const example = buildExampleFromSchema(schemaVariant);
|
|
88
|
+
return {
|
|
89
|
+
title: option.title || 'Option',
|
|
90
|
+
example,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
property: propertyName,
|
|
96
|
+
options,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { getConstraints } from './getConstraints';
|
|
2
|
+
|
|
3
|
+
function processOptions(
|
|
4
|
+
choices,
|
|
5
|
+
level,
|
|
6
|
+
path,
|
|
7
|
+
isNestedInProperty,
|
|
8
|
+
requiredArray = [],
|
|
9
|
+
continuingLevels = [],
|
|
10
|
+
) {
|
|
11
|
+
return choices.map((optionSchema, index) => {
|
|
12
|
+
const optionTitle = optionSchema.title || 'Option';
|
|
13
|
+
|
|
14
|
+
// Determine if this is the last option in the list.
|
|
15
|
+
// If it is NOT the last option, its children must not close the visual tree branch.
|
|
16
|
+
const isLastOption = index === choices.length - 1;
|
|
17
|
+
|
|
18
|
+
let optionRows = [];
|
|
19
|
+
|
|
20
|
+
// This is a primitive type (string, number, etc.) within a choice
|
|
21
|
+
if (optionSchema.type && !optionSchema.properties) {
|
|
22
|
+
const isRequired = requiredArray.includes(path[path.length - 1]);
|
|
23
|
+
const constraints = getConstraints(optionSchema);
|
|
24
|
+
if (isRequired) {
|
|
25
|
+
constraints.unshift('required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
optionRows.push({
|
|
29
|
+
type: 'property',
|
|
30
|
+
// The name of the property is the name of the parent property that holds the choice
|
|
31
|
+
name: path.length > 0 ? path[path.length - 1] : optionTitle,
|
|
32
|
+
path: [...path, `(${optionTitle})`],
|
|
33
|
+
// If it's a top-level choice (like user_id), the level is the same as the choice itself.
|
|
34
|
+
// Otherwise, it's nested.
|
|
35
|
+
level: level,
|
|
36
|
+
required: isRequired,
|
|
37
|
+
propertyType: optionSchema.type,
|
|
38
|
+
description: optionSchema.description,
|
|
39
|
+
example: optionSchema.examples || optionSchema.example,
|
|
40
|
+
constraints: constraints,
|
|
41
|
+
isLastInGroup: isLastOption, // Updated: Uses the calculated flag instead of always true
|
|
42
|
+
hasChildren: false,
|
|
43
|
+
containerType: null,
|
|
44
|
+
continuingLevels: [...continuingLevels],
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
// This is a complex object within a choice
|
|
48
|
+
optionRows = schemaToTableData(
|
|
49
|
+
optionSchema,
|
|
50
|
+
// If nested in a property (like payment_method), the sub-properties start at the same level as the choice
|
|
51
|
+
// Otherwise, they are one level deeper.
|
|
52
|
+
level,
|
|
53
|
+
isNestedInProperty ? [] : path,
|
|
54
|
+
continuingLevels,
|
|
55
|
+
isLastOption,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
title: optionTitle,
|
|
61
|
+
description: optionSchema.description,
|
|
62
|
+
rows: optionRows,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function schemaToTableData(
|
|
68
|
+
schema,
|
|
69
|
+
level = 0,
|
|
70
|
+
path = [],
|
|
71
|
+
parentContinuingLevels = [],
|
|
72
|
+
isLastOption = true,
|
|
73
|
+
) {
|
|
74
|
+
const flatRows = [];
|
|
75
|
+
|
|
76
|
+
function isEffectivelyEmpty(schemaNode) {
|
|
77
|
+
if (
|
|
78
|
+
schemaNode.type !== 'object' &&
|
|
79
|
+
typeof schemaNode.properties === 'undefined'
|
|
80
|
+
) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (schemaNode.oneOf || schemaNode.anyOf) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
!schemaNode.properties ||
|
|
88
|
+
Object.keys(schemaNode.properties).length === 0
|
|
89
|
+
) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return Object.values(schemaNode.properties).every(isEffectivelyEmpty);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildRows(
|
|
96
|
+
subSchema,
|
|
97
|
+
currentLevel,
|
|
98
|
+
currentPath,
|
|
99
|
+
requiredFromParent = [],
|
|
100
|
+
continuingLevels = [],
|
|
101
|
+
) {
|
|
102
|
+
if (!subSchema) return;
|
|
103
|
+
|
|
104
|
+
if (subSchema.properties) {
|
|
105
|
+
const propKeys = Object.keys(subSchema.properties);
|
|
106
|
+
const hasSiblingChoices = !!(subSchema.oneOf || subSchema.anyOf);
|
|
107
|
+
|
|
108
|
+
// Filter out properties that should be skipped to get accurate count
|
|
109
|
+
const visiblePropKeys = propKeys.filter((name) => {
|
|
110
|
+
const propSchema = subSchema.properties[name];
|
|
111
|
+
return !(
|
|
112
|
+
propSchema['x-gtm-clear'] === true && isEffectivelyEmpty(propSchema)
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
visiblePropKeys.forEach((name, index) => {
|
|
117
|
+
const propSchema = subSchema.properties[name];
|
|
118
|
+
const newPath = [...currentPath, name];
|
|
119
|
+
|
|
120
|
+
const isLastProp =
|
|
121
|
+
index === visiblePropKeys.length - 1 && !hasSiblingChoices;
|
|
122
|
+
|
|
123
|
+
// Updated Logic:
|
|
124
|
+
// A property is visually "last" only if it is the last property
|
|
125
|
+
// AND (it is deeper in the hierarchy OR the parent option itself is the last one).
|
|
126
|
+
const isLast = isLastProp && (currentLevel !== level || isLastOption);
|
|
127
|
+
|
|
128
|
+
const isChoiceWrapper = !!(propSchema.oneOf || propSchema.anyOf);
|
|
129
|
+
|
|
130
|
+
// Determine if this property has children and what type
|
|
131
|
+
const hasNestedProperties = !!propSchema.properties;
|
|
132
|
+
const hasArrayItems =
|
|
133
|
+
propSchema.type === 'array' && !!propSchema.items?.properties;
|
|
134
|
+
const hasNestedChoice = isChoiceWrapper;
|
|
135
|
+
const hasChildren =
|
|
136
|
+
hasNestedProperties || hasArrayItems || hasNestedChoice;
|
|
137
|
+
|
|
138
|
+
// Determine container type for the symbol
|
|
139
|
+
let containerType = null;
|
|
140
|
+
if (
|
|
141
|
+
hasNestedProperties ||
|
|
142
|
+
(isChoiceWrapper && propSchema.type === 'object')
|
|
143
|
+
) {
|
|
144
|
+
containerType = 'object';
|
|
145
|
+
} else if (hasArrayItems) {
|
|
146
|
+
containerType = 'array';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Calculate continuing levels for children
|
|
150
|
+
// If this is not the last item, add current level to continuing levels for children
|
|
151
|
+
// If this IS the last item, remove the immediate parent level (currentLevel - 1) because
|
|
152
|
+
// that line stops at this item and should not continue through its children
|
|
153
|
+
const childContinuingLevels = isLast
|
|
154
|
+
? continuingLevels.filter((lvl) => lvl !== currentLevel - 1)
|
|
155
|
+
: [...continuingLevels, currentLevel];
|
|
156
|
+
|
|
157
|
+
// This is a "simple" choice property like user_id.
|
|
158
|
+
// It gets unwrapped into a choice row directly.
|
|
159
|
+
if (
|
|
160
|
+
isChoiceWrapper &&
|
|
161
|
+
!propSchema.properties &&
|
|
162
|
+
propSchema.type !== 'object'
|
|
163
|
+
) {
|
|
164
|
+
const choiceType = propSchema.oneOf ? 'oneOf' : 'anyOf';
|
|
165
|
+
const choices = propSchema[choiceType];
|
|
166
|
+
flatRows.push({
|
|
167
|
+
type: 'choice',
|
|
168
|
+
choiceType,
|
|
169
|
+
path: newPath,
|
|
170
|
+
level: currentLevel,
|
|
171
|
+
title: propSchema.title,
|
|
172
|
+
description: propSchema.description,
|
|
173
|
+
isLastInGroup: isLast,
|
|
174
|
+
hasChildren: false,
|
|
175
|
+
containerType: null,
|
|
176
|
+
continuingLevels: [...continuingLevels],
|
|
177
|
+
options: processOptions(
|
|
178
|
+
choices,
|
|
179
|
+
currentLevel,
|
|
180
|
+
newPath,
|
|
181
|
+
false,
|
|
182
|
+
subSchema.required || requiredFromParent,
|
|
183
|
+
childContinuingLevels,
|
|
184
|
+
),
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
// This is a "normal" property or a complex one with a nested choice.
|
|
188
|
+
const isRequired =
|
|
189
|
+
(subSchema.required || requiredFromParent)?.includes(name) || false;
|
|
190
|
+
const constraints = getConstraints(propSchema);
|
|
191
|
+
if (isRequired) {
|
|
192
|
+
constraints.unshift('required');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
flatRows.push({
|
|
196
|
+
type: 'property',
|
|
197
|
+
name,
|
|
198
|
+
path: newPath,
|
|
199
|
+
level: currentLevel,
|
|
200
|
+
required: isRequired,
|
|
201
|
+
propertyType:
|
|
202
|
+
propSchema.type || (propSchema.enum ? 'enum' : 'object'),
|
|
203
|
+
description: propSchema.description,
|
|
204
|
+
example: propSchema.examples || propSchema.example,
|
|
205
|
+
constraints,
|
|
206
|
+
isLastInGroup: isLast,
|
|
207
|
+
hasChildren,
|
|
208
|
+
containerType,
|
|
209
|
+
continuingLevels: [...continuingLevels],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (propSchema.properties) {
|
|
213
|
+
buildRows(
|
|
214
|
+
propSchema,
|
|
215
|
+
currentLevel + 1,
|
|
216
|
+
newPath,
|
|
217
|
+
propSchema.required,
|
|
218
|
+
childContinuingLevels,
|
|
219
|
+
);
|
|
220
|
+
} else if (
|
|
221
|
+
propSchema.type === 'array' &&
|
|
222
|
+
propSchema.items?.properties
|
|
223
|
+
) {
|
|
224
|
+
buildRows(
|
|
225
|
+
propSchema.items,
|
|
226
|
+
currentLevel + 1,
|
|
227
|
+
[...newPath, '[n]'],
|
|
228
|
+
propSchema.items.required,
|
|
229
|
+
childContinuingLevels,
|
|
230
|
+
);
|
|
231
|
+
} else if (isChoiceWrapper) {
|
|
232
|
+
// This handles the "complex" choice property like payment_method.
|
|
233
|
+
// A property row has already been created above, now we add the choice row.
|
|
234
|
+
const choiceType = propSchema.oneOf ? 'oneOf' : 'anyOf';
|
|
235
|
+
const choices = propSchema[choiceType];
|
|
236
|
+
flatRows.push({
|
|
237
|
+
type: 'choice',
|
|
238
|
+
choiceType,
|
|
239
|
+
path: [...newPath, choiceType], // Make path unique
|
|
240
|
+
level: currentLevel + 1,
|
|
241
|
+
title: propSchema.title,
|
|
242
|
+
description: null,
|
|
243
|
+
isLastInGroup: true,
|
|
244
|
+
hasChildren: false,
|
|
245
|
+
containerType: null,
|
|
246
|
+
continuingLevels: childContinuingLevels,
|
|
247
|
+
options: processOptions(
|
|
248
|
+
choices,
|
|
249
|
+
currentLevel + 1,
|
|
250
|
+
newPath,
|
|
251
|
+
true,
|
|
252
|
+
propSchema.required,
|
|
253
|
+
childContinuingLevels,
|
|
254
|
+
),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// This handles choices at the root of a schema
|
|
262
|
+
const choiceType = subSchema.oneOf
|
|
263
|
+
? 'oneOf'
|
|
264
|
+
: subSchema.anyOf
|
|
265
|
+
? 'anyOf'
|
|
266
|
+
: null;
|
|
267
|
+
if (choiceType) {
|
|
268
|
+
const choices = subSchema[choiceType];
|
|
269
|
+
flatRows.push({
|
|
270
|
+
type: 'choice',
|
|
271
|
+
choiceType,
|
|
272
|
+
path: currentPath,
|
|
273
|
+
level: currentLevel,
|
|
274
|
+
title: subSchema.title,
|
|
275
|
+
description: subSchema.description,
|
|
276
|
+
isLastInGroup: true,
|
|
277
|
+
hasChildren: false,
|
|
278
|
+
containerType: null,
|
|
279
|
+
continuingLevels: [...continuingLevels],
|
|
280
|
+
options: processOptions(
|
|
281
|
+
choices,
|
|
282
|
+
currentLevel,
|
|
283
|
+
currentPath,
|
|
284
|
+
false,
|
|
285
|
+
subSchema.required || requiredFromParent,
|
|
286
|
+
continuingLevels,
|
|
287
|
+
),
|
|
288
|
+
});
|
|
289
|
+
} else if (!subSchema.properties && subSchema.type) {
|
|
290
|
+
// This handles a schema that is just a single primitive type
|
|
291
|
+
flatRows.push({
|
|
292
|
+
type: 'property',
|
|
293
|
+
name: subSchema.title || '<value>',
|
|
294
|
+
path: currentPath,
|
|
295
|
+
level: currentLevel,
|
|
296
|
+
required: false,
|
|
297
|
+
propertyType: subSchema.type,
|
|
298
|
+
description: subSchema.description,
|
|
299
|
+
example: subSchema.examples || subSchema.example,
|
|
300
|
+
constraints: getConstraints(subSchema),
|
|
301
|
+
isLastInGroup: true,
|
|
302
|
+
hasChildren: false,
|
|
303
|
+
containerType: null,
|
|
304
|
+
continuingLevels: [...continuingLevels],
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
buildRows(schema, level, path, schema.required, parentContinuingLevels);
|
|
310
|
+
return flatRows;
|
|
311
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default function updateSchemaIds(siteDir, url, version = null) {
|
|
5
|
+
const versionsJsonPath = path.join(siteDir, 'versions.json');
|
|
6
|
+
if (!fs.existsSync(versionsJsonPath)) {
|
|
7
|
+
console.log('No versions.json file found, skipping schema ID update.');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getAllFiles = (dir, allFiles = []) => {
|
|
12
|
+
const files = fs.readdirSync(dir);
|
|
13
|
+
|
|
14
|
+
files.forEach((file) => {
|
|
15
|
+
const filePath = path.join(dir, file);
|
|
16
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
17
|
+
getAllFiles(filePath, allFiles);
|
|
18
|
+
} else {
|
|
19
|
+
if (file.endsWith('.json')) {
|
|
20
|
+
allFiles.push(filePath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return allFiles;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const versions = version
|
|
28
|
+
? [version]
|
|
29
|
+
: JSON.parse(fs.readFileSync(versionsJsonPath, 'utf8'));
|
|
30
|
+
|
|
31
|
+
for (const version of versions) {
|
|
32
|
+
const schemaDir = path.join(siteDir, 'static/schemas', version);
|
|
33
|
+
if (!fs.existsSync(schemaDir)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const files = getAllFiles(schemaDir);
|
|
37
|
+
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const schema = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
40
|
+
const baseUrl = url.endsWith('/') ? url.slice(0, -1) : url;
|
|
41
|
+
const relativePath = path.relative(path.join(siteDir, 'static'), file);
|
|
42
|
+
schema.$id = `${baseUrl}/${relativePath.replace(/\\/g, '/')}`;
|
|
43
|
+
fs.writeFileSync(file, JSON.stringify(schema, null, 2));
|
|
44
|
+
console.log(`Updated $id for ${file} in version ${version}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|