@c15t/cli 1.7.1 → 1.8.0-canary-20251112105612
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 +2 -2
- package/dist/index.mjs +243 -209
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://github.com/c15t/c15t)
|
|
13
13
|
[](https://github.com/c15t/c15t/actions/workflows/ci.yml)
|
|
14
14
|
[](https://github.com/c15t/c15t/blob/main/LICENSE.md)
|
|
15
|
-
[](https://c15t.
|
|
15
|
+
[](https://c15t.link/discord)
|
|
16
16
|
[](https://www.npmjs.com/package/@c15t/cli)
|
|
17
17
|
[](https://github.com/c15t/c15t)
|
|
18
18
|
[](https://github.com/c15t/c15t/commits/main)
|
|
@@ -92,7 +92,7 @@ For further information, guides, and examples visit the [reference documentation
|
|
|
92
92
|
|
|
93
93
|
## Support
|
|
94
94
|
|
|
95
|
-
- Join our [Discord community](https://c15t.
|
|
95
|
+
- Join our [Discord community](https://c15t.link/discord)
|
|
96
96
|
- Open an issue on our [GitHub repository](https://github.com/c15t/c15t/issues)
|
|
97
97
|
- Visit [consent.io](https://consent.io) and use the chat widget
|
|
98
98
|
- Contact our support team via email [support@consent.io](mailto:support@consent.io)
|
package/dist/index.mjs
CHANGED
|
@@ -1,23 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { confirm as prompts_confirm, isCancel, log as prompts_log, note, outro, select as prompts_select, spinner as prompts_spinner, text as prompts_text } from "@clack/prompts";
|
|
3
3
|
import "dotenv/config";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
4
|
+
import open_0 from "open";
|
|
5
|
+
import picocolors from "picocolors";
|
|
6
|
+
import node_path, { join } from "node:path";
|
|
7
|
+
import promises, { writeFile } from "node:fs/promises";
|
|
8
|
+
import node_crypto from "node:crypto";
|
|
9
|
+
import node_os from "node:os";
|
|
10
|
+
import { PostHog } from "posthog-node";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { once } from "node:events";
|
|
13
|
+
import { createLogger } from "@c15t/logger";
|
|
14
|
+
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
15
|
+
import { migrator } from "@c15t/backend/v2/db/migrator";
|
|
16
|
+
import { DB } from "@c15t/backend/v2/db/schema";
|
|
17
|
+
import { loadConfig } from "c12";
|
|
18
|
+
import figlet from "figlet";
|
|
19
|
+
import fs_extra from "fs-extra";
|
|
20
|
+
import { detect } from "package-manager-detector/detect";
|
|
21
|
+
var __webpack_require__ = {};
|
|
22
|
+
(()=>{
|
|
23
|
+
__webpack_require__.d = (exports, definition)=>{
|
|
24
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: definition[key]
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
})();
|
|
30
|
+
(()=>{
|
|
31
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
32
|
+
})();
|
|
33
|
+
(()=>{
|
|
34
|
+
__webpack_require__.r = (exports)=>{
|
|
35
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports, Symbol.toStringTag, {
|
|
36
|
+
value: 'Module'
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(exports, '__esModule', {
|
|
39
|
+
value: true
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var prompts_namespaceObject = {};
|
|
44
|
+
__webpack_require__.r(prompts_namespaceObject);
|
|
45
|
+
__webpack_require__.d(prompts_namespaceObject, {
|
|
46
|
+
confirm: ()=>prompts_confirm,
|
|
47
|
+
isCancel: ()=>isCancel,
|
|
48
|
+
log: ()=>prompts_log,
|
|
49
|
+
note: ()=>note,
|
|
50
|
+
outro: ()=>outro,
|
|
51
|
+
select: ()=>prompts_select,
|
|
52
|
+
spinner: ()=>prompts_spinner,
|
|
53
|
+
text: ()=>prompts_text
|
|
54
|
+
});
|
|
21
55
|
function showHelpMenu(context, version, commands, flags) {
|
|
22
56
|
const { logger } = context;
|
|
23
57
|
logger.debug('Displaying help menu using command and flag structures.');
|
|
@@ -35,7 +69,7 @@ ${commandLines}
|
|
|
35
69
|
Options:
|
|
36
70
|
${optionLines}
|
|
37
71
|
|
|
38
|
-
Run a command directly (e.g., ${
|
|
72
|
+
Run a command directly (e.g., ${picocolors.cyan('c15t generate')}) or select one interactively when no command is provided.
|
|
39
73
|
|
|
40
74
|
For more help, visit: https://c15t.dev`;
|
|
41
75
|
logger.debug('Help menu content generated.');
|
|
@@ -44,8 +78,8 @@ For more help, visit: https://c15t.dev`;
|
|
|
44
78
|
async function detectFramework(projectRoot, logger) {
|
|
45
79
|
try {
|
|
46
80
|
logger?.debug(`Detecting framework in ${projectRoot}`);
|
|
47
|
-
const packageJsonPath =
|
|
48
|
-
const packageJson = JSON.parse(await
|
|
81
|
+
const packageJsonPath = node_path.join(projectRoot, 'package.json');
|
|
82
|
+
const packageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf-8'));
|
|
49
83
|
const deps = {
|
|
50
84
|
...packageJson.dependencies,
|
|
51
85
|
...packageJson.devDependencies
|
|
@@ -102,15 +136,15 @@ async function detectProjectRoot(cwd, logger) {
|
|
|
102
136
|
while(projectRoot !== prevDir && depth < maxDepth){
|
|
103
137
|
logger?.debug(`Checking directory (depth ${depth}): ${projectRoot}`);
|
|
104
138
|
try {
|
|
105
|
-
const packageJsonPath =
|
|
139
|
+
const packageJsonPath = node_path.join(projectRoot, 'package.json');
|
|
106
140
|
logger?.debug(`Looking for package.json at: ${packageJsonPath}`);
|
|
107
|
-
await
|
|
141
|
+
await promises.access(packageJsonPath);
|
|
108
142
|
logger?.debug(`Found package.json at: ${projectRoot}`);
|
|
109
143
|
break;
|
|
110
144
|
} catch (error) {
|
|
111
145
|
logger?.debug(`No package.json found in ${projectRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
112
146
|
prevDir = projectRoot;
|
|
113
|
-
projectRoot =
|
|
147
|
+
projectRoot = node_path.dirname(projectRoot);
|
|
114
148
|
depth++;
|
|
115
149
|
}
|
|
116
150
|
}
|
|
@@ -304,7 +338,7 @@ class Telemetry {
|
|
|
304
338
|
requestTimeout: 3000
|
|
305
339
|
};
|
|
306
340
|
if (this.debug) this.logDebug('Initializing PostHog client with config:', JSON.stringify(clientConfig));
|
|
307
|
-
this.client = new
|
|
341
|
+
this.client = new PostHog(this.apiKey, clientConfig);
|
|
308
342
|
const initTime = Date.now() - startTime;
|
|
309
343
|
if (this.debug) this.logDebug('PostHog client initialized in', initTime, 'ms');
|
|
310
344
|
} catch (error) {
|
|
@@ -319,7 +353,7 @@ class Telemetry {
|
|
|
319
353
|
if (this.debug) this.logDebug('Telemetry disabled due to initialization error:', JSON.stringify(errorDetails, null, 2));
|
|
320
354
|
try {
|
|
321
355
|
if (this.debug) this.logDebug('Attempting fallback PostHog initialization');
|
|
322
|
-
this.client = new
|
|
356
|
+
this.client = new PostHog(this.apiKey);
|
|
323
357
|
this.disabled = false;
|
|
324
358
|
if (this.debug) this.logDebug('PostHog client initialized using fallback method');
|
|
325
359
|
} catch (fallbackError) {
|
|
@@ -329,7 +363,7 @@ class Telemetry {
|
|
|
329
363
|
}
|
|
330
364
|
}
|
|
331
365
|
generateAnonymousId() {
|
|
332
|
-
const machineId =
|
|
366
|
+
const machineId = node_crypto.createHash('sha256').update(node_os.hostname() + node_os.platform() + node_os.arch() + node_os.totalmem()).digest('hex');
|
|
333
367
|
return machineId;
|
|
334
368
|
}
|
|
335
369
|
flushSync() {
|
|
@@ -381,11 +415,11 @@ async function addAndInstallDependenciesViaPM(projectRoot, dependencies, package
|
|
|
381
415
|
default:
|
|
382
416
|
throw new Error(`Unsupported package manager for dependency addition: ${packageManager}`);
|
|
383
417
|
}
|
|
384
|
-
const child =
|
|
418
|
+
const child = spawn(command, args, {
|
|
385
419
|
cwd: projectRoot,
|
|
386
420
|
stdio: 'inherit'
|
|
387
421
|
});
|
|
388
|
-
await
|
|
422
|
+
await once(child, 'exit');
|
|
389
423
|
}
|
|
390
424
|
function getManualInstallCommand(dependencies, packageManager) {
|
|
391
425
|
switch(packageManager){
|
|
@@ -403,15 +437,15 @@ function getManualInstallCommand(dependencies, packageManager) {
|
|
|
403
437
|
}
|
|
404
438
|
async function installDependencies({ context, dependenciesToAdd, handleCancel, autoInstall = false }) {
|
|
405
439
|
const { telemetry, logger } = context;
|
|
406
|
-
const s =
|
|
440
|
+
const s = prompts_spinner();
|
|
407
441
|
if (0 === dependenciesToAdd.length) return {
|
|
408
442
|
installDepsConfirmed: false,
|
|
409
443
|
ranInstall: false
|
|
410
444
|
};
|
|
411
|
-
const depsString = dependenciesToAdd.map((d)=>
|
|
445
|
+
const depsString = dependenciesToAdd.map((d)=>picocolors.cyan(d)).join(', ');
|
|
412
446
|
if (!autoInstall) {
|
|
413
|
-
const addDepsSelection = await
|
|
414
|
-
message: `Add required dependencies using ${
|
|
447
|
+
const addDepsSelection = await prompts_confirm({
|
|
448
|
+
message: `Add required dependencies using ${picocolors.cyan(context.packageManager.name)}? (${depsString})`,
|
|
415
449
|
initialValue: true
|
|
416
450
|
});
|
|
417
451
|
if (handleCancel?.(addDepsSelection)) return {
|
|
@@ -423,10 +457,10 @@ async function installDependencies({ context, dependenciesToAdd, handleCancel, a
|
|
|
423
457
|
ranInstall: false
|
|
424
458
|
};
|
|
425
459
|
}
|
|
426
|
-
s.start(`Running ${
|
|
460
|
+
s.start(`Running ${picocolors.cyan(context.packageManager.name)} to add and install dependencies... (this might take a moment)`);
|
|
427
461
|
try {
|
|
428
462
|
await addAndInstallDependenciesViaPM(context.projectRoot, dependenciesToAdd, context.packageManager.name);
|
|
429
|
-
s.stop(`✅ Dependencies installed: ${dependenciesToAdd.map((d)=>
|
|
463
|
+
s.stop(`✅ Dependencies installed: ${dependenciesToAdd.map((d)=>picocolors.cyan(d)).join(', ')}`);
|
|
430
464
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_DEPENDENCIES_INSTALLED, {
|
|
431
465
|
success: true,
|
|
432
466
|
dependencies: dependenciesToAdd.join(','),
|
|
@@ -437,7 +471,7 @@ async function installDependencies({ context, dependenciesToAdd, handleCancel, a
|
|
|
437
471
|
ranInstall: true
|
|
438
472
|
};
|
|
439
473
|
} catch (installError) {
|
|
440
|
-
s.stop(
|
|
474
|
+
s.stop(picocolors.yellow('⚠️ Dependency installation failed.'));
|
|
441
475
|
logger.error('Installation Error:', installError);
|
|
442
476
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_DEPENDENCIES_INSTALLED, {
|
|
443
477
|
success: false,
|
|
@@ -446,7 +480,7 @@ async function installDependencies({ context, dependenciesToAdd, handleCancel, a
|
|
|
446
480
|
packageManager: context.packageManager.name
|
|
447
481
|
});
|
|
448
482
|
const pmCommand = getManualInstallCommand(dependenciesToAdd, context.packageManager.name);
|
|
449
|
-
logger.info(`Please try running '${pmCommand}' manually in ${
|
|
483
|
+
logger.info(`Please try running '${pmCommand}' manually in ${picocolors.cyan(node_path.relative(context.cwd, context.projectRoot))}.`);
|
|
450
484
|
return {
|
|
451
485
|
installDepsConfirmed: true,
|
|
452
486
|
ranInstall: false
|
|
@@ -468,17 +502,17 @@ const formatLogMessage = (logLevel, message, args = [])=>{
|
|
|
468
502
|
const formattedArgs = formatArgs(args);
|
|
469
503
|
switch(logLevel){
|
|
470
504
|
case 'error':
|
|
471
|
-
return `${
|
|
505
|
+
return `${picocolors.bgRed(picocolors.black(' error '))} ${messageStr}${formattedArgs}`;
|
|
472
506
|
case 'warn':
|
|
473
|
-
return `${
|
|
507
|
+
return `${picocolors.bgYellow(picocolors.black(' warning '))} ${messageStr}${formattedArgs}`;
|
|
474
508
|
case 'info':
|
|
475
|
-
return `${
|
|
509
|
+
return `${picocolors.bgGreen(picocolors.black(' info '))} ${messageStr}${formattedArgs}`;
|
|
476
510
|
case 'debug':
|
|
477
|
-
return `${
|
|
511
|
+
return `${picocolors.bgBlack(picocolors.white(' debug '))} ${messageStr}${formattedArgs}`;
|
|
478
512
|
case 'success':
|
|
479
|
-
return `${
|
|
513
|
+
return `${picocolors.bgGreen(picocolors.white(' success '))} ${messageStr}${formattedArgs}`;
|
|
480
514
|
case 'failed':
|
|
481
|
-
return `${
|
|
515
|
+
return `${picocolors.bgRed(picocolors.white(' failed '))} ${messageStr}${formattedArgs}`;
|
|
482
516
|
default:
|
|
483
517
|
{
|
|
484
518
|
const levelStr = logLevel;
|
|
@@ -490,25 +524,25 @@ const logMessage = (logLevel, message, ...args)=>{
|
|
|
490
524
|
const formattedMessage = formatLogMessage(logLevel, message, args);
|
|
491
525
|
switch(logLevel){
|
|
492
526
|
case 'error':
|
|
493
|
-
|
|
527
|
+
prompts_log.error(formattedMessage);
|
|
494
528
|
break;
|
|
495
529
|
case 'warn':
|
|
496
|
-
|
|
530
|
+
prompts_log.warn(formattedMessage);
|
|
497
531
|
break;
|
|
498
532
|
case 'info':
|
|
499
533
|
case 'debug':
|
|
500
|
-
|
|
534
|
+
prompts_log.info(formattedMessage);
|
|
501
535
|
break;
|
|
502
536
|
case 'success':
|
|
503
537
|
case 'failed':
|
|
504
|
-
|
|
538
|
+
outro(formattedMessage);
|
|
505
539
|
break;
|
|
506
540
|
default:
|
|
507
|
-
|
|
541
|
+
prompts_log.message(formattedMessage);
|
|
508
542
|
}
|
|
509
543
|
};
|
|
510
544
|
const createCliLogger = (level)=>{
|
|
511
|
-
const baseLogger =
|
|
545
|
+
const baseLogger = createLogger({
|
|
512
546
|
level,
|
|
513
547
|
appName: 'c15t',
|
|
514
548
|
log: (logLevel, message, ...args)=>{
|
|
@@ -517,12 +551,12 @@ const createCliLogger = (level)=>{
|
|
|
517
551
|
});
|
|
518
552
|
const extendedLogger = baseLogger;
|
|
519
553
|
extendedLogger.message = (message)=>{
|
|
520
|
-
|
|
554
|
+
prompts_log.message(message);
|
|
521
555
|
};
|
|
522
556
|
extendedLogger.note = (message, ...args)=>{
|
|
523
557
|
const messageStr = 'string' == typeof message ? message : String(message);
|
|
524
558
|
const title = args.length > 0 && 'string' == typeof args[0] ? args[0] : void 0;
|
|
525
|
-
|
|
559
|
+
note(messageStr, title, {
|
|
526
560
|
format: (line)=>line
|
|
527
561
|
});
|
|
528
562
|
};
|
|
@@ -534,7 +568,7 @@ const createCliLogger = (level)=>{
|
|
|
534
568
|
process.exit(0);
|
|
535
569
|
};
|
|
536
570
|
extendedLogger.outro = (message)=>{
|
|
537
|
-
|
|
571
|
+
outro(message);
|
|
538
572
|
};
|
|
539
573
|
return extendedLogger;
|
|
540
574
|
};
|
|
@@ -784,15 +818,15 @@ function findAppLayoutFile(project, projectRoot) {
|
|
|
784
818
|
}
|
|
785
819
|
}
|
|
786
820
|
function getAppDirectory(layoutFilePath) {
|
|
787
|
-
const normalizedPath =
|
|
788
|
-
const srcAppSegment =
|
|
789
|
-
if (normalizedPath.includes(srcAppSegment)) return
|
|
821
|
+
const normalizedPath = node_path.normalize(layoutFilePath);
|
|
822
|
+
const srcAppSegment = node_path.join('src', 'app');
|
|
823
|
+
if (normalizedPath.includes(srcAppSegment)) return node_path.join('src', 'app');
|
|
790
824
|
return 'app';
|
|
791
825
|
}
|
|
792
826
|
function computeRelativeModuleSpecifier(fromFilePath, toFilePath) {
|
|
793
|
-
const fromDir =
|
|
794
|
-
let relativePath =
|
|
795
|
-
relativePath = relativePath.split(
|
|
827
|
+
const fromDir = node_path.dirname(fromFilePath);
|
|
828
|
+
let relativePath = node_path.relative(fromDir, toFilePath);
|
|
829
|
+
relativePath = relativePath.split(node_path.sep).join('/');
|
|
796
830
|
relativePath = relativePath.replace(/\.(tsx?|jsx?)$/, '');
|
|
797
831
|
if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
|
|
798
832
|
return relativePath;
|
|
@@ -840,14 +874,14 @@ function wrapAppJsxContent(originalJsx) {
|
|
|
840
874
|
return consentWrapper(originalJsx);
|
|
841
875
|
}
|
|
842
876
|
async function createConsentManagerComponents(projectRoot, appDir, optionsText) {
|
|
843
|
-
const appDirPath =
|
|
877
|
+
const appDirPath = node_path.join(projectRoot, appDir);
|
|
844
878
|
const consentManagerContent = generateConsentManagerTemplate(optionsText);
|
|
845
879
|
const consentManagerClientContent = generateConsentManagerClientTemplate();
|
|
846
|
-
const consentManagerPath =
|
|
847
|
-
const consentManagerClientPath =
|
|
880
|
+
const consentManagerPath = node_path.join(appDirPath, 'consent-manager.tsx');
|
|
881
|
+
const consentManagerClientPath = node_path.join(appDirPath, 'consent-manager.client.tsx');
|
|
848
882
|
await Promise.all([
|
|
849
|
-
|
|
850
|
-
|
|
883
|
+
promises.writeFile(consentManagerPath, consentManagerContent, 'utf-8'),
|
|
884
|
+
promises.writeFile(consentManagerClientPath, consentManagerClientContent, 'utf-8')
|
|
851
885
|
]);
|
|
852
886
|
return {
|
|
853
887
|
consentManager: consentManagerPath,
|
|
@@ -855,7 +889,7 @@ async function createConsentManagerComponents(projectRoot, appDir, optionsText)
|
|
|
855
889
|
};
|
|
856
890
|
}
|
|
857
891
|
async function updateAppLayout({ projectRoot, mode, backendURL, useEnvFile, proxyNextjs }) {
|
|
858
|
-
const project = new
|
|
892
|
+
const project = new Project();
|
|
859
893
|
const layoutFile = findAppLayoutFile(project, projectRoot);
|
|
860
894
|
if (!layoutFile) return {
|
|
861
895
|
updated: false,
|
|
@@ -874,7 +908,7 @@ async function updateAppLayout({ projectRoot, mode, backendURL, useEnvFile, prox
|
|
|
874
908
|
const optionsText = generateOptionsText(mode, backendURL, useEnvFile, proxyNextjs);
|
|
875
909
|
const componentFiles = await createConsentManagerComponents(projectRoot, appDir, optionsText);
|
|
876
910
|
addConsentManagerImport(layoutFile, componentFiles.consentManager);
|
|
877
|
-
const returnStatement = layoutFile.getDescendantsOfKind(
|
|
911
|
+
const returnStatement = layoutFile.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
|
|
878
912
|
if (!returnStatement) return {
|
|
879
913
|
updated: false,
|
|
880
914
|
filePath: layoutFilePath,
|
|
@@ -975,15 +1009,15 @@ function findPagesAppFile(project, projectRoot) {
|
|
|
975
1009
|
}
|
|
976
1010
|
}
|
|
977
1011
|
function getPagesDirectory(appFilePath) {
|
|
978
|
-
const normalizedPath =
|
|
979
|
-
const srcPagesSegment =
|
|
980
|
-
if (normalizedPath.includes(srcPagesSegment)) return
|
|
1012
|
+
const normalizedPath = node_path.normalize(appFilePath);
|
|
1013
|
+
const srcPagesSegment = node_path.join('src', 'pages');
|
|
1014
|
+
if (normalizedPath.includes(srcPagesSegment)) return node_path.join('src', 'pages');
|
|
981
1015
|
return 'pages';
|
|
982
1016
|
}
|
|
983
1017
|
function layout_computeRelativeModuleSpecifier(fromFilePath, toFilePath) {
|
|
984
|
-
const fromDir =
|
|
985
|
-
let relativePath =
|
|
986
|
-
relativePath = relativePath.split(
|
|
1018
|
+
const fromDir = node_path.dirname(fromFilePath);
|
|
1019
|
+
let relativePath = node_path.relative(fromDir, toFilePath);
|
|
1020
|
+
relativePath = relativePath.split(node_path.sep).join('/');
|
|
987
1021
|
relativePath = relativePath.replace(/\.(tsx?|jsx?)$/, '');
|
|
988
1022
|
if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
|
|
989
1023
|
return relativePath;
|
|
@@ -1016,14 +1050,14 @@ function wrapPagesJsxContent(originalJsx) {
|
|
|
1016
1050
|
}
|
|
1017
1051
|
async function createConsentManagerComponent(projectRoot, pagesDir, optionsText) {
|
|
1018
1052
|
let componentsDir;
|
|
1019
|
-
componentsDir = pagesDir.includes('src') ?
|
|
1020
|
-
const componentsDirPath =
|
|
1021
|
-
await
|
|
1053
|
+
componentsDir = pagesDir.includes('src') ? node_path.join('src', 'components') : 'components';
|
|
1054
|
+
const componentsDirPath = node_path.join(projectRoot, componentsDir);
|
|
1055
|
+
await promises.mkdir(componentsDirPath, {
|
|
1022
1056
|
recursive: true
|
|
1023
1057
|
});
|
|
1024
1058
|
const consentManagerContent = components_generateConsentManagerTemplate(optionsText);
|
|
1025
|
-
const consentManagerPath =
|
|
1026
|
-
await
|
|
1059
|
+
const consentManagerPath = node_path.join(componentsDirPath, 'consent-manager.tsx');
|
|
1060
|
+
await promises.writeFile(consentManagerPath, consentManagerContent, 'utf-8');
|
|
1027
1061
|
return {
|
|
1028
1062
|
consentManager: consentManagerPath
|
|
1029
1063
|
};
|
|
@@ -1061,7 +1095,7 @@ function updateAppComponentTyping(appFile) {
|
|
|
1061
1095
|
}
|
|
1062
1096
|
}
|
|
1063
1097
|
async function updatePagesLayout({ projectRoot, mode, backendURL, useEnvFile, proxyNextjs }) {
|
|
1064
|
-
const project = new
|
|
1098
|
+
const project = new Project();
|
|
1065
1099
|
const appFile = findPagesAppFile(project, projectRoot);
|
|
1066
1100
|
if (!appFile) return {
|
|
1067
1101
|
updated: false,
|
|
@@ -1085,7 +1119,7 @@ async function updatePagesLayout({ projectRoot, mode, backendURL, useEnvFile, pr
|
|
|
1085
1119
|
layout_addConsentManagerImport(appFile, componentFiles.consentManager);
|
|
1086
1120
|
updateAppComponentTyping(appFile);
|
|
1087
1121
|
addServerSideDataComment(appFile, backendURL, useEnvFile, proxyNextjs);
|
|
1088
|
-
const returnStatement = appFile.getDescendantsOfKind(
|
|
1122
|
+
const returnStatement = appFile.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
|
|
1089
1123
|
if (!returnStatement) return {
|
|
1090
1124
|
updated: false,
|
|
1091
1125
|
filePath: appFilePath,
|
|
@@ -1109,7 +1143,7 @@ async function updatePagesLayout({ projectRoot, mode, backendURL, useEnvFile, pr
|
|
|
1109
1143
|
};
|
|
1110
1144
|
}
|
|
1111
1145
|
function detectNextJsStructure(projectRoot) {
|
|
1112
|
-
const project = new
|
|
1146
|
+
const project = new Project();
|
|
1113
1147
|
const appLayoutPatterns = [
|
|
1114
1148
|
'app/layout.tsx',
|
|
1115
1149
|
'src/app/layout.tsx',
|
|
@@ -1194,9 +1228,9 @@ export function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
1194
1228
|
`;
|
|
1195
1229
|
}
|
|
1196
1230
|
function templates_layout_computeRelativeModuleSpecifier(fromFilePath, toFilePath) {
|
|
1197
|
-
const fromDir =
|
|
1198
|
-
let relativePath =
|
|
1199
|
-
relativePath = relativePath.split(
|
|
1231
|
+
const fromDir = node_path.dirname(fromFilePath);
|
|
1232
|
+
let relativePath = node_path.relative(fromDir, toFilePath);
|
|
1233
|
+
relativePath = relativePath.split(node_path.sep).join('/');
|
|
1200
1234
|
relativePath = relativePath.replace(/\.(tsx?|jsx?)$/, '');
|
|
1201
1235
|
if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`;
|
|
1202
1236
|
return relativePath;
|
|
@@ -1217,8 +1251,8 @@ function templates_layout_addConsentManagerImport(layoutFile, consentManagerFile
|
|
|
1217
1251
|
});
|
|
1218
1252
|
}
|
|
1219
1253
|
function getSourceDirectory(layoutFilePath) {
|
|
1220
|
-
const normalizedPath =
|
|
1221
|
-
const segments = normalizedPath.split(
|
|
1254
|
+
const normalizedPath = node_path.normalize(layoutFilePath);
|
|
1255
|
+
const segments = normalizedPath.split(node_path.sep);
|
|
1222
1256
|
if (segments.includes('src')) return 'src';
|
|
1223
1257
|
return '';
|
|
1224
1258
|
}
|
|
@@ -1226,13 +1260,13 @@ function updateGenericReactJsx(layoutFile) {
|
|
|
1226
1260
|
const functionDeclarations = layoutFile.getFunctions();
|
|
1227
1261
|
const variableDeclarations = layoutFile.getVariableDeclarations();
|
|
1228
1262
|
for (const func of functionDeclarations){
|
|
1229
|
-
const returnStatement = func.getDescendantsOfKind(
|
|
1263
|
+
const returnStatement = func.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
|
|
1230
1264
|
if (returnStatement) return wrapReturnStatementWithConsentManager(returnStatement);
|
|
1231
1265
|
}
|
|
1232
1266
|
for (const varDecl of variableDeclarations){
|
|
1233
1267
|
const initializer = varDecl.getInitializer();
|
|
1234
1268
|
if (initializer) {
|
|
1235
|
-
const returnStatement = initializer.getDescendantsOfKind(
|
|
1269
|
+
const returnStatement = initializer.getDescendantsOfKind(SyntaxKind.ReturnStatement)[0];
|
|
1236
1270
|
if (returnStatement) return wrapReturnStatementWithConsentManager(returnStatement);
|
|
1237
1271
|
}
|
|
1238
1272
|
}
|
|
@@ -1251,10 +1285,10 @@ function wrapReturnStatementWithConsentManager(returnStatement) {
|
|
|
1251
1285
|
return true;
|
|
1252
1286
|
}
|
|
1253
1287
|
async function layout_createConsentManagerComponent(projectRoot, sourceDir, optionsText) {
|
|
1254
|
-
const targetDir = sourceDir ?
|
|
1288
|
+
const targetDir = sourceDir ? node_path.join(projectRoot, sourceDir) : projectRoot;
|
|
1255
1289
|
const consentManagerContent = react_components_generateConsentManagerTemplate(optionsText);
|
|
1256
|
-
const consentManagerPath =
|
|
1257
|
-
await
|
|
1290
|
+
const consentManagerPath = node_path.join(targetDir, 'consent-manager.tsx');
|
|
1291
|
+
await promises.writeFile(consentManagerPath, consentManagerContent, 'utf-8');
|
|
1258
1292
|
return {
|
|
1259
1293
|
consentManager: consentManagerPath
|
|
1260
1294
|
};
|
|
@@ -1274,7 +1308,7 @@ async function updateGenericReactLayout({ projectRoot, mode, backendURL, useEnvF
|
|
|
1274
1308
|
'src/app/app.jsx',
|
|
1275
1309
|
'src/app/App.jsx'
|
|
1276
1310
|
];
|
|
1277
|
-
const project = new
|
|
1311
|
+
const project = new Project();
|
|
1278
1312
|
let layoutFile;
|
|
1279
1313
|
for (const pattern of layoutPatterns)try {
|
|
1280
1314
|
const files = project.addSourceFilesAtPaths(`${projectRoot}/${pattern}`);
|
|
@@ -1333,7 +1367,7 @@ async function updateReactLayout(options) {
|
|
|
1333
1367
|
return updateGenericReactLayout(options);
|
|
1334
1368
|
}
|
|
1335
1369
|
async function updateNextConfig({ projectRoot, backendURL, useEnvFile }) {
|
|
1336
|
-
const project = new
|
|
1370
|
+
const project = new Project();
|
|
1337
1371
|
const configFile = findNextConfigFile(project, projectRoot);
|
|
1338
1372
|
if (!configFile) {
|
|
1339
1373
|
const newConfigPath = `${projectRoot}/next.config.ts`;
|
|
@@ -1421,8 +1455,8 @@ function updateExistingConfig(configFile, backendURL, useEnvFile) {
|
|
|
1421
1455
|
const configObject = findConfigObject(configFile);
|
|
1422
1456
|
if (!configObject) return false;
|
|
1423
1457
|
const rewritesProperty = configObject.getProperty('rewrites');
|
|
1424
|
-
if (rewritesProperty &&
|
|
1425
|
-
if (rewritesProperty &&
|
|
1458
|
+
if (rewritesProperty && Node.isMethodDeclaration(rewritesProperty)) return updateExistingRewrites(rewritesProperty, destination, isTemplateLiteral);
|
|
1459
|
+
if (rewritesProperty && Node.isPropertyAssignment(rewritesProperty)) return updatePropertyAssignmentRewrites(rewritesProperty, destination, isTemplateLiteral);
|
|
1426
1460
|
return addNewRewritesMethod(configObject, destination, isTemplateLiteral);
|
|
1427
1461
|
}
|
|
1428
1462
|
function findConfigObject(configFile) {
|
|
@@ -1432,29 +1466,29 @@ function findConfigFromExportDefault(configFile) {
|
|
|
1432
1466
|
const exportDefault = configFile.getDefaultExportSymbol();
|
|
1433
1467
|
if (!exportDefault) return;
|
|
1434
1468
|
const declarations = exportDefault.getDeclarations();
|
|
1435
|
-
for (const declaration of declarations)if (
|
|
1469
|
+
for (const declaration of declarations)if (Node.isExportAssignment(declaration)) {
|
|
1436
1470
|
const result = findConfigFromExpression(declaration.getExpression(), configFile);
|
|
1437
1471
|
if (result) return result;
|
|
1438
1472
|
}
|
|
1439
1473
|
}
|
|
1440
1474
|
function findConfigFromExpression(expression, configFile) {
|
|
1441
|
-
if (
|
|
1442
|
-
if (
|
|
1443
|
-
if (
|
|
1475
|
+
if (Node.isCallExpression(expression)) return findConfigFromCallExpression(expression, configFile);
|
|
1476
|
+
if (Node.isObjectLiteralExpression(expression)) return expression;
|
|
1477
|
+
if (Node.isIdentifier(expression)) return findConfigFromIdentifier(expression.getText(), configFile);
|
|
1444
1478
|
}
|
|
1445
1479
|
function findConfigFromCallExpression(expression, configFile) {
|
|
1446
1480
|
const args = expression.getArguments();
|
|
1447
1481
|
if (0 === args.length) return;
|
|
1448
1482
|
const firstArg = args[0];
|
|
1449
|
-
if (
|
|
1483
|
+
if (Node.isCallExpression(firstArg)) {
|
|
1450
1484
|
const innerArgs = firstArg.getArguments();
|
|
1451
|
-
if (innerArgs.length > 0 &&
|
|
1485
|
+
if (innerArgs.length > 0 && Node.isIdentifier(innerArgs[0])) return findConfigFromIdentifier(innerArgs[0].getText(), configFile);
|
|
1452
1486
|
}
|
|
1453
1487
|
}
|
|
1454
1488
|
function findConfigFromIdentifier(identifierText, configFile) {
|
|
1455
1489
|
const configVar = configFile.getVariableDeclaration(identifierText);
|
|
1456
1490
|
const initializer = configVar?.getInitializer();
|
|
1457
|
-
return initializer &&
|
|
1491
|
+
return initializer && Node.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1458
1492
|
}
|
|
1459
1493
|
function findConfigFromVariableDeclarations(configFile) {
|
|
1460
1494
|
const variableDeclarations = configFile.getVariableDeclarations();
|
|
@@ -1462,17 +1496,17 @@ function findConfigFromVariableDeclarations(configFile) {
|
|
|
1462
1496
|
const typeNode = varDecl.getTypeNode();
|
|
1463
1497
|
if (typeNode?.getText().includes('NextConfig')) {
|
|
1464
1498
|
const initializer = varDecl.getInitializer();
|
|
1465
|
-
if (
|
|
1499
|
+
if (Node.isObjectLiteralExpression(initializer)) return initializer;
|
|
1466
1500
|
}
|
|
1467
1501
|
}
|
|
1468
1502
|
}
|
|
1469
1503
|
function updateExistingRewrites(rewritesMethod, destination, isTemplateLiteral) {
|
|
1470
1504
|
const body = rewritesMethod.getBody();
|
|
1471
|
-
if (!
|
|
1472
|
-
const returnStatement = body.getStatements().find((stmt)=>
|
|
1473
|
-
if (!returnStatement || !
|
|
1505
|
+
if (!Node.isBlock(body)) return false;
|
|
1506
|
+
const returnStatement = body.getStatements().find((stmt)=>Node.isReturnStatement(stmt));
|
|
1507
|
+
if (!returnStatement || !Node.isReturnStatement(returnStatement)) return false;
|
|
1474
1508
|
const expression = returnStatement.getExpression();
|
|
1475
|
-
if (!expression || !
|
|
1509
|
+
if (!expression || !Node.isArrayLiteralExpression(expression)) return false;
|
|
1476
1510
|
const newRewrite = createRewriteRule(destination, isTemplateLiteral);
|
|
1477
1511
|
const elements = expression.getElements();
|
|
1478
1512
|
if (elements.length > 0) expression.insertElement(0, newRewrite);
|
|
@@ -1481,7 +1515,7 @@ function updateExistingRewrites(rewritesMethod, destination, isTemplateLiteral)
|
|
|
1481
1515
|
}
|
|
1482
1516
|
function updatePropertyAssignmentRewrites(rewritesProperty, destination, isTemplateLiteral) {
|
|
1483
1517
|
const initializer = rewritesProperty.getInitializer();
|
|
1484
|
-
if (
|
|
1518
|
+
if (Node.isArrayLiteralExpression(initializer)) {
|
|
1485
1519
|
const newRewrite = createRewriteRule(destination, isTemplateLiteral);
|
|
1486
1520
|
initializer.insertElement(0, newRewrite);
|
|
1487
1521
|
return true;
|
|
@@ -1520,17 +1554,17 @@ async function handleReactLayout(options) {
|
|
|
1520
1554
|
if (layoutResult.updated) {
|
|
1521
1555
|
const typedResult = layoutResult;
|
|
1522
1556
|
if (typedResult.componentFiles) {
|
|
1523
|
-
const relativeConsentManager =
|
|
1524
|
-
const relativeLayout =
|
|
1557
|
+
const relativeConsentManager = node_path.relative(cwd, typedResult.componentFiles.consentManager);
|
|
1558
|
+
const relativeLayout = node_path.relative(cwd, layoutResult.filePath || '');
|
|
1525
1559
|
if (typedResult.componentFiles.consentManagerClient) {
|
|
1526
|
-
const relativeConsentManagerClient =
|
|
1560
|
+
const relativeConsentManagerClient = node_path.relative(cwd, typedResult.componentFiles.consentManagerClient);
|
|
1527
1561
|
return {
|
|
1528
|
-
message: `Layout setup complete!\n ${
|
|
1562
|
+
message: `Layout setup complete!\n ${picocolors.green('✓')} Created: ${picocolors.cyan(relativeConsentManager)}\n ${picocolors.green('✓')} Created: ${picocolors.cyan(relativeConsentManagerClient)}\n ${picocolors.green('✓')} Updated: ${picocolors.cyan(relativeLayout)}`,
|
|
1529
1563
|
type: 'info'
|
|
1530
1564
|
};
|
|
1531
1565
|
}
|
|
1532
1566
|
return {
|
|
1533
|
-
message: `Layout setup complete!\n ${
|
|
1567
|
+
message: `Layout setup complete!\n ${picocolors.green('✓')} Created: ${picocolors.cyan(relativeConsentManager)}\n ${picocolors.green('✓')} Updated: ${picocolors.cyan(relativeLayout)}`,
|
|
1534
1568
|
type: 'info'
|
|
1535
1569
|
};
|
|
1536
1570
|
}
|
|
@@ -1587,26 +1621,26 @@ async function handleNextConfig(options) {
|
|
|
1587
1621
|
}
|
|
1588
1622
|
async function handleEnvFiles(options) {
|
|
1589
1623
|
const { projectRoot, backendURL, pkg, spinner, cwd } = options;
|
|
1590
|
-
const envPath =
|
|
1591
|
-
const envExamplePath =
|
|
1624
|
+
const envPath = node_path.join(projectRoot, '.env.local');
|
|
1625
|
+
const envExamplePath = node_path.join(projectRoot, '.env.example');
|
|
1592
1626
|
spinner.start('Creating/updating environment files...');
|
|
1593
1627
|
const envContent = generateEnvFileContent(backendURL, pkg);
|
|
1594
1628
|
const envExampleContent = generateEnvExampleContent(pkg);
|
|
1595
1629
|
const envVarName = getEnvVarName(pkg);
|
|
1596
1630
|
try {
|
|
1597
1631
|
const [envExists, envExampleExists] = await Promise.all([
|
|
1598
|
-
|
|
1599
|
-
|
|
1632
|
+
promises.access(envPath).then(()=>true).catch(()=>false),
|
|
1633
|
+
promises.access(envExamplePath).then(()=>true).catch(()=>false)
|
|
1600
1634
|
]);
|
|
1601
1635
|
if (envExists) {
|
|
1602
|
-
const currentEnvContent = await
|
|
1603
|
-
if (!currentEnvContent.includes(envVarName)) await
|
|
1604
|
-
} else await
|
|
1636
|
+
const currentEnvContent = await promises.readFile(envPath, 'utf-8');
|
|
1637
|
+
if (!currentEnvContent.includes(envVarName)) await promises.appendFile(envPath, envContent);
|
|
1638
|
+
} else await promises.writeFile(envPath, envContent);
|
|
1605
1639
|
if (envExampleExists) {
|
|
1606
|
-
const currentExampleContent = await
|
|
1607
|
-
if (!currentExampleContent.includes(envVarName)) await
|
|
1608
|
-
} else await
|
|
1609
|
-
spinner.stop(formatLogMessage('info', `Environment files added/updated successfully: ${
|
|
1640
|
+
const currentExampleContent = await promises.readFile(envExamplePath, 'utf-8');
|
|
1641
|
+
if (!currentExampleContent.includes(envVarName)) await promises.appendFile(envExamplePath, envExampleContent);
|
|
1642
|
+
} else await promises.writeFile(envExamplePath, envExampleContent);
|
|
1643
|
+
spinner.stop(formatLogMessage('info', `Environment files added/updated successfully: ${picocolors.cyan(node_path.relative(cwd, envPath))} and ${picocolors.cyan(node_path.relative(cwd, envExamplePath))}`));
|
|
1610
1644
|
} catch (error) {
|
|
1611
1645
|
spinner.stop(formatLogMessage('error', `Error processing environment files: ${error instanceof Error ? error.message : String(error)}`));
|
|
1612
1646
|
throw error;
|
|
@@ -1645,7 +1679,7 @@ async function generateFiles({ context, mode, spinner, useEnvFile, proxyNextjs,
|
|
|
1645
1679
|
if ('c15t' === pkg) {
|
|
1646
1680
|
spinner.start('Generating client configuration file...');
|
|
1647
1681
|
result.configContent = generateClientConfigContent(mode, backendURL, useEnvFile);
|
|
1648
|
-
result.configPath =
|
|
1682
|
+
result.configPath = node_path.join(projectRoot, 'c15t.config.ts');
|
|
1649
1683
|
spinner.stop(formatLogMessage('info', `Client configuration file generated: ${result.configContent}`));
|
|
1650
1684
|
}
|
|
1651
1685
|
if (useEnvFile && backendURL) await handleEnvFiles({
|
|
@@ -1659,7 +1693,7 @@ async function generateFiles({ context, mode, spinner, useEnvFile, proxyNextjs,
|
|
|
1659
1693
|
}
|
|
1660
1694
|
async function getScriptsToAdd({ context, handleCancel }) {
|
|
1661
1695
|
context.logger.info("@c15t/scripts has various prebuilt scripts for you to use. Learn more: https://c15t.com/docs/integrations");
|
|
1662
|
-
const addScriptsSelection = await
|
|
1696
|
+
const addScriptsSelection = await prompts_confirm({
|
|
1663
1697
|
message: "Do you want to add @c15t/scripts to your project?",
|
|
1664
1698
|
initialValue: true
|
|
1665
1699
|
});
|
|
@@ -1677,7 +1711,7 @@ async function getSharedFrontendOptions({ backendURL, context, handleCancel }) {
|
|
|
1677
1711
|
useEnvFile: void 0,
|
|
1678
1712
|
dependenciesToAdd: []
|
|
1679
1713
|
};
|
|
1680
|
-
const useEnvFileSelection = await
|
|
1714
|
+
const useEnvFileSelection = await prompts_confirm({
|
|
1681
1715
|
message: 'Store the backendURL in a .env file? (Recommended, URL is public)',
|
|
1682
1716
|
initialValue: true
|
|
1683
1717
|
});
|
|
@@ -1688,7 +1722,7 @@ async function getSharedFrontendOptions({ backendURL, context, handleCancel }) {
|
|
|
1688
1722
|
useEnvFile = useEnvFileSelection;
|
|
1689
1723
|
if ('@c15t/nextjs' === context.framework.pkg) {
|
|
1690
1724
|
context.logger.info('Learn more about Next.js Rewrites: https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites');
|
|
1691
|
-
const proxyNextjsSelection = await
|
|
1725
|
+
const proxyNextjsSelection = await prompts_confirm({
|
|
1692
1726
|
message: 'Proxy requests to your instance with Next.js Rewrites? (Recommended)',
|
|
1693
1727
|
initialValue: true
|
|
1694
1728
|
});
|
|
@@ -1714,7 +1748,7 @@ async function getSharedFrontendOptions({ backendURL, context, handleCancel }) {
|
|
|
1714
1748
|
}
|
|
1715
1749
|
async function handleAccountCreation(context, handleCancel) {
|
|
1716
1750
|
const { logger } = context;
|
|
1717
|
-
const needsAccount = await
|
|
1751
|
+
const needsAccount = await prompts_confirm({
|
|
1718
1752
|
message: 'Do you need to create a consent.io account?',
|
|
1719
1753
|
initialValue: true
|
|
1720
1754
|
});
|
|
@@ -1723,8 +1757,8 @@ async function handleAccountCreation(context, handleCancel) {
|
|
|
1723
1757
|
stage: 'c15t_account_creation'
|
|
1724
1758
|
});
|
|
1725
1759
|
if (!needsAccount) return;
|
|
1726
|
-
|
|
1727
|
-
const shouldOpen = await
|
|
1760
|
+
note(`We'll open your browser to create a consent.io account and set up your instance.\nFollow these steps:\n1. Sign up for a consent.io account\n2. Create a new instance in the dashboard\n3. Configure your trusted origins (domains that can connect)\n4. Copy the provided backendURL (e.g., https://your-instance.c15t.dev)`, 'consent.io Setup');
|
|
1761
|
+
const shouldOpen = await prompts_confirm({
|
|
1728
1762
|
message: 'Open browser to sign up for consent.io?',
|
|
1729
1763
|
initialValue: true
|
|
1730
1764
|
});
|
|
@@ -1733,8 +1767,8 @@ async function handleAccountCreation(context, handleCancel) {
|
|
|
1733
1767
|
stage: 'c15t_browser_open'
|
|
1734
1768
|
});
|
|
1735
1769
|
if (shouldOpen) try {
|
|
1736
|
-
await (
|
|
1737
|
-
const enterPressed = await
|
|
1770
|
+
await open_0('https://consent.io/dashboard/register?ref=cli');
|
|
1771
|
+
const enterPressed = await prompts_text({
|
|
1738
1772
|
message: 'Press Enter once you have created your instance and have the backendURL'
|
|
1739
1773
|
});
|
|
1740
1774
|
if (handleCancel?.(enterPressed)) context.error.handleCancel('Setup cancelled.', {
|
|
@@ -1746,7 +1780,7 @@ async function handleAccountCreation(context, handleCancel) {
|
|
|
1746
1780
|
}
|
|
1747
1781
|
}
|
|
1748
1782
|
async function getBackendURL(context, initialBackendURL, handleCancel) {
|
|
1749
|
-
const backendURLSelection = await
|
|
1783
|
+
const backendURLSelection = await prompts_text({
|
|
1750
1784
|
message: 'Enter your consent.io instance URL:',
|
|
1751
1785
|
placeholder: 'https://your-instance.c15t.dev',
|
|
1752
1786
|
initialValue: initialBackendURL,
|
|
@@ -1807,7 +1841,7 @@ async function setupCustomMode({ context, spinner }) {
|
|
|
1807
1841
|
mode: 'custom',
|
|
1808
1842
|
spinner
|
|
1809
1843
|
});
|
|
1810
|
-
logger.info(`Remember to implement custom endpoint handlers ${result.configPath ? `(see ${
|
|
1844
|
+
logger.info(`Remember to implement custom endpoint handlers ${result.configPath ? `(see ${picocolors.cyan(node_path.relative(cwd, result.configPath))})` : ''}`);
|
|
1811
1845
|
const { ranInstall, installDepsConfirmed } = await installDependencies({
|
|
1812
1846
|
context,
|
|
1813
1847
|
dependenciesToAdd: [
|
|
@@ -1947,7 +1981,7 @@ const CONFIG_BUILDERS = {
|
|
|
1947
1981
|
};
|
|
1948
1982
|
async function pathExists(filePath) {
|
|
1949
1983
|
try {
|
|
1950
|
-
await
|
|
1984
|
+
await promises.access(filePath);
|
|
1951
1985
|
return true;
|
|
1952
1986
|
} catch {
|
|
1953
1987
|
return false;
|
|
@@ -2061,14 +2095,14 @@ export default defineConfig({
|
|
|
2061
2095
|
`;
|
|
2062
2096
|
}
|
|
2063
2097
|
async function promptSelectAdapter() {
|
|
2064
|
-
const selection = await
|
|
2098
|
+
const selection = await prompts_select({
|
|
2065
2099
|
message: 'Select database adapter:',
|
|
2066
2100
|
options: Object.keys(ADAPTER_LABELS).map((key)=>({
|
|
2067
2101
|
value: key,
|
|
2068
2102
|
label: ADAPTER_LABELS[key]
|
|
2069
2103
|
}))
|
|
2070
2104
|
});
|
|
2071
|
-
if (
|
|
2105
|
+
if (isCancel(selection)) throw new Cancelled('adapter_select');
|
|
2072
2106
|
return selection;
|
|
2073
2107
|
}
|
|
2074
2108
|
async function promptSelectProvider(adapter) {
|
|
@@ -2078,14 +2112,14 @@ async function promptSelectProvider(adapter) {
|
|
|
2078
2112
|
const [first] = providers;
|
|
2079
2113
|
return first.value;
|
|
2080
2114
|
}
|
|
2081
|
-
const selection = await
|
|
2115
|
+
const selection = await prompts_select({
|
|
2082
2116
|
message: 'Select database provider:',
|
|
2083
2117
|
options: providers.map((opt)=>({
|
|
2084
2118
|
value: opt.value,
|
|
2085
2119
|
label: opt.label
|
|
2086
2120
|
}))
|
|
2087
2121
|
});
|
|
2088
|
-
if (
|
|
2122
|
+
if (isCancel(selection)) throw new Cancelled('provider_select');
|
|
2089
2123
|
return selection;
|
|
2090
2124
|
}
|
|
2091
2125
|
async function promptConnection(adapter, provider) {
|
|
@@ -2093,42 +2127,42 @@ async function promptConnection(adapter, provider) {
|
|
|
2093
2127
|
useEnv: true
|
|
2094
2128
|
};
|
|
2095
2129
|
if ('sqlite' === provider) {
|
|
2096
|
-
const sqliteFile = await
|
|
2130
|
+
const sqliteFile = await prompts_text({
|
|
2097
2131
|
message: 'SQLite file path:',
|
|
2098
2132
|
initialValue: './db.sqlite'
|
|
2099
2133
|
});
|
|
2100
|
-
if (
|
|
2134
|
+
if (isCancel(sqliteFile)) throw new Cancelled('sqlite_path');
|
|
2101
2135
|
connection.sqliteFile = String(sqliteFile);
|
|
2102
2136
|
return connection;
|
|
2103
2137
|
}
|
|
2104
|
-
const useEnv = await
|
|
2138
|
+
const useEnv = await prompts_confirm({
|
|
2105
2139
|
message: 'Store connection string in an environment variable?',
|
|
2106
2140
|
initialValue: true
|
|
2107
2141
|
});
|
|
2108
|
-
if (
|
|
2142
|
+
if (isCancel(useEnv)) throw new Cancelled('use_env_confirm');
|
|
2109
2143
|
connection.useEnv = Boolean(useEnv);
|
|
2110
2144
|
if (connection.useEnv) {
|
|
2111
2145
|
const defaultVar = 'mongoAdapter' === adapter ? 'MONGODB_URI' : 'DATABASE_URL';
|
|
2112
|
-
const envVarName = await
|
|
2146
|
+
const envVarName = await prompts_text({
|
|
2113
2147
|
message: 'Env var name for connection string:',
|
|
2114
2148
|
initialValue: defaultVar
|
|
2115
2149
|
});
|
|
2116
|
-
if (
|
|
2150
|
+
if (isCancel(envVarName)) throw new Cancelled('env_var_name');
|
|
2117
2151
|
connection.envVar = String(envVarName);
|
|
2118
2152
|
return connection;
|
|
2119
2153
|
}
|
|
2120
2154
|
const placeholder = 'mongoAdapter' === adapter ? 'mongodb+srv://user:pass@host/db' : 'postgresql://user:pass@host:5432/db';
|
|
2121
|
-
const connectionString = await
|
|
2155
|
+
const connectionString = await prompts_text({
|
|
2122
2156
|
message: 'Connection string:',
|
|
2123
2157
|
placeholder
|
|
2124
2158
|
});
|
|
2125
|
-
if (
|
|
2159
|
+
if (isCancel(connectionString)) throw new Cancelled('connection_string');
|
|
2126
2160
|
connection.value = String(connectionString);
|
|
2127
2161
|
return connection;
|
|
2128
2162
|
}
|
|
2129
2163
|
async function ensureBackendConfig(context) {
|
|
2130
2164
|
const { cwd, logger } = context;
|
|
2131
|
-
const targetPath =
|
|
2165
|
+
const targetPath = node_path.join(cwd, 'c15t-backend.config.ts');
|
|
2132
2166
|
if (await pathExists(targetPath)) {
|
|
2133
2167
|
logger.debug(`Backend config already exists at ${targetPath}`);
|
|
2134
2168
|
return {
|
|
@@ -2157,8 +2191,8 @@ async function ensureBackendConfig(context) {
|
|
|
2157
2191
|
else if ('mongoAdapter' === adapter) dependencies.push('mongodb');
|
|
2158
2192
|
const dbConfig = buildDatabaseConfig(adapter, provider, connection);
|
|
2159
2193
|
const fileContent = buildFileContent(adapter, String(provider), dbConfig, connection);
|
|
2160
|
-
await
|
|
2161
|
-
context.logger.success(`Created ${
|
|
2194
|
+
await promises.writeFile(targetPath, fileContent, 'utf8');
|
|
2195
|
+
context.logger.success(`Created ${node_path.relative(cwd, targetPath)}`);
|
|
2162
2196
|
if ('sqlite' !== provider && connection.useEnv && connection.envVar) context.logger.note(`Remember to set ${connection.envVar} in your environment or .env file.`, 'Environment');
|
|
2163
2197
|
return {
|
|
2164
2198
|
path: targetPath,
|
|
@@ -2177,26 +2211,26 @@ async function handleMigrationResult(context, result) {
|
|
|
2177
2211
|
telemetry.trackEvent(TelemetryEventName.MIGRATION_PLANNED, {
|
|
2178
2212
|
success: true
|
|
2179
2213
|
});
|
|
2180
|
-
const saveSQL = await
|
|
2214
|
+
const saveSQL = await prompts_confirm({
|
|
2181
2215
|
message: 'Save SQL to file?',
|
|
2182
2216
|
initialValue: true
|
|
2183
2217
|
});
|
|
2184
|
-
if (
|
|
2218
|
+
if (isCancel(saveSQL)) return void telemetry.trackEvent(TelemetryEventName.MIGRATION_FAILED, {
|
|
2185
2219
|
saveSql: false
|
|
2186
2220
|
});
|
|
2187
2221
|
if (saveSQL) {
|
|
2188
2222
|
const sql = result.getSQL?.() ?? '';
|
|
2189
2223
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
2190
2224
|
const filename = `migration-${timestamp}.sql`;
|
|
2191
|
-
const filepath =
|
|
2192
|
-
await
|
|
2225
|
+
const filepath = join(process.cwd(), filename);
|
|
2226
|
+
await writeFile(filepath, sql, 'utf-8');
|
|
2193
2227
|
logger.success(`SQL saved to: ${filename}`);
|
|
2194
2228
|
}
|
|
2195
|
-
const execute = await
|
|
2229
|
+
const execute = await prompts_confirm({
|
|
2196
2230
|
message: 'Execute this migration?',
|
|
2197
2231
|
initialValue: false
|
|
2198
2232
|
});
|
|
2199
|
-
if (
|
|
2233
|
+
if (isCancel(execute)) return void telemetry.trackEvent(TelemetryEventName.MIGRATION_FAILED, {
|
|
2200
2234
|
execute: false
|
|
2201
2235
|
});
|
|
2202
2236
|
await result.execute();
|
|
@@ -2207,11 +2241,11 @@ async function handleMigrationResult(context, result) {
|
|
|
2207
2241
|
}
|
|
2208
2242
|
async function handleORMResult(context, result) {
|
|
2209
2243
|
const { logger, telemetry, cwd } = context;
|
|
2210
|
-
const filePath =
|
|
2211
|
-
await
|
|
2244
|
+
const filePath = node_path.join(cwd, result.path);
|
|
2245
|
+
await promises.mkdir(node_path.dirname(filePath), {
|
|
2212
2246
|
recursive: true
|
|
2213
2247
|
});
|
|
2214
|
-
await
|
|
2248
|
+
await promises.writeFile(filePath, result.code);
|
|
2215
2249
|
logger.info(`Migration file created at ${filePath}`);
|
|
2216
2250
|
telemetry.trackEvent(TelemetryEventName.MIGRATION_COMPLETED, {
|
|
2217
2251
|
success: true,
|
|
@@ -2221,14 +2255,14 @@ async function handleORMResult(context, result) {
|
|
|
2221
2255
|
async function readConfigAndGetDb(context, absoluteConfigPath) {
|
|
2222
2256
|
const { logger } = context;
|
|
2223
2257
|
logger.info(`Loading backend config from ${absoluteConfigPath}`);
|
|
2224
|
-
const resolvedPath =
|
|
2258
|
+
const resolvedPath = node_path.resolve(absoluteConfigPath);
|
|
2225
2259
|
try {
|
|
2226
|
-
await
|
|
2260
|
+
await promises.access(resolvedPath);
|
|
2227
2261
|
} catch {
|
|
2228
2262
|
throw new Error(`Backend config not found at: ${resolvedPath}`);
|
|
2229
2263
|
}
|
|
2230
2264
|
try {
|
|
2231
|
-
const { config } = await
|
|
2265
|
+
const { config } = await loadConfig({
|
|
2232
2266
|
configFile: absoluteConfigPath,
|
|
2233
2267
|
jitiOptions: {
|
|
2234
2268
|
extensions: [
|
|
@@ -2246,7 +2280,7 @@ async function readConfigAndGetDb(context, absoluteConfigPath) {
|
|
|
2246
2280
|
});
|
|
2247
2281
|
logger.debug('Imported Config');
|
|
2248
2282
|
return {
|
|
2249
|
-
db:
|
|
2283
|
+
db: DB.client(config.adapter)
|
|
2250
2284
|
};
|
|
2251
2285
|
} catch (error) {
|
|
2252
2286
|
logger.error('Failed to load backend config', error);
|
|
@@ -2267,7 +2301,7 @@ async function migrate(context) {
|
|
|
2267
2301
|
});
|
|
2268
2302
|
const { db } = await readConfigAndGetDb(context, path);
|
|
2269
2303
|
logger.info('Loaded c15t-backend.config.ts');
|
|
2270
|
-
const result = await
|
|
2304
|
+
const result = await migrator({
|
|
2271
2305
|
db,
|
|
2272
2306
|
schema: 'latest'
|
|
2273
2307
|
});
|
|
@@ -2279,14 +2313,14 @@ async function setupSelfHostedMode({ context, spinner, handleCancel }) {
|
|
|
2279
2313
|
context.framework.pkg,
|
|
2280
2314
|
'@c15t/backend'
|
|
2281
2315
|
]);
|
|
2282
|
-
const targetPath =
|
|
2316
|
+
const targetPath = node_path.join(context.cwd, 'c15t-backend.config.ts');
|
|
2283
2317
|
let createBackendConfig = false;
|
|
2284
2318
|
if (!await pathExists(targetPath)) {
|
|
2285
2319
|
const config = await ensureBackendConfig(context);
|
|
2286
2320
|
createBackendConfig = true;
|
|
2287
2321
|
for (const dep of config?.dependencies ?? [])dependenciesToAdd.add(dep);
|
|
2288
2322
|
}
|
|
2289
|
-
const backendURL = await
|
|
2323
|
+
const backendURL = await prompts_text({
|
|
2290
2324
|
message: 'Enter the backend URL:',
|
|
2291
2325
|
initialValue: 'http://localhost:3000'
|
|
2292
2326
|
});
|
|
@@ -2313,7 +2347,7 @@ async function setupSelfHostedMode({ context, spinner, handleCancel }) {
|
|
|
2313
2347
|
dependenciesToAdd: Array.from(dependenciesToAdd),
|
|
2314
2348
|
handleCancel
|
|
2315
2349
|
});
|
|
2316
|
-
const runMigrations = await
|
|
2350
|
+
const runMigrations = await prompts_confirm({
|
|
2317
2351
|
message: 'Would you like to run migrations?',
|
|
2318
2352
|
initialValue: true
|
|
2319
2353
|
});
|
|
@@ -2339,7 +2373,7 @@ async function generate(context, mode) {
|
|
|
2339
2373
|
logger.debug('Starting generate command...');
|
|
2340
2374
|
logger.debug(`Mode: ${mode}`);
|
|
2341
2375
|
const handleCancel = (value)=>{
|
|
2342
|
-
if (
|
|
2376
|
+
if (isCancel(value)) {
|
|
2343
2377
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_EXITED, {
|
|
2344
2378
|
reason: 'user_cancelled',
|
|
2345
2379
|
command: 'onboarding',
|
|
@@ -2359,7 +2393,7 @@ async function generate(context, mode) {
|
|
|
2359
2393
|
await performOnboarding(context, handleCancel, mode);
|
|
2360
2394
|
logger.success('🚀 Setup completed successfully!');
|
|
2361
2395
|
} catch (error) {
|
|
2362
|
-
if (!
|
|
2396
|
+
if (!isCancel(error)) telemetry.trackEvent(TelemetryEventName.ONBOARDING_COMPLETED, {
|
|
2363
2397
|
success: false,
|
|
2364
2398
|
error: error instanceof Error ? error.message : String(error)
|
|
2365
2399
|
});
|
|
@@ -2376,7 +2410,7 @@ async function performOnboarding(context, handleCancel, mode) {
|
|
|
2376
2410
|
if (!selectedMode) return;
|
|
2377
2411
|
const sharedOptions = {
|
|
2378
2412
|
context,
|
|
2379
|
-
spinner:
|
|
2413
|
+
spinner: prompts_spinner(),
|
|
2380
2414
|
handleCancel
|
|
2381
2415
|
};
|
|
2382
2416
|
let installDepsConfirmed = false;
|
|
@@ -2399,7 +2433,7 @@ async function performOnboarding(context, handleCancel, mode) {
|
|
|
2399
2433
|
{
|
|
2400
2434
|
const offlineResult = await setupOfflineMode({
|
|
2401
2435
|
context,
|
|
2402
|
-
spinner:
|
|
2436
|
+
spinner: prompts_spinner()
|
|
2403
2437
|
});
|
|
2404
2438
|
installDepsConfirmed = offlineResult.installDepsConfirmed;
|
|
2405
2439
|
ranInstall = offlineResult.ranInstall;
|
|
@@ -2411,7 +2445,7 @@ async function performOnboarding(context, handleCancel, mode) {
|
|
|
2411
2445
|
{
|
|
2412
2446
|
const selfHostedResult = await setupSelfHostedMode({
|
|
2413
2447
|
context,
|
|
2414
|
-
spinner:
|
|
2448
|
+
spinner: prompts_spinner(),
|
|
2415
2449
|
handleCancel
|
|
2416
2450
|
});
|
|
2417
2451
|
installDepsConfirmed = selfHostedResult.installDepsConfirmed;
|
|
@@ -2424,7 +2458,7 @@ async function performOnboarding(context, handleCancel, mode) {
|
|
|
2424
2458
|
{
|
|
2425
2459
|
const customResult = await setupCustomMode({
|
|
2426
2460
|
context,
|
|
2427
|
-
spinner:
|
|
2461
|
+
spinner: prompts_spinner()
|
|
2428
2462
|
});
|
|
2429
2463
|
installDepsConfirmed = customResult.installDepsConfirmed;
|
|
2430
2464
|
ranInstall = customResult.ranInstall;
|
|
@@ -2451,7 +2485,7 @@ async function performOnboarding(context, handleCancel, mode) {
|
|
|
2451
2485
|
}
|
|
2452
2486
|
async function handleStorageModeSelection(context, handleCancel) {
|
|
2453
2487
|
const { telemetry } = context;
|
|
2454
|
-
const storageModeSelection = await
|
|
2488
|
+
const storageModeSelection = await prompts_select({
|
|
2455
2489
|
message: 'How would you like to store consent decisions?',
|
|
2456
2490
|
initialValue: 'c15t',
|
|
2457
2491
|
options: [
|
|
@@ -2487,11 +2521,11 @@ async function handleStorageModeSelection(context, handleCancel) {
|
|
|
2487
2521
|
async function displayNextSteps(options) {
|
|
2488
2522
|
const { context, projectRoot, storageMode, installDepsConfirmed, ranInstall, dependenciesToAdd, packageManager } = options;
|
|
2489
2523
|
const { logger, cwd } = context;
|
|
2490
|
-
const { log } =
|
|
2491
|
-
const configPath =
|
|
2492
|
-
const relativeConfigPath =
|
|
2524
|
+
const { log } = prompts_namespaceObject;
|
|
2525
|
+
const configPath = node_path.join(projectRoot, 'c15t.config.ts');
|
|
2526
|
+
const relativeConfigPath = node_path.relative(cwd, configPath);
|
|
2493
2527
|
const importPath = `./${relativeConfigPath.replace(WINDOWS_PATH_SEPARATOR_REGEX, '/').replace(FILE_EXTENSION_REGEX, '')}`;
|
|
2494
|
-
const importStatement =
|
|
2528
|
+
const importStatement = picocolors.cyan(`import { c15tConfig } from '${importPath}';`);
|
|
2495
2529
|
switch(storageMode){
|
|
2496
2530
|
case 'c15t':
|
|
2497
2531
|
break;
|
|
@@ -2504,28 +2538,28 @@ async function displayNextSteps(options) {
|
|
|
2504
2538
|
case 'custom':
|
|
2505
2539
|
{
|
|
2506
2540
|
log.step('Configuration Complete! Next Steps:');
|
|
2507
|
-
const steps = `1. Implement your custom endpoint handlers (referenced in ${
|
|
2541
|
+
const steps = `1. Implement your custom endpoint handlers (referenced in ${picocolors.cyan(relativeConfigPath)}).\n 2. Import and use configuration in your app: ${importStatement}`;
|
|
2508
2542
|
logger.info(steps);
|
|
2509
2543
|
break;
|
|
2510
2544
|
}
|
|
2511
2545
|
}
|
|
2512
|
-
if (installDepsConfirmed && !ranInstall) logger.info(` - ${
|
|
2546
|
+
if (installDepsConfirmed && !ranInstall) logger.info(` - ${picocolors.yellow('Dependency installation failed.')} Please check errors and install manually.`);
|
|
2513
2547
|
else if (!ranInstall && dependenciesToAdd.length > 0) {
|
|
2514
2548
|
const pmCommand = getManualInstallCommand(dependenciesToAdd, packageManager.name);
|
|
2515
|
-
logger.warn(` - Run ${
|
|
2549
|
+
logger.warn(` - Run ${picocolors.cyan(pmCommand)} to install required dependencies.`);
|
|
2516
2550
|
}
|
|
2517
2551
|
}
|
|
2518
2552
|
async function handleGitHubStar(context) {
|
|
2519
2553
|
const { logger, telemetry } = context;
|
|
2520
|
-
logger.note(`${
|
|
2554
|
+
logger.note(`${picocolors.bold('✨ Setup complete!')} Your c15t configuration is ready to use. \n
|
|
2521
2555
|
|
|
2522
|
-
We're building c15t as an ${
|
|
2556
|
+
We're building c15t as an ${picocolors.bold('open source')} project to make consent management more accessible.
|
|
2523
2557
|
If you find this useful, we'd really appreciate a GitHub star - it helps others discover the project!`, '🎉 Thanks for using c15t');
|
|
2524
|
-
const shouldOpenGitHub = await
|
|
2558
|
+
const shouldOpenGitHub = await prompts_confirm({
|
|
2525
2559
|
message: 'Would you like to star c15t on GitHub now?',
|
|
2526
2560
|
initialValue: true
|
|
2527
2561
|
});
|
|
2528
|
-
if (
|
|
2562
|
+
if (isCancel(shouldOpenGitHub)) {
|
|
2529
2563
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_GITHUB_STAR, {
|
|
2530
2564
|
action: 'cancelled'
|
|
2531
2565
|
});
|
|
@@ -2538,12 +2572,12 @@ If you find this useful, we'd really appreciate a GitHub star - it helps others
|
|
|
2538
2572
|
action: shouldOpenGitHub ? 'opened_browser' : 'declined'
|
|
2539
2573
|
});
|
|
2540
2574
|
if (shouldOpenGitHub) try {
|
|
2541
|
-
|
|
2542
|
-
await (
|
|
2575
|
+
note('Your support helps us continue improving c15t.\nThank you for being part of our community!', '⭐ Star Us on GitHub');
|
|
2576
|
+
await open_0('https://github.com/c15t/c15t');
|
|
2543
2577
|
logger.success('GitHub repository opened. Thank you for your support!');
|
|
2544
2578
|
} catch (error) {
|
|
2545
2579
|
logger.debug('Failed to open browser:', error);
|
|
2546
|
-
logger.info(`You can star us later by visiting: ${
|
|
2580
|
+
logger.info(`You can star us later by visiting: ${picocolors.cyan('https://github.com/c15t/c15t')}`);
|
|
2547
2581
|
}
|
|
2548
2582
|
}
|
|
2549
2583
|
const subcommands = [
|
|
@@ -2598,14 +2632,14 @@ async function selfHost(context) {
|
|
|
2598
2632
|
label: 'Exit',
|
|
2599
2633
|
hint: 'Return to main menu'
|
|
2600
2634
|
});
|
|
2601
|
-
const selectedSubcommandName = await
|
|
2635
|
+
const selectedSubcommandName = await prompts_select({
|
|
2602
2636
|
message: formatLogMessage('info', 'Which self-host command would you like to run?'),
|
|
2603
2637
|
options: promptOptions
|
|
2604
2638
|
});
|
|
2605
|
-
if (
|
|
2639
|
+
if (isCancel(selectedSubcommandName) || 'exit' === selectedSubcommandName) {
|
|
2606
2640
|
logger.debug('Interactive selection cancelled or exit chosen.');
|
|
2607
2641
|
telemetry.trackEvent(TelemetryEventName.INTERACTIVE_MENU_EXITED, {
|
|
2608
|
-
action:
|
|
2642
|
+
action: isCancel(selectedSubcommandName) ? 'cancelled' : 'exit',
|
|
2609
2643
|
context: 'self-host'
|
|
2610
2644
|
});
|
|
2611
2645
|
error.handleCancel('Operation cancelled.', {
|
|
@@ -2633,12 +2667,12 @@ async function selfHost(context) {
|
|
|
2633
2667
|
}
|
|
2634
2668
|
async function displayIntro(context, version) {
|
|
2635
2669
|
const { logger } = context;
|
|
2636
|
-
logger.info(`${
|
|
2670
|
+
logger.info(`${picocolors.bold('Welcome!')} Let's get you set up.`);
|
|
2637
2671
|
logger.message('');
|
|
2638
2672
|
let figletText = 'c15t';
|
|
2639
2673
|
try {
|
|
2640
2674
|
figletText = await new Promise((resolve)=>{
|
|
2641
|
-
|
|
2675
|
+
figlet.text('c15t', {
|
|
2642
2676
|
font: 'Nancyj-Improved',
|
|
2643
2677
|
horizontalLayout: 'default',
|
|
2644
2678
|
verticalLayout: 'default',
|
|
@@ -2685,7 +2719,7 @@ function createErrorHandlers(context) {
|
|
|
2685
2719
|
logger.error(message, error);
|
|
2686
2720
|
if (error instanceof Error) logger.error(error.message);
|
|
2687
2721
|
else logger.error(String(error));
|
|
2688
|
-
logger.failed(`${
|
|
2722
|
+
logger.failed(`${picocolors.red('Operation failed unexpectedly.')}`);
|
|
2689
2723
|
process.exit(1);
|
|
2690
2724
|
},
|
|
2691
2725
|
handleCancel: (message = 'Operation cancelled.', context)=>{
|
|
@@ -2705,10 +2739,10 @@ function createFileSystem(context) {
|
|
|
2705
2739
|
return {
|
|
2706
2740
|
getPackageInfo: ()=>{
|
|
2707
2741
|
logger.debug('Reading package.json');
|
|
2708
|
-
const packageJsonPath =
|
|
2742
|
+
const packageJsonPath = node_path.join(cwd, 'package.json');
|
|
2709
2743
|
logger.debug(`package.json path: ${packageJsonPath}`);
|
|
2710
2744
|
try {
|
|
2711
|
-
const packageInfo =
|
|
2745
|
+
const packageInfo = fs_extra.readJSONSync(packageJsonPath);
|
|
2712
2746
|
logger.debug('Successfully read package.json');
|
|
2713
2747
|
return {
|
|
2714
2748
|
name: packageInfo?.name || 'unknown',
|
|
@@ -2743,7 +2777,7 @@ async function getPackageManagerVersion(pm) {
|
|
|
2743
2777
|
async function detectPackageManager(projectRoot, logger) {
|
|
2744
2778
|
try {
|
|
2745
2779
|
logger?.debug('Detecting package manager');
|
|
2746
|
-
const pm = await
|
|
2780
|
+
const pm = await detect({
|
|
2747
2781
|
cwd: projectRoot
|
|
2748
2782
|
});
|
|
2749
2783
|
if (!pm) throw new Error('No package manager detected');
|
|
@@ -2756,7 +2790,7 @@ async function detectPackageManager(projectRoot, logger) {
|
|
|
2756
2790
|
} catch (error) {
|
|
2757
2791
|
logger?.error(`Error detecting package manager: ${error instanceof Error ? error.message : String(error)}`);
|
|
2758
2792
|
}
|
|
2759
|
-
const selectedPackageManager = await
|
|
2793
|
+
const selectedPackageManager = await prompts_select({
|
|
2760
2794
|
message: 'Please select your package manager:',
|
|
2761
2795
|
options: [
|
|
2762
2796
|
{
|
|
@@ -2778,7 +2812,7 @@ async function detectPackageManager(projectRoot, logger) {
|
|
|
2778
2812
|
],
|
|
2779
2813
|
initialValue: 'npm'
|
|
2780
2814
|
});
|
|
2781
|
-
if (
|
|
2815
|
+
if (isCancel(selectedPackageManager)) {
|
|
2782
2816
|
logger?.debug('Package manager selection cancelled by user');
|
|
2783
2817
|
logger?.failed('Package manager selection cancelled. Exiting.');
|
|
2784
2818
|
process.exit(0);
|
|
@@ -2873,7 +2907,7 @@ function parseCliArgs(rawArgs, commands) {
|
|
|
2873
2907
|
if (nextArg && !nextArg.startsWith('-')) {
|
|
2874
2908
|
parsedFlags[primaryName] = nextArg;
|
|
2875
2909
|
i++;
|
|
2876
|
-
} else
|
|
2910
|
+
} else prompts_log.warn(formatLogMessage('warn', `Flag ${arg} expects a value, but none was found or the next item is a flag.`));
|
|
2877
2911
|
} else parsedFlags[primaryName] = true;
|
|
2878
2912
|
}
|
|
2879
2913
|
break;
|
|
@@ -2894,11 +2928,11 @@ function createUserInteraction(context) {
|
|
|
2894
2928
|
return {
|
|
2895
2929
|
confirm: async (message, initialValue)=>{
|
|
2896
2930
|
logger.debug(`Confirm action: "${message}", Initial: ${initialValue}`);
|
|
2897
|
-
const confirmed = await
|
|
2931
|
+
const confirmed = await prompts_confirm({
|
|
2898
2932
|
message,
|
|
2899
2933
|
initialValue
|
|
2900
2934
|
});
|
|
2901
|
-
if (
|
|
2935
|
+
if (isCancel(confirmed)) {
|
|
2902
2936
|
error.handleCancel();
|
|
2903
2937
|
return false;
|
|
2904
2938
|
}
|
|
@@ -2984,8 +3018,8 @@ const src_commands = [
|
|
|
2984
3018
|
description: 'Open our GitHub repository to give us a star.',
|
|
2985
3019
|
action: async (context)=>{
|
|
2986
3020
|
const { logger } = context;
|
|
2987
|
-
logger.note(`We're building c15t as an ${
|
|
2988
|
-
await (
|
|
3021
|
+
logger.note(`We're building c15t as an ${picocolors.bold('open source')} project to make consent management more accessible.\nIf you find this useful, we'd really appreciate a GitHub star - it helps others discover the project!`, '⭐ Star Us on GitHub');
|
|
3022
|
+
await open_0('https://github.com/c15t/c15t');
|
|
2989
3023
|
logger.success('Thank you for your support!');
|
|
2990
3024
|
}
|
|
2991
3025
|
},
|
|
@@ -2996,7 +3030,7 @@ const src_commands = [
|
|
|
2996
3030
|
description: 'Open the c15t documentation in your browser.',
|
|
2997
3031
|
action: async (context)=>{
|
|
2998
3032
|
const { logger } = context;
|
|
2999
|
-
await (
|
|
3033
|
+
await open_0('https://c15t.com/docs?ref=cli');
|
|
3000
3034
|
logger.success('Documentation opened in your browser.');
|
|
3001
3035
|
}
|
|
3002
3036
|
}
|
|
@@ -3010,8 +3044,8 @@ async function main() {
|
|
|
3010
3044
|
const version = packageInfo.version;
|
|
3011
3045
|
if (!telemetry.isDisabled()) logger.note(`c15t collects anonymous usage data to help improve the CLI.
|
|
3012
3046
|
This data is not personally identifiable and helps us prioritize features.
|
|
3013
|
-
To disable telemetry, use the ${
|
|
3014
|
-
flag or set ${
|
|
3047
|
+
To disable telemetry, use the ${picocolors.cyan('--no-telemetry')}
|
|
3048
|
+
flag or set ${picocolors.cyan('C15T_TELEMETRY_DISABLED=1')} in your environment.`, `${formatLogMessage('info', 'Telemetry Notice')}`);
|
|
3015
3049
|
try {
|
|
3016
3050
|
telemetry.trackEvent(TelemetryEventName.CLI_INVOKED, {
|
|
3017
3051
|
version,
|
|
@@ -3084,14 +3118,14 @@ flag or set ${__WEBPACK_EXTERNAL_MODULE_picocolors__["default"].cyan('C15T_TELEM
|
|
|
3084
3118
|
label: 'exit',
|
|
3085
3119
|
hint: 'Close the CLI'
|
|
3086
3120
|
});
|
|
3087
|
-
const selectedCommandName = await
|
|
3121
|
+
const selectedCommandName = await prompts_select({
|
|
3088
3122
|
message: formatLogMessage('info', 'Which command would you like to run?'),
|
|
3089
3123
|
options: promptOptions
|
|
3090
3124
|
});
|
|
3091
|
-
if (
|
|
3125
|
+
if (isCancel(selectedCommandName) || 'exit' === selectedCommandName) {
|
|
3092
3126
|
logger.debug('Interactive selection cancelled or exit chosen.');
|
|
3093
3127
|
telemetry.trackEvent(TelemetryEventName.INTERACTIVE_MENU_EXITED, {
|
|
3094
|
-
action:
|
|
3128
|
+
action: isCancel(selectedCommandName) ? 'cancelled' : 'exit'
|
|
3095
3129
|
});
|
|
3096
3130
|
context.error.handleCancel('Operation cancelled.', {
|
|
3097
3131
|
command: 'interactive_menu',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0-canary-20251112105612",
|
|
4
4
|
"description": "CLI for rapid c15t setup. Scaffold React and Next.js cookie banners and a preferences centre, generate types and config, and run migration tooling for self-hosted deployments.",
|
|
5
5
|
"homepage": "https://c15t.com",
|
|
6
6
|
"repository": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"posthog-node": "^4.11.7",
|
|
36
36
|
"ts-morph": "^25.0.1",
|
|
37
37
|
"zod": "^3.24.2",
|
|
38
|
-
"@c15t/backend": "1.
|
|
39
|
-
"@c15t/logger": "1.0.
|
|
40
|
-
"@c15t/react": "1.
|
|
38
|
+
"@c15t/backend": "1.8.0-canary-20251112105612",
|
|
39
|
+
"@c15t/logger": "1.0.1-canary-20251112105612",
|
|
40
|
+
"@c15t/react": "1.8.0-canary-20251112105612"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/figlet": "^1.7.0",
|