@c15t/cli 1.8.3 → 2.0.0-rc.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.
Files changed (187) hide show
  1. package/dist/760.mjs +1720 -0
  2. package/dist/__tests__/auth/config-store.test.d.ts +2 -0
  3. package/dist/__tests__/auth/config-store.test.d.ts.map +1 -0
  4. package/dist/__tests__/constants.test.d.ts +2 -0
  5. package/dist/__tests__/constants.test.d.ts.map +1 -0
  6. package/dist/__tests__/detection/layout.test.d.ts +2 -0
  7. package/dist/__tests__/detection/layout.test.d.ts.map +1 -0
  8. package/dist/__tests__/mocks/prompts.d.ts +25 -0
  9. package/dist/__tests__/mocks/prompts.d.ts.map +1 -0
  10. package/dist/__tests__/utils/validation.test.d.ts +2 -0
  11. package/dist/__tests__/utils/validation.test.d.ts.map +1 -0
  12. package/dist/auth/config-store.d.ts +63 -0
  13. package/dist/auth/config-store.d.ts.map +1 -0
  14. package/dist/auth/device-flow.d.ts +41 -0
  15. package/dist/auth/device-flow.d.ts.map +1 -0
  16. package/dist/auth/index.d.ts +7 -0
  17. package/dist/auth/index.d.ts.map +1 -0
  18. package/dist/auth/types.d.ts +90 -0
  19. package/dist/auth/types.d.ts.map +1 -0
  20. package/dist/commands/auth/index.d.ts +21 -0
  21. package/dist/commands/auth/index.d.ts.map +1 -0
  22. package/dist/commands/generate/index.d.ts +23 -2
  23. package/dist/commands/generate/index.d.ts.map +1 -1
  24. package/dist/commands/generate/options/c15t-mode.d.ts.map +1 -1
  25. package/dist/commands/generate/options/custom-mode.d.ts +3 -1
  26. package/dist/commands/generate/options/custom-mode.d.ts.map +1 -1
  27. package/dist/commands/generate/options/offline-mode.d.ts.map +1 -1
  28. package/dist/commands/generate/options/self-hosted-mode.d.ts.map +1 -1
  29. package/dist/commands/generate/options/shared/backend-options.d.ts +26 -0
  30. package/dist/commands/generate/options/shared/backend-options.d.ts.map +1 -0
  31. package/dist/commands/generate/options/shared/dev-tools.d.ts +9 -0
  32. package/dist/commands/generate/options/shared/dev-tools.d.ts.map +1 -0
  33. package/dist/commands/generate/options/shared/frontend-ui-options.d.ts +44 -0
  34. package/dist/commands/generate/options/shared/frontend-ui-options.d.ts.map +1 -0
  35. package/dist/commands/generate/options/shared/index.d.ts +9 -0
  36. package/dist/commands/generate/options/shared/index.d.ts.map +1 -0
  37. package/dist/commands/generate/options/shared/ssr.d.ts +21 -0
  38. package/dist/commands/generate/options/shared/ssr.d.ts.map +1 -0
  39. package/dist/commands/generate/options/utils/generate-files.d.ts +10 -2
  40. package/dist/commands/generate/options/utils/generate-files.d.ts.map +1 -1
  41. package/dist/commands/generate/preflight.d.ts +31 -0
  42. package/dist/commands/generate/preflight.d.ts.map +1 -0
  43. package/dist/commands/generate/prompts/expanded-theme.d.ts +46 -0
  44. package/dist/commands/generate/prompts/expanded-theme.d.ts.map +1 -0
  45. package/dist/commands/generate/prompts/index.d.ts +10 -0
  46. package/dist/commands/generate/prompts/index.d.ts.map +1 -0
  47. package/dist/commands/generate/prompts/instance.d.ts +33 -0
  48. package/dist/commands/generate/prompts/instance.d.ts.map +1 -0
  49. package/dist/commands/generate/prompts/mode-select.d.ts +42 -0
  50. package/dist/commands/generate/prompts/mode-select.d.ts.map +1 -0
  51. package/dist/commands/generate/prompts/scripts.d.ts +66 -0
  52. package/dist/commands/generate/prompts/scripts.d.ts.map +1 -0
  53. package/dist/commands/generate/prompts/theme.d.ts +46 -0
  54. package/dist/commands/generate/prompts/theme.d.ts.map +1 -0
  55. package/dist/commands/generate/prompts/ui-style.d.ts +36 -0
  56. package/dist/commands/generate/prompts/ui-style.d.ts.map +1 -0
  57. package/dist/commands/generate/summary.d.ts +37 -0
  58. package/dist/commands/generate/summary.d.ts.map +1 -0
  59. package/dist/commands/generate/templates/config.d.ts +7 -6
  60. package/dist/commands/generate/templates/config.d.ts.map +1 -1
  61. package/dist/commands/generate/templates/css.d.ts +12 -0
  62. package/dist/commands/generate/templates/css.d.ts.map +1 -0
  63. package/dist/commands/generate/templates/index.d.ts +1 -1
  64. package/dist/commands/generate/templates/index.d.ts.map +1 -1
  65. package/dist/commands/generate/templates/layout.d.ts +7 -0
  66. package/dist/commands/generate/templates/layout.d.ts.map +1 -1
  67. package/dist/commands/generate/templates/next/app/layout.d.ts +9 -2
  68. package/dist/commands/generate/templates/next/app/layout.d.ts.map +1 -1
  69. package/dist/commands/generate/templates/next/index.d.ts +7 -0
  70. package/dist/commands/generate/templates/next/index.d.ts.map +1 -1
  71. package/dist/commands/generate/templates/next/pages/layout.d.ts +3 -1
  72. package/dist/commands/generate/templates/next/pages/layout.d.ts.map +1 -1
  73. package/dist/commands/generate/templates/shared/components.d.ts +69 -0
  74. package/dist/commands/generate/templates/shared/components.d.ts.map +1 -0
  75. package/dist/commands/generate/templates/shared/directory.d.ts +71 -0
  76. package/dist/commands/generate/templates/shared/directory.d.ts.map +1 -0
  77. package/dist/commands/generate/templates/shared/expanded-components.d.ts +50 -0
  78. package/dist/commands/generate/templates/shared/expanded-components.d.ts.map +1 -0
  79. package/dist/commands/generate/templates/shared/framework-config.d.ts +18 -0
  80. package/dist/commands/generate/templates/shared/framework-config.d.ts.map +1 -0
  81. package/dist/commands/generate/templates/shared/layout-pipeline.d.ts +47 -0
  82. package/dist/commands/generate/templates/shared/layout-pipeline.d.ts.map +1 -0
  83. package/dist/commands/generate/templates/shared/module-specifier.d.ts +68 -0
  84. package/dist/commands/generate/templates/shared/module-specifier.d.ts.map +1 -0
  85. package/dist/commands/generate/templates/shared/options.d.ts +37 -20
  86. package/dist/commands/generate/templates/shared/options.d.ts.map +1 -1
  87. package/dist/commands/generate/templates/shared/scripts.d.ts +62 -0
  88. package/dist/commands/generate/templates/shared/scripts.d.ts.map +1 -0
  89. package/dist/commands/generate/templates/shared/server-components.d.ts +38 -0
  90. package/dist/commands/generate/templates/shared/server-components.d.ts.map +1 -0
  91. package/dist/commands/index.d.ts +8 -0
  92. package/dist/commands/index.d.ts.map +1 -0
  93. package/dist/commands/instances/index.d.ts +22 -0
  94. package/dist/commands/instances/index.d.ts.map +1 -0
  95. package/dist/commands/self-host/migrate/migrator-result.d.ts +1 -1
  96. package/dist/commands/self-host/migrate/migrator-result.d.ts.map +1 -1
  97. package/dist/commands/self-host/migrate/orm-result.d.ts +1 -1
  98. package/dist/commands/self-host/migrate/orm-result.d.ts.map +1 -1
  99. package/dist/commands/self-host/migrate/read-config.d.ts +1 -1
  100. package/dist/commands/self-host/migrate/read-config.d.ts.map +1 -1
  101. package/dist/constants.d.ts +101 -0
  102. package/dist/constants.d.ts.map +1 -0
  103. package/dist/context/framework-detection.d.ts +1 -0
  104. package/dist/context/framework-detection.d.ts.map +1 -1
  105. package/dist/context/types.d.ts +3 -3
  106. package/dist/context/types.d.ts.map +1 -1
  107. package/dist/core/context.d.ts +24 -0
  108. package/dist/core/context.d.ts.map +1 -0
  109. package/dist/core/errors.d.ts +214 -0
  110. package/dist/core/errors.d.ts.map +1 -0
  111. package/dist/core/index.d.ts +9 -0
  112. package/dist/core/index.d.ts.map +1 -0
  113. package/dist/core/logger.d.ts +50 -0
  114. package/dist/core/logger.d.ts.map +1 -0
  115. package/dist/core/parser.d.ts +35 -0
  116. package/dist/core/parser.d.ts.map +1 -0
  117. package/dist/core/telemetry.d.ts +78 -0
  118. package/dist/core/telemetry.d.ts.map +1 -0
  119. package/dist/detection/framework.d.ts +31 -0
  120. package/dist/detection/framework.d.ts.map +1 -0
  121. package/dist/detection/index.d.ts +7 -0
  122. package/dist/detection/index.d.ts.map +1 -0
  123. package/dist/detection/layout.d.ts +36 -0
  124. package/dist/detection/layout.d.ts.map +1 -0
  125. package/dist/detection/package-manager.d.ts +31 -0
  126. package/dist/detection/package-manager.d.ts.map +1 -0
  127. package/dist/index.d.ts +9 -0
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.mjs +2066 -1836
  130. package/dist/machines/generate/actions.d.ts +116 -0
  131. package/dist/machines/generate/actions.d.ts.map +1 -0
  132. package/dist/machines/generate/actors/dependencies.d.ts +43 -0
  133. package/dist/machines/generate/actors/dependencies.d.ts.map +1 -0
  134. package/dist/machines/generate/actors/file-generation.d.ts +54 -0
  135. package/dist/machines/generate/actors/file-generation.d.ts.map +1 -0
  136. package/dist/machines/generate/actors/preflight.d.ts +46 -0
  137. package/dist/machines/generate/actors/preflight.d.ts.map +1 -0
  138. package/dist/machines/generate/actors/prompts.d.ts +122 -0
  139. package/dist/machines/generate/actors/prompts.d.ts.map +1 -0
  140. package/dist/machines/generate/guards.d.ts +180 -0
  141. package/dist/machines/generate/guards.d.ts.map +1 -0
  142. package/dist/machines/generate/machine.d.ts +250 -0
  143. package/dist/machines/generate/machine.d.ts.map +1 -0
  144. package/dist/machines/generate/runner.d.ts +40 -0
  145. package/dist/machines/generate/runner.d.ts.map +1 -0
  146. package/dist/machines/generate/types.d.ts +189 -0
  147. package/dist/machines/generate/types.d.ts.map +1 -0
  148. package/dist/machines/index.d.ts +20 -0
  149. package/dist/machines/index.d.ts.map +1 -0
  150. package/dist/machines/persistence.d.ts +65 -0
  151. package/dist/machines/persistence.d.ts.map +1 -0
  152. package/dist/machines/telemetry-plugin.d.ts +54 -0
  153. package/dist/machines/telemetry-plugin.d.ts.map +1 -0
  154. package/dist/machines/types.d.ts +104 -0
  155. package/dist/machines/types.d.ts.map +1 -0
  156. package/dist/mcp/client.d.ts +61 -0
  157. package/dist/mcp/client.d.ts.map +1 -0
  158. package/dist/mcp/index.d.ts +6 -0
  159. package/dist/mcp/index.d.ts.map +1 -0
  160. package/dist/mcp/types.d.ts +84 -0
  161. package/dist/mcp/types.d.ts.map +1 -0
  162. package/dist/types.d.ts +238 -0
  163. package/dist/types.d.ts.map +1 -0
  164. package/dist/utils/formatter.d.ts +71 -0
  165. package/dist/utils/formatter.d.ts.map +1 -0
  166. package/dist/utils/fs.d.ts +80 -0
  167. package/dist/utils/fs.d.ts.map +1 -0
  168. package/dist/utils/index.d.ts +8 -0
  169. package/dist/utils/index.d.ts.map +1 -0
  170. package/dist/utils/spinner.d.ts +60 -0
  171. package/dist/utils/spinner.d.ts.map +1 -0
  172. package/dist/utils/telemetry.d.ts +4 -0
  173. package/dist/utils/telemetry.d.ts.map +1 -1
  174. package/dist/utils/validation.d.ts +68 -0
  175. package/dist/utils/validation.d.ts.map +1 -0
  176. package/package.json +63 -64
  177. package/LICENSE.md +0 -595
  178. package/dist/commands/generate/options/utils/shared-frontend.d.ts +0 -14
  179. package/dist/commands/generate/options/utils/shared-frontend.d.ts.map +0 -1
  180. package/dist/commands/generate/templates/backend.d.ts +0 -10
  181. package/dist/commands/generate/templates/backend.d.ts.map +0 -1
  182. package/dist/commands/generate/templates/next/app/components.d.ts +0 -44
  183. package/dist/commands/generate/templates/next/app/components.d.ts.map +0 -1
  184. package/dist/commands/generate/templates/next/pages/components.d.ts +0 -22
  185. package/dist/commands/generate/templates/next/pages/components.d.ts.map +0 -1
  186. package/dist/commands/generate/templates/react/components.d.ts +0 -21
  187. package/dist/commands/generate/templates/react/components.d.ts.map +0 -1
package/dist/760.mjs ADDED
@@ -0,0 +1,1720 @@
1
+ import { Node, Project, SyntaxKind } from "ts-morph";
2
+ export const __rspack_esm_id = "760";
3
+ export const __rspack_esm_ids = [
4
+ "760"
5
+ ];
6
+ export const __webpack_modules__ = {
7
+ "./src/commands/generate/options/utils/generate-files.ts" (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8
+ __webpack_require__.d(__webpack_exports__, {
9
+ generateFiles: ()=>generateFiles
10
+ });
11
+ var promises_ = __webpack_require__("node:fs/promises");
12
+ var external_node_path_ = __webpack_require__("node:path");
13
+ var external_picocolors_ = __webpack_require__("picocolors");
14
+ var logger = __webpack_require__("./src/utils/logger.ts");
15
+ var constants = __webpack_require__("./src/constants.ts");
16
+ function generateClientConfigContent(mode, backendURL, useEnvFile, enableDevTools = false) {
17
+ switch(mode){
18
+ case constants.$V.C15T:
19
+ return generateC15tConfig(backendURL, useEnvFile, enableDevTools);
20
+ case constants.$V.OFFLINE:
21
+ return generateOfflineConfig(enableDevTools);
22
+ case constants.$V.SELF_HOSTED:
23
+ return generateSelfHostedConfig(backendURL, useEnvFile, enableDevTools);
24
+ case constants.$V.CUSTOM:
25
+ return generateCustomConfig(backendURL, useEnvFile, enableDevTools);
26
+ default:
27
+ return generateOfflineConfig(enableDevTools);
28
+ }
29
+ }
30
+ function generateC15tConfig(backendURL, useEnvFile, enableDevTools = false) {
31
+ const url = useEnvFile ? 'process.env.NEXT_PUBLIC_C15T_URL' : `'${backendURL || 'https://your-instance.c15t.dev'}'`;
32
+ const devToolsImport = enableDevTools ? "import { createDevTools } from '@c15t/dev-tools';\n" : '';
33
+ const devToolsCall = enableDevTools ? 'createDevTools();\n' : '';
34
+ return `import { getOrCreateConsentRuntime } from 'c15t';
35
+ ${devToolsImport}
36
+ const runtime = getOrCreateConsentRuntime(
37
+ {
38
+ mode: 'c15t',
39
+ backendURL: ${url},
40
+ consentCategories: ['necessary', 'measurement', 'marketing'],
41
+ },
42
+ );
43
+
44
+ export const store = runtime.consentStore;
45
+ ${devToolsCall}
46
+ /**
47
+ * Usage Examples
48
+ **/
49
+
50
+ // View all consents
51
+ // store.getState().consents;
52
+
53
+ // Update a single consent type: (does not save automically, allowing you to batch updates together before saving)
54
+ // store.getState().setSelectedConsent('measurement', true);
55
+
56
+ // Update a single consent type and automically saves it
57
+ // store.getState().setConsent('marketing', true);
58
+
59
+ // When a user rejects all consents:
60
+ // store.getState().saveConsents("necessary")
61
+ `;
62
+ }
63
+ function generateOfflineConfig(enableDevTools = false) {
64
+ const devToolsImport = enableDevTools ? "import { createDevTools } from '@c15t/dev-tools';\n" : '';
65
+ const devToolsCall = enableDevTools ? 'createDevTools();\n' : '';
66
+ return `import { getOrCreateConsentRuntime } from 'c15t';
67
+ ${devToolsImport}
68
+ const runtime = getOrCreateConsentRuntime(
69
+ {
70
+ mode: 'offline',
71
+ consentCategories: ['necessary', 'measurement', 'marketing'],
72
+ },
73
+ );
74
+
75
+ export const store = runtime.consentStore;
76
+ ${devToolsCall}
77
+ /**
78
+ * Usage Examples
79
+ **/
80
+
81
+ // View all consents
82
+ // store.getState().consents;
83
+
84
+ // Update a single consent type: (does not save automically, allowing you to batch updates together before saving)
85
+ // store.getState().setSelectedConsent('measurement', true);
86
+
87
+ // Update a single consent type and automically saves it
88
+ // store.getState().setConsent('marketing', true);
89
+
90
+ // When a user rejects all consents:
91
+ // store.getState().saveConsents("necessary")
92
+ `;
93
+ }
94
+ function generateSelfHostedConfig(backendURL, useEnvFile, enableDevTools = false) {
95
+ const url = useEnvFile ? 'process.env.NEXT_PUBLIC_C15T_URL' : `'${backendURL || 'http://localhost:3001'}'`;
96
+ const devToolsImport = enableDevTools ? "import { createDevTools } from '@c15t/dev-tools';\n" : '';
97
+ const devToolsCall = enableDevTools ? 'createDevTools();\n' : '';
98
+ return `import { getOrCreateConsentRuntime } from 'c15t';
99
+ ${devToolsImport}
100
+ const runtime = getOrCreateConsentRuntime(
101
+ {
102
+ mode: 'c15t',
103
+ backendURL: ${url},
104
+ consentCategories: ['necessary', 'measurement', 'marketing'],
105
+ },
106
+ );
107
+
108
+ export const store = runtime.consentStore;
109
+ ${devToolsCall}
110
+ /**
111
+ * Usage Examples
112
+ **/
113
+
114
+ // View all consents
115
+ // store.getState().consents;
116
+
117
+ // Update a single consent type: (does not save automically, allowing you to batch updates together before saving)
118
+ // store.getState().setSelectedConsent('measurement', true);
119
+
120
+ // Update a single consent type and automically saves it
121
+ // store.getState().setConsent('marketing', true);
122
+
123
+ // When a user rejects all consents:
124
+ // store.getState().saveConsents("necessary")
125
+ `;
126
+ }
127
+ function generateCustomConfig(backendURL, useEnvFile, enableDevTools = false) {
128
+ const url = useEnvFile ? 'process.env.NEXT_PUBLIC_CONSENT_API_URL' : `'${backendURL || '/api/consent'}'`;
129
+ const devToolsImport = enableDevTools ? "import { createDevTools } from '@c15t/dev-tools';\n" : '';
130
+ const devToolsCall = enableDevTools ? 'createDevTools();\n' : '';
131
+ return `import { getOrCreateConsentRuntime, type EndpointHandlers } from 'c15t';
132
+ ${devToolsImport}
133
+ function createCustomHandlers(): EndpointHandlers {
134
+ return {
135
+ async getConsent() {
136
+ const response = await fetch(${url});
137
+ return response.json();
138
+ },
139
+ async setConsent(consent) {
140
+ const response = await fetch(${url}, {
141
+ method: 'POST',
142
+ headers: { 'Content-Type': 'application/json' },
143
+ body: JSON.stringify(consent),
144
+ });
145
+ return response.json();
146
+ },
147
+ };
148
+ }
149
+
150
+ const runtime = getOrCreateConsentRuntime(
151
+ {
152
+ mode: 'custom',
153
+ endpointHandlers: createCustomHandlers(),
154
+ consentCategories: ['necessary', 'measurement', 'marketing'],
155
+ },
156
+ );
157
+
158
+ export const store = runtime.consentStore;
159
+ ${devToolsCall}
160
+ /**
161
+ * Usage Examples
162
+ **/
163
+
164
+ // View all consents
165
+ // store.getState().consents;
166
+
167
+ // Update a single consent type: (does not save automically, allowing you to batch updates together before saving)
168
+ // store.getState().setSelectedConsent('measurement', true);
169
+
170
+ // Update a single consent type and automically saves it
171
+ // store.getState().setConsent('marketing', true);
172
+
173
+ // When a user rejects all consents:
174
+ // store.getState().saveConsents("necessary")
175
+ `;
176
+ }
177
+ const CSS_PATTERNS = [
178
+ 'app/globals.css',
179
+ 'src/app/globals.css',
180
+ 'app/global.css',
181
+ 'src/app/global.css',
182
+ 'styles/globals.css',
183
+ 'src/styles/globals.css',
184
+ 'styles/global.css',
185
+ 'src/styles/global.css',
186
+ 'src/index.css',
187
+ 'src/App.css'
188
+ ];
189
+ async function updateTailwindCss(projectRoot, tailwindVersion) {
190
+ if (!tailwindVersion || !tailwindVersion.match(/^(?:\^|~)?3/)) return {
191
+ updated: false,
192
+ filePath: null
193
+ };
194
+ for (const pattern of CSS_PATTERNS){
195
+ const filePath = external_node_path_["default"].join(projectRoot, pattern);
196
+ try {
197
+ await promises_["default"].access(filePath);
198
+ const content = await promises_["default"].readFile(filePath, 'utf-8');
199
+ if (content.includes('@layer base, components;')) return {
200
+ updated: false,
201
+ filePath
202
+ };
203
+ if (content.includes('@tailwind base') || content.includes('@tailwind components') || content.includes('@tailwind utilities')) {
204
+ let newContent = content;
205
+ if (newContent.includes('@tailwind base')) {
206
+ newContent = newContent.replace('@tailwind base;', '@layer base {\n @tailwind base;\n}');
207
+ newContent = newContent.replace('@tailwind base\n', '@layer base {\n @tailwind base;\n}\n');
208
+ }
209
+ if (!newContent.includes('@layer base, components;')) newContent = `@layer base, components;\n\n${newContent}`;
210
+ await promises_["default"].writeFile(filePath, newContent, 'utf-8');
211
+ return {
212
+ updated: true,
213
+ filePath
214
+ };
215
+ }
216
+ } catch {}
217
+ }
218
+ return {
219
+ updated: false,
220
+ filePath: null
221
+ };
222
+ }
223
+ function getEnvVarName(pkg) {
224
+ return '@c15t/nextjs' === pkg ? 'NEXT_PUBLIC_C15T_URL' : 'PUBLIC_C15T_URL';
225
+ }
226
+ function generateEnvFileContent(backendURL, pkg) {
227
+ const envVarName = getEnvVarName(pkg);
228
+ return `\n${envVarName}=${backendURL}\n`;
229
+ }
230
+ function generateEnvExampleContent(pkg) {
231
+ const envVarName = getEnvVarName(pkg);
232
+ return `\n# c15t Configuration\n${envVarName}=https://your-instance.c15t.dev\n`;
233
+ }
234
+ function toCamelCase(scriptName) {
235
+ return scriptName.replace(/-([a-z])/g, (_, letter)=>letter.toUpperCase());
236
+ }
237
+ function generateScriptsImport(selectedScripts) {
238
+ if (!selectedScripts.length) return '';
239
+ return selectedScripts.map((script)=>`import { ${toCamelCase(script)} } from '@c15t/scripts/${script}';`).join('\n');
240
+ }
241
+ function generateScriptsConfig(selectedScripts) {
242
+ if (!selectedScripts.length) return '';
243
+ const scriptConfigs = selectedScripts.map((script)=>{
244
+ const funcName = toCamelCase(script);
245
+ const idPlaceholder = getIdPlaceholder(script);
246
+ return `${funcName}({ id: '${idPlaceholder}' })`;
247
+ });
248
+ return `scripts: [
249
+ ${scriptConfigs.join(',\n\t\t\t\t\t')},
250
+ ],`;
251
+ }
252
+ function getIdPlaceholder(scriptName) {
253
+ switch(scriptName){
254
+ case 'google-tag-manager':
255
+ return 'GTM-XXXXXX';
256
+ case 'google-tag':
257
+ return 'G-XXXXXXXXXX';
258
+ case 'meta-pixel':
259
+ return 'XXXXXXXXXXXXXXXXXX';
260
+ case 'posthog':
261
+ return 'phc_XXXXXXXXXX';
262
+ case 'linkedin-insights':
263
+ return 'XXXXXXX';
264
+ case 'tiktok-pixel':
265
+ return 'XXXXXXXXXXXXXXXXX';
266
+ case 'x-pixel':
267
+ return 'XXXXX';
268
+ case 'microsoft-uet':
269
+ return 'XXXXXXXXXX';
270
+ case 'databuddy':
271
+ return 'YOUR_ID_HERE';
272
+ default:
273
+ return 'YOUR_ID_HERE';
274
+ }
275
+ }
276
+ function generateScriptsCommentPlaceholder() {
277
+ return `// Add your scripts here:
278
+ // import { googleTagManager } from '@c15t/scripts/google-tag-manager';
279
+ // scripts: [
280
+ // googleTagManager({ id: 'GTM-XXXXXX' }),
281
+ // ],`;
282
+ }
283
+ function generateConsentComponent({ importSource, optionsText, selectedScripts = [], initialDataProp = false, useClientDirective = false, defaultExport = false, ssrDataOption = false, includeOverrides = false, enableDevTools = false, useFrameworkProps, includeTheme = false, docsSlug }) {
284
+ const scriptsImport = generateScriptsImport(selectedScripts);
285
+ const scriptsConfig = selectedScripts.length ? generateScriptsConfig(selectedScripts) : generateScriptsCommentPlaceholder();
286
+ const ssrDataLine = ssrDataOption ? '\n\t\t\t\tssrData,' : '';
287
+ const themeLine = includeTheme ? '\n\t\t\t\ttheme,' : '';
288
+ const overridesLine = includeOverrides ? `\n\t\t\t\t// Shows banner during development. Remove for production.\n\t\t\t\toverrides: { country: 'DE' },` : '';
289
+ const fullOptionsText = `{
290
+ ${optionsText}${ssrDataLine}${themeLine}
291
+ ${scriptsConfig}${overridesLine}
292
+ }`;
293
+ const useConsentManagerProps = useFrameworkProps && ssrDataOption;
294
+ const needsDataType = (initialDataProp || ssrDataOption) && !useConsentManagerProps;
295
+ const namedImports = needsDataType ? `ConsentDialog,
296
+ ConsentManagerProvider,
297
+ ConsentBanner,
298
+ type InitialDataPromise` : `ConsentDialog,
299
+ ConsentManagerProvider,
300
+ ConsentBanner,`;
301
+ const frameworkPropsImport = useConsentManagerProps ? `import type { ConsentManagerProps } from '${useFrameworkProps}';\n` : '';
302
+ let propsDestructure;
303
+ propsDestructure = useConsentManagerProps ? "{ children, ssrData }: ConsentManagerProps" : ssrDataOption ? `{
304
+ children,
305
+ ssrData,
306
+ }: {
307
+ children: ReactNode;
308
+ ssrData?: InitialDataPromise;
309
+ }` : initialDataProp ? `{
310
+ children,
311
+ initialData,
312
+ }: {
313
+ children: ReactNode;
314
+ initialData?: InitialDataPromise;
315
+ }` : '{ children }: { children: ReactNode }';
316
+ const providerProps = initialDataProp ? `\n\t\t\tinitialData={initialData}\n\t\t\toptions={${fullOptionsText}}` : ` options={${fullOptionsText}}`;
317
+ const directive = useClientDirective ? "'use client';\n\n" : '';
318
+ const devToolsImport = enableDevTools ? "import { C15TDevTools } from '@c15t/dev-tools/react';\n" : '';
319
+ const themeImport = includeTheme ? "import { theme } from './theme';\n" : '';
320
+ const componentName = defaultExport ? 'ConsentManagerClient' : 'ConsentManager';
321
+ const exportPrefix = defaultExport ? 'export default function' : 'export function';
322
+ const docComment = buildDocComment({
323
+ defaultExport,
324
+ initialDataProp,
325
+ ssrDataOption,
326
+ docsSlug
327
+ });
328
+ const preDocComment = initialDataProp ? `// For client-only apps (non-SSR), you can use:
329
+ // import { ConsentManagerProvider } from '@c15t/nextjs/client';
330
+
331
+ ` : '';
332
+ return `${directive}import type { ReactNode } from 'react';
333
+ import {
334
+ ${namedImports}
335
+ } from '${importSource}';
336
+ ${frameworkPropsImport}${devToolsImport}${themeImport}${scriptsImport ? `${scriptsImport}\n` : ''}${preDocComment}${docComment}
337
+ ${exportPrefix} ${componentName}(${propsDestructure}) {
338
+ return (
339
+ <ConsentManagerProvider${providerProps}>
340
+ <ConsentBanner />
341
+ <ConsentDialog />
342
+ ${enableDevTools ? "<C15TDevTools disabled={process.env.NODE_ENV === 'production'} />" : ''}
343
+ {children}
344
+ </ConsentManagerProvider>
345
+ );
346
+ }
347
+ `;
348
+ }
349
+ function buildDocComment({ defaultExport, initialDataProp, ssrDataOption, docsSlug }) {
350
+ if (defaultExport) {
351
+ const slug = docsSlug || 'nextjs';
352
+ return `/**
353
+ * Client-side consent manager provider.
354
+ * @see https://c15t.com/docs/frameworks/${slug}/quickstart
355
+ */`;
356
+ }
357
+ if (initialDataProp) return `/**
358
+ * Consent management wrapper for Next.js Pages Router.
359
+ * @see https://c15t.com/docs/frameworks/nextjs/quickstart
360
+ */`;
361
+ const slug = docsSlug || 'react';
362
+ return `/**
363
+ * Consent manager provider.
364
+ * @see https://c15t.com/docs/frameworks/${slug}/quickstart
365
+ */`;
366
+ }
367
+ async function getComponentsDirectory(projectRoot, sourceDir) {
368
+ const isSrcDir = 'src' === sourceDir || sourceDir.startsWith('src');
369
+ const candidates = isSrcDir ? [
370
+ 'src/components',
371
+ 'components'
372
+ ] : [
373
+ 'components',
374
+ 'src/components'
375
+ ];
376
+ for (const candidate of candidates)try {
377
+ const candidatePath = external_node_path_["default"].join(projectRoot, candidate);
378
+ const stat = await promises_["default"].stat(candidatePath);
379
+ if (stat.isDirectory()) return candidate;
380
+ } catch {}
381
+ return isSrcDir ? 'src/components' : 'components';
382
+ }
383
+ function getFrameworkDirectory(filePath, dirName) {
384
+ const normalizedPath = external_node_path_["default"].normalize(filePath);
385
+ const srcSegment = external_node_path_["default"].join('src', dirName);
386
+ if (normalizedPath.includes(srcSegment)) return external_node_path_["default"].join('src', dirName);
387
+ return dirName;
388
+ }
389
+ function getSourceDirectory(filePath) {
390
+ const normalizedPath = external_node_path_["default"].normalize(filePath);
391
+ const segments = normalizedPath.split(external_node_path_["default"].sep);
392
+ if (segments.includes('src')) return 'src';
393
+ return '';
394
+ }
395
+ function generateExpandedProviderTemplate({ enableSSR, enableDevTools, optionsText, framework }) {
396
+ const useConsentManagerProps = enableSSR && framework.hasSSRProps;
397
+ let propsInterface;
398
+ let propsDestructure;
399
+ let typeImports;
400
+ if (useConsentManagerProps) {
401
+ propsInterface = '';
402
+ propsDestructure = '{ children, ssrData }: ConsentManagerProps';
403
+ typeImports = `import type { ConsentManagerProps } from '${framework.importSource}';`;
404
+ } else if (enableSSR) {
405
+ propsInterface = `\ninterface Props {
406
+ children: ReactNode;
407
+ ssrData?: InitialDataPromise;
408
+ }\n`;
409
+ propsDestructure = '{ children, ssrData }: Props';
410
+ typeImports = `import type { InitialDataPromise } from '${framework.importSource}';`;
411
+ } else {
412
+ propsInterface = `\ninterface Props {
413
+ children: ReactNode;
414
+ }\n`;
415
+ propsDestructure = '{ children }: Props';
416
+ typeImports = '';
417
+ }
418
+ const ssrDataOption = enableSSR ? '\n\t\t\t\tssrData,' : '';
419
+ const devToolsImport = enableDevTools ? "import { C15TDevTools } from '@c15t/dev-tools/react';\n" : '';
420
+ const reactNodeImport = useConsentManagerProps ? '' : "import type { ReactNode } from 'react';\n";
421
+ return `'use client';
422
+
423
+ ${reactNodeImport}import { ConsentManagerProvider } from '${framework.importSource}';
424
+ ${typeImports}
425
+ ${devToolsImport}import ConsentBanner from './consent-banner';
426
+ import ConsentDialog from './consent-dialog';
427
+ import { theme } from './theme';
428
+ ${propsInterface}
429
+ /**
430
+ * Client-side consent manager provider with compound components.
431
+ * @see https://c15t.com/docs/frameworks/${framework.docsSlug}/quickstart
432
+ */
433
+ export default function ConsentManagerClient(${propsDestructure}) {
434
+ return (
435
+ <ConsentManagerProvider
436
+ options={{
437
+ ${optionsText}${ssrDataOption}
438
+ theme,
439
+ // Add your scripts here:
440
+ // scripts: [
441
+ // googleTagManager({ id: 'GTM-XXXXXX' }),
442
+ // ],${!enableSSR ? "\n\t\t\t\t// Shows banner during development. Remove for production.\n\t\t\t\toverrides: { country: 'DE' }," : ''}
443
+ }}
444
+ >
445
+ <ConsentBanner />
446
+ <ConsentDialog />
447
+ ${enableDevTools ? "<C15TDevTools disabled={process.env.NODE_ENV === 'production'} />" : ''}
448
+ {children}
449
+ </ConsentManagerProvider>
450
+ );
451
+ }
452
+ `;
453
+ }
454
+ function generateExpandedConsentDialogTemplate(framework) {
455
+ return `'use client';
456
+
457
+ import { ConsentWidget } from '${framework.importSource}';
458
+ import { ConsentDialog } from '${framework.consentDialogImport}';
459
+
460
+ /**
461
+ * Consent dialog using compound components.
462
+ * @see https://c15t.com/docs/frameworks/${framework.docsSlug}/components/consent-dialog
463
+ */
464
+ export default function () {
465
+ return (
466
+ <ConsentDialog.Root>
467
+ <ConsentDialog.Card>
468
+ <ConsentDialog.Header>
469
+ <ConsentDialog.HeaderTitle />
470
+ <ConsentDialog.HeaderDescription />
471
+ </ConsentDialog.Header>
472
+ <ConsentDialog.Content>
473
+ <ConsentWidget />
474
+ </ConsentDialog.Content>
475
+ <ConsentDialog.Footer />
476
+ </ConsentDialog.Card>
477
+ </ConsentDialog.Root>
478
+ );
479
+ }
480
+ `;
481
+ }
482
+ function generateExpandedConsentBannerTemplate(framework) {
483
+ return `'use client';
484
+
485
+ import { ConsentBanner } from '${framework.consentBannerImport}';
486
+
487
+ /**
488
+ * Consent banner using compound components.
489
+ * @see https://c15t.com/docs/frameworks/${framework.docsSlug}/components/consent-banner
490
+ */
491
+ export default function () {
492
+ return (
493
+ <ConsentBanner.Root>
494
+ <ConsentBanner.Card>
495
+ <ConsentBanner.Header>
496
+ <ConsentBanner.Title />
497
+ <ConsentBanner.Description
498
+ legalLinks={['privacyPolicy', 'termsOfService']}
499
+ />
500
+ </ConsentBanner.Header>
501
+ <ConsentBanner.Footer>
502
+ <ConsentBanner.FooterSubGroup>
503
+ <ConsentBanner.RejectButton />
504
+ <ConsentBanner.AcceptButton />
505
+ </ConsentBanner.FooterSubGroup>
506
+ <ConsentBanner.CustomizeButton />
507
+ </ConsentBanner.Footer>
508
+ </ConsentBanner.Card>
509
+ </ConsentBanner.Root>
510
+ );
511
+ }
512
+ `;
513
+ }
514
+ function generateExpandedThemeTemplate(theme, framework) {
515
+ switch(theme){
516
+ case 'tailwind':
517
+ return generateTailwindTheme(framework);
518
+ case 'minimal':
519
+ return generateMinimalTheme(framework);
520
+ case 'dark':
521
+ return generateDarkTheme(framework);
522
+ default:
523
+ return generateTailwindTheme(framework);
524
+ }
525
+ }
526
+ function generateTailwindTheme(framework) {
527
+ return `import type { Theme } from '${framework.importSource}';
528
+
529
+ /**
530
+ * Tailwind Theme
531
+ *
532
+ * Uses standard Tailwind colors (Slate/Blue) with backdrop blur effects.
533
+ * This theme works well with Tailwind CSS projects.
534
+ *
535
+ * Customize the colors, typography, and slots below to match your design.
536
+ *
537
+ * @see https://c15t.com/docs/customization/theming
538
+ */
539
+ export const theme: Theme = {
540
+ colors: {
541
+ primary: '#3b82f6', // blue-500
542
+ primaryHover: '#2563eb', // blue-600
543
+ surface: '#ffffff',
544
+ surfaceHover: '#f8fafc', // slate-50
545
+ border: '#e2e8f0', // slate-200
546
+ borderHover: '#cbd5e1', // slate-300
547
+ text: '#0f172a', // slate-900
548
+ textMuted: '#64748b', // slate-500
549
+ textOnPrimary: '#ffffff',
550
+ switchTrack: '#e2e8f0',
551
+ switchTrackActive: '#3b82f6',
552
+ switchThumb: '#ffffff',
553
+ },
554
+ typography: {
555
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, sans-serif',
556
+ },
557
+ radius: {
558
+ sm: '0.125rem',
559
+ md: '0.375rem',
560
+ lg: '0.5rem',
561
+ full: '9999px',
562
+ },
563
+ slots: {
564
+ consentBannerCard:
565
+ 'border border-slate-200 bg-white/95 backdrop-blur-sm shadow-md',
566
+ consentDialogCard:
567
+ 'border border-slate-200 bg-white/95 backdrop-blur-md shadow-xl',
568
+ buttonPrimary:
569
+ 'bg-blue-600 text-white hover:bg-blue-700 shadow-sm transition-colors',
570
+ buttonSecondary:
571
+ 'bg-white text-slate-600 border border-slate-200 hover:bg-slate-50 transition-colors',
572
+ consentBannerTitle: 'text-slate-900 font-semibold',
573
+ consentBannerDescription: 'text-slate-500',
574
+ },
575
+ };
576
+ `;
577
+ }
578
+ function generateMinimalTheme(framework) {
579
+ return `import type { Theme } from '${framework.importSource}';
580
+
581
+ /**
582
+ * Minimal Theme
583
+ *
584
+ * A clean, light theme with subtle grays and refined typography.
585
+ * Uses standard CSS (no Tailwind dependency).
586
+ *
587
+ * Customize the colors, typography, and slots below to match your design.
588
+ *
589
+ * @see https://c15t.com/docs/customization/theming
590
+ */
591
+ export const theme: Theme = {
592
+ colors: {
593
+ primary: '#18181b',
594
+ primaryHover: '#27272a',
595
+ surface: '#ffffff',
596
+ surfaceHover: '#fafafa',
597
+ border: '#e4e4e7',
598
+ borderHover: '#d4d4d8',
599
+ text: '#18181b',
600
+ textMuted: '#71717a',
601
+ textOnPrimary: '#ffffff',
602
+ switchTrack: '#d4d4d8',
603
+ switchTrackActive: '#18181b',
604
+ switchThumb: '#ffffff',
605
+ },
606
+ dark: {
607
+ primary: '#fafafa',
608
+ primaryHover: '#e4e4e7',
609
+ surface: '#0a0a0a',
610
+ surfaceHover: '#171717',
611
+ border: '#27272a',
612
+ borderHover: '#3f3f46',
613
+ text: '#fafafa',
614
+ textMuted: '#a1a1aa',
615
+ textOnPrimary: '#09090b',
616
+ },
617
+ typography: {
618
+ fontFamily: 'var(--font-inter), system-ui, sans-serif',
619
+ fontSize: {
620
+ sm: '0.8125rem',
621
+ base: '0.875rem',
622
+ lg: '1rem',
623
+ },
624
+ fontWeight: {
625
+ normal: 400,
626
+ medium: 500,
627
+ semibold: 500,
628
+ },
629
+ lineHeight: {
630
+ tight: '1.3',
631
+ normal: '1.5',
632
+ relaxed: '1.7',
633
+ },
634
+ },
635
+ radius: {
636
+ sm: '0.25rem',
637
+ md: '0.375rem',
638
+ lg: '0.5rem',
639
+ full: '9999px',
640
+ },
641
+ shadows: {
642
+ sm: '0 1px 2px rgba(0, 0, 0, 0.04)',
643
+ md: '0 2px 8px rgba(0, 0, 0, 0.06)',
644
+ lg: '0 4px 16px rgba(0, 0, 0, 0.08)',
645
+ },
646
+ slots: {
647
+ consentBannerCard: {
648
+ style: {
649
+ border: '1px solid var(--c15t-border)',
650
+ boxShadow: 'var(--c15t-shadow-sm)',
651
+ },
652
+ },
653
+ consentDialogCard: {
654
+ style: {
655
+ border: '1px solid var(--c15t-border)',
656
+ boxShadow: 'var(--c15t-shadow-lg)',
657
+ },
658
+ },
659
+ buttonPrimary: {
660
+ style: {
661
+ borderRadius: 'var(--c15t-radius-sm)',
662
+ boxShadow: 'none',
663
+ fontWeight: 500,
664
+ },
665
+ },
666
+ buttonSecondary: {
667
+ style: {
668
+ borderRadius: 'var(--c15t-radius-sm)',
669
+ backgroundColor: 'transparent',
670
+ border: '1px solid var(--c15t-border)',
671
+ color: 'var(--c15t-text-muted)',
672
+ boxShadow: 'none',
673
+ fontWeight: 500,
674
+ },
675
+ },
676
+ },
677
+ };
678
+ `;
679
+ }
680
+ function generateDarkTheme(framework) {
681
+ return `import type { Theme } from '${framework.importSource}';
682
+
683
+ /**
684
+ * Dark Mode Theme
685
+ *
686
+ * High contrast black and white theme.
687
+ * Stays dark regardless of system preference.
688
+ * Uses standard CSS (no Tailwind dependency).
689
+ *
690
+ * Customize the colors, typography, and slots below to match your design.
691
+ *
692
+ * @see https://c15t.com/docs/customization/theming
693
+ */
694
+ export const theme: Theme = {
695
+ colors: {
696
+ // Define dark colors as the default to enforce dark mode
697
+ primary: '#ffffff',
698
+ primaryHover: '#ededed',
699
+ surface: '#000000',
700
+ surfaceHover: '#111111',
701
+ border: '#333333',
702
+ borderHover: '#444444',
703
+ text: '#ffffff',
704
+ textMuted: '#888888',
705
+ textOnPrimary: '#000000',
706
+ switchTrack: '#333333',
707
+ switchTrackActive: '#ffffff',
708
+ switchThumb: '#000000',
709
+ },
710
+ // No 'dark' overrides needed as the base IS dark
711
+ typography: {
712
+ fontFamily: 'var(--font-inter), system-ui, sans-serif',
713
+ fontSize: {
714
+ sm: '0.8125rem',
715
+ base: '0.875rem',
716
+ lg: '1rem',
717
+ },
718
+ fontWeight: {
719
+ normal: 400,
720
+ medium: 500,
721
+ semibold: 600,
722
+ },
723
+ },
724
+ radius: {
725
+ sm: '0.25rem',
726
+ md: '0.375rem',
727
+ lg: '0.5rem',
728
+ full: '9999px',
729
+ },
730
+ shadows: {
731
+ sm: '0 1px 2px rgba(255, 255, 255, 0.1)',
732
+ md: '0 4px 8px rgba(0, 0, 0, 0.5)',
733
+ lg: '0 8px 16px rgba(0, 0, 0, 0.5)',
734
+ },
735
+ slots: {
736
+ consentBannerCard: {
737
+ style: {
738
+ backgroundColor: '#000000',
739
+ border: '1px solid #333333',
740
+ boxShadow: 'none',
741
+ },
742
+ },
743
+ consentDialogCard: {
744
+ style: {
745
+ backgroundColor: '#000000',
746
+ border: '1px solid #333333',
747
+ boxShadow: '0 0 0 1px #333333, 0 8px 40px rgba(0,0,0,0.5)',
748
+ },
749
+ },
750
+ buttonPrimary: {
751
+ style: {
752
+ backgroundColor: '#ffffff',
753
+ color: '#000000',
754
+ border: '1px solid #ffffff',
755
+ boxShadow: 'none',
756
+ fontWeight: 500,
757
+ },
758
+ },
759
+ buttonSecondary: {
760
+ style: {
761
+ backgroundColor: '#000000',
762
+ border: '1px solid #333333',
763
+ color: '#888888',
764
+ boxShadow: 'none',
765
+ fontWeight: 500,
766
+ },
767
+ },
768
+ },
769
+ };
770
+ `;
771
+ }
772
+ const NEXTJS_CONFIG = {
773
+ importSource: '@c15t/nextjs',
774
+ consentBannerImport: '@c15t/nextjs',
775
+ consentDialogImport: '@c15t/nextjs',
776
+ frameworkName: 'Next.js App Router',
777
+ ssrMechanism: 'Next.js headers() API',
778
+ docsSlug: 'nextjs',
779
+ envVarPrefix: 'NEXT_PUBLIC',
780
+ hasSSRProps: true
781
+ };
782
+ const REACT_CONFIG = {
783
+ importSource: '@c15t/react',
784
+ consentBannerImport: '@c15t/react',
785
+ consentDialogImport: '@c15t/react',
786
+ frameworkName: 'React',
787
+ ssrMechanism: '',
788
+ docsSlug: 'react',
789
+ envVarPrefix: '',
790
+ hasSSRProps: false
791
+ };
792
+ function computeRelativeModuleSpecifier(fromFilePath, toFilePath) {
793
+ const fromDir = external_node_path_["default"].dirname(fromFilePath);
794
+ let relativePath = external_node_path_["default"].relative(fromDir, toFilePath);
795
+ relativePath = relativePath.split(external_node_path_["default"].sep).join('/');
796
+ relativePath = relativePath.replace(/\.(tsx?|jsx?)$/, '');
797
+ relativePath = relativePath.replace(/\/index$/, '');
798
+ if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
799
+ return relativePath;
800
+ }
801
+ function hasConsentManagerImport(sourceFile, moduleSpecifier) {
802
+ const existingImports = sourceFile.getImportDeclarations();
803
+ return existingImports.some((importDecl)=>{
804
+ const spec = importDecl.getModuleSpecifierValue();
805
+ return './consent-manager' === spec || './consent-manager.tsx' === spec || spec.endsWith('/consent-manager') || spec.endsWith('/consent-manager/index') || void 0 !== moduleSpecifier && spec === moduleSpecifier;
806
+ });
807
+ }
808
+ function addConsentManagerImport(sourceFile, consentManagerFilePath) {
809
+ const sourceFilePath = sourceFile.getFilePath();
810
+ const moduleSpecifier = computeRelativeModuleSpecifier(sourceFilePath, consentManagerFilePath);
811
+ if (!hasConsentManagerImport(sourceFile, moduleSpecifier)) sourceFile.addImportDeclaration({
812
+ namedImports: [
813
+ 'ConsentManager'
814
+ ],
815
+ moduleSpecifier
816
+ });
817
+ }
818
+ async function runLayoutUpdatePipeline(config) {
819
+ const { filePatterns, projectRoot, frameworkDirName, createComponents, wrapJsx, afterImport } = config;
820
+ const project = new Project();
821
+ let layoutFile;
822
+ for (const pattern of filePatterns){
823
+ const files = project.addSourceFilesAtPaths(`${projectRoot}/${pattern}`);
824
+ if (files.length > 0) {
825
+ layoutFile = files[0];
826
+ break;
827
+ }
828
+ }
829
+ if (!layoutFile) return {
830
+ updated: false,
831
+ filePath: null,
832
+ alreadyModified: false
833
+ };
834
+ const layoutFilePath = layoutFile.getFilePath();
835
+ const frameworkDir = getFrameworkDirectory(layoutFilePath, frameworkDirName);
836
+ if (hasConsentManagerImport(layoutFile)) return {
837
+ updated: false,
838
+ filePath: layoutFilePath,
839
+ alreadyModified: true
840
+ };
841
+ const componentFiles = await createComponents(layoutFilePath, frameworkDir);
842
+ addConsentManagerImport(layoutFile, componentFiles.consentManager);
843
+ if (afterImport) afterImport(layoutFile);
844
+ const returnStatement = layoutFile.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
845
+ if (!returnStatement) return {
846
+ updated: false,
847
+ filePath: layoutFilePath,
848
+ alreadyModified: false
849
+ };
850
+ const expression = returnStatement.getExpression();
851
+ if (!expression) return {
852
+ updated: false,
853
+ filePath: layoutFilePath,
854
+ alreadyModified: false
855
+ };
856
+ const originalJsx = expression.getText();
857
+ const newJsx = wrapJsx(originalJsx);
858
+ returnStatement.replaceWithText(`return ${newJsx}`);
859
+ await layoutFile.save();
860
+ return {
861
+ updated: true,
862
+ filePath: layoutFilePath,
863
+ alreadyModified: false,
864
+ componentFiles
865
+ };
866
+ }
867
+ function getBackendURLValue(backendURL, useEnvFile, proxyNextjs, envVarPrefix = 'NEXT_PUBLIC') {
868
+ if (proxyNextjs) return '"/api/c15t"';
869
+ if (useEnvFile) return `process.env.${envVarPrefix}_C15T_URL!`;
870
+ return `'${backendURL || 'https://your-instance.c15t.dev'}'`;
871
+ }
872
+ function generateOptionsText(mode, backendURL, useEnvFile, proxyNextjs, inlineCustomHandlers, envVarPrefix = 'NEXT_PUBLIC') {
873
+ switch(mode){
874
+ case 'c15t':
875
+ case 'self-hosted':
876
+ {
877
+ const backendURLValue = getBackendURLValue(backendURL, useEnvFile, proxyNextjs, envVarPrefix);
878
+ return `mode: 'c15t',
879
+ backendURL: ${backendURLValue},`;
880
+ }
881
+ case 'custom':
882
+ if (inlineCustomHandlers) {
883
+ const url = useEnvFile ? `process.env.${envVarPrefix}_CONSENT_API_URL` : `'${backendURL || '/api/consent'}'`;
884
+ return `mode: 'custom',
885
+ endpointHandlers: {
886
+ async getConsent() {
887
+ const res = await fetch(${url});
888
+ return res.json();
889
+ },
890
+ async setConsent(consent) {
891
+ const res = await fetch(${url}, {
892
+ method: 'POST',
893
+ headers: { 'Content-Type': 'application/json' },
894
+ body: JSON.stringify(consent),
895
+ });
896
+ return res.json();
897
+ },
898
+ },`;
899
+ }
900
+ return `mode: 'custom',
901
+ endpointHandlers: createCustomHandlers(),`;
902
+ default:
903
+ return "mode: 'offline',";
904
+ }
905
+ }
906
+ function generateServerComponent({ enableSSR, backendURLValue, framework }) {
907
+ if (enableSSR) return `import { fetchInitialData } from '${framework.importSource}';
908
+ import type { ReactNode } from 'react';
909
+ import ConsentManagerProvider from './provider';
910
+
911
+ /**
912
+ * Server-side consent management wrapper with SSR data prefetching.
913
+ * @see https://c15t.com/docs/frameworks/${framework.docsSlug}/quickstart
914
+ */
915
+ export function ConsentManager({ children }: { children: ReactNode }) {
916
+ const ssrData = fetchInitialData({
917
+ backendURL: ${backendURLValue},
918
+ // Shows banner during development. Remove for production.
919
+ overrides: { country: 'DE' },
920
+ });
921
+
922
+ return (
923
+ <ConsentManagerProvider ssrData={ssrData}>
924
+ {children}
925
+ </ConsentManagerProvider>
926
+ );
927
+ }
928
+ `;
929
+ return `import type { ReactNode } from 'react';
930
+ import ConsentManagerProvider from './provider';
931
+
932
+ /**
933
+ * Consent management wrapper.
934
+ * @see https://c15t.com/docs/frameworks/${framework.docsSlug}/quickstart
935
+ */
936
+ export function ConsentManager({ children }: { children: ReactNode }) {
937
+ return (
938
+ <ConsentManagerProvider>
939
+ {children}
940
+ </ConsentManagerProvider>
941
+ );
942
+ }
943
+ `;
944
+ }
945
+ function generateSimpleWrapperComponent(_frameworkName, docsSlug) {
946
+ return `import type { ReactNode } from 'react';
947
+ import ConsentManagerProvider from './provider';
948
+
949
+ /**
950
+ * Consent management wrapper.
951
+ * @see https://c15t.com/docs/frameworks/${docsSlug}/quickstart
952
+ */
953
+ export function ConsentManager({ children }: { children: ReactNode }) {
954
+ return <ConsentManagerProvider>{children}</ConsentManagerProvider>;
955
+ }
956
+ `;
957
+ }
958
+ const HTML_TAG_REGEX = /<html[^>]*>([\s\S]*)<\/html>/;
959
+ const BODY_TAG_REGEX = /<body[^>]*>([\s\S]*)<\/body>/;
960
+ const BODY_OPENING_TAG_REGEX = /<body[^>]*>/;
961
+ const HTML_CONTENT_REGEX = /([\s\S]*<\/html>)/;
962
+ function wrapAppJsxContent(originalJsx) {
963
+ const hasHtmlTag = originalJsx.includes('<html') || originalJsx.includes('</html>');
964
+ const hasBodyTag = originalJsx.includes('<body') || originalJsx.includes('</body>');
965
+ const consentWrapper = (content)=>`
966
+ <ConsentManager>
967
+ ${content}
968
+ </ConsentManager>
969
+ `;
970
+ if (hasHtmlTag) {
971
+ const htmlMatch = originalJsx.match(HTML_TAG_REGEX);
972
+ const htmlContent = htmlMatch?.[1] || '';
973
+ if (!htmlContent) return consentWrapper(originalJsx);
974
+ const bodyMatch = htmlContent.match(BODY_TAG_REGEX);
975
+ if (!bodyMatch) return originalJsx.replace(HTML_CONTENT_REGEX, `<html>${consentWrapper('$1')}</html>`);
976
+ const bodyContent = bodyMatch[1] || '';
977
+ const bodyOpeningTag = originalJsx.match(BODY_OPENING_TAG_REGEX)?.[0] || '<body>';
978
+ return originalJsx.replace(BODY_TAG_REGEX, `${bodyOpeningTag}${consentWrapper(bodyContent)}</body>`);
979
+ }
980
+ if (hasBodyTag) {
981
+ const bodyMatch = originalJsx.match(BODY_TAG_REGEX);
982
+ const bodyContent = bodyMatch?.[1] || '';
983
+ if (!bodyContent) return consentWrapper(originalJsx);
984
+ const bodyOpeningTag = originalJsx.match(BODY_OPENING_TAG_REGEX)?.[0] || '<body>';
985
+ return originalJsx.replace(BODY_TAG_REGEX, `${bodyOpeningTag}${consentWrapper(bodyContent)}</body>`);
986
+ }
987
+ return consentWrapper(originalJsx);
988
+ }
989
+ async function createExpandedConsentManagerComponents(projectRoot, appDir, options) {
990
+ const { mode, backendURL, useEnvFile, proxyNextjs, enableSSR, enableDevTools, expandedTheme } = options;
991
+ const componentsDir = await getComponentsDirectory(projectRoot, appDir);
992
+ const consentManagerDirPath = external_node_path_["default"].join(projectRoot, componentsDir, 'consent-manager');
993
+ const backendURLValue = getBackendURLValue(backendURL, useEnvFile, proxyNextjs, NEXTJS_CONFIG.envVarPrefix);
994
+ const optionsText = generateOptionsText(mode, backendURL, useEnvFile, proxyNextjs, void 0, NEXTJS_CONFIG.envVarPrefix);
995
+ const serverComponentContent = generateServerComponent({
996
+ enableSSR,
997
+ backendURLValue,
998
+ framework: NEXTJS_CONFIG
999
+ });
1000
+ const providerContent = generateExpandedProviderTemplate({
1001
+ enableSSR,
1002
+ enableDevTools: Boolean(enableDevTools),
1003
+ optionsText,
1004
+ framework: NEXTJS_CONFIG
1005
+ });
1006
+ const consentBannerContent = generateExpandedConsentBannerTemplate(NEXTJS_CONFIG);
1007
+ const consentDialogContent = generateExpandedConsentDialogTemplate(NEXTJS_CONFIG);
1008
+ const themeContent = generateExpandedThemeTemplate(expandedTheme, NEXTJS_CONFIG);
1009
+ const indexPath = external_node_path_["default"].join(consentManagerDirPath, 'index.tsx');
1010
+ const providerPath = external_node_path_["default"].join(consentManagerDirPath, 'provider.tsx');
1011
+ const consentBannerPath = external_node_path_["default"].join(consentManagerDirPath, 'consent-banner.tsx');
1012
+ const consentDialogPath = external_node_path_["default"].join(consentManagerDirPath, 'consent-dialog.tsx');
1013
+ const themePath = external_node_path_["default"].join(consentManagerDirPath, 'theme.ts');
1014
+ await promises_["default"].mkdir(consentManagerDirPath, {
1015
+ recursive: true
1016
+ });
1017
+ await Promise.all([
1018
+ promises_["default"].writeFile(indexPath, serverComponentContent, 'utf-8'),
1019
+ promises_["default"].writeFile(providerPath, providerContent, 'utf-8'),
1020
+ promises_["default"].writeFile(consentBannerPath, consentBannerContent, 'utf-8'),
1021
+ promises_["default"].writeFile(consentDialogPath, consentDialogContent, 'utf-8'),
1022
+ promises_["default"].writeFile(themePath, themeContent, 'utf-8')
1023
+ ]);
1024
+ return {
1025
+ consentManager: indexPath,
1026
+ consentManagerDir: consentManagerDirPath
1027
+ };
1028
+ }
1029
+ async function createPrebuiltConsentManagerComponents(projectRoot, appDir, options) {
1030
+ const { mode, backendURL, useEnvFile, proxyNextjs, enableSSR, enableDevTools, expandedTheme, selectedScripts } = options;
1031
+ const hasTheme = expandedTheme && 'none' !== expandedTheme;
1032
+ const componentsDir = await getComponentsDirectory(projectRoot, appDir);
1033
+ const consentManagerDirPath = external_node_path_["default"].join(projectRoot, componentsDir, 'consent-manager');
1034
+ const backendURLValue = getBackendURLValue(backendURL, useEnvFile, proxyNextjs, NEXTJS_CONFIG.envVarPrefix);
1035
+ const optionsText = generateOptionsText(mode, backendURL, useEnvFile, proxyNextjs, void 0, NEXTJS_CONFIG.envVarPrefix);
1036
+ const consentManagerContent = generateServerComponent({
1037
+ enableSSR,
1038
+ backendURLValue,
1039
+ framework: NEXTJS_CONFIG
1040
+ });
1041
+ const consentManagerClientContent = generateConsentComponent({
1042
+ importSource: NEXTJS_CONFIG.importSource,
1043
+ optionsText,
1044
+ selectedScripts,
1045
+ useClientDirective: true,
1046
+ defaultExport: true,
1047
+ ssrDataOption: enableSSR,
1048
+ includeOverrides: !enableSSR,
1049
+ enableDevTools: Boolean(enableDevTools),
1050
+ useFrameworkProps: enableSSR ? NEXTJS_CONFIG.importSource : void 0,
1051
+ includeTheme: Boolean(hasTheme),
1052
+ docsSlug: NEXTJS_CONFIG.docsSlug
1053
+ });
1054
+ const indexPath = external_node_path_["default"].join(consentManagerDirPath, 'index.tsx');
1055
+ const providerPath = external_node_path_["default"].join(consentManagerDirPath, 'provider.tsx');
1056
+ await promises_["default"].mkdir(consentManagerDirPath, {
1057
+ recursive: true
1058
+ });
1059
+ const writePromises = [
1060
+ promises_["default"].writeFile(indexPath, consentManagerContent, 'utf-8'),
1061
+ promises_["default"].writeFile(providerPath, consentManagerClientContent, 'utf-8')
1062
+ ];
1063
+ if (hasTheme) {
1064
+ const themeContent = generateExpandedThemeTemplate(expandedTheme, NEXTJS_CONFIG);
1065
+ const themePath = external_node_path_["default"].join(consentManagerDirPath, 'theme.ts');
1066
+ writePromises.push(promises_["default"].writeFile(themePath, themeContent, 'utf-8'));
1067
+ }
1068
+ await Promise.all(writePromises);
1069
+ return {
1070
+ consentManager: indexPath,
1071
+ consentManagerClient: providerPath
1072
+ };
1073
+ }
1074
+ const APP_LAYOUT_PATTERNS = [
1075
+ 'app/layout.tsx',
1076
+ 'src/app/layout.tsx',
1077
+ 'app/layout.ts',
1078
+ 'src/app/layout.ts'
1079
+ ];
1080
+ async function updateAppLayout({ projectRoot, mode, backendURL, useEnvFile, proxyNextjs, enableSSR = false, enableDevTools = false, uiStyle = 'prebuilt', expandedTheme = 'tailwind', selectedScripts }) {
1081
+ return runLayoutUpdatePipeline({
1082
+ filePatterns: APP_LAYOUT_PATTERNS,
1083
+ projectRoot,
1084
+ frameworkDirName: 'app',
1085
+ wrapJsx: wrapAppJsxContent,
1086
+ createComponents: async (_layoutFilePath, appDir)=>{
1087
+ if ('expanded' === uiStyle) return createExpandedConsentManagerComponents(projectRoot, appDir, {
1088
+ mode,
1089
+ backendURL,
1090
+ useEnvFile,
1091
+ proxyNextjs,
1092
+ enableSSR,
1093
+ enableDevTools,
1094
+ expandedTheme
1095
+ });
1096
+ return createPrebuiltConsentManagerComponents(projectRoot, appDir, {
1097
+ mode,
1098
+ backendURL,
1099
+ useEnvFile,
1100
+ proxyNextjs,
1101
+ enableSSR,
1102
+ enableDevTools,
1103
+ expandedTheme,
1104
+ selectedScripts
1105
+ });
1106
+ }
1107
+ });
1108
+ }
1109
+ function wrapPagesJsxContent(originalJsx) {
1110
+ const trimmedJsx = originalJsx.trim();
1111
+ const hasParentheses = trimmedJsx.startsWith('(') && trimmedJsx.endsWith(')');
1112
+ const cleanJsx = hasParentheses ? trimmedJsx.slice(1, -1).trim() : originalJsx;
1113
+ const wrappedContent = `
1114
+ <ConsentManager initialData={pageProps.initialC15TData}>
1115
+ ${cleanJsx}
1116
+ </ConsentManager>
1117
+ `;
1118
+ return `(${wrappedContent})`;
1119
+ }
1120
+ async function createConsentManagerComponent(projectRoot, pagesDir, optionsText, selectedScripts, enableDevTools) {
1121
+ let componentsDir;
1122
+ componentsDir = pagesDir.includes('src') ? external_node_path_["default"].join('src', 'components') : 'components';
1123
+ const componentsDirPath = external_node_path_["default"].join(projectRoot, componentsDir);
1124
+ await promises_["default"].mkdir(componentsDirPath, {
1125
+ recursive: true
1126
+ });
1127
+ const consentManagerContent = generateConsentComponent({
1128
+ importSource: '@c15t/nextjs',
1129
+ optionsText,
1130
+ selectedScripts,
1131
+ initialDataProp: true,
1132
+ enableDevTools,
1133
+ includeOverrides: true
1134
+ });
1135
+ const consentManagerPath = external_node_path_["default"].join(componentsDirPath, 'consent-manager.tsx');
1136
+ await promises_["default"].writeFile(consentManagerPath, consentManagerContent, 'utf-8');
1137
+ return {
1138
+ consentManager: consentManagerPath
1139
+ };
1140
+ }
1141
+ function addServerSideDataComment(appFile, backendURL, useEnvFile, proxyNextjs) {
1142
+ const existingComments = appFile.getLeadingCommentRanges();
1143
+ let urlExample;
1144
+ urlExample = proxyNextjs ? "'/api/c15t'" : useEnvFile ? 'process.env.NEXT_PUBLIC_C15T_URL!' : `'${backendURL || 'https://your-instance.c15t.dev'}'`;
1145
+ const serverSideComment = `/**
1146
+ * Note: To get the initial server-side data on other pages, add this to each page:
1147
+ *
1148
+ * import { withInitialC15TData } from '@c15t/nextjs';
1149
+ *
1150
+ * export const getServerSideProps = withInitialC15TData(${urlExample});
1151
+ *
1152
+ * This will automatically pass initialC15TData to pageProps.initialC15TData
1153
+ */`;
1154
+ const hasServerSideComment = existingComments.some((comment)=>comment.getText().includes('withInitialC15TData'));
1155
+ if (!hasServerSideComment) appFile.insertText(0, `${serverSideComment}\n\n`);
1156
+ }
1157
+ function updateAppComponentTyping(appFile) {
1158
+ const exportAssignment = appFile.getExportAssignment(()=>true);
1159
+ if (!exportAssignment) return;
1160
+ const declaration = exportAssignment.getExpression();
1161
+ if (!declaration) return;
1162
+ const text = declaration.getText();
1163
+ if (text.includes('pageProps') && !text.includes('AppProps')) {
1164
+ const hasAppPropsImport = appFile.getImportDeclarations().some((importDecl)=>'next/app' === importDecl.getModuleSpecifierValue() && importDecl.getNamedImports().some((namedImport)=>'AppProps' === namedImport.getName()));
1165
+ if (!hasAppPropsImport) appFile.addImportDeclaration({
1166
+ namedImports: [
1167
+ 'AppProps'
1168
+ ],
1169
+ moduleSpecifier: 'next/app'
1170
+ });
1171
+ }
1172
+ }
1173
+ const PAGES_APP_PATTERNS = [
1174
+ 'pages/_app.tsx',
1175
+ 'pages/_app.ts',
1176
+ 'src/pages/_app.tsx',
1177
+ 'src/pages/_app.ts'
1178
+ ];
1179
+ async function updatePagesLayout({ projectRoot, mode, backendURL, useEnvFile, proxyNextjs, enableDevTools = false, selectedScripts }) {
1180
+ const optionsText = generateOptionsText(mode, backendURL, useEnvFile, proxyNextjs);
1181
+ return runLayoutUpdatePipeline({
1182
+ filePatterns: PAGES_APP_PATTERNS,
1183
+ projectRoot,
1184
+ frameworkDirName: 'pages',
1185
+ wrapJsx: wrapPagesJsxContent,
1186
+ createComponents: async (_layoutFilePath, pagesDir)=>createConsentManagerComponent(projectRoot, pagesDir, optionsText, selectedScripts, enableDevTools),
1187
+ afterImport: (appFile)=>{
1188
+ updateAppComponentTyping(appFile);
1189
+ addServerSideDataComment(appFile, backendURL, useEnvFile, proxyNextjs);
1190
+ }
1191
+ });
1192
+ }
1193
+ function detectNextJsStructure(projectRoot) {
1194
+ const project = new Project();
1195
+ const appLayoutPatterns = [
1196
+ 'app/layout.tsx',
1197
+ 'src/app/layout.tsx',
1198
+ 'app/layout.ts',
1199
+ 'src/app/layout.ts'
1200
+ ];
1201
+ for (const pattern of appLayoutPatterns){
1202
+ const files = project.addSourceFilesAtPaths(`${projectRoot}/${pattern}`);
1203
+ if (files.length > 0) return 'app';
1204
+ }
1205
+ const pagesAppPatterns = [
1206
+ 'pages/_app.tsx',
1207
+ 'pages/_app.ts',
1208
+ 'src/pages/_app.tsx',
1209
+ 'src/pages/_app.ts'
1210
+ ];
1211
+ for (const pattern of pagesAppPatterns){
1212
+ const files = project.addSourceFilesAtPaths(`${projectRoot}/${pattern}`);
1213
+ if (files.length > 0) return 'pages';
1214
+ }
1215
+ return null;
1216
+ }
1217
+ async function updateNextLayout(options) {
1218
+ const structureType = detectNextJsStructure(options.projectRoot);
1219
+ if (!structureType) return {
1220
+ updated: false,
1221
+ filePath: null,
1222
+ alreadyModified: false,
1223
+ structureType: null
1224
+ };
1225
+ let result;
1226
+ result = 'app' === structureType ? await updateAppLayout(options) : await updatePagesLayout(options);
1227
+ return {
1228
+ ...result,
1229
+ structureType
1230
+ };
1231
+ }
1232
+ function updateGenericReactJsx(layoutFile) {
1233
+ const functionDeclarations = layoutFile.getFunctions();
1234
+ const variableDeclarations = layoutFile.getVariableDeclarations();
1235
+ for (const func of functionDeclarations){
1236
+ const returnStatement = func.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
1237
+ if (returnStatement) return wrapReturnStatementWithConsentManager(returnStatement);
1238
+ }
1239
+ for (const varDecl of variableDeclarations){
1240
+ const initializer = varDecl.getInitializer();
1241
+ if (initializer) {
1242
+ const returnStatement = initializer.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
1243
+ if (returnStatement) return wrapReturnStatementWithConsentManager(returnStatement);
1244
+ }
1245
+ }
1246
+ return false;
1247
+ }
1248
+ function wrapReturnStatementWithConsentManager(returnStatement) {
1249
+ const expression = returnStatement.getExpression();
1250
+ if (!expression) return false;
1251
+ let originalJsx = expression.getText();
1252
+ if (originalJsx.startsWith('(') && originalJsx.endsWith(')')) originalJsx = originalJsx.slice(1, -1).trim();
1253
+ const newJsx = `(
1254
+ <ConsentManager>
1255
+ ${originalJsx}
1256
+ </ConsentManager>
1257
+ )`;
1258
+ returnStatement.replaceWithText(`return ${newJsx}`);
1259
+ return true;
1260
+ }
1261
+ async function layout_createConsentManagerComponent(projectRoot, sourceDir, mode, backendURL, useEnvFile, selectedScripts, enableDevTools, expandedTheme) {
1262
+ const hasTheme = expandedTheme && 'none' !== expandedTheme;
1263
+ const componentsDir = await getComponentsDirectory(projectRoot, sourceDir);
1264
+ const consentManagerDirPath = external_node_path_["default"].join(projectRoot, componentsDir, 'consent-manager');
1265
+ const optionsText = generateOptionsText(mode, backendURL, useEnvFile, void 0, true);
1266
+ const providerContent = generateConsentComponent({
1267
+ importSource: '@c15t/react',
1268
+ optionsText,
1269
+ selectedScripts,
1270
+ enableDevTools,
1271
+ useClientDirective: true,
1272
+ defaultExport: true,
1273
+ includeTheme: Boolean(hasTheme),
1274
+ includeOverrides: true,
1275
+ docsSlug: 'react'
1276
+ });
1277
+ const indexContent = generateSimpleWrapperComponent('React', 'react');
1278
+ const indexPath = external_node_path_["default"].join(consentManagerDirPath, 'index.tsx');
1279
+ const providerPath = external_node_path_["default"].join(consentManagerDirPath, 'provider.tsx');
1280
+ await promises_["default"].mkdir(consentManagerDirPath, {
1281
+ recursive: true
1282
+ });
1283
+ const writePromises = [
1284
+ promises_["default"].writeFile(indexPath, indexContent, 'utf-8'),
1285
+ promises_["default"].writeFile(providerPath, providerContent, 'utf-8')
1286
+ ];
1287
+ if (hasTheme) {
1288
+ const themeContent = generateExpandedThemeTemplate(expandedTheme, REACT_CONFIG);
1289
+ const themePath = external_node_path_["default"].join(consentManagerDirPath, 'theme.ts');
1290
+ writePromises.push(promises_["default"].writeFile(themePath, themeContent, 'utf-8'));
1291
+ }
1292
+ await Promise.all(writePromises);
1293
+ return {
1294
+ consentManager: indexPath,
1295
+ consentManagerDir: consentManagerDirPath
1296
+ };
1297
+ }
1298
+ async function updateGenericReactLayout({ projectRoot, mode, backendURL, useEnvFile, proxyNextjs, selectedScripts, enableDevTools, expandedTheme }) {
1299
+ const layoutPatterns = [
1300
+ 'app.tsx',
1301
+ 'App.tsx',
1302
+ 'app.jsx',
1303
+ 'App.jsx',
1304
+ 'src/app.tsx',
1305
+ 'src/App.tsx',
1306
+ 'src/app.jsx',
1307
+ 'src/App.jsx',
1308
+ 'src/app/app.tsx',
1309
+ 'src/app/App.tsx',
1310
+ 'src/app/app.jsx',
1311
+ 'src/app/App.jsx'
1312
+ ];
1313
+ const project = new Project();
1314
+ let layoutFile;
1315
+ for (const pattern of layoutPatterns)try {
1316
+ const files = project.addSourceFilesAtPaths(`${projectRoot}/${pattern}`);
1317
+ if (files.length > 0) {
1318
+ layoutFile = files[0];
1319
+ break;
1320
+ }
1321
+ } catch {}
1322
+ if (!layoutFile) return {
1323
+ updated: false,
1324
+ filePath: null,
1325
+ alreadyModified: false
1326
+ };
1327
+ const layoutFilePath = layoutFile.getFilePath();
1328
+ const sourceDir = getSourceDirectory(layoutFilePath);
1329
+ if (hasConsentManagerImport(layoutFile)) return {
1330
+ updated: false,
1331
+ filePath: layoutFilePath,
1332
+ alreadyModified: true
1333
+ };
1334
+ try {
1335
+ const componentFiles = await layout_createConsentManagerComponent(projectRoot, sourceDir, mode, backendURL, useEnvFile, selectedScripts, enableDevTools, expandedTheme);
1336
+ addConsentManagerImport(layoutFile, componentFiles.consentManager);
1337
+ const updated = updateGenericReactJsx(layoutFile);
1338
+ if (updated) {
1339
+ await layoutFile.save();
1340
+ return {
1341
+ updated: true,
1342
+ filePath: layoutFilePath,
1343
+ alreadyModified: false,
1344
+ componentFiles
1345
+ };
1346
+ }
1347
+ return {
1348
+ updated: false,
1349
+ filePath: layoutFilePath,
1350
+ alreadyModified: false
1351
+ };
1352
+ } catch (error) {
1353
+ throw new Error(`Failed to update generic React layout: ${error instanceof Error ? error.message : String(error)}`);
1354
+ }
1355
+ }
1356
+ async function updateReactLayout(options) {
1357
+ if ('@c15t/nextjs' === options.pkg) {
1358
+ const nextResult = await updateNextLayout(options);
1359
+ if (nextResult.structureType) return {
1360
+ updated: nextResult.updated,
1361
+ filePath: nextResult.filePath,
1362
+ alreadyModified: nextResult.alreadyModified,
1363
+ componentFiles: nextResult.componentFiles
1364
+ };
1365
+ }
1366
+ return updateGenericReactLayout(options);
1367
+ }
1368
+ async function updateNextConfig({ projectRoot, backendURL, useEnvFile }) {
1369
+ const project = new Project();
1370
+ const configFile = findNextConfigFile(project, projectRoot);
1371
+ if (!configFile) {
1372
+ const newConfigPath = `${projectRoot}/next.config.ts`;
1373
+ const newConfig = createNewNextConfig(backendURL, useEnvFile);
1374
+ const newConfigFile = project.createSourceFile(newConfigPath, newConfig);
1375
+ await newConfigFile.save();
1376
+ return {
1377
+ updated: true,
1378
+ filePath: newConfigPath,
1379
+ alreadyModified: false,
1380
+ created: true
1381
+ };
1382
+ }
1383
+ if (hasC15tRewriteRule(configFile)) return {
1384
+ updated: false,
1385
+ filePath: configFile.getFilePath(),
1386
+ alreadyModified: true,
1387
+ created: false
1388
+ };
1389
+ const updated = await updateExistingConfig(configFile, backendURL, useEnvFile);
1390
+ if (updated) await configFile.save();
1391
+ return {
1392
+ updated,
1393
+ filePath: configFile.getFilePath(),
1394
+ alreadyModified: false,
1395
+ created: false
1396
+ };
1397
+ }
1398
+ function findNextConfigFile(project, projectRoot) {
1399
+ const configPatterns = [
1400
+ 'next.config.ts',
1401
+ 'next.config.js',
1402
+ 'next.config.mjs'
1403
+ ];
1404
+ for (const pattern of configPatterns){
1405
+ const configPath = `${projectRoot}/${pattern}`;
1406
+ try {
1407
+ const files = project.addSourceFilesAtPaths(configPath);
1408
+ if (files.length > 0) return files[0];
1409
+ } catch {}
1410
+ }
1411
+ }
1412
+ function hasC15tRewriteRule(configFile) {
1413
+ const text = configFile.getFullText();
1414
+ return text.includes('/api/c15t/') || text.includes("'/api/c15t/:path*'");
1415
+ }
1416
+ function generateRewriteDestination(backendURL, useEnvFile) {
1417
+ if (useEnvFile) return {
1418
+ destination: '${process.env.NEXT_PUBLIC_C15T_URL}/:path*',
1419
+ isTemplateLiteral: true
1420
+ };
1421
+ return {
1422
+ destination: `${backendURL || 'https://your-instance.c15t.dev'}/:path*`,
1423
+ isTemplateLiteral: false
1424
+ };
1425
+ }
1426
+ function createNewNextConfig(backendURL, useEnvFile) {
1427
+ const { destination, isTemplateLiteral } = generateRewriteDestination(backendURL, useEnvFile);
1428
+ const destinationValue = isTemplateLiteral ? `\`${destination}\`` : `'${destination}'`;
1429
+ return `import type { NextConfig } from 'next';
1430
+
1431
+ const config: NextConfig = {
1432
+ async rewrites() {
1433
+ return [
1434
+ {
1435
+ source: '/api/c15t/:path*',
1436
+ destination: ${destinationValue},
1437
+ },
1438
+ ];
1439
+ },
1440
+ };
1441
+
1442
+ export default config;
1443
+ `;
1444
+ }
1445
+ function createRewriteRule(destination, isTemplateLiteral) {
1446
+ const destinationValue = isTemplateLiteral ? `\`${destination}\`` : `'${destination}'`;
1447
+ return `{
1448
+ source: '/api/c15t/:path*',
1449
+ destination: ${destinationValue},
1450
+ }`;
1451
+ }
1452
+ function updateExistingConfig(configFile, backendURL, useEnvFile) {
1453
+ const { destination, isTemplateLiteral } = generateRewriteDestination(backendURL, useEnvFile);
1454
+ const configObject = findConfigObject(configFile);
1455
+ if (!configObject) return false;
1456
+ const rewritesProperty = configObject.getProperty('rewrites');
1457
+ if (rewritesProperty && Node.isMethodDeclaration(rewritesProperty)) return updateExistingRewrites(rewritesProperty, destination, isTemplateLiteral);
1458
+ if (rewritesProperty && Node.isPropertyAssignment(rewritesProperty)) return updatePropertyAssignmentRewrites(rewritesProperty, destination, isTemplateLiteral);
1459
+ return addNewRewritesMethod(configObject, destination, isTemplateLiteral);
1460
+ }
1461
+ function findConfigObject(configFile) {
1462
+ return findConfigFromExportDefault(configFile) || findConfigFromVariableDeclarations(configFile);
1463
+ }
1464
+ function findConfigFromExportDefault(configFile) {
1465
+ const exportDefault = configFile.getDefaultExportSymbol();
1466
+ if (!exportDefault) return;
1467
+ const declarations = exportDefault.getDeclarations();
1468
+ for (const declaration of declarations)if (Node.isExportAssignment(declaration)) {
1469
+ const result = findConfigFromExpression(declaration.getExpression(), configFile);
1470
+ if (result) return result;
1471
+ }
1472
+ }
1473
+ function findConfigFromExpression(expression, configFile) {
1474
+ if (Node.isCallExpression(expression)) return findConfigFromCallExpression(expression, configFile);
1475
+ if (Node.isObjectLiteralExpression(expression)) return expression;
1476
+ if (Node.isIdentifier(expression)) return findConfigFromIdentifier(expression.getText(), configFile);
1477
+ }
1478
+ function findConfigFromCallExpression(expression, configFile) {
1479
+ const args = expression.getArguments();
1480
+ if (0 === args.length) return;
1481
+ const firstArg = args[0];
1482
+ if (Node.isCallExpression(firstArg)) {
1483
+ const innerArgs = firstArg.getArguments();
1484
+ if (innerArgs.length > 0 && Node.isIdentifier(innerArgs[0])) return findConfigFromIdentifier(innerArgs[0].getText(), configFile);
1485
+ }
1486
+ }
1487
+ function findConfigFromIdentifier(identifierText, configFile) {
1488
+ const configVar = configFile.getVariableDeclaration(identifierText);
1489
+ const initializer = configVar?.getInitializer();
1490
+ return initializer && Node.isObjectLiteralExpression(initializer) ? initializer : void 0;
1491
+ }
1492
+ function findConfigFromVariableDeclarations(configFile) {
1493
+ const variableDeclarations = configFile.getVariableDeclarations();
1494
+ for (const varDecl of variableDeclarations){
1495
+ const typeNode = varDecl.getTypeNode();
1496
+ if (typeNode?.getText().includes('NextConfig')) {
1497
+ const initializer = varDecl.getInitializer();
1498
+ if (Node.isObjectLiteralExpression(initializer)) return initializer;
1499
+ }
1500
+ }
1501
+ }
1502
+ function updateExistingRewrites(rewritesMethod, destination, isTemplateLiteral) {
1503
+ const body = rewritesMethod.getBody();
1504
+ if (!Node.isBlock(body)) return false;
1505
+ const returnStatement = body.getStatements().find((stmt)=>Node.isReturnStatement(stmt));
1506
+ if (!returnStatement || !Node.isReturnStatement(returnStatement)) return false;
1507
+ const expression = returnStatement.getExpression();
1508
+ if (!expression || !Node.isArrayLiteralExpression(expression)) return false;
1509
+ const newRewrite = createRewriteRule(destination, isTemplateLiteral);
1510
+ const elements = expression.getElements();
1511
+ if (elements.length > 0) expression.insertElement(0, newRewrite);
1512
+ else expression.addElement(newRewrite);
1513
+ return true;
1514
+ }
1515
+ function updatePropertyAssignmentRewrites(rewritesProperty, destination, isTemplateLiteral) {
1516
+ const initializer = rewritesProperty.getInitializer();
1517
+ if (Node.isArrayLiteralExpression(initializer)) {
1518
+ const newRewrite = createRewriteRule(destination, isTemplateLiteral);
1519
+ initializer.insertElement(0, newRewrite);
1520
+ return true;
1521
+ }
1522
+ return false;
1523
+ }
1524
+ function addNewRewritesMethod(configObject, destination, isTemplateLiteral) {
1525
+ const destinationValue = isTemplateLiteral ? `\`${destination}\`` : `'${destination}'`;
1526
+ const rewritesMethod = `async rewrites() {
1527
+ return [
1528
+ {
1529
+ source: '/api/c15t/:path*',
1530
+ destination: ${destinationValue},
1531
+ },
1532
+ ];
1533
+ }`;
1534
+ configObject.addProperty(rewritesMethod);
1535
+ return true;
1536
+ }
1537
+ async function handleReactLayout(options) {
1538
+ const { projectRoot, mode, backendURL, useEnvFile, proxyNextjs, enableSSR, enableDevTools, uiStyle, expandedTheme, selectedScripts, pkg, spinner, cwd } = options;
1539
+ spinner.start('Updating layout file...');
1540
+ const layoutResult = await updateReactLayout({
1541
+ projectRoot,
1542
+ mode,
1543
+ backendURL,
1544
+ useEnvFile,
1545
+ proxyNextjs,
1546
+ enableSSR,
1547
+ enableDevTools,
1548
+ uiStyle,
1549
+ expandedTheme,
1550
+ selectedScripts,
1551
+ pkg
1552
+ });
1553
+ const spinnerMessage = ()=>{
1554
+ if (layoutResult.alreadyModified) return {
1555
+ message: 'ConsentManager is already imported. Skipped layout file update.',
1556
+ type: 'info'
1557
+ };
1558
+ if (layoutResult.updated) {
1559
+ const typedResult = layoutResult;
1560
+ if (typedResult.componentFiles) {
1561
+ const relativeConsentManager = external_node_path_["default"].relative(cwd, typedResult.componentFiles.consentManager);
1562
+ const relativeLayout = external_node_path_["default"].relative(cwd, layoutResult.filePath || '');
1563
+ if (typedResult.componentFiles.consentManagerDir) {
1564
+ const relativeConsentManagerDir = external_node_path_["default"].relative(cwd, typedResult.componentFiles.consentManagerDir);
1565
+ return {
1566
+ message: `Layout setup complete!\n ${external_picocolors_["default"].green('✓')} Created: ${external_picocolors_["default"].cyan(relativeConsentManagerDir + '/')} (expanded components)\n ${external_picocolors_["default"].green('✓')} Created: ${external_picocolors_["default"].cyan(relativeConsentManager)}\n ${external_picocolors_["default"].green('✓')} Updated: ${external_picocolors_["default"].cyan(relativeLayout)}`,
1567
+ type: 'info'
1568
+ };
1569
+ }
1570
+ if (typedResult.componentFiles.consentManagerClient) {
1571
+ const relativeConsentManagerClient = external_node_path_["default"].relative(cwd, typedResult.componentFiles.consentManagerClient);
1572
+ return {
1573
+ message: `Layout setup complete!\n ${external_picocolors_["default"].green('✓')} Created: ${external_picocolors_["default"].cyan(relativeConsentManager)}\n ${external_picocolors_["default"].green('✓')} Created: ${external_picocolors_["default"].cyan(relativeConsentManagerClient)}\n ${external_picocolors_["default"].green('✓')} Updated: ${external_picocolors_["default"].cyan(relativeLayout)}`,
1574
+ type: 'info'
1575
+ };
1576
+ }
1577
+ return {
1578
+ message: `Layout setup complete!\n ${external_picocolors_["default"].green('✓')} Created: ${external_picocolors_["default"].cyan(relativeConsentManager)}\n ${external_picocolors_["default"].green('✓')} Updated: ${external_picocolors_["default"].cyan(relativeLayout)}`,
1579
+ type: 'info'
1580
+ };
1581
+ }
1582
+ return {
1583
+ message: `Layout file updated: ${layoutResult.filePath}`,
1584
+ type: 'info'
1585
+ };
1586
+ }
1587
+ return {
1588
+ message: 'Layout file not updated.',
1589
+ type: 'error'
1590
+ };
1591
+ };
1592
+ const { message, type } = spinnerMessage();
1593
+ spinner.stop((0, logger.$e)(type, message));
1594
+ return {
1595
+ layoutUpdated: layoutResult.updated,
1596
+ layoutPath: layoutResult.filePath
1597
+ };
1598
+ }
1599
+ async function handleNextConfig(options) {
1600
+ const { projectRoot, backendURL, useEnvFile, spinner } = options;
1601
+ spinner.start('Updating Next.js config...');
1602
+ const configResult = await updateNextConfig({
1603
+ projectRoot,
1604
+ backendURL,
1605
+ useEnvFile
1606
+ });
1607
+ const spinnerMessage = ()=>{
1608
+ if (configResult.alreadyModified) return {
1609
+ message: 'Next.js config already has c15t rewrite rule. Skipped config update.',
1610
+ type: 'info'
1611
+ };
1612
+ if (configResult.updated && configResult.created) return {
1613
+ message: `Next.js config created: ${configResult.filePath}`,
1614
+ type: 'info'
1615
+ };
1616
+ if (configResult.updated) return {
1617
+ message: `Next.js config updated: ${configResult.filePath}`,
1618
+ type: 'info'
1619
+ };
1620
+ return {
1621
+ message: 'Next.js config not updated.',
1622
+ type: 'error'
1623
+ };
1624
+ };
1625
+ const { message, type } = spinnerMessage();
1626
+ spinner.stop((0, logger.$e)(type, message));
1627
+ return {
1628
+ nextConfigUpdated: configResult.updated,
1629
+ nextConfigPath: configResult.filePath,
1630
+ nextConfigCreated: configResult.created
1631
+ };
1632
+ }
1633
+ async function handleEnvFiles(options) {
1634
+ const { projectRoot, backendURL, pkg, spinner, cwd } = options;
1635
+ const envPath = external_node_path_["default"].join(projectRoot, '.env.local');
1636
+ const envExamplePath = external_node_path_["default"].join(projectRoot, '.env.example');
1637
+ spinner.start('Creating/updating environment files...');
1638
+ const envContent = generateEnvFileContent(backendURL, pkg);
1639
+ const envExampleContent = generateEnvExampleContent(pkg);
1640
+ const envVarName = getEnvVarName(pkg);
1641
+ try {
1642
+ const [envExists, envExampleExists] = await Promise.all([
1643
+ promises_["default"].access(envPath).then(()=>true).catch(()=>false),
1644
+ promises_["default"].access(envExamplePath).then(()=>true).catch(()=>false)
1645
+ ]);
1646
+ if (envExists) {
1647
+ const currentEnvContent = await promises_["default"].readFile(envPath, 'utf-8');
1648
+ if (!currentEnvContent.includes(envVarName)) await promises_["default"].appendFile(envPath, envContent);
1649
+ } else await promises_["default"].writeFile(envPath, envContent);
1650
+ if (envExampleExists) {
1651
+ const currentExampleContent = await promises_["default"].readFile(envExamplePath, 'utf-8');
1652
+ if (!currentExampleContent.includes(envVarName)) await promises_["default"].appendFile(envExamplePath, envExampleContent);
1653
+ } else await promises_["default"].writeFile(envExamplePath, envExampleContent);
1654
+ spinner.stop((0, logger.$e)('info', `Environment files added/updated successfully: ${external_picocolors_["default"].cyan(external_node_path_["default"].relative(cwd, envPath))} and ${external_picocolors_["default"].cyan(external_node_path_["default"].relative(cwd, envExamplePath))}`));
1655
+ } catch (error) {
1656
+ spinner.stop((0, logger.$e)('error', `Error processing environment files: ${error instanceof Error ? error.message : String(error)}`));
1657
+ throw error;
1658
+ }
1659
+ }
1660
+ async function generateFiles({ context, mode, spinner, useEnvFile, proxyNextjs, backendURL, enableSSR, enableDevTools, uiStyle, expandedTheme, selectedScripts }) {
1661
+ const result = {
1662
+ layoutUpdated: false
1663
+ };
1664
+ const { projectRoot, framework: { pkg } } = context;
1665
+ if ('@c15t/nextjs' === pkg || '@c15t/react' === pkg) {
1666
+ const layoutResult = await handleReactLayout({
1667
+ projectRoot,
1668
+ mode,
1669
+ backendURL,
1670
+ useEnvFile,
1671
+ proxyNextjs,
1672
+ enableSSR,
1673
+ enableDevTools,
1674
+ uiStyle,
1675
+ expandedTheme,
1676
+ selectedScripts,
1677
+ pkg,
1678
+ spinner,
1679
+ cwd: context.cwd
1680
+ });
1681
+ result.layoutUpdated = layoutResult.layoutUpdated;
1682
+ result.layoutPath = layoutResult.layoutPath;
1683
+ }
1684
+ if ('@c15t/nextjs' === pkg && proxyNextjs && ('c15t' === mode || 'self-hosted' === mode)) {
1685
+ const configResult = await handleNextConfig({
1686
+ projectRoot,
1687
+ backendURL,
1688
+ useEnvFile,
1689
+ spinner
1690
+ });
1691
+ result.nextConfigUpdated = configResult.nextConfigUpdated;
1692
+ result.nextConfigPath = configResult.nextConfigPath;
1693
+ result.nextConfigCreated = configResult.nextConfigCreated;
1694
+ }
1695
+ if ('c15t' === pkg) {
1696
+ spinner.start('Generating client configuration file...');
1697
+ result.configContent = generateClientConfigContent(mode, backendURL, useEnvFile, enableDevTools);
1698
+ result.configPath = external_node_path_["default"].join(projectRoot, 'c15t.config.ts');
1699
+ spinner.stop((0, logger.$e)('info', `Client configuration file generated: ${result.configContent}`));
1700
+ }
1701
+ if (useEnvFile && backendURL) await handleEnvFiles({
1702
+ projectRoot,
1703
+ backendURL,
1704
+ pkg,
1705
+ spinner,
1706
+ cwd: context.cwd
1707
+ });
1708
+ if (context.framework.tailwindVersion) {
1709
+ spinner.start('Checking Tailwind CSS compatibility...');
1710
+ const tailwindResult = await updateTailwindCss(projectRoot, context.framework.tailwindVersion);
1711
+ if (tailwindResult.updated) {
1712
+ result.tailwindCssUpdated = true;
1713
+ result.tailwindCssPath = tailwindResult.filePath;
1714
+ spinner.stop((0, logger.$e)('info', `Tailwind CSS updated for v3 compatibility: ${external_picocolors_["default"].cyan(external_node_path_["default"].relative(context.cwd, tailwindResult.filePath || ''))}`));
1715
+ } else spinner.stop((0, logger.$e)('debug', 'Tailwind CSS update not needed.'));
1716
+ }
1717
+ return result;
1718
+ }
1719
+ }
1720
+ };