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
@@ -41,6 +41,36 @@ function materializeConditionalBranchSchema(branchSchema, parentSchema) {
41
41
  };
42
42
  }
43
43
 
44
+ function hasRenderableAdditionalProperties(schemaNode) {
45
+ return !!(
46
+ schemaNode &&
47
+ schemaNode.additionalProperties &&
48
+ typeof schemaNode.additionalProperties === 'object' &&
49
+ !Array.isArray(schemaNode.additionalProperties)
50
+ );
51
+ }
52
+
53
+ function getRenderablePatternProperties(schemaNode) {
54
+ if (!schemaNode?.patternProperties) {
55
+ return [];
56
+ }
57
+
58
+ return Object.entries(schemaNode.patternProperties)
59
+ .filter(
60
+ ([, patternSchema]) =>
61
+ patternSchema &&
62
+ typeof patternSchema === 'object' &&
63
+ !Array.isArray(patternSchema),
64
+ )
65
+ .map(([pattern, patternSchema]) => [
66
+ `patternProperties /${pattern}/`,
67
+ {
68
+ ...patternSchema,
69
+ 'x-schema-keyword-row': true,
70
+ },
71
+ ]);
72
+ }
73
+
44
74
  function processOptions(
45
75
  choices,
46
76
  level,
@@ -247,8 +277,26 @@ export function schemaToTableData(
247
277
  ) {
248
278
  if (!subSchema) return;
249
279
 
250
- if (subSchema.properties) {
251
- const propKeys = Object.keys(subSchema.properties);
280
+ const patternPropertyEntries = getRenderablePatternProperties(subSchema);
281
+
282
+ if (
283
+ subSchema.properties ||
284
+ hasRenderableAdditionalProperties(subSchema) ||
285
+ patternPropertyEntries.length > 0
286
+ ) {
287
+ const propEntries = subSchema.properties
288
+ ? Object.entries(subSchema.properties)
289
+ : [];
290
+ if (hasRenderableAdditionalProperties(subSchema)) {
291
+ propEntries.push([
292
+ 'additionalProperties',
293
+ {
294
+ ...subSchema.additionalProperties,
295
+ 'x-schema-keyword-row': true,
296
+ },
297
+ ]);
298
+ }
299
+ propEntries.push(...patternPropertyEntries);
252
300
  const hasSiblingChoices = !!(
253
301
  subSchema.oneOf ||
254
302
  subSchema.anyOf ||
@@ -256,19 +304,17 @@ export function schemaToTableData(
256
304
  );
257
305
 
258
306
  // Filter out properties that should be skipped to get accurate count
259
- const visiblePropKeys = propKeys.filter((name) => {
260
- const propSchema = subSchema.properties[name];
307
+ const visiblePropEntries = propEntries.filter(([name, propSchema]) => {
261
308
  return !(
262
309
  propSchema['x-gtm-clear'] === true && isEffectivelyEmpty(propSchema)
263
310
  );
264
311
  });
265
312
 
266
- visiblePropKeys.forEach((name, index) => {
267
- const propSchema = subSchema.properties[name];
313
+ visiblePropEntries.forEach(([name, propSchema], index) => {
268
314
  const newPath = [...currentPath, name];
269
315
 
270
316
  const isLastProp =
271
- index === visiblePropKeys.length - 1 && !hasSiblingChoices;
317
+ index === visiblePropEntries.length - 1 && !hasSiblingChoices;
272
318
 
273
319
  // Updated Logic:
274
320
  // A property is visually "last" only if it is the last property
@@ -283,6 +329,8 @@ export function schemaToTableData(
283
329
 
284
330
  // Determine if this property has children and what type
285
331
  const hasNestedProperties = !!propSchema.properties;
332
+ const hasAdditionalProperties =
333
+ hasRenderableAdditionalProperties(propSchema);
286
334
  const hasArrayItems =
287
335
  propSchema.type === 'array' &&
288
336
  !!(propSchema.items?.properties || propSchema.items?.if);
@@ -290,6 +338,7 @@ export function schemaToTableData(
290
338
  const hasNestedConditional = isConditionalWrapper;
291
339
  const hasChildren =
292
340
  hasNestedProperties ||
341
+ hasAdditionalProperties ||
293
342
  hasArrayItems ||
294
343
  hasNestedChoice ||
295
344
  hasNestedConditional;
@@ -302,6 +351,7 @@ export function schemaToTableData(
302
351
  choiceOptions.some((opt) => opt.type === 'object' || opt.properties);
303
352
  if (
304
353
  hasNestedProperties ||
354
+ hasAdditionalProperties ||
305
355
  (isChoiceWrapper && propSchema.type === 'object') ||
306
356
  (isConditionalWrapper && propSchema.type === 'object') ||
307
357
  choiceOptionsAreObjects
@@ -388,6 +438,8 @@ export function schemaToTableData(
388
438
  containerType,
389
439
  continuingLevels: [...continuingLevels],
390
440
  groupBrackets: [...currentGroupBrackets],
441
+ isSchemaKeywordRow: propSchema['x-schema-keyword-row'] === true,
442
+ keepConnectorOpen: propSchema['x-keep-connector-open'] === true,
391
443
  });
392
444
 
393
445
  if (propSchema.properties) {
@@ -399,6 +451,15 @@ export function schemaToTableData(
399
451
  childContinuingLevels,
400
452
  currentGroupBrackets,
401
453
  );
454
+ } else if (propSchema.type === 'object' && hasAdditionalProperties) {
455
+ buildRows(
456
+ propSchema,
457
+ currentLevel + 1,
458
+ newPath,
459
+ [],
460
+ childContinuingLevels,
461
+ currentGroupBrackets,
462
+ );
402
463
  } else if (
403
464
  propSchema.type === 'array' &&
404
465
  (propSchema.items?.properties || propSchema.items?.if)
@@ -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.2",
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
  );