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