docusaurus-plugin-generate-schema-docs 1.3.0 → 1.5.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.
Files changed (47) hide show
  1. package/README.md +28 -0
  2. package/__tests__/ExampleDataLayer.test.js +13 -0
  3. package/__tests__/__fixtures__/static/schemas/anchor/parent-event-anchor.json +29 -0
  4. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/add-to-cart-event.json +45 -0
  5. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/choice-event.json +78 -0
  6. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/complex-event.json +193 -0
  7. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/components/dataLayer.json +56 -0
  8. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/components/product.json +126 -0
  9. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/purchase-event.json +73 -0
  10. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/root-any-of-event.json +40 -0
  11. package/__tests__/__fixtures__/validateSchemas/complex-validation/events/root-choice-event.json +54 -0
  12. package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +79 -0
  13. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +8 -2
  14. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +20 -5
  15. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +8 -2
  16. package/__tests__/components/PropertiesTable.test.js +34 -1
  17. package/__tests__/components/PropertyRow.test.js +5 -5
  18. package/__tests__/generateEventDocs.anchor.test.js +71 -0
  19. package/__tests__/helpers/example-helper.test.js +71 -0
  20. package/__tests__/helpers/getConstraints.test.js +72 -27
  21. package/__tests__/helpers/schemaToTableData.test.js +28 -33
  22. package/__tests__/helpers/validator.test.js +115 -0
  23. package/__tests__/validateSchemas-integration.test.js +29 -0
  24. package/__tests__/validateSchemas.test.js +18 -102
  25. package/components/ExampleDataLayer.js +14 -5
  26. package/components/PropertiesTable.js +22 -7
  27. package/components/PropertiesTable.module.css +27 -0
  28. package/components/PropertyRow.js +24 -25
  29. package/components/SchemaRows.css +6 -0
  30. package/components/SchemaViewer.js +2 -2
  31. package/components/WordWrapButton.js +31 -0
  32. package/components/wordWrapButton.module.css +8 -0
  33. package/generateEventDocs.js +2 -1
  34. package/helpers/buildExampleFromSchema.js +49 -52
  35. package/helpers/example-helper.js +41 -0
  36. package/helpers/file-system.js +1 -2
  37. package/helpers/getConstraints.js +1 -0
  38. package/helpers/schema-doc-template.js +8 -1
  39. package/helpers/schema-processing.js +2 -15
  40. package/helpers/schemaToExamples.js +46 -74
  41. package/helpers/schemaToTableData.js +4 -3
  42. package/helpers/validator.js +108 -0
  43. package/index.js +18 -9
  44. package/package.json +1 -2
  45. package/validateSchemas.js +70 -53
  46. package/__tests__/helpers/loadSchema.test.js +0 -20
  47. package/helpers/loadSchema.js +0 -11
