@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/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
+ };