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