@@ -0,0 +1,108 @@
1
+ import Ajv from 'ajv';
2
+ import Ajv2020 from 'ajv/dist/2020.js';
3
+ import Ajv2019 from 'ajv/dist/2019.js';
4
+ import AjvDraft4 from 'ajv-draft-04';
5
+ import addFormats from 'ajv-formats';
6
+ import ajvKeywords from 'ajv-keywords';
7
+ import path from 'path';
8
+ import { promises as fs } from 'fs';
9
+ import { URL } from 'url';
10
+
11
+ function createAjvInstance(schemas, mainSchema, schemaPath) {
12
+ const schemaVersion = mainSchema?.$schema;
13
+
14
+ const loadSchema = async (uri) => {
15
+ let localPath;
16
+ if (uri.startsWith('http')) {
17
+ const url = new URL(uri);
18
+ const pathFromUrl = url.pathname.startsWith('/schemas/')
19
+ ? url.pathname.substring('/schemas/'.length)
20
+ : url.pathname;
21
+ localPath = path.join(schemaPath, pathFromUrl);
22
+ } else {
23
+ localPath = path.resolve(schemaPath, uri);
24
+ }
25
+ const schemaContent = await fs.readFile(localPath, 'utf-8');
26
+ return JSON.parse(schemaContent);
27
+ };
28
+
29
+ const options = { allErrors: true, schemas: schemas };
30
+
31
+ let ajv;
32
+ if (schemaVersion?.includes('2020-12')) {
33
+ options.strict = false;
34
+ options.loadSchema = loadSchema;
35
+ ajv = new Ajv2020(options);
36
+ } else if (schemaVersion?.includes('2019-09')) {
37
+ options.strict = false;
38
+ options.loadSchema = loadSchema;
39
+ ajv = new Ajv2019(options);
40
+ } else if (schemaVersion?.includes('draft-07')) {
41
+ options.strict = false;
42
+ options.loadSchema = loadSchema;
43
+ ajv = new Ajv(options);
44
+ } else if (schemaVersion?.includes('draft-06')) {
45
+ options.strict = false;
46
+ options.loadSchema = loadSchema;
47
+ ajv = new Ajv(options);
48
+ } else if (schemaVersion?.includes('draft-04')) {
49
+ ajv = new AjvDraft4();
50
+ schemas.forEach((s) => ajv.addSchema(s));
51
+ } else {
52
+ options.strict = false;
53
+ options.loadSchema = loadSchema;
54
+ ajv = new Ajv(options);
55
+ }
56
+
57
+ addFormats(ajv);
58
+ if (ajv.addKeyword) {
59
+ ajv.addKeyword('x-gtm-clear');
60
+ }
61
+ ajvKeywords(ajv);
62
+
63
+ return ajv;
64
+ }
65
+
66
+ /**
67
+ * Creates a validation function for a given set of JSON schemas.
68
+ *
69
+ * @param {object[]} schemas An array of all JSON schemas.
70
+ * @param {object} mainSchema The main JSON schema to validate against.
71
+ * @returns {function(object): {valid: boolean, errors: object[]}} A function that takes data and returns a validation result.
72
+ */
73
+ export async function createValidator(schemas, mainSchema, schemaPath) {
74
+ if (!mainSchema) {
75
+ mainSchema = schemas;
76
+ schemas = [mainSchema];
77
+ }
78
+ const ajv = createAjvInstance(schemas, mainSchema, schemaPath);
79
+
80
+ let validate;
81
+ if (mainSchema?.$schema?.includes('draft-04')) {
82
+ validate = mainSchema['$id']
83
+ ? ajv.getSchema(mainSchema['$id'])
84
+ : ajv.compile(mainSchema);
85
+ } else if (ajv.compileAsync) {
86
+ validate = await ajv.compileAsync(mainSchema);
87
+ } else {
88
+ validate = mainSchema['$id']
89
+ ? ajv.getSchema(mainSchema['$id'])
90
+ : ajv.compile(mainSchema);
91
+ }
92
+
93
+ if (!validate) {
94
+ throw new Error(
95
+ `Could not compile schema or find compiled schema for ${
96
+ mainSchema['$id'] || 'main schema'
97
+ }`,
98
+ );
99
+ }
100
+
101
+ return (data) => {
102
+ const valid = validate(data);
103
+ if (!valid) {
104
+ return { valid: false, errors: validate.errors };
105
+ }
106
+ return { valid: true, errors: [] };
107
+ };
108
+ }
package/index.js CHANGED
@@ -5,10 +5,18 @@ import validateSchemas from './validateSchemas.js';
5
5
  import generateEventDocs from './generateEventDocs.js';
6
6
  import path from 'path';
7
7
 
8
- export default async function (context) {
8
+ export default async function (context, options) {
9
9
  const { siteDir } = context;
10
+ const { dataLayerName } = options;
10
11
  const { organizationName, projectName, url } = context.siteConfig;
11
- const options = { organizationName, projectName, siteDir, url };
12
+
13
+ const pluginOptions = {
14
+ organizationName,
15
+ projectName,
16
+ siteDir,
17
+ url,
18
+ dataLayerName,
19
+ };
12
20
  const versionsJsonPath = path.join(siteDir, 'versions.json');
13
21
  const isVersioned = fs.existsSync(versionsJsonPath);
14
22
 
@@ -40,11 +48,12 @@ export default async function (context) {
40
48
  fs.readFileSync(versionsJsonPath, 'utf8'),
41
49
  );
42
50
  for (const version of versions) {
43
- await generateEventDocs({ ...options, version });
51
+ // FIX 3: Removed 'newOptions' and used the consolidated pluginOptions
52
+ await generateEventDocs({ ...pluginOptions, version });
44
53
  }
45
- await generateEventDocs({ ...options, version: 'current' });
54
+ await generateEventDocs({ ...pluginOptions, version: 'current' });
46
55
  } else {
47
- await generateEventDocs(options);
56
+ await generateEventDocs(pluginOptions);
48
57
  }
49
58
  });
50
59
 
@@ -105,7 +114,7 @@ export default async function (context) {
105
114
 
106
115
  // Generate documentation for the new version
107
116
  console.log(`📝 Generating documentation for version ${version}...`);
108
- await generateEventDocs({ ...options, version });
117
+ await generateEventDocs({ ...pluginOptions, version });
109
118
 
110
119
  console.log(`\n✅ Version ${version} created successfully!`);
111
120
  console.log(`\nNext steps:`);
@@ -132,11 +141,11 @@ export default async function (context) {
132
141
  if (isVersioned) {
133
142
  const versions = JSON.parse(fs.readFileSync(versionsJsonPath, 'utf8'));
134
143
  for (const version of versions) {
135
- await generateEventDocs({ ...options, version });
144
+ await generateEventDocs({ ...pluginOptions, version });
136
145
  }
137
- await generateEventDocs({ ...options, version: 'current' });
146
+ await generateEventDocs({ ...pluginOptions, version: 'current' });
138
147
  } else {
139
- await generateEventDocs(options);
148
+ await generateEventDocs(pluginOptions);
140
149
  }
141
150
  },
142
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docusaurus-plugin-generate-schema-docs",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Docusaurus plugin to generate documentation from JSON schemas.",
5
5
  "main": "index.js",
6
6
  "license": "MIT",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@apidevtools/json-schema-ref-parser": "^15.1.3",
13
- "fs-extra": "^11.3.3",
14
13
  "json-schema-merge-allof": "^0.8.1"
15
14
  },
16
15
  "publishConfig": {
@@ -1,72 +1,89 @@
1
+ import { createValidator } from './helpers/validator.js';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
- import buildExampleFromSchema from './helpers/buildExampleFromSchema';
4
- import Ajv2020 from 'ajv/dist/2020.js';
5
- import addFormats from 'ajv-formats';
6
- import processSchema from './helpers/processSchema';
4
+ import processSchema from './helpers/processSchema.js';
5
+ import { schemaToExamples } from './helpers/schemaToExamples.js';
7
6
 
8
- const validateSchemas = async (schemaPath) => {
9
- const ajv = new Ajv2020();
10
- addFormats(ajv);
11
- ajv.addKeyword('x-gtm-clear');
7
+ const validateSingleSchema = async (filePath, schemaPath) => {
8
+ const file = path.basename(filePath);
9
+ const errors = [];
10
+ let allValid = true;
11
+
12
+ try {
13
+ const mergedSchema = await processSchema(filePath);
14
+ const exampleGroups = schemaToExamples(mergedSchema);
15
+ const originalSchema = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
16
+ const validate = await createValidator([], originalSchema, schemaPath);
17
+
18
+ if (exampleGroups.length === 0) {
19
+ errors.push(`x Schema ${file} does not produce any examples.`);
20
+ return { allValid: false, errors };
21
+ }
12
22
 
13
- const getAllFiles = (dir, allFiles = []) => {
14
- const files = fs.readdirSync(dir);
23
+ let fileHasValidExample = false;
24
+ let fileHasAnyExample = false;
15
25
 
16
- files.forEach((file) => {
17
- const filePath = path.join(dir, file);
18
- if (fs.statSync(filePath).isDirectory()) {
19
- getAllFiles(filePath, allFiles);
20
- } else {
21
- if (file.endsWith('.json')) {
22
- allFiles.push(filePath);
26
+ for (const group of exampleGroups) {
27
+ for (const option of group.options) {
28
+ fileHasAnyExample = true;
29
+ const { example, title } = option;
30
+
31
+ if (!example) {
32
+ errors.push(
33
+ `x Schema ${file} (option: ${title}) does not produce a valid example.`,
34
+ );
35
+ allValid = false;
36
+ continue;
37
+ }
38
+
39
+ const result = validate(example);
40
+ if (result.valid) {
41
+ fileHasValidExample = true;
42
+ } else {
43
+ errors.push(
44
+ `x Schema ${file} (option: ${title}) example data failed validation:`,
45
+ );
46
+ errors.push(JSON.stringify(result.errors, null, 2));
47
+ allValid = false;
23
48
  }
24
49
  }
25
- });
26
- return allFiles;
27
- };
50
+ }
28
51
 
29
- const allSchemaFiles = getAllFiles(schemaPath);
30
- for (const file of allSchemaFiles) {
31
- const schemaContent = fs.readFileSync(file, 'utf-8');
32
- const schema = JSON.parse(schemaContent);
33
- ajv.addSchema(schema);
52
+ if (fileHasAnyExample && !fileHasValidExample) {
53
+ errors.push(
54
+ `x Schema ${file} had examples, but none of them were valid.`,
55
+ );
56
+ allValid = false;
57
+ }
58
+ } catch (error) {
59
+ errors.push(`x Error processing ${file}: ${error.message}`);
60
+ allValid = false;
34
61
  }
35
62
 
36
- const schemaFiles = fs
63
+ return { allValid, errors };
64
+ };
65
+
66
+ const validateSchemas = async (schemaPath) => {
67
+ const topLevelSchemaFiles = fs
37
68
  .readdirSync(schemaPath)
38
69
  .filter((file) => file.endsWith('.json'));
39
- let allValid = true;
40
- for (const file of schemaFiles) {
41
- const filePath = path.join(schemaPath, file);
42
70
 
43
- const mergedSchema = await processSchema(filePath);
71
+ const results = await Promise.all(
72
+ topLevelSchemaFiles.map((file) => {
73
+ const filePath = path.join(schemaPath, file);
74
+ return validateSingleSchema(filePath, schemaPath);
75
+ }),
76
+ );
44
77
 
45
- const example_data = buildExampleFromSchema(mergedSchema);
78
+ const allSucceeded = results.every((r) => r.allValid);
46
79
 
47
- if (!example_data) {
48
- console.error(`x Schema ${file} does not produce a valid example.`);
49
- allValid = false;
50
- } else {
51
- const originalSchema = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
52
- const validate = ajv.getSchema(originalSchema.$id);
53
- if (!validate) {
54
- console.error(
55
- `x Could not find compiled schema for ${originalSchema.$id}`,
56
- );
57
- allValid = false;
58
- continue;
59
- }
60
- if (validate(example_data)) {
61
- console.log(`OK Schema ${file} produced a valid example.`);
62
- } else {
63
- console.error(`x Schema ${file} example data failed validation:`);
64
- console.error(validate.errors);
65
- allValid = false;
66
- }
67
- }
80
+ if (!allSucceeded) {
81
+ results.forEach((r) => {
82
+ r.errors.forEach((e) => console.error(e));
83
+ });
68
84
  }
69
- return allValid;
85
+
86
+ return allSucceeded;
70
87
  };
71
88
 
72
89
  export default validateSchemas;
@@ -1,20 +0,0 @@
1
- /**
2
- * @jest-environment node
3
- */
4
-
5
- import path from 'path';
6
- import loadSchema from '../../helpers/loadSchema';
7
-
8
- describe('loadSchema', () => {
9
- it('should load and parse a JSON file', () => {
10
- const filePath = path.join(
11
- __dirname,
12
- '..',
13
- '__fixtures__',
14
- 'validateSchemas',
15
- 'circular-schema.json',
16
- );
17
- const schema = loadSchema(filePath);
18
- expect(schema.title).toBe('Circular Schema');
19
- });
20
- });
@@ -1,11 +0,0 @@
1
- import fs from 'fs';
2
-
3
- /**
4
- * Loads a JSON schema file and returns its content as a JSON object.
5
- * @param {string} filePath Path to the JSON schema file.
6
- * @returns {object} The parsed JSON schema.
7
- */
8
- export default function loadSchema(filePath) {
9
- const rawContent = fs.readFileSync(filePath, 'utf-8');
10
- return JSON.parse(rawContent);
11
- }