@jwiedeman/gtm-kit-cli 1.0.1
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 +263 -0
- package/dist/cli.js +1204 -0
- package/dist/index.cjs +1425 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +115 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +1392 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined")
|
|
7
|
+
return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// src/cli.ts
|
|
12
|
+
import * as fs2 from "fs";
|
|
13
|
+
import * as path2 from "path";
|
|
14
|
+
import * as readline from "readline";
|
|
15
|
+
|
|
16
|
+
// src/detect.ts
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
var readPackageJson = (dir) => {
|
|
20
|
+
const pkgPath = path.join(dir, "package.json");
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(pkgPath)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const content = fs.readFileSync(pkgPath, "utf-8");
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var fileExists = (dir, filename) => {
|
|
32
|
+
return fs.existsSync(path.join(dir, filename));
|
|
33
|
+
};
|
|
34
|
+
var getDependencyVersion = (pkg, name) => {
|
|
35
|
+
var _a, _b, _c;
|
|
36
|
+
return (_c = (_a = pkg.dependencies) == null ? void 0 : _a[name]) != null ? _c : (_b = pkg.devDependencies) == null ? void 0 : _b[name];
|
|
37
|
+
};
|
|
38
|
+
var detectPackageManager = (dir, pkg) => {
|
|
39
|
+
if (fileExists(dir, "pnpm-lock.yaml"))
|
|
40
|
+
return "pnpm";
|
|
41
|
+
if (fileExists(dir, "yarn.lock"))
|
|
42
|
+
return "yarn";
|
|
43
|
+
if (fileExists(dir, "bun.lockb"))
|
|
44
|
+
return "bun";
|
|
45
|
+
if (fileExists(dir, "package-lock.json"))
|
|
46
|
+
return "npm";
|
|
47
|
+
if (pkg == null ? void 0 : pkg.packageManager) {
|
|
48
|
+
if (pkg.packageManager.startsWith("pnpm"))
|
|
49
|
+
return "pnpm";
|
|
50
|
+
if (pkg.packageManager.startsWith("yarn"))
|
|
51
|
+
return "yarn";
|
|
52
|
+
if (pkg.packageManager.startsWith("bun"))
|
|
53
|
+
return "bun";
|
|
54
|
+
}
|
|
55
|
+
return "npm";
|
|
56
|
+
};
|
|
57
|
+
var getInstallCommand = (packageManager, packages) => {
|
|
58
|
+
const pkgList = packages.join(" ");
|
|
59
|
+
switch (packageManager) {
|
|
60
|
+
case "pnpm":
|
|
61
|
+
return `pnpm add ${pkgList}`;
|
|
62
|
+
case "yarn":
|
|
63
|
+
return `yarn add ${pkgList}`;
|
|
64
|
+
case "bun":
|
|
65
|
+
return `bun add ${pkgList}`;
|
|
66
|
+
case "npm":
|
|
67
|
+
default:
|
|
68
|
+
return `npm install ${pkgList}`;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var detectFramework = (dir = process.cwd()) => {
|
|
72
|
+
var _a;
|
|
73
|
+
const pkg = readPackageJson(dir);
|
|
74
|
+
const packageManager = detectPackageManager(dir, pkg);
|
|
75
|
+
if (pkg && getDependencyVersion(pkg, "nuxt")) {
|
|
76
|
+
const version = getDependencyVersion(pkg, "nuxt");
|
|
77
|
+
return {
|
|
78
|
+
framework: "nuxt",
|
|
79
|
+
version: version == null ? void 0 : version.replace(/^\^|~/, ""),
|
|
80
|
+
packageManager,
|
|
81
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-nuxt"],
|
|
82
|
+
displayName: "Nuxt 3",
|
|
83
|
+
confidence: 100,
|
|
84
|
+
reason: 'Found "nuxt" in dependencies'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (fileExists(dir, "nuxt.config.ts") || fileExists(dir, "nuxt.config.js")) {
|
|
88
|
+
return {
|
|
89
|
+
framework: "nuxt",
|
|
90
|
+
packageManager,
|
|
91
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-nuxt"],
|
|
92
|
+
displayName: "Nuxt 3",
|
|
93
|
+
confidence: 95,
|
|
94
|
+
reason: "Found nuxt.config file"
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (pkg && getDependencyVersion(pkg, "next")) {
|
|
98
|
+
const version = getDependencyVersion(pkg, "next");
|
|
99
|
+
return {
|
|
100
|
+
framework: "next",
|
|
101
|
+
version: version == null ? void 0 : version.replace(/^\^|~/, ""),
|
|
102
|
+
packageManager,
|
|
103
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-next"],
|
|
104
|
+
displayName: "Next.js",
|
|
105
|
+
confidence: 100,
|
|
106
|
+
reason: 'Found "next" in dependencies'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (fileExists(dir, "next.config.js") || fileExists(dir, "next.config.mjs") || fileExists(dir, "next.config.ts")) {
|
|
110
|
+
return {
|
|
111
|
+
framework: "next",
|
|
112
|
+
packageManager,
|
|
113
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-next"],
|
|
114
|
+
displayName: "Next.js",
|
|
115
|
+
confidence: 90,
|
|
116
|
+
reason: "Found next.config file"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (pkg && getDependencyVersion(pkg, "vue")) {
|
|
120
|
+
const version = getDependencyVersion(pkg, "vue");
|
|
121
|
+
return {
|
|
122
|
+
framework: "vue",
|
|
123
|
+
version: version == null ? void 0 : version.replace(/^\^|~/, ""),
|
|
124
|
+
packageManager,
|
|
125
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-vue"],
|
|
126
|
+
displayName: "Vue 3",
|
|
127
|
+
confidence: 100,
|
|
128
|
+
reason: 'Found "vue" in dependencies'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (fileExists(dir, "vite.config.ts") || fileExists(dir, "vite.config.js")) {
|
|
132
|
+
const viteConfig = path.join(dir, fileExists(dir, "vite.config.ts") ? "vite.config.ts" : "vite.config.js");
|
|
133
|
+
try {
|
|
134
|
+
const content = fs.readFileSync(viteConfig, "utf-8");
|
|
135
|
+
if (content.includes("@vitejs/plugin-vue") || content.includes("vue()")) {
|
|
136
|
+
return {
|
|
137
|
+
framework: "vue",
|
|
138
|
+
packageManager,
|
|
139
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-vue"],
|
|
140
|
+
displayName: "Vue 3 (Vite)",
|
|
141
|
+
confidence: 85,
|
|
142
|
+
reason: "Found Vue plugin in vite.config"
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (pkg && getDependencyVersion(pkg, "react")) {
|
|
149
|
+
const version = getDependencyVersion(pkg, "react");
|
|
150
|
+
const majorVersion = parseInt((_a = version == null ? void 0 : version.replace(/^\^|~/, "").split(".")[0]) != null ? _a : "18", 10);
|
|
151
|
+
if (majorVersion >= 16) {
|
|
152
|
+
return {
|
|
153
|
+
framework: "react",
|
|
154
|
+
version: version == null ? void 0 : version.replace(/^\^|~/, ""),
|
|
155
|
+
packageManager,
|
|
156
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-react"],
|
|
157
|
+
displayName: majorVersion >= 18 ? "React 18+" : "React 16.8+",
|
|
158
|
+
confidence: 100,
|
|
159
|
+
reason: 'Found "react" in dependencies'
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
framework: "react",
|
|
164
|
+
version: version == null ? void 0 : version.replace(/^\^|~/, ""),
|
|
165
|
+
packageManager,
|
|
166
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-react-legacy"],
|
|
167
|
+
displayName: "React (Legacy)",
|
|
168
|
+
confidence: 100,
|
|
169
|
+
reason: 'Found older "react" version in dependencies'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const srcDir = path.join(dir, "src");
|
|
173
|
+
if (fs.existsSync(srcDir)) {
|
|
174
|
+
try {
|
|
175
|
+
const files = fs.readdirSync(srcDir);
|
|
176
|
+
if (files.some((f) => f.endsWith(".jsx") || f.endsWith(".tsx"))) {
|
|
177
|
+
return {
|
|
178
|
+
framework: "react",
|
|
179
|
+
packageManager,
|
|
180
|
+
packages: ["@jwiedeman/gtm-kit", "@jwiedeman/gtm-kit-react"],
|
|
181
|
+
displayName: "React (detected from .jsx/.tsx files)",
|
|
182
|
+
confidence: 70,
|
|
183
|
+
reason: "Found .jsx or .tsx files in src/"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
} catch (e) {
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
framework: "vanilla",
|
|
191
|
+
packageManager,
|
|
192
|
+
packages: ["@jwiedeman/gtm-kit"],
|
|
193
|
+
displayName: "Vanilla JavaScript",
|
|
194
|
+
confidence: 50,
|
|
195
|
+
reason: "No framework detected, using core package only"
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
var getDetectionSummary = (info) => {
|
|
199
|
+
const lines = [
|
|
200
|
+
`Framework: ${info.displayName}`,
|
|
201
|
+
`Package Manager: ${info.packageManager}`,
|
|
202
|
+
`Confidence: ${info.confidence}%`,
|
|
203
|
+
`Reason: ${info.reason}`,
|
|
204
|
+
"",
|
|
205
|
+
"Packages to install:",
|
|
206
|
+
...info.packages.map((p) => ` - ${p}`)
|
|
207
|
+
];
|
|
208
|
+
if (info.version) {
|
|
209
|
+
lines.splice(1, 0, `Version: ${info.version}`);
|
|
210
|
+
}
|
|
211
|
+
return lines.join("\n");
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/validate.ts
|
|
215
|
+
var GTM_ID_PATTERN = /^GTM-[A-Z0-9]{6,8}$/;
|
|
216
|
+
var COMMON_MISTAKES = [
|
|
217
|
+
{
|
|
218
|
+
pattern: /^gtm-[A-Za-z0-9]/,
|
|
219
|
+
message: 'GTM ID should use uppercase "GTM-" prefix',
|
|
220
|
+
suggestion: "Use uppercase: GTM-XXXXXX"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
pattern: /^G-/,
|
|
224
|
+
message: "This looks like a GA4 Measurement ID, not a GTM container ID",
|
|
225
|
+
suggestion: 'GTM IDs start with "GTM-", GA4 IDs start with "G-"'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
pattern: /^UA-/,
|
|
229
|
+
message: "This is a Universal Analytics ID, not a GTM container ID",
|
|
230
|
+
suggestion: 'GTM IDs start with "GTM-", UA IDs start with "UA-"'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
pattern: /^AW-/,
|
|
234
|
+
message: "This looks like a Google Ads conversion ID, not a GTM container ID",
|
|
235
|
+
suggestion: 'GTM IDs start with "GTM-", Google Ads IDs start with "AW-"'
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
pattern: /^DC-/,
|
|
239
|
+
message: "This looks like a DoubleClick ID, not a GTM container ID",
|
|
240
|
+
suggestion: 'GTM IDs start with "GTM-"'
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
pattern: /^GTM-[A-Za-z0-9]{1,5}$/,
|
|
244
|
+
message: "GTM container ID appears too short",
|
|
245
|
+
suggestion: "GTM IDs are typically 6-8 characters after the prefix (e.g., GTM-ABCD123)"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
pattern: /^GTM-[A-Za-z0-9]{9,}$/,
|
|
249
|
+
message: "GTM container ID appears too long",
|
|
250
|
+
suggestion: "GTM IDs are typically 6-8 characters after the prefix (e.g., GTM-ABCD123)"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
pattern: /\s/,
|
|
254
|
+
message: "GTM container ID should not contain spaces",
|
|
255
|
+
suggestion: "Remove any spaces from the ID"
|
|
256
|
+
}
|
|
257
|
+
];
|
|
258
|
+
var validateGtmId = (id) => {
|
|
259
|
+
if (!id || typeof id !== "string") {
|
|
260
|
+
return {
|
|
261
|
+
valid: false,
|
|
262
|
+
error: "GTM container ID is required",
|
|
263
|
+
suggestion: "Provide a valid GTM container ID (e.g., GTM-XXXXXX)"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const trimmedId = id.trim();
|
|
267
|
+
if (trimmedId.length === 0) {
|
|
268
|
+
return {
|
|
269
|
+
valid: false,
|
|
270
|
+
error: "GTM container ID cannot be empty",
|
|
271
|
+
suggestion: "Provide a valid GTM container ID (e.g., GTM-XXXXXX)"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
for (const mistake of COMMON_MISTAKES) {
|
|
275
|
+
if (mistake.pattern.test(trimmedId)) {
|
|
276
|
+
return {
|
|
277
|
+
valid: false,
|
|
278
|
+
error: mistake.message,
|
|
279
|
+
suggestion: mistake.suggestion
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!GTM_ID_PATTERN.test(trimmedId)) {
|
|
284
|
+
if (!trimmedId.startsWith("GTM-")) {
|
|
285
|
+
return {
|
|
286
|
+
valid: false,
|
|
287
|
+
error: 'GTM container ID must start with "GTM-"',
|
|
288
|
+
suggestion: `Did you mean: GTM-${trimmedId.replace(/^[A-Za-z]+-?/, "")}?`
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const afterPrefix = trimmedId.slice(4);
|
|
292
|
+
if (/[a-z]/.test(afterPrefix)) {
|
|
293
|
+
return {
|
|
294
|
+
valid: false,
|
|
295
|
+
error: "GTM container ID should use uppercase letters",
|
|
296
|
+
suggestion: `Did you mean: GTM-${afterPrefix.toUpperCase()}?`
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (/[^A-Z0-9]/.test(afterPrefix)) {
|
|
300
|
+
return {
|
|
301
|
+
valid: false,
|
|
302
|
+
error: "GTM container ID should only contain letters and numbers after GTM-",
|
|
303
|
+
suggestion: "Remove any special characters from the ID"
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
valid: false,
|
|
308
|
+
error: "Invalid GTM container ID format",
|
|
309
|
+
suggestion: "GTM container IDs follow the format: GTM-XXXXXX (6-8 alphanumeric characters)"
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return { valid: true };
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/codegen.ts
|
|
316
|
+
var generateSetupCode = (options) => {
|
|
317
|
+
const { framework, containers, dataLayerName, includeConsent = false, typescript = true } = options;
|
|
318
|
+
const ext = typescript ? "ts" : "js";
|
|
319
|
+
const containerValue = Array.isArray(containers) ? `[${containers.map((c2) => `'${c2}'`).join(", ")}]` : `'${containers}'`;
|
|
320
|
+
switch (framework) {
|
|
321
|
+
case "next":
|
|
322
|
+
return generateNextSetupCode({ containerValue, dataLayerName, includeConsent, ext });
|
|
323
|
+
case "nuxt":
|
|
324
|
+
return generateNuxtSetupCode({ containerValue, dataLayerName, includeConsent, ext });
|
|
325
|
+
case "react":
|
|
326
|
+
return generateReactSetupCode({ containerValue, dataLayerName, includeConsent, ext });
|
|
327
|
+
case "vue":
|
|
328
|
+
return generateVueSetupCode({ containerValue, dataLayerName, includeConsent, ext });
|
|
329
|
+
case "vanilla":
|
|
330
|
+
default:
|
|
331
|
+
return generateVanillaSetupCode({ containerValue, dataLayerName, includeConsent, ext });
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var generateNextSetupCode = (ctx) => {
|
|
335
|
+
const { containerValue, dataLayerName, includeConsent, ext } = ctx;
|
|
336
|
+
const dataLayerOption = dataLayerName ? `
|
|
337
|
+
dataLayerName: '${dataLayerName}',` : "";
|
|
338
|
+
const providerCode = `// app/providers/gtm-provider.${ext}x
|
|
339
|
+
'use client';
|
|
340
|
+
|
|
341
|
+
import { GtmProvider } from '@jwiedeman/gtm-kit-react';
|
|
342
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-next';
|
|
343
|
+
${ctx.ext === "ts" ? "import type { ReactNode } from 'react';\n" : ""}
|
|
344
|
+
${includeConsent ? `import { eeaDefault } from '@jwiedeman/gtm-kit';
|
|
345
|
+
` : ""}
|
|
346
|
+
${ctx.ext === "ts" ? `interface GtmProviderWrapperProps {
|
|
347
|
+
children: ReactNode;
|
|
348
|
+
}
|
|
349
|
+
` : ""}
|
|
350
|
+
export function GtmProviderWrapper({ children }${ctx.ext === "ts" ? ": GtmProviderWrapperProps" : ""}) {
|
|
351
|
+
return (
|
|
352
|
+
<GtmProvider
|
|
353
|
+
containers={${containerValue}}${dataLayerOption}${includeConsent ? "\n consentDefaults={eeaDefault}" : ""}
|
|
354
|
+
>
|
|
355
|
+
<PageViewTracker />
|
|
356
|
+
{children}
|
|
357
|
+
</GtmProvider>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function PageViewTracker() {
|
|
362
|
+
useTrackPageViews();
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
`;
|
|
366
|
+
const layoutCode = `// app/layout.${ext}x
|
|
367
|
+
import { GtmProviderWrapper } from './providers/gtm-provider';
|
|
368
|
+
import { GtmNoScript } from '@jwiedeman/gtm-kit-next';
|
|
369
|
+
${ctx.ext === "ts" ? "import type { ReactNode } from 'react';\n" : ""}
|
|
370
|
+
export default function RootLayout({ children }${ctx.ext === "ts" ? ": { children: ReactNode }" : ""}) {
|
|
371
|
+
return (
|
|
372
|
+
<html lang="en">
|
|
373
|
+
<body>
|
|
374
|
+
<GtmNoScript containerId="${Array.isArray(containerValue) ? containerValue[0] : containerValue.replace(/'/g, "")}" />
|
|
375
|
+
<GtmProviderWrapper>
|
|
376
|
+
{children}
|
|
377
|
+
</GtmProviderWrapper>
|
|
378
|
+
</body>
|
|
379
|
+
</html>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
`;
|
|
383
|
+
const exampleUsage = `// Example: Track a button click
|
|
384
|
+
'use client';
|
|
385
|
+
|
|
386
|
+
import { useGtmPush } from '@jwiedeman/gtm-kit-react';
|
|
387
|
+
|
|
388
|
+
export function MyButton() {
|
|
389
|
+
const push = useGtmPush();
|
|
390
|
+
|
|
391
|
+
const handleClick = () => {
|
|
392
|
+
push({
|
|
393
|
+
event: 'button_click',
|
|
394
|
+
button_name: 'signup_cta'
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
return <button onClick={handleClick}>Sign Up</button>;
|
|
399
|
+
}
|
|
400
|
+
`;
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
filename: `app/providers/gtm-provider.${ext}x`,
|
|
404
|
+
content: providerCode,
|
|
405
|
+
description: "GTM Provider wrapper with page view tracking"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
filename: `app/layout.${ext}x`,
|
|
409
|
+
content: layoutCode,
|
|
410
|
+
description: "Root layout with GTM noscript tag"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
filename: `components/example-button.${ext}x`,
|
|
414
|
+
content: exampleUsage,
|
|
415
|
+
description: "Example component showing how to track events"
|
|
416
|
+
}
|
|
417
|
+
];
|
|
418
|
+
};
|
|
419
|
+
var generateNuxtSetupCode = (ctx) => {
|
|
420
|
+
const { containerValue, dataLayerName, includeConsent, ext } = ctx;
|
|
421
|
+
const dataLayerOption = dataLayerName ? `
|
|
422
|
+
dataLayerName: '${dataLayerName}',` : "";
|
|
423
|
+
const pluginCode = `// plugins/gtm.client.${ext}
|
|
424
|
+
import { createNuxtGtmPlugin } from '@jwiedeman/gtm-kit-nuxt';
|
|
425
|
+
${includeConsent ? `import { eeaDefault } from '@jwiedeman/gtm-kit';
|
|
426
|
+
` : ""}
|
|
427
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
428
|
+
createNuxtGtmPlugin(nuxtApp.vueApp, {
|
|
429
|
+
containers: ${containerValue},${dataLayerOption}
|
|
430
|
+
trackPageViews: true${includeConsent ? ",\n consentDefaults: eeaDefault" : ""}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
`;
|
|
434
|
+
const pageTrackingCode = `// composables/usePageTracking.${ext}
|
|
435
|
+
import { useTrackPageViews } from '@jwiedeman/gtm-kit-nuxt';
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Call this composable in your app.vue or layouts to enable automatic page tracking
|
|
439
|
+
*/
|
|
440
|
+
export function usePageTracking() {
|
|
441
|
+
const route = useRoute();
|
|
442
|
+
|
|
443
|
+
useTrackPageViews({
|
|
444
|
+
route,
|
|
445
|
+
eventName: 'page_view',
|
|
446
|
+
includeQueryParams: true
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
`;
|
|
450
|
+
const appVueCode = `<!-- app.vue -->
|
|
451
|
+
<script setup${ext === "ts" ? ' lang="ts"' : ""}>
|
|
452
|
+
import { usePageTracking } from '~/composables/usePageTracking';
|
|
453
|
+
|
|
454
|
+
// Enable automatic page view tracking
|
|
455
|
+
usePageTracking();
|
|
456
|
+
</script>
|
|
457
|
+
|
|
458
|
+
<template>
|
|
459
|
+
<NuxtLayout>
|
|
460
|
+
<NuxtPage />
|
|
461
|
+
</NuxtLayout>
|
|
462
|
+
</template>
|
|
463
|
+
`;
|
|
464
|
+
const exampleUsage = `<!-- Example: Track a button click -->
|
|
465
|
+
<script setup${ext === "ts" ? ' lang="ts"' : ""}>
|
|
466
|
+
import { useNuxtGtmPush } from '@jwiedeman/gtm-kit-nuxt';
|
|
467
|
+
|
|
468
|
+
const push = useNuxtGtmPush();
|
|
469
|
+
|
|
470
|
+
const handleClick = () => {
|
|
471
|
+
push({
|
|
472
|
+
event: 'button_click',
|
|
473
|
+
button_name: 'signup_cta'
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
</script>
|
|
477
|
+
|
|
478
|
+
<template>
|
|
479
|
+
<button @click="handleClick">Sign Up</button>
|
|
480
|
+
</template>
|
|
481
|
+
`;
|
|
482
|
+
return [
|
|
483
|
+
{
|
|
484
|
+
filename: `plugins/gtm.client.${ext}`,
|
|
485
|
+
content: pluginCode,
|
|
486
|
+
description: "Nuxt plugin for GTM (client-side only)"
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
filename: `composables/usePageTracking.${ext}`,
|
|
490
|
+
content: pageTrackingCode,
|
|
491
|
+
description: "Composable for automatic page view tracking"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
filename: "app.vue",
|
|
495
|
+
content: appVueCode,
|
|
496
|
+
description: "App root with page tracking enabled"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
filename: `components/ExampleButton.vue`,
|
|
500
|
+
content: exampleUsage,
|
|
501
|
+
description: "Example component showing how to track events"
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
};
|
|
505
|
+
var generateReactSetupCode = (ctx) => {
|
|
506
|
+
const { containerValue, dataLayerName, includeConsent, ext } = ctx;
|
|
507
|
+
const dataLayerOption = dataLayerName ? `
|
|
508
|
+
dataLayerName: '${dataLayerName}',` : "";
|
|
509
|
+
const appCode = `// src/App.${ext}x
|
|
510
|
+
import { GtmProvider } from '@jwiedeman/gtm-kit-react';
|
|
511
|
+
${includeConsent ? `import { eeaDefault } from '@jwiedeman/gtm-kit';
|
|
512
|
+
` : ""}
|
|
513
|
+
function App() {
|
|
514
|
+
return (
|
|
515
|
+
<GtmProvider
|
|
516
|
+
containers={${containerValue}}${dataLayerOption}${includeConsent ? "\n consentDefaults={eeaDefault}" : ""}
|
|
517
|
+
>
|
|
518
|
+
{/* Your app content */}
|
|
519
|
+
<main>
|
|
520
|
+
<h1>Hello GTM Kit!</h1>
|
|
521
|
+
</main>
|
|
522
|
+
</GtmProvider>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export default App;
|
|
527
|
+
`;
|
|
528
|
+
const routerCode = `// src/AppWithRouter.${ext}x
|
|
529
|
+
// Use this if you have react-router-dom
|
|
530
|
+
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
|
|
531
|
+
import { GtmProvider, useGtmPush } from '@jwiedeman/gtm-kit-react';
|
|
532
|
+
import { useEffect } from 'react';
|
|
533
|
+
${includeConsent ? `import { eeaDefault } from '@jwiedeman/gtm-kit';
|
|
534
|
+
` : ""}
|
|
535
|
+
// Automatic page view tracking
|
|
536
|
+
function PageViewTracker() {
|
|
537
|
+
const location = useLocation();
|
|
538
|
+
const push = useGtmPush();
|
|
539
|
+
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
push({
|
|
542
|
+
event: 'page_view',
|
|
543
|
+
page_path: location.pathname + location.search
|
|
544
|
+
});
|
|
545
|
+
}, [location, push]);
|
|
546
|
+
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function App() {
|
|
551
|
+
return (
|
|
552
|
+
<GtmProvider
|
|
553
|
+
containers={${containerValue}}${dataLayerOption}${includeConsent ? "\n consentDefaults={eeaDefault}" : ""}
|
|
554
|
+
>
|
|
555
|
+
<BrowserRouter>
|
|
556
|
+
<PageViewTracker />
|
|
557
|
+
<Routes>
|
|
558
|
+
<Route path="/" element={<Home />} />
|
|
559
|
+
{/* Add your routes */}
|
|
560
|
+
</Routes>
|
|
561
|
+
</BrowserRouter>
|
|
562
|
+
</GtmProvider>
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function Home() {
|
|
567
|
+
return <h1>Home Page</h1>;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export default App;
|
|
571
|
+
`;
|
|
572
|
+
const exampleUsage = `// src/components/TrackingExample.${ext}x
|
|
573
|
+
import { useGtmPush } from '@jwiedeman/gtm-kit-react';
|
|
574
|
+
|
|
575
|
+
export function SignupButton() {
|
|
576
|
+
const push = useGtmPush();
|
|
577
|
+
|
|
578
|
+
const handleClick = () => {
|
|
579
|
+
push({
|
|
580
|
+
event: 'button_click',
|
|
581
|
+
button_name: 'signup_cta'
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
return <button onClick={handleClick}>Sign Up</button>;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Track form submission
|
|
589
|
+
export function ContactForm() {
|
|
590
|
+
const push = useGtmPush();
|
|
591
|
+
|
|
592
|
+
const handleSubmit = (e${ctx.ext === "ts" ? ": React.FormEvent" : ""}) => {
|
|
593
|
+
e.preventDefault();
|
|
594
|
+
push({
|
|
595
|
+
event: 'form_submit',
|
|
596
|
+
form_name: 'contact'
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<form onSubmit={handleSubmit}>
|
|
602
|
+
<input type="email" placeholder="Email" />
|
|
603
|
+
<button type="submit">Submit</button>
|
|
604
|
+
</form>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
`;
|
|
608
|
+
return [
|
|
609
|
+
{
|
|
610
|
+
filename: `src/App.${ext}x`,
|
|
611
|
+
content: appCode,
|
|
612
|
+
description: "Basic App setup with GTM Provider"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
filename: `src/AppWithRouter.${ext}x`,
|
|
616
|
+
content: routerCode,
|
|
617
|
+
description: "App setup with React Router and page tracking"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
filename: `src/components/TrackingExample.${ext}x`,
|
|
621
|
+
content: exampleUsage,
|
|
622
|
+
description: "Example components showing how to track events"
|
|
623
|
+
}
|
|
624
|
+
];
|
|
625
|
+
};
|
|
626
|
+
var generateVueSetupCode = (ctx) => {
|
|
627
|
+
const { containerValue, dataLayerName, includeConsent, ext } = ctx;
|
|
628
|
+
const dataLayerOption = dataLayerName ? `
|
|
629
|
+
dataLayerName: '${dataLayerName}',` : "";
|
|
630
|
+
const mainCode = `// src/main.${ext}
|
|
631
|
+
import { createApp } from 'vue';
|
|
632
|
+
import { GtmPlugin } from '@jwiedeman/gtm-kit-vue';
|
|
633
|
+
${includeConsent ? `import { eeaDefault } from '@jwiedeman/gtm-kit';
|
|
634
|
+
` : ""}import App from './App.vue';
|
|
635
|
+
|
|
636
|
+
const app = createApp(App);
|
|
637
|
+
|
|
638
|
+
app.use(GtmPlugin, {
|
|
639
|
+
containers: ${containerValue}${dataLayerOption}${includeConsent ? ",\n consentDefaults: eeaDefault" : ""}
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
app.mount('#app');
|
|
643
|
+
`;
|
|
644
|
+
const routerCode = `// src/router-tracking.${ext}
|
|
645
|
+
// Add this to your router setup for automatic page tracking
|
|
646
|
+
import { useGtmPush } from '@jwiedeman/gtm-kit-vue';
|
|
647
|
+
import { watch } from 'vue';
|
|
648
|
+
import { useRoute } from 'vue-router';
|
|
649
|
+
|
|
650
|
+
export function usePageTracking() {
|
|
651
|
+
const route = useRoute();
|
|
652
|
+
const push = useGtmPush();
|
|
653
|
+
|
|
654
|
+
watch(
|
|
655
|
+
() => route.fullPath,
|
|
656
|
+
(path) => {
|
|
657
|
+
push({
|
|
658
|
+
event: 'page_view',
|
|
659
|
+
page_path: path,
|
|
660
|
+
page_title: document.title
|
|
661
|
+
});
|
|
662
|
+
},
|
|
663
|
+
{ immediate: true }
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
`;
|
|
667
|
+
const appVueCode = `<!-- src/App.vue -->
|
|
668
|
+
<script setup${ext === "ts" ? ' lang="ts"' : ""}>
|
|
669
|
+
import { usePageTracking } from './router-tracking';
|
|
670
|
+
|
|
671
|
+
// Enable page view tracking if using vue-router
|
|
672
|
+
// usePageTracking();
|
|
673
|
+
</script>
|
|
674
|
+
|
|
675
|
+
<template>
|
|
676
|
+
<main>
|
|
677
|
+
<h1>Hello GTM Kit!</h1>
|
|
678
|
+
<RouterView v-if="$router" />
|
|
679
|
+
</main>
|
|
680
|
+
</template>
|
|
681
|
+
`;
|
|
682
|
+
const exampleUsage = `<!-- src/components/TrackingExample.vue -->
|
|
683
|
+
<script setup${ext === "ts" ? ' lang="ts"' : ""}>
|
|
684
|
+
import { useGtmPush } from '@jwiedeman/gtm-kit-vue';
|
|
685
|
+
|
|
686
|
+
const push = useGtmPush();
|
|
687
|
+
|
|
688
|
+
const handleClick = () => {
|
|
689
|
+
push({
|
|
690
|
+
event: 'button_click',
|
|
691
|
+
button_name: 'signup_cta'
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
const handleSubmit = () => {
|
|
696
|
+
push({
|
|
697
|
+
event: 'form_submit',
|
|
698
|
+
form_name: 'contact'
|
|
699
|
+
});
|
|
700
|
+
};
|
|
701
|
+
</script>
|
|
702
|
+
|
|
703
|
+
<template>
|
|
704
|
+
<div>
|
|
705
|
+
<!-- Track button click -->
|
|
706
|
+
<button @click="handleClick">Sign Up</button>
|
|
707
|
+
|
|
708
|
+
<!-- Track form submission -->
|
|
709
|
+
<form @submit.prevent="handleSubmit">
|
|
710
|
+
<input type="email" placeholder="Email" />
|
|
711
|
+
<button type="submit">Submit</button>
|
|
712
|
+
</form>
|
|
713
|
+
</div>
|
|
714
|
+
</template>
|
|
715
|
+
`;
|
|
716
|
+
return [
|
|
717
|
+
{
|
|
718
|
+
filename: `src/main.${ext}`,
|
|
719
|
+
content: mainCode,
|
|
720
|
+
description: "Main entry point with GTM Plugin"
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
filename: `src/router-tracking.${ext}`,
|
|
724
|
+
content: routerCode,
|
|
725
|
+
description: "Page tracking composable for Vue Router"
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
filename: "src/App.vue",
|
|
729
|
+
content: appVueCode,
|
|
730
|
+
description: "App component with tracking setup"
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
filename: "src/components/TrackingExample.vue",
|
|
734
|
+
content: exampleUsage,
|
|
735
|
+
description: "Example component showing how to track events"
|
|
736
|
+
}
|
|
737
|
+
];
|
|
738
|
+
};
|
|
739
|
+
var generateVanillaSetupCode = (ctx) => {
|
|
740
|
+
const { containerValue, dataLayerName, includeConsent, ext } = ctx;
|
|
741
|
+
const dataLayerOption = dataLayerName ? `
|
|
742
|
+
dataLayerName: '${dataLayerName}',` : "";
|
|
743
|
+
const esmCode = `// gtm-setup.${ext}
|
|
744
|
+
import { createGtmClient${includeConsent ? ", eeaDefault" : ""} } from '@jwiedeman/gtm-kit';
|
|
745
|
+
|
|
746
|
+
// Create the GTM client
|
|
747
|
+
const gtm = createGtmClient({
|
|
748
|
+
containers: ${containerValue}${dataLayerOption}
|
|
749
|
+
});
|
|
750
|
+
${includeConsent ? "\n// Set consent defaults BEFORE init (for GDPR compliance)\ngtm.setConsentDefaults(eeaDefault);\n" : ""}
|
|
751
|
+
// Initialize GTM
|
|
752
|
+
gtm.init();
|
|
753
|
+
|
|
754
|
+
// Track events
|
|
755
|
+
export function trackEvent(event${ctx.ext === "ts" ? ": string" : ""}, data${ctx.ext === "ts" ? "?: Record<string, unknown>" : ""} = {}) {
|
|
756
|
+
gtm.push({
|
|
757
|
+
event,
|
|
758
|
+
...data
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Track page views
|
|
763
|
+
export function trackPageView(path${ctx.ext === "ts" ? "?: string" : ""}) {
|
|
764
|
+
gtm.push({
|
|
765
|
+
event: 'page_view',
|
|
766
|
+
page_path: path || window.location.pathname + window.location.search,
|
|
767
|
+
page_title: document.title
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Update consent (call this when user accepts/rejects)
|
|
772
|
+
export function updateConsent(analytics${ctx.ext === "ts" ? ": boolean" : ""}, marketing${ctx.ext === "ts" ? ": boolean" : ""}) {
|
|
773
|
+
gtm.updateConsent({
|
|
774
|
+
analytics_storage: analytics ? 'granted' : 'denied',
|
|
775
|
+
ad_storage: marketing ? 'granted' : 'denied',
|
|
776
|
+
ad_user_data: marketing ? 'granted' : 'denied',
|
|
777
|
+
ad_personalization: marketing ? 'granted' : 'denied'
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Example usage
|
|
782
|
+
trackPageView();
|
|
783
|
+
|
|
784
|
+
// Export client for advanced usage
|
|
785
|
+
export { gtm };
|
|
786
|
+
`;
|
|
787
|
+
const htmlCode = `<!-- index.html -->
|
|
788
|
+
<!DOCTYPE html>
|
|
789
|
+
<html lang="en">
|
|
790
|
+
<head>
|
|
791
|
+
<meta charset="UTF-8">
|
|
792
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
793
|
+
<title>GTM Kit - Vanilla JS</title>
|
|
794
|
+
</head>
|
|
795
|
+
<body>
|
|
796
|
+
<!-- GTM noscript fallback (optional but recommended) -->
|
|
797
|
+
<noscript>
|
|
798
|
+
<iframe
|
|
799
|
+
src="https://www.googletagmanager.com/ns.html?id=${Array.isArray(containerValue) ? "YOUR-GTM-ID" : containerValue.replace(/'/g, "")}"
|
|
800
|
+
height="0"
|
|
801
|
+
width="0"
|
|
802
|
+
style="display:none;visibility:hidden"
|
|
803
|
+
></iframe>
|
|
804
|
+
</noscript>
|
|
805
|
+
|
|
806
|
+
<main>
|
|
807
|
+
<h1>Hello GTM Kit!</h1>
|
|
808
|
+
<button id="signup-btn">Sign Up</button>
|
|
809
|
+
<button id="consent-btn">Accept Cookies</button>
|
|
810
|
+
</main>
|
|
811
|
+
|
|
812
|
+
<script type="module">
|
|
813
|
+
import { trackEvent, updateConsent } from './gtm-setup.${ext}';
|
|
814
|
+
|
|
815
|
+
// Track button click
|
|
816
|
+
document.getElementById('signup-btn').addEventListener('click', () => {
|
|
817
|
+
trackEvent('button_click', { button_name: 'signup_cta' });
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// Handle consent
|
|
821
|
+
document.getElementById('consent-btn').addEventListener('click', () => {
|
|
822
|
+
updateConsent(true, true);
|
|
823
|
+
});
|
|
824
|
+
</script>
|
|
825
|
+
</body>
|
|
826
|
+
</html>
|
|
827
|
+
`;
|
|
828
|
+
const umdCode = `<!-- Alternative: UMD/Script Tag Setup -->
|
|
829
|
+
<!-- Add this in your HTML head -->
|
|
830
|
+
<script src="https://unpkg.com/@jwiedeman/gtm-kit/dist/index.umd.js"></script>
|
|
831
|
+
<script>
|
|
832
|
+
// GTM Kit is available as window.GtmKit
|
|
833
|
+
var gtm = GtmKit.createGtmClient({
|
|
834
|
+
containers: ${containerValue}${dataLayerOption ? dataLayerOption.replace(/\n/g, " ") : ""}
|
|
835
|
+
});
|
|
836
|
+
${includeConsent ? "\n // Set consent defaults\n gtm.setConsentDefaults(GtmKit.eeaDefault);\n" : ""}
|
|
837
|
+
// Initialize
|
|
838
|
+
gtm.init();
|
|
839
|
+
|
|
840
|
+
// Track page view
|
|
841
|
+
gtm.push({
|
|
842
|
+
event: 'page_view',
|
|
843
|
+
page_path: window.location.pathname
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Make gtm available globally for other scripts
|
|
847
|
+
window.gtm = gtm;
|
|
848
|
+
</script>
|
|
849
|
+
`;
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
filename: `gtm-setup.${ext}`,
|
|
853
|
+
content: esmCode,
|
|
854
|
+
description: "ESM setup with helper functions"
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
filename: "index.html",
|
|
858
|
+
content: htmlCode,
|
|
859
|
+
description: "Example HTML with tracking"
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
filename: "umd-setup.html",
|
|
863
|
+
content: umdCode,
|
|
864
|
+
description: "Alternative UMD/script tag setup"
|
|
865
|
+
}
|
|
866
|
+
];
|
|
867
|
+
};
|
|
868
|
+
var formatGeneratedCode = (files) => {
|
|
869
|
+
return files.map((file) => {
|
|
870
|
+
const separator = "\u2500".repeat(60);
|
|
871
|
+
return `${separator}
|
|
872
|
+
\u{1F4C4} ${file.filename}
|
|
873
|
+
${file.description}
|
|
874
|
+
${separator}
|
|
875
|
+
${file.content}`;
|
|
876
|
+
}).join("\n\n");
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
// src/cli.ts
|
|
880
|
+
var colors = {
|
|
881
|
+
reset: "\x1B[0m",
|
|
882
|
+
bold: "\x1B[1m",
|
|
883
|
+
dim: "\x1B[2m",
|
|
884
|
+
red: "\x1B[31m",
|
|
885
|
+
green: "\x1B[32m",
|
|
886
|
+
yellow: "\x1B[33m",
|
|
887
|
+
blue: "\x1B[34m",
|
|
888
|
+
magenta: "\x1B[35m",
|
|
889
|
+
cyan: "\x1B[36m"
|
|
890
|
+
};
|
|
891
|
+
var c = (color, text) => `${colors[color]}${text}${colors.reset}`;
|
|
892
|
+
var print = {
|
|
893
|
+
header: (text) => console.log(`
|
|
894
|
+
${c("bold", c("cyan", text))}
|
|
895
|
+
`),
|
|
896
|
+
success: (text) => console.log(`${c("green", "\u2713")} ${text}`),
|
|
897
|
+
error: (text) => console.log(`${c("red", "\u2717")} ${text}`),
|
|
898
|
+
warning: (text) => console.log(`${c("yellow", "\u26A0")} ${text}`),
|
|
899
|
+
info: (text) => console.log(`${c("blue", "\u2139")} ${text}`),
|
|
900
|
+
step: (n, text) => console.log(`
|
|
901
|
+
${c("bold", `Step ${n}:`)} ${text}`),
|
|
902
|
+
code: (text) => console.log(` ${c("dim", "$")} ${c("cyan", text)}`),
|
|
903
|
+
box: (lines) => {
|
|
904
|
+
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
905
|
+
const border = "\u2500".repeat(maxLen + 2);
|
|
906
|
+
console.log(`\u250C${border}\u2510`);
|
|
907
|
+
lines.forEach((line) => console.log(`\u2502 ${line.padEnd(maxLen)} \u2502`));
|
|
908
|
+
console.log(`\u2514${border}\u2518`);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
var prompt = (question) => {
|
|
912
|
+
const rl = readline.createInterface({
|
|
913
|
+
input: process.stdin,
|
|
914
|
+
output: process.stdout
|
|
915
|
+
});
|
|
916
|
+
return new Promise((resolve) => {
|
|
917
|
+
rl.question(question, (answer) => {
|
|
918
|
+
rl.close();
|
|
919
|
+
resolve(answer.trim());
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
};
|
|
923
|
+
var confirm = async (question, defaultYes = true) => {
|
|
924
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
925
|
+
const answer = await prompt(`${question} ${suffix} `);
|
|
926
|
+
if (!answer)
|
|
927
|
+
return defaultYes;
|
|
928
|
+
return answer.toLowerCase().startsWith("y");
|
|
929
|
+
};
|
|
930
|
+
var showBanner = () => {
|
|
931
|
+
console.log(`
|
|
932
|
+
${c("cyan", "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
933
|
+
${c("cyan", "\u2551")} ${c("bold", "GTM Kit")} - Easy Google Tag Manager Setup ${c("cyan", "\u2551")}
|
|
934
|
+
${c("cyan", "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
935
|
+
`);
|
|
936
|
+
};
|
|
937
|
+
var showHelp = () => {
|
|
938
|
+
showBanner();
|
|
939
|
+
console.log(`${c("bold", "Usage:")}
|
|
940
|
+
npx @jwiedeman/gtm-kit-cli <command> [options]
|
|
941
|
+
|
|
942
|
+
${c("bold", "Commands:")}
|
|
943
|
+
${c("cyan", "init")} [GTM-ID] Interactive setup (or quick setup with ID)
|
|
944
|
+
${c("cyan", "detect")} Detect framework and show install command
|
|
945
|
+
${c("cyan", "validate")} <ID> Validate a GTM container ID
|
|
946
|
+
${c("cyan", "generate")} <ID> Generate setup code for your framework
|
|
947
|
+
${c("cyan", "help")} Show this help message
|
|
948
|
+
|
|
949
|
+
${c("bold", "Examples:")}
|
|
950
|
+
${c("dim", "$")} npx @jwiedeman/gtm-kit-cli init
|
|
951
|
+
${c("dim", "$")} npx @jwiedeman/gtm-kit-cli init GTM-ABC1234
|
|
952
|
+
${c("dim", "$")} npx @jwiedeman/gtm-kit-cli detect
|
|
953
|
+
${c("dim", "$")} npx @jwiedeman/gtm-kit-cli validate GTM-ABC1234
|
|
954
|
+
|
|
955
|
+
${c("bold", "Options:")}
|
|
956
|
+
--typescript, -ts Generate TypeScript code (default)
|
|
957
|
+
--javascript, -js Generate JavaScript code
|
|
958
|
+
--consent Include consent mode setup
|
|
959
|
+
--dry-run Show what would be done without doing it
|
|
960
|
+
|
|
961
|
+
${c("bold", "More info:")} https://github.com/react-gtm-kit/react-gtm-kit
|
|
962
|
+
`);
|
|
963
|
+
};
|
|
964
|
+
var runDetect = (dir = process.cwd()) => {
|
|
965
|
+
showBanner();
|
|
966
|
+
print.header("Framework Detection");
|
|
967
|
+
const info = detectFramework(dir);
|
|
968
|
+
console.log(getDetectionSummary(info));
|
|
969
|
+
console.log("\n" + c("bold", "Install command:"));
|
|
970
|
+
print.code(getInstallCommand(info.packageManager, info.packages));
|
|
971
|
+
return info;
|
|
972
|
+
};
|
|
973
|
+
var runValidate = (id) => {
|
|
974
|
+
var _a;
|
|
975
|
+
showBanner();
|
|
976
|
+
print.header("GTM ID Validation");
|
|
977
|
+
const result = validateGtmId(id);
|
|
978
|
+
console.log(`ID: ${c("cyan", id)}`);
|
|
979
|
+
if (result.valid) {
|
|
980
|
+
print.success("Valid GTM container ID");
|
|
981
|
+
if (result.warning) {
|
|
982
|
+
print.warning(result.warning);
|
|
983
|
+
}
|
|
984
|
+
} else {
|
|
985
|
+
print.error((_a = result.error) != null ? _a : "Invalid");
|
|
986
|
+
if (result.suggestion) {
|
|
987
|
+
print.info(`Suggestion: ${result.suggestion}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
};
|
|
992
|
+
var runGenerate = (containerId, options = {}) => {
|
|
993
|
+
var _a, _b, _c;
|
|
994
|
+
showBanner();
|
|
995
|
+
print.header("Generating Setup Code");
|
|
996
|
+
const info = detectFramework();
|
|
997
|
+
const validation = validateGtmId(containerId);
|
|
998
|
+
if (!validation.valid) {
|
|
999
|
+
print.error((_a = validation.error) != null ? _a : "Invalid GTM ID");
|
|
1000
|
+
if (validation.suggestion) {
|
|
1001
|
+
print.info(`Suggestion: ${validation.suggestion}`);
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
1004
|
+
}
|
|
1005
|
+
print.success(`Framework: ${info.displayName}`);
|
|
1006
|
+
print.success(`Container: ${containerId}`);
|
|
1007
|
+
const files = generateSetupCode({
|
|
1008
|
+
framework: info.framework,
|
|
1009
|
+
containers: containerId,
|
|
1010
|
+
typescript: (_b = options.typescript) != null ? _b : true,
|
|
1011
|
+
includeConsent: (_c = options.consent) != null ? _c : false
|
|
1012
|
+
});
|
|
1013
|
+
console.log("\n" + formatGeneratedCode(files));
|
|
1014
|
+
return files;
|
|
1015
|
+
};
|
|
1016
|
+
var runInit = async (quickId, options = {}) => {
|
|
1017
|
+
var _a, _b, _c;
|
|
1018
|
+
showBanner();
|
|
1019
|
+
print.header("GTM Kit Setup");
|
|
1020
|
+
const info = detectFramework();
|
|
1021
|
+
print.step(1, "Detecting your project...");
|
|
1022
|
+
console.log(`
|
|
1023
|
+
Framework: ${c("green", info.displayName)}`);
|
|
1024
|
+
console.log(` Package Manager: ${c("green", info.packageManager)}`);
|
|
1025
|
+
console.log(` Confidence: ${info.confidence}%`);
|
|
1026
|
+
print.step(2, "GTM Container ID");
|
|
1027
|
+
let containerId = quickId != null ? quickId : "";
|
|
1028
|
+
if (!containerId) {
|
|
1029
|
+
containerId = await prompt(`
|
|
1030
|
+
Enter your GTM container ID (e.g., GTM-ABC1234): `);
|
|
1031
|
+
}
|
|
1032
|
+
if (!containerId) {
|
|
1033
|
+
print.error("GTM container ID is required");
|
|
1034
|
+
console.log(`
|
|
1035
|
+
${c("dim", "Tip: Get your GTM ID from https://tagmanager.google.com")}
|
|
1036
|
+
`);
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
const validation = validateGtmId(containerId);
|
|
1040
|
+
if (!validation.valid) {
|
|
1041
|
+
print.error((_a = validation.error) != null ? _a : "Invalid GTM ID");
|
|
1042
|
+
if (validation.suggestion) {
|
|
1043
|
+
print.info(validation.suggestion);
|
|
1044
|
+
}
|
|
1045
|
+
process.exit(1);
|
|
1046
|
+
}
|
|
1047
|
+
print.success(`Valid container ID: ${containerId}`);
|
|
1048
|
+
print.step(3, "Configuration");
|
|
1049
|
+
const useConsent = (_b = options.consent) != null ? _b : await confirm("\n Include Consent Mode v2 setup (GDPR)?", true);
|
|
1050
|
+
const useTypescript = (_c = options.typescript) != null ? _c : await confirm(" Use TypeScript?", true);
|
|
1051
|
+
print.step(4, "Installing packages...");
|
|
1052
|
+
const installCmd = getInstallCommand(info.packageManager, info.packages);
|
|
1053
|
+
console.log(`
|
|
1054
|
+
Command: ${c("cyan", installCmd)}`);
|
|
1055
|
+
if (options.dryRun) {
|
|
1056
|
+
print.warning("Dry run - skipping installation");
|
|
1057
|
+
} else {
|
|
1058
|
+
const shouldInstall = await confirm("\n Run installation now?", true);
|
|
1059
|
+
if (shouldInstall) {
|
|
1060
|
+
const { execSync } = await import("child_process");
|
|
1061
|
+
try {
|
|
1062
|
+
console.log("");
|
|
1063
|
+
execSync(installCmd, { stdio: "inherit" });
|
|
1064
|
+
print.success("Packages installed successfully");
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
print.error("Installation failed");
|
|
1067
|
+
console.log(`
|
|
1068
|
+
${c("dim", "Try running manually:")} ${c("cyan", installCmd)}
|
|
1069
|
+
`);
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
print.info("Skipped installation. Run manually:");
|
|
1073
|
+
print.code(installCmd);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
print.step(5, "Generating setup code...");
|
|
1077
|
+
const files = generateSetupCode({
|
|
1078
|
+
framework: info.framework,
|
|
1079
|
+
containers: containerId,
|
|
1080
|
+
typescript: useTypescript,
|
|
1081
|
+
includeConsent: useConsent
|
|
1082
|
+
});
|
|
1083
|
+
console.log("\n" + formatGeneratedCode(files));
|
|
1084
|
+
print.step(6, "Creating files...");
|
|
1085
|
+
if (options.dryRun) {
|
|
1086
|
+
print.warning("Dry run - skipping file creation");
|
|
1087
|
+
files.forEach((file) => {
|
|
1088
|
+
console.log(` Would create: ${c("cyan", file.filename)}`);
|
|
1089
|
+
});
|
|
1090
|
+
} else {
|
|
1091
|
+
const shouldWrite = await confirm("\n Create these files in your project?", false);
|
|
1092
|
+
if (shouldWrite) {
|
|
1093
|
+
for (const file of files) {
|
|
1094
|
+
const filePath = path2.join(process.cwd(), file.filename);
|
|
1095
|
+
const dir = path2.dirname(filePath);
|
|
1096
|
+
if (!fs2.existsSync(dir)) {
|
|
1097
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1098
|
+
}
|
|
1099
|
+
if (fs2.existsSync(filePath)) {
|
|
1100
|
+
const overwrite = await confirm(` ${file.filename} exists. Overwrite?`, false);
|
|
1101
|
+
if (!overwrite) {
|
|
1102
|
+
print.info(`Skipped: ${file.filename}`);
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
fs2.writeFileSync(filePath, file.content);
|
|
1107
|
+
print.success(`Created: ${file.filename}`);
|
|
1108
|
+
}
|
|
1109
|
+
} else {
|
|
1110
|
+
print.info("Files not created. Copy the code above manually.");
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
print.header("Setup Complete!");
|
|
1114
|
+
print.box([
|
|
1115
|
+
`GTM Kit is ready to use with ${info.displayName}!`,
|
|
1116
|
+
"",
|
|
1117
|
+
`Container: ${containerId}`,
|
|
1118
|
+
useConsent ? "Consent Mode: Enabled" : "Consent Mode: Disabled",
|
|
1119
|
+
"",
|
|
1120
|
+
"Next steps:",
|
|
1121
|
+
"1. Review the generated code",
|
|
1122
|
+
"2. Add your routes/pages",
|
|
1123
|
+
"3. Test with GTM Preview mode",
|
|
1124
|
+
"",
|
|
1125
|
+
"Docs: https://github.com/react-gtm-kit/react-gtm-kit"
|
|
1126
|
+
]);
|
|
1127
|
+
return { info, containerId, files };
|
|
1128
|
+
};
|
|
1129
|
+
var parseArgs = (args) => {
|
|
1130
|
+
var _a;
|
|
1131
|
+
const command = (_a = args[0]) != null ? _a : "help";
|
|
1132
|
+
const positional = [];
|
|
1133
|
+
const flags = {};
|
|
1134
|
+
for (let i = 1; i < args.length; i++) {
|
|
1135
|
+
const arg = args[i];
|
|
1136
|
+
if (arg.startsWith("--")) {
|
|
1137
|
+
flags[arg.slice(2)] = true;
|
|
1138
|
+
} else if (arg.startsWith("-")) {
|
|
1139
|
+
flags[arg.slice(1)] = true;
|
|
1140
|
+
} else {
|
|
1141
|
+
positional.push(arg);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return { command, positional, flags };
|
|
1145
|
+
};
|
|
1146
|
+
var run = async (args = process.argv.slice(2)) => {
|
|
1147
|
+
var _a, _b;
|
|
1148
|
+
const { command, positional, flags } = parseArgs(args);
|
|
1149
|
+
const options = {
|
|
1150
|
+
typescript: flags.typescript || flags.ts ? true : flags.javascript || flags.js ? false : void 0,
|
|
1151
|
+
consent: (_a = flags.consent) != null ? _a : void 0,
|
|
1152
|
+
dryRun: (_b = flags["dry-run"]) != null ? _b : false
|
|
1153
|
+
};
|
|
1154
|
+
try {
|
|
1155
|
+
switch (command) {
|
|
1156
|
+
case "init":
|
|
1157
|
+
await runInit(positional[0], options);
|
|
1158
|
+
break;
|
|
1159
|
+
case "detect":
|
|
1160
|
+
runDetect(positional[0]);
|
|
1161
|
+
break;
|
|
1162
|
+
case "validate":
|
|
1163
|
+
if (!positional[0]) {
|
|
1164
|
+
print.error("Please provide a GTM container ID to validate");
|
|
1165
|
+
console.log(`
|
|
1166
|
+
${c("dim", "Usage:")} npx @jwiedeman/gtm-kit-cli validate GTM-ABC1234
|
|
1167
|
+
`);
|
|
1168
|
+
process.exit(1);
|
|
1169
|
+
}
|
|
1170
|
+
runValidate(positional[0]);
|
|
1171
|
+
break;
|
|
1172
|
+
case "generate":
|
|
1173
|
+
case "gen":
|
|
1174
|
+
if (!positional[0]) {
|
|
1175
|
+
print.error("Please provide a GTM container ID");
|
|
1176
|
+
console.log(`
|
|
1177
|
+
${c("dim", "Usage:")} npx @jwiedeman/gtm-kit-cli generate GTM-ABC1234
|
|
1178
|
+
`);
|
|
1179
|
+
process.exit(1);
|
|
1180
|
+
}
|
|
1181
|
+
runGenerate(positional[0], options);
|
|
1182
|
+
break;
|
|
1183
|
+
case "help":
|
|
1184
|
+
case "-h":
|
|
1185
|
+
case "--help":
|
|
1186
|
+
default:
|
|
1187
|
+
showHelp();
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
} catch (error) {
|
|
1191
|
+
if (error instanceof Error) {
|
|
1192
|
+
print.error(error.message);
|
|
1193
|
+
} else {
|
|
1194
|
+
print.error("An unexpected error occurred");
|
|
1195
|
+
}
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
if (typeof __require !== "undefined" && __require.main === module) {
|
|
1200
|
+
run();
|
|
1201
|
+
}
|
|
1202
|
+
export {
|
|
1203
|
+
run
|
|
1204
|
+
};
|