docusaurus-plugin-generate-schema-docs 1.7.0 → 1.8.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 +39 -0
- package/__tests__/ExampleDataLayer.test.js +149 -2
- package/__tests__/__fixtures__/schema-processing/components/dataLayer.json +9 -0
- package/__tests__/__fixtures__/schema-processing/event-reference.json +14 -0
- package/__tests__/__fixtures__/schema-processing/purchase-event.json +14 -0
- package/__tests__/components/PropertyRow.test.js +30 -0
- package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
- package/__tests__/helpers/exampleModel.test.js +88 -0
- package/__tests__/helpers/schema-processing.test.js +56 -0
- package/__tests__/helpers/snippetTargets.test.js +744 -0
- package/__tests__/helpers/trackingTargets.test.js +42 -0
- package/__tests__/runtimePayload.android.test.js +292 -0
- package/__tests__/runtimePayload.ios.test.js +282 -0
- package/__tests__/runtimePayload.web.test.js +32 -0
- package/__tests__/syncGtm.test.js +346 -0
- package/__tests__/validateSchemas.test.js +53 -1
- package/components/ExampleDataLayer.js +191 -56
- package/components/PropertyRow.js +3 -2
- package/components/SchemaRows.css +10 -1
- package/helpers/exampleModel.js +70 -0
- package/helpers/schema-processing.js +41 -5
- package/helpers/snippetTargets.js +853 -0
- package/helpers/trackingTargets.js +73 -0
- package/helpers/validator.js +1 -0
- package/index.js +34 -0
- package/package.json +1 -1
- package/scripts/sync-gtm.js +397 -0
- package/test-data/payloadContracts.js +155 -0
- package/validateSchemas.js +15 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const DEFAULT_TRACKING_TARGET = 'web-datalayer-js';
|
|
2
|
+
|
|
3
|
+
export const SUPPORTED_TRACKING_TARGETS = [
|
|
4
|
+
DEFAULT_TRACKING_TARGET,
|
|
5
|
+
'android-firebase-kotlin-sdk',
|
|
6
|
+
'android-firebase-java-sdk',
|
|
7
|
+
'ios-firebase-swift-sdk',
|
|
8
|
+
'ios-firebase-objc-sdk',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const TARGET_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+){2,}$/;
|
|
12
|
+
|
|
13
|
+
export function resolveTrackingTargets(
|
|
14
|
+
schema,
|
|
15
|
+
{ schemaFile = 'schema', isQuiet = false } = {},
|
|
16
|
+
) {
|
|
17
|
+
const configuredTargets = schema?.['x-tracking-targets'];
|
|
18
|
+
|
|
19
|
+
if (configuredTargets == null) {
|
|
20
|
+
const warning = isQuiet
|
|
21
|
+
? null
|
|
22
|
+
: `Schema ${schemaFile} is missing x-tracking-targets. Falling back to "${DEFAULT_TRACKING_TARGET}".`;
|
|
23
|
+
return {
|
|
24
|
+
targets: [DEFAULT_TRACKING_TARGET],
|
|
25
|
+
warning,
|
|
26
|
+
errors: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
!Array.isArray(configuredTargets) ||
|
|
32
|
+
configuredTargets.length === 0 ||
|
|
33
|
+
configuredTargets.some((target) => typeof target !== 'string')
|
|
34
|
+
) {
|
|
35
|
+
return {
|
|
36
|
+
targets: [],
|
|
37
|
+
warning: null,
|
|
38
|
+
errors: [
|
|
39
|
+
'x-tracking-targets must be a non-empty array of string target IDs.',
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const errors = [];
|
|
45
|
+
const unknownTargets = configuredTargets.filter(
|
|
46
|
+
(target) => !SUPPORTED_TRACKING_TARGETS.includes(target),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (unknownTargets.length > 0) {
|
|
50
|
+
errors.push(
|
|
51
|
+
`x-tracking-targets contains unsupported target(s): ${unknownTargets.join(
|
|
52
|
+
', ',
|
|
53
|
+
)}.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const badlyFormattedTargets = configuredTargets.filter(
|
|
58
|
+
(target) => !TARGET_ID_PATTERN.test(target),
|
|
59
|
+
);
|
|
60
|
+
if (badlyFormattedTargets.length > 0) {
|
|
61
|
+
errors.push(
|
|
62
|
+
`x-tracking-targets must use lowercase kebab-case IDs like "web-datalayer-js". Invalid value(s): ${badlyFormattedTargets.join(
|
|
63
|
+
', ',
|
|
64
|
+
)}.`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
targets: errors.length === 0 ? configuredTargets : [],
|
|
70
|
+
warning: null,
|
|
71
|
+
errors,
|
|
72
|
+
};
|
|
73
|
+
}
|
package/helpers/validator.js
CHANGED
package/index.js
CHANGED
|
@@ -4,6 +4,11 @@ import fs from 'fs';
|
|
|
4
4
|
import validateSchemas from './validateSchemas.js';
|
|
5
5
|
import generateEventDocs from './generateEventDocs.js';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
7
12
|
|
|
8
13
|
export default async function (context, options) {
|
|
9
14
|
const { siteDir } = context;
|
|
@@ -64,6 +69,35 @@ export default async function (context, options) {
|
|
|
64
69
|
updateSchemaIds(siteDir, url, version);
|
|
65
70
|
});
|
|
66
71
|
|
|
72
|
+
cli
|
|
73
|
+
.command('sync-gtm')
|
|
74
|
+
.description('Synchronize GTM Data Layer Variables from JSON schemas')
|
|
75
|
+
.option(
|
|
76
|
+
'--path <siteDir>',
|
|
77
|
+
'Docusaurus site directory that contains static/schemas',
|
|
78
|
+
siteDir,
|
|
79
|
+
)
|
|
80
|
+
.option('--json', 'Output JSON summary')
|
|
81
|
+
.option('--quiet', 'Suppress non-error logs')
|
|
82
|
+
.option(
|
|
83
|
+
'--skip-array-sub-properties',
|
|
84
|
+
'Skip array item sub-properties (e.g., list.0.item)',
|
|
85
|
+
)
|
|
86
|
+
.action((commandOptions) => {
|
|
87
|
+
const scriptPath = path.join(__dirname, 'scripts', 'sync-gtm.js');
|
|
88
|
+
const args = [`--path=${commandOptions.path}`];
|
|
89
|
+
|
|
90
|
+
if (commandOptions.json) args.push('--json');
|
|
91
|
+
if (commandOptions.quiet) args.push('--quiet');
|
|
92
|
+
if (commandOptions.skipArraySubProperties)
|
|
93
|
+
args.push('--skip-array-sub-properties');
|
|
94
|
+
|
|
95
|
+
execSync(`node "${scriptPath}" ${args.join(' ')}`, {
|
|
96
|
+
cwd: siteDir,
|
|
97
|
+
stdio: 'inherit',
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
67
101
|
cli
|
|
68
102
|
.command('version-with-schemas <version>')
|
|
69
103
|
.description(
|
package/package.json
CHANGED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const RefParser = require('@apidevtools/json-schema-ref-parser');
|
|
5
|
+
const mergeAllOf = require('json-schema-merge-allof');
|
|
6
|
+
|
|
7
|
+
const logger = {
|
|
8
|
+
_isQuiet: false,
|
|
9
|
+
_isJson: false,
|
|
10
|
+
setup: function (isJson, isQuiet) {
|
|
11
|
+
this._isQuiet = isQuiet;
|
|
12
|
+
this._isJson = isJson;
|
|
13
|
+
},
|
|
14
|
+
log: function (...args) {
|
|
15
|
+
if (!this._isQuiet && !this._isJson) {
|
|
16
|
+
console.log(...args);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
error: function (...args) {
|
|
20
|
+
if (!this._isQuiet) {
|
|
21
|
+
console.error(...args);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function assertGtmCliAvailable() {
|
|
27
|
+
try {
|
|
28
|
+
execSync('gtm --version', { stdio: 'pipe' });
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'GTM CLI is not installed or not available in PATH. Install it with "npm install --save-optional @owntag/gtm-cli" (or "npm install -g @owntag/gtm-cli").',
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function safeJsonParse(cliOutput) {
|
|
37
|
+
if (!cliOutput) return null;
|
|
38
|
+
const arrayStartIndex = cliOutput.indexOf('[');
|
|
39
|
+
const arrayEndIndex = cliOutput.lastIndexOf(']');
|
|
40
|
+
if (arrayStartIndex !== -1 && arrayEndIndex !== -1) {
|
|
41
|
+
const jsonString = cliOutput.substring(arrayStartIndex, arrayEndIndex + 1);
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(jsonString);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const objStartIndex = cliOutput.indexOf('{');
|
|
49
|
+
const objEndIndex = cliOutput.lastIndexOf('}');
|
|
50
|
+
if (objStartIndex !== -1 && objEndIndex !== -1) {
|
|
51
|
+
const jsonString = cliOutput.substring(objStartIndex, objEndIndex + 1);
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(jsonString);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getLatestSchemaPath(siteDir = '.') {
|
|
62
|
+
const versionsJsonPath = path.join(siteDir, 'versions.json');
|
|
63
|
+
const schemasBasePath = path.join(siteDir, 'static', 'schemas');
|
|
64
|
+
if (fs.existsSync(versionsJsonPath)) {
|
|
65
|
+
const versions = JSON.parse(fs.readFileSync(versionsJsonPath, 'utf-8'));
|
|
66
|
+
if (versions.length > 0) return path.join(schemasBasePath, versions[0]);
|
|
67
|
+
}
|
|
68
|
+
const nextVersionPath = path.join(schemasBasePath, 'next');
|
|
69
|
+
if (fs.existsSync(nextVersionPath)) return nextVersionPath;
|
|
70
|
+
return schemasBasePath;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function findJsonFiles(dir) {
|
|
74
|
+
let results = [];
|
|
75
|
+
if (!fs.existsSync(dir)) return results;
|
|
76
|
+
const list = fs.readdirSync(dir);
|
|
77
|
+
list.forEach((file) => {
|
|
78
|
+
const filePath = path.join(dir, file);
|
|
79
|
+
const stat = fs.statSync(filePath);
|
|
80
|
+
if (stat && stat.isDirectory()) {
|
|
81
|
+
results = results.concat(findJsonFiles(filePath));
|
|
82
|
+
} else if (path.extname(filePath) === '.json') {
|
|
83
|
+
results.push(filePath);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseSchema(schema, options, prefix = '') {
|
|
90
|
+
if (!schema || !schema.properties) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
|
|
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
|
+
return variables;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function getVariablesFromSchemas(
|
|
121
|
+
schemaPath,
|
|
122
|
+
{ skipArraySubProperties = false },
|
|
123
|
+
) {
|
|
124
|
+
const allVariables = new Map();
|
|
125
|
+
const jsonFiles = findJsonFiles(schemaPath);
|
|
126
|
+
const eventFiles = jsonFiles.filter((f) => !f.includes('components'));
|
|
127
|
+
|
|
128
|
+
for (const file of eventFiles) {
|
|
129
|
+
try {
|
|
130
|
+
let schema = await RefParser.bundle(file);
|
|
131
|
+
schema = mergeAllOf(schema);
|
|
132
|
+
const fileVariables = parseSchema(schema, { skipArraySubProperties });
|
|
133
|
+
for (const variable of fileVariables) {
|
|
134
|
+
if (!allVariables.has(variable.name)) {
|
|
135
|
+
allVariables.set(variable.name, variable);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
logger.error(`Error processing schema ${file}:`, e);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return Array.from(allVariables.values());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getGtmVariables() {
|
|
146
|
+
logger.log('Fetching existing GTM variables...');
|
|
147
|
+
const gtmVariablesOutput = execSync(
|
|
148
|
+
'gtm variables list -o json --quiet',
|
|
149
|
+
).toString();
|
|
150
|
+
return safeJsonParse(gtmVariablesOutput) || [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getVariablesToCreate(schemaVariables, gtmVariables) {
|
|
154
|
+
const gtmVarMap = new Map();
|
|
155
|
+
for (const gtmVar of gtmVariables) {
|
|
156
|
+
const nameParam = gtmVar.parameter?.find((p) => p.key === 'name');
|
|
157
|
+
if (gtmVar.type === 'v' && nameParam?.value) {
|
|
158
|
+
gtmVarMap.set(nameParam.value, gtmVar);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return schemaVariables.filter((sv) => !gtmVarMap.has(sv.name));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getVariablesToDelete(schemaVariables, gtmVariables) {
|
|
165
|
+
const schemaVarMap = new Map(schemaVariables.map((v) => [v.name, v]));
|
|
166
|
+
return gtmVariables.filter((gv) => {
|
|
167
|
+
const nameParam = gv.parameter?.find((p) => p.key === 'name');
|
|
168
|
+
return (
|
|
169
|
+
gv.type === 'v' &&
|
|
170
|
+
nameParam?.value &&
|
|
171
|
+
!schemaVarMap.has(nameParam.value) &&
|
|
172
|
+
gv.name.startsWith('DLV -')
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createGtmVariables(variablesToCreate) {
|
|
178
|
+
logger.log(`Found ${variablesToCreate.length} variables to create.`);
|
|
179
|
+
for (const v of variablesToCreate) {
|
|
180
|
+
const isSchemaVar = v.name === '$schema';
|
|
181
|
+
const name = `DLV - ${v.name.replace(/\$/g, '\\$')}`;
|
|
182
|
+
const notes = isSchemaVar
|
|
183
|
+
? `References the '$schema' property. ${(v.description || '').replace(
|
|
184
|
+
/'/g,
|
|
185
|
+
'`',
|
|
186
|
+
)}`
|
|
187
|
+
: (v.description || '').replace(/'/g, '`');
|
|
188
|
+
|
|
189
|
+
const config = {
|
|
190
|
+
notes,
|
|
191
|
+
parameter: [
|
|
192
|
+
{ type: 'INTEGER', key: 'dataLayerVersion', value: '2' },
|
|
193
|
+
{ type: 'BOOLEAN', key: 'setDefaultValue', value: 'false' },
|
|
194
|
+
{ type: 'TEMPLATE', key: 'name', value: v.name },
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
const command = `gtm variables create --name "${name}" --type v --config '${JSON.stringify(
|
|
198
|
+
config,
|
|
199
|
+
)}' --quiet`;
|
|
200
|
+
logger.log(`Executing: ${command}`);
|
|
201
|
+
execSync(command, { stdio: 'inherit' });
|
|
202
|
+
}
|
|
203
|
+
return variablesToCreate.map((v) => v.name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function deleteGtmVariables(variablesToDelete) {
|
|
207
|
+
logger.log(`Found ${variablesToDelete.length} variables to delete.`);
|
|
208
|
+
for (const v of variablesToDelete) {
|
|
209
|
+
const command = `gtm variables delete --variable-id ${v.variableId} --force --quiet`;
|
|
210
|
+
logger.log(`Executing: ${command}`);
|
|
211
|
+
execSync(command, { stdio: 'inherit' });
|
|
212
|
+
}
|
|
213
|
+
return variablesToDelete.map(
|
|
214
|
+
(v) => v.parameter.find((p) => p.key === 'name').value,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function syncGtmVariables(
|
|
219
|
+
schemaVariables,
|
|
220
|
+
{ skipArraySubProperties = false },
|
|
221
|
+
) {
|
|
222
|
+
const gtmVariables = getGtmVariables();
|
|
223
|
+
|
|
224
|
+
let finalSchemaVariables = schemaVariables;
|
|
225
|
+
if (skipArraySubProperties) {
|
|
226
|
+
finalSchemaVariables = schemaVariables.filter(
|
|
227
|
+
(v) => !v.name.includes('.0.'),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const toCreate = getVariablesToCreate(finalSchemaVariables, gtmVariables);
|
|
232
|
+
const toDelete = getVariablesToDelete(finalSchemaVariables, gtmVariables);
|
|
233
|
+
const inSync = schemaVariables.filter(
|
|
234
|
+
(s) => !toCreate.find((c) => c.name === s.name),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const created = createGtmVariables(toCreate);
|
|
238
|
+
const deleted = deleteGtmVariables(toDelete);
|
|
239
|
+
|
|
240
|
+
logger.log('GTM variable synchronization complete.');
|
|
241
|
+
return {
|
|
242
|
+
created,
|
|
243
|
+
deleted,
|
|
244
|
+
inSync: inSync.map((v) => v.name),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function setupGtmWorkspace() {
|
|
249
|
+
const branchName =
|
|
250
|
+
process.env.GTM_WORKSPACE_BRANCH ||
|
|
251
|
+
execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
|
252
|
+
const workspaceName = `feat/${branchName.replace(/[^a-zA-Z0-9]/g, '-')}`;
|
|
253
|
+
|
|
254
|
+
logger.log('Listing GTM workspaces to find and delete existing...');
|
|
255
|
+
const workspacesOutput = execSync(
|
|
256
|
+
'gtm workspaces list -o json --quiet',
|
|
257
|
+
).toString();
|
|
258
|
+
const workspaces = safeJsonParse(workspacesOutput) || [];
|
|
259
|
+
const existingWorkspace = workspaces.find((w) => w.name === workspaceName);
|
|
260
|
+
|
|
261
|
+
if (existingWorkspace) {
|
|
262
|
+
logger.log(
|
|
263
|
+
`Found and deleting existing GTM workspace named "${workspaceName}" with ID: ${existingWorkspace.workspaceId}`,
|
|
264
|
+
);
|
|
265
|
+
execSync(
|
|
266
|
+
`gtm workspaces delete --workspace-id ${existingWorkspace.workspaceId} --force --quiet`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
logger.log(`Creating GTM workspace named "${workspaceName}"...`);
|
|
271
|
+
const createWorkspaceOutput = execSync(
|
|
272
|
+
`gtm workspaces create --name "${workspaceName}" -o json --quiet`,
|
|
273
|
+
).toString();
|
|
274
|
+
const newWorkspace = safeJsonParse(createWorkspaceOutput);
|
|
275
|
+
const workspaceId =
|
|
276
|
+
newWorkspace &&
|
|
277
|
+
(Array.isArray(newWorkspace)
|
|
278
|
+
? newWorkspace[0]?.workspaceId
|
|
279
|
+
: newWorkspace.workspaceId);
|
|
280
|
+
|
|
281
|
+
if (!workspaceId) {
|
|
282
|
+
throw new Error('Failed to create GTM workspace or parse its ID.');
|
|
283
|
+
}
|
|
284
|
+
logger.log(`Successfully created workspace with ID: ${workspaceId}`);
|
|
285
|
+
|
|
286
|
+
execSync(`gtm config set defaultWorkspaceId ${workspaceId}`);
|
|
287
|
+
logger.log(`Set default workspace to ${workspaceId}`);
|
|
288
|
+
return { workspaceName, workspaceId };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function parseArgs(argv) {
|
|
292
|
+
const args = argv.slice(2);
|
|
293
|
+
const pathArg = args.find((arg) => arg.startsWith('--path=')) || '';
|
|
294
|
+
const siteDir = pathArg.split('=')[1] || '.';
|
|
295
|
+
return {
|
|
296
|
+
isJson: args.includes('--json'),
|
|
297
|
+
isQuiet: args.includes('--quiet'),
|
|
298
|
+
skipArraySubProperties: args.includes('--skip-array-sub-properties'),
|
|
299
|
+
siteDir,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function main(argv, deps) {
|
|
304
|
+
try {
|
|
305
|
+
const {
|
|
306
|
+
setupGtmWorkspace: setup,
|
|
307
|
+
getLatestSchemaPath: getPath,
|
|
308
|
+
getVariablesFromSchemas: getVars,
|
|
309
|
+
syncGtmVariables: sync,
|
|
310
|
+
assertGtmCliAvailable: assertCli = assertGtmCliAvailable,
|
|
311
|
+
logger: log,
|
|
312
|
+
parseArgs: parse,
|
|
313
|
+
process: proc,
|
|
314
|
+
} = deps;
|
|
315
|
+
const { isJson, isQuiet, skipArraySubProperties, siteDir } = parse(argv);
|
|
316
|
+
log.setup(isJson, isQuiet);
|
|
317
|
+
|
|
318
|
+
log.log('Starting GTM variable synchronization script...');
|
|
319
|
+
assertCli();
|
|
320
|
+
|
|
321
|
+
const { workspaceName, workspaceId } = await setup();
|
|
322
|
+
|
|
323
|
+
const schemaPath = getPath(siteDir);
|
|
324
|
+
if (!fs.existsSync(schemaPath)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Schema directory not found: ${schemaPath}. Use --path=<siteDir> to point to your Docusaurus project root.`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
log.log(`Scanning for schemas in: ${schemaPath}`);
|
|
330
|
+
const schemaVariables = await getVars(schemaPath, {
|
|
331
|
+
skipArraySubProperties,
|
|
332
|
+
});
|
|
333
|
+
if (schemaVariables.length === 0) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`No schema variables found in ${schemaPath}. Aborting to avoid deleting existing GTM variables.`,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
log.log(`Found ${schemaVariables.length} variables defined in schemas.`);
|
|
339
|
+
|
|
340
|
+
const summary = await sync(schemaVariables, { skipArraySubProperties });
|
|
341
|
+
|
|
342
|
+
if (isJson) {
|
|
343
|
+
console.log(
|
|
344
|
+
JSON.stringify(
|
|
345
|
+
{ workspace: { workspaceName, workspaceId }, ...summary },
|
|
346
|
+
null,
|
|
347
|
+
2,
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
} else {
|
|
351
|
+
log.log('Synchronization successful!');
|
|
352
|
+
log.log(
|
|
353
|
+
`All changes applied in GTM workspace: "${workspaceName}" (ID: ${workspaceId})`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
logger.error('An error occurred during GTM synchronization:');
|
|
358
|
+
logger.error(error.message);
|
|
359
|
+
if (deps.process && typeof deps.process.exit === 'function') {
|
|
360
|
+
deps.process.exit(1);
|
|
361
|
+
} else {
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (require.main === module) {
|
|
368
|
+
const dependencies = {
|
|
369
|
+
setupGtmWorkspace,
|
|
370
|
+
getLatestSchemaPath,
|
|
371
|
+
getVariablesFromSchemas,
|
|
372
|
+
syncGtmVariables,
|
|
373
|
+
assertGtmCliAvailable,
|
|
374
|
+
logger,
|
|
375
|
+
parseArgs,
|
|
376
|
+
process,
|
|
377
|
+
};
|
|
378
|
+
main(process.argv, dependencies);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
module.exports = {
|
|
382
|
+
getLatestSchemaPath,
|
|
383
|
+
getVariablesFromSchemas,
|
|
384
|
+
syncGtmVariables,
|
|
385
|
+
main,
|
|
386
|
+
getVariablesToCreate,
|
|
387
|
+
getVariablesToDelete,
|
|
388
|
+
createGtmVariables,
|
|
389
|
+
deleteGtmVariables,
|
|
390
|
+
parseSchema,
|
|
391
|
+
findJsonFiles,
|
|
392
|
+
safeJsonParse,
|
|
393
|
+
logger,
|
|
394
|
+
parseArgs,
|
|
395
|
+
assertGtmCliAvailable,
|
|
396
|
+
setupGtmWorkspace,
|
|
397
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const PAYLOAD_CONTRACTS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'screen-view-predefined',
|
|
4
|
+
class: 'predefined',
|
|
5
|
+
example: {
|
|
6
|
+
event: 'screen_view',
|
|
7
|
+
firebase_screen: 'Checkout',
|
|
8
|
+
firebase_screen_class: 'CheckoutViewController',
|
|
9
|
+
},
|
|
10
|
+
expected: {
|
|
11
|
+
web: {
|
|
12
|
+
eventName: 'screen_view',
|
|
13
|
+
payload: {
|
|
14
|
+
event: 'screen_view',
|
|
15
|
+
firebase_screen: 'Checkout',
|
|
16
|
+
firebase_screen_class: 'CheckoutViewController',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
firebase: {
|
|
20
|
+
eventName: 'screen_view',
|
|
21
|
+
parameters: {
|
|
22
|
+
screen_name: 'Checkout',
|
|
23
|
+
screen_class: 'CheckoutViewController',
|
|
24
|
+
},
|
|
25
|
+
userProperties: {},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'add-to-cart-with-items',
|
|
31
|
+
class: 'ecommerce_items',
|
|
32
|
+
example: {
|
|
33
|
+
event: 'add_to_cart',
|
|
34
|
+
currency: 'EUR',
|
|
35
|
+
value: 42.5,
|
|
36
|
+
items: [
|
|
37
|
+
{
|
|
38
|
+
item_id: 'sku-1',
|
|
39
|
+
item_name: 'Socks',
|
|
40
|
+
price: 21.25,
|
|
41
|
+
quantity: 2,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
expected: {
|
|
46
|
+
web: {
|
|
47
|
+
eventName: 'add_to_cart',
|
|
48
|
+
payload: {
|
|
49
|
+
event: 'add_to_cart',
|
|
50
|
+
currency: 'EUR',
|
|
51
|
+
value: 42.5,
|
|
52
|
+
items: [
|
|
53
|
+
{
|
|
54
|
+
item_id: 'sku-1',
|
|
55
|
+
item_name: 'Socks',
|
|
56
|
+
price: 21.25,
|
|
57
|
+
quantity: 2,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
firebase: {
|
|
63
|
+
eventName: 'add_to_cart',
|
|
64
|
+
parameters: {
|
|
65
|
+
currency: 'EUR',
|
|
66
|
+
value: 42.5,
|
|
67
|
+
items: [
|
|
68
|
+
{
|
|
69
|
+
item_id: 'sku-1',
|
|
70
|
+
item_name: 'Socks',
|
|
71
|
+
price: 21.25,
|
|
72
|
+
quantity: 2,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
userProperties: {},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'custom-event-with-user-properties',
|
|
82
|
+
class: 'custom',
|
|
83
|
+
example: {
|
|
84
|
+
event: 'my_custom_event',
|
|
85
|
+
plan: 'pro',
|
|
86
|
+
count: 2,
|
|
87
|
+
premium: true,
|
|
88
|
+
user_properties: {
|
|
89
|
+
sign_up_method: 'email',
|
|
90
|
+
allow_ad_personalization_signals: false,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
expected: {
|
|
94
|
+
web: {
|
|
95
|
+
eventName: 'my_custom_event',
|
|
96
|
+
payload: {
|
|
97
|
+
event: 'my_custom_event',
|
|
98
|
+
plan: 'pro',
|
|
99
|
+
count: 2,
|
|
100
|
+
premium: true,
|
|
101
|
+
user_properties: {
|
|
102
|
+
sign_up_method: 'email',
|
|
103
|
+
allow_ad_personalization_signals: false,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
firebase: {
|
|
108
|
+
eventName: 'my_custom_event',
|
|
109
|
+
parameters: {
|
|
110
|
+
plan: 'pro',
|
|
111
|
+
count: 2,
|
|
112
|
+
premium: 1,
|
|
113
|
+
},
|
|
114
|
+
userProperties: {
|
|
115
|
+
sign_up_method: 'email',
|
|
116
|
+
allow_personalized_ads: 'false',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'web-fallback-json-serialization',
|
|
123
|
+
class: 'fallback_json_serialization',
|
|
124
|
+
targets: ['web-datalayer-js'],
|
|
125
|
+
example: {
|
|
126
|
+
event: 'metadata_capture',
|
|
127
|
+
metadata: {
|
|
128
|
+
checkout_step: 2,
|
|
129
|
+
flags: ['beta', 'ios'],
|
|
130
|
+
},
|
|
131
|
+
user: {
|
|
132
|
+
id: 'U-123',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
expected: {
|
|
136
|
+
web: {
|
|
137
|
+
eventName: 'metadata_capture',
|
|
138
|
+
payload: {
|
|
139
|
+
event: 'metadata_capture',
|
|
140
|
+
metadata: {
|
|
141
|
+
checkout_step: 2,
|
|
142
|
+
flags: ['beta', 'ios'],
|
|
143
|
+
},
|
|
144
|
+
user: {
|
|
145
|
+
id: 'U-123',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
PAYLOAD_CONTRACTS,
|
|
155
|
+
};
|