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
@@ -12,6 +12,7 @@ export default function MdxTemplate(data) {
12
12
  sourcePath,
13
13
  schemaSources,
14
14
  } = data;
15
+ const sourceSchema = schemaSources?.[sourcePath] || schema;
15
16
 
16
17
  return `---
17
18
  title: ${schema.title}
@@ -33,6 +34,9 @@ ${topPartialComponent}
33
34
 
34
35
  <SchemaViewer
35
36
  schema={${JSON.stringify(mergedSchema)}}
37
+ sourceSchema={${JSON.stringify(sourceSchema)}}
38
+ sourcePath={${JSON.stringify(sourcePath)}}
39
+ schemaSources={${JSON.stringify(schemaSources)}}
36
40
  ${dataLayerName ? ` dataLayerName={'${dataLayerName}'}` : ''}
37
41
  />
38
42
  <SchemaJsonViewer
@@ -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
  }
@@ -0,0 +1,148 @@
1
+ function visitSchemaNodes(schema, visitor, path = []) {
2
+ if (!schema || typeof schema !== 'object') {
3
+ return;
4
+ }
5
+
6
+ visitor(schema, { path });
7
+
8
+ if (schema.properties && typeof schema.properties === 'object') {
9
+ Object.entries(schema.properties).forEach(([key, propertySchema]) => {
10
+ visitSchemaNodes(propertySchema, visitor, [...path, 'properties', key]);
11
+ });
12
+ }
13
+
14
+ if (schema.items && typeof schema.items === 'object') {
15
+ visitSchemaNodes(schema.items, visitor, [...path, 'items']);
16
+ }
17
+
18
+ if (Array.isArray(schema.oneOf)) {
19
+ schema.oneOf.forEach((optionSchema, index) => {
20
+ visitSchemaNodes(optionSchema, visitor, [...path, 'oneOf', index]);
21
+ });
22
+ }
23
+
24
+ if (Array.isArray(schema.anyOf)) {
25
+ schema.anyOf.forEach((optionSchema, index) => {
26
+ visitSchemaNodes(optionSchema, visitor, [...path, 'anyOf', index]);
27
+ });
28
+ }
29
+
30
+ if (schema.if && typeof schema.if === 'object') {
31
+ visitSchemaNodes(schema.if, visitor, [...path, 'if']);
32
+ }
33
+
34
+ if (schema.then && typeof schema.then === 'object') {
35
+ visitSchemaNodes(schema.then, visitor, [...path, 'then']);
36
+ }
37
+
38
+ if (schema.else && typeof schema.else === 'object') {
39
+ visitSchemaNodes(schema.else, visitor, [...path, 'else']);
40
+ }
41
+ }
42
+
43
+ function visitPropertyEntryBranches(
44
+ schema,
45
+ visitor,
46
+ { prefix = '', path = [], skipArraySubProperties = false } = {},
47
+ ) {
48
+ if (!schema || typeof schema !== 'object') {
49
+ return;
50
+ }
51
+
52
+ if (Array.isArray(schema.oneOf)) {
53
+ schema.oneOf.forEach((optionSchema, index) => {
54
+ visitSchemaPropertyEntries(optionSchema, visitor, {
55
+ prefix,
56
+ path: [...path, 'oneOf', index],
57
+ skipArraySubProperties,
58
+ });
59
+ });
60
+ }
61
+
62
+ if (Array.isArray(schema.anyOf)) {
63
+ schema.anyOf.forEach((optionSchema, index) => {
64
+ visitSchemaPropertyEntries(optionSchema, visitor, {
65
+ prefix,
66
+ path: [...path, 'anyOf', index],
67
+ skipArraySubProperties,
68
+ });
69
+ });
70
+ }
71
+
72
+ if (schema.then && typeof schema.then === 'object') {
73
+ visitSchemaPropertyEntries(schema.then, visitor, {
74
+ prefix,
75
+ path: [...path, 'then'],
76
+ skipArraySubProperties,
77
+ });
78
+ }
79
+
80
+ if (schema.else && typeof schema.else === 'object') {
81
+ visitSchemaPropertyEntries(schema.else, visitor, {
82
+ prefix,
83
+ path: [...path, 'else'],
84
+ skipArraySubProperties,
85
+ });
86
+ }
87
+ }
88
+
89
+ function visitSchemaPropertyEntries(
90
+ schema,
91
+ visitor,
92
+ { prefix = '', path = [], skipArraySubProperties = false } = {},
93
+ ) {
94
+ if (!schema || typeof schema !== 'object') {
95
+ return;
96
+ }
97
+
98
+ if (schema.properties && typeof schema.properties === 'object') {
99
+ Object.entries(schema.properties).forEach(([key, propertySchema]) => {
100
+ const currentName = prefix ? `${prefix}.${key}` : key;
101
+ const currentPath = [...path, 'properties', key];
102
+
103
+ visitor(propertySchema, {
104
+ key,
105
+ name: currentName,
106
+ path: currentPath,
107
+ parentSchema: schema,
108
+ });
109
+
110
+ if (propertySchema?.type === 'object' && propertySchema.properties) {
111
+ visitSchemaPropertyEntries(propertySchema, visitor, {
112
+ prefix: currentName,
113
+ path: currentPath,
114
+ skipArraySubProperties,
115
+ });
116
+ }
117
+
118
+ if (
119
+ propertySchema?.type === 'array' &&
120
+ propertySchema.items?.properties &&
121
+ !skipArraySubProperties
122
+ ) {
123
+ visitSchemaPropertyEntries(propertySchema.items, visitor, {
124
+ prefix: `${currentName}.0`,
125
+ path: [...currentPath, 'items'],
126
+ skipArraySubProperties,
127
+ });
128
+ }
129
+
130
+ visitPropertyEntryBranches(propertySchema, visitor, {
131
+ prefix: currentName,
132
+ path: currentPath,
133
+ skipArraySubProperties,
134
+ });
135
+ });
136
+ }
137
+
138
+ visitPropertyEntryBranches(schema, visitor, {
139
+ prefix,
140
+ path,
141
+ skipArraySubProperties,
142
+ });
143
+ }
144
+
145
+ module.exports = {
146
+ visitSchemaNodes,
147
+ visitSchemaPropertyEntries,
148
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docusaurus-plugin-generate-schema-docs",
3
- "version": "1.8.3",
3
+ "version": "1.8.4",
4
4
  "description": "Docusaurus plugin to generate documentation from JSON schemas.",
5
5
  "main": "index.js",
6
6
  "license": "MIT",
@@ -3,6 +3,9 @@ const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
  const RefParser = require('@apidevtools/json-schema-ref-parser');
5
5
  const mergeAllOf = require('json-schema-merge-allof');
6
+ const {
7
+ visitSchemaPropertyEntries,
8
+ } = require('../helpers/schemaTraversal.cjs');
6
9
 
7
10
  const logger = {
8
11
  _isQuiet: false,
@@ -86,49 +89,58 @@ function findJsonFiles(dir) {
86
89
  return results;
87
90
  }
88
91
 
92
+ function isRootTrackingSchema(schema) {
93
+ return Boolean(schema?.properties?.event || schema?.['x-tracking-targets']);
94
+ }
95
+
89
96
  function parseSchema(schema, options, prefix = '') {
90
- if (!schema || !schema.properties) {
91
- return [];
92
- }
97
+ const variables = [];
98
+
99
+ visitSchemaPropertyEntries(
100
+ schema,
101
+ (property, context) => {
102
+ variables.push({
103
+ name: context.name,
104
+ description: property.description,
105
+ type: property.type,
106
+ });
107
+ },
108
+ {
109
+ prefix,
110
+ skipArraySubProperties: options.skipArraySubProperties,
111
+ },
112
+ );
93
113
 
94
- let variables = [];
95
- for (const key in schema.properties) {
96
- const property = schema.properties[key];
97
- const currentPath = prefix ? `${prefix}.${key}` : key;
98
- variables.push({
99
- name: currentPath,
100
- description: property.description,
101
- type: property.type,
102
- });
103
- if (
104
- property.type === 'array' &&
105
- property.items &&
106
- !options.skipArraySubProperties
107
- ) {
108
- if (property.items.properties) {
109
- variables.push(
110
- ...parseSchema(property.items, options, `${currentPath}.0`),
111
- );
112
- }
113
- } else if (property.type === 'object' && property.properties) {
114
- variables.push(...parseSchema(property, options, currentPath));
115
- }
116
- }
117
114
  return variables;
118
115
  }
119
116
 
117
+ function shouldIncludeSchemaForGtm(schema) {
118
+ if (!isRootTrackingSchema(schema)) {
119
+ return false;
120
+ }
121
+
122
+ const trackingTargets = schema?.['x-tracking-targets'];
123
+
124
+ return (
125
+ Array.isArray(trackingTargets) &&
126
+ trackingTargets.includes('web-datalayer-js')
127
+ );
128
+ }
129
+
120
130
  async function getVariablesFromSchemas(
121
131
  schemaPath,
122
132
  { skipArraySubProperties = false },
123
133
  ) {
124
134
  const allVariables = new Map();
125
135
  const jsonFiles = findJsonFiles(schemaPath);
126
- const eventFiles = jsonFiles.filter((f) => !f.includes('components'));
127
136
 
128
- for (const file of eventFiles) {
137
+ for (const file of jsonFiles) {
129
138
  try {
130
139
  let schema = await RefParser.bundle(file);
131
140
  schema = mergeAllOf(schema);
141
+ if (!shouldIncludeSchemaForGtm(schema)) {
142
+ continue;
143
+ }
132
144
  const fileVariables = parseSchema(schema, { skipArraySubProperties });
133
145
  for (const variable of fileVariables) {
134
146
  if (!allVariables.has(variable.name)) {
@@ -388,6 +400,7 @@ module.exports = {
388
400
  createGtmVariables,
389
401
  deleteGtmVariables,
390
402
  parseSchema,
403
+ shouldIncludeSchemaForGtm,
391
404
  findJsonFiles,
392
405
  safeJsonParse,
393
406
  logger,
@@ -118,6 +118,41 @@ const PAYLOAD_CONTRACTS = [
118
118
  },
119
119
  },
120
120
  },
121
+ {
122
+ id: 'login-with-user-properties',
123
+ class: 'user_properties',
124
+ example: {
125
+ event: 'login',
126
+ method: 'email',
127
+ user_properties: {
128
+ sign_up_method: 'email',
129
+ allow_ad_personalization_signals: 'false',
130
+ },
131
+ },
132
+ expected: {
133
+ web: {
134
+ eventName: 'login',
135
+ payload: {
136
+ event: 'login',
137
+ method: 'email',
138
+ user_properties: {
139
+ sign_up_method: 'email',
140
+ allow_ad_personalization_signals: 'false',
141
+ },
142
+ },
143
+ },
144
+ firebase: {
145
+ eventName: 'login',
146
+ parameters: {
147
+ method: 'email',
148
+ },
149
+ userProperties: {
150
+ sign_up_method: 'email',
151
+ allow_personalized_ads: 'false',
152
+ },
153
+ },
154
+ },
155
+ },
121
156
  {
122
157
  id: 'web-fallback-json-serialization',
123
158
  class: 'fallback_json_serialization',
@@ -43,7 +43,7 @@ const validateSingleSchema = async (filePath, schemaPath) => {
43
43
  fileHasAnyExample = true;
44
44
  const { example, title } = option;
45
45
 
46
- if (!example) {
46
+ if (typeof example === 'undefined') {
47
47
  errors.push(
48
48
  `x Schema ${file} (option: ${title}) does not produce a valid example.`,
49
49
  );