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.
- package/README.md +2 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
- package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +21 -3
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +26 -4
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +45 -6
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +16 -2
- package/__tests__/components/ConditionalRows.test.js +28 -0
- package/__tests__/components/FoldableRows.test.js +31 -290
- package/__tests__/components/PropertiesTable.test.js +66 -0
- package/__tests__/components/PropertyRow.test.js +297 -0
- package/__tests__/components/SchemaJsonViewer.test.js +194 -10
- package/__tests__/components/SchemaRows.test.js +62 -12
- package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
- package/__tests__/generateEventDocs.test.js +3 -0
- package/__tests__/helpers/example-helper.test.js +12 -0
- package/__tests__/helpers/getConstraints.test.js +16 -0
- package/__tests__/helpers/processSchema.test.js +18 -0
- package/__tests__/helpers/schemaToTableData.test.js +112 -0
- package/__tests__/helpers/schemaTraversal.test.js +110 -0
- package/__tests__/syncGtm.test.js +227 -3
- package/__tests__/validateSchemas.test.js +50 -0
- package/components/ConditionalRows.js +6 -3
- package/components/FoldableRows.js +9 -3
- package/components/PropertiesTable.js +34 -3
- package/components/PropertyRow.js +118 -6
- package/components/SchemaJsonViewer.js +324 -4
- package/components/SchemaRows.css +138 -7
- package/components/SchemaRows.js +11 -1
- package/components/SchemaViewer.js +11 -2
- package/generateEventDocs.js +87 -1
- package/helpers/choice-index-template.js +6 -2
- package/helpers/example-helper.js +2 -2
- package/helpers/file-system.js +28 -0
- package/helpers/getConstraints.js +20 -0
- package/helpers/processSchema.js +32 -1
- package/helpers/schema-doc-template.js +11 -1
- package/helpers/schemaToExamples.js +29 -35
- package/helpers/schemaToTableData.js +68 -7
- package/helpers/schemaTraversal.cjs +148 -0
- package/package.json +1 -1
- package/scripts/sync-gtm.js +41 -28
- package/test-data/payloadContracts.js +35 -0
- 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
|
-
|
|
251
|
-
|
|
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
|
|
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
|
-
|
|
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 ===
|
|
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
package/scripts/sync-gtm.js
CHANGED
|
@@ -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
|
-
|
|
91
|
-
|
|
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
|
|
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',
|
package/validateSchemas.js
CHANGED
|
@@ -43,7 +43,7 @@ const validateSingleSchema = async (filePath, schemaPath) => {
|
|
|
43
43
|
fileHasAnyExample = true;
|
|
44
44
|
const { example, title } = option;
|
|
45
45
|
|
|
46
|
-
if (
|
|
46
|
+
if (typeof example === 'undefined') {
|
|
47
47
|
errors.push(
|
|
48
48
|
`x Schema ${file} (option: ${title}) does not produce a valid example.`,
|
|
49
49
|
);
|