@c15t/cli 2.0.0-rc.6 → 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 +1208 -316
- package/dist/generate-files.mjs +51 -77
- 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/generate/templates/css.d.ts +10 -11
- package/dist-types/commands/generate/templates/shared/components.d.ts +1 -1
- package/dist-types/commands/generate/templates/shared/framework-config.d.ts +0 -4
- 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 +6 -6
package/dist/145.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
2
|
import open_0 from "open";
|
|
3
3
|
import picocolors from "picocolors";
|
|
4
|
-
import promises, { readdir, writeFile } from "node:fs/promises";
|
|
5
|
-
import node_path, { extname, join as external_node_path_join } from "node:path";
|
|
4
|
+
import promises, { readFile, readdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import node_path, { dirname, extname, join as external_node_path_join, relative, resolve as external_node_path_resolve } from "node:path";
|
|
6
6
|
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
7
|
-
import { existsSync as external_node_fs_existsSync } from "node:fs";
|
|
7
|
+
import node_fs, { existsSync as external_node_fs_existsSync } from "node:fs";
|
|
8
8
|
import { assign as external_xstate_assign, createActor, fromPromise, setup } from "xstate";
|
|
9
9
|
import node_crypto from "node:crypto";
|
|
10
10
|
import node_os from "node:os";
|
|
11
|
-
import {
|
|
11
|
+
import { createLogger, initLogger } from "evlog";
|
|
12
|
+
import { createDrainPipeline } from "evlog/pipeline";
|
|
12
13
|
import { spawn as external_node_child_process_spawn } from "node:child_process";
|
|
13
14
|
import { once } from "node:events";
|
|
14
|
-
import { createLogger } from "@c15t/logger";
|
|
15
|
+
import { createLogger as logger_createLogger } from "@c15t/logger";
|
|
15
16
|
import { migrator } from "@c15t/backend/db/migrator";
|
|
16
17
|
import { DB } from "@c15t/backend/db/schema";
|
|
17
18
|
import { loadConfig as external_c12_loadConfig } from "c12";
|
|
@@ -83,14 +84,17 @@ __webpack_require__.r(config_store_namespaceObject);
|
|
|
83
84
|
__webpack_require__.d(config_store_namespaceObject, {
|
|
84
85
|
getAccessToken: ()=>config_store_getAccessToken,
|
|
85
86
|
IM: ()=>getAuthState,
|
|
87
|
+
qs: ()=>getSelectedInstanceId,
|
|
88
|
+
M3: ()=>isLoggedIn,
|
|
86
89
|
_g: ()=>setSelectedInstanceId,
|
|
87
90
|
Ex: ()=>storeTokens
|
|
88
91
|
});
|
|
89
92
|
function showHelpMenu(context, version, commands, flags) {
|
|
90
93
|
const { logger } = context;
|
|
91
94
|
logger.debug('Displaying help menu using command and flag structures.');
|
|
92
|
-
const
|
|
93
|
-
const
|
|
95
|
+
const visibleCommands = commands.filter((cmd)=>!cmd.hidden);
|
|
96
|
+
const commandColumnWidth = Math.max(...visibleCommands.map((cmd)=>cmd.name.length), 10) + 2;
|
|
97
|
+
const commandLines = visibleCommands.map((cmd)=>` ${cmd.name.padEnd(commandColumnWidth)}${cmd.description}`).join('\n');
|
|
94
98
|
const flagDisplays = flags.map((flag)=>{
|
|
95
99
|
const names = flag.names.join(', ');
|
|
96
100
|
const valuePlaceholder = flag.expectsValue ? ' <value>' : '';
|
|
@@ -351,6 +355,191 @@ async function runActiveUiApiCodemod(options) {
|
|
|
351
355
|
errors
|
|
352
356
|
};
|
|
353
357
|
}
|
|
358
|
+
const CSS_ENTRYPOINT_CANDIDATES = [
|
|
359
|
+
'app/globals.css',
|
|
360
|
+
'src/app/globals.css',
|
|
361
|
+
'app/global.css',
|
|
362
|
+
'src/app/global.css',
|
|
363
|
+
'styles/globals.css',
|
|
364
|
+
'src/styles/globals.css',
|
|
365
|
+
'styles/global.css',
|
|
366
|
+
'src/styles/global.css',
|
|
367
|
+
'src/index.css',
|
|
368
|
+
'src/styles.css',
|
|
369
|
+
'src/style.css',
|
|
370
|
+
'styles.css',
|
|
371
|
+
'app.css',
|
|
372
|
+
'src/App.css'
|
|
373
|
+
];
|
|
374
|
+
const LOCAL_CSS_IMPORT_RE = /^\s*import(?:\s+[^'"]+\s+from\s+)?['"]([^'"]+\.css)['"];\s*$/gm;
|
|
375
|
+
const TAILWIND_V4_IMPORT_RE = /^\s*@import\s+['"]tailwindcss['"];\s*$/;
|
|
376
|
+
const TAILWIND_COMPONENTS_RE = /^\s*@tailwind\s+components\s*;\s*$/;
|
|
377
|
+
const TAILWIND_UTILITIES_RE = /^\s*@tailwind\s+utilities\s*;\s*$/;
|
|
378
|
+
function normalizePath(projectRoot, filePath) {
|
|
379
|
+
if (filePath.startsWith(projectRoot)) return filePath;
|
|
380
|
+
return external_node_path_resolve(projectRoot, filePath);
|
|
381
|
+
}
|
|
382
|
+
function dedupePaths(paths) {
|
|
383
|
+
return [
|
|
384
|
+
...new Set(paths)
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
function isNonModuleLocalCssImport(moduleSpecifier) {
|
|
388
|
+
return moduleSpecifier.startsWith('.') && moduleSpecifier.endsWith('.css') && !moduleSpecifier.endsWith('.module.css');
|
|
389
|
+
}
|
|
390
|
+
function getManagedPackages(packageName) {
|
|
391
|
+
if ('@c15t/react' === packageName || '@c15t/nextjs' === packageName) return [
|
|
392
|
+
'@c15t/react',
|
|
393
|
+
'@c15t/nextjs'
|
|
394
|
+
];
|
|
395
|
+
return [
|
|
396
|
+
'@c15t/ui'
|
|
397
|
+
];
|
|
398
|
+
}
|
|
399
|
+
function getImportVariants(packageName, kind) {
|
|
400
|
+
if ('base' === kind) return [
|
|
401
|
+
`${packageName}/styles.css`,
|
|
402
|
+
`${packageName}/styles.tw3.css`
|
|
403
|
+
];
|
|
404
|
+
return [
|
|
405
|
+
`${packageName}/iab/styles.css`,
|
|
406
|
+
`${packageName}/iab/styles.tw3.css`
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
function getDesiredImportPath(packageName, kind, tailwindVersion) {
|
|
410
|
+
const suffix = isTailwindV3(tailwindVersion) ? 'styles.tw3.css' : 'styles.css';
|
|
411
|
+
return 'base' === kind ? `${packageName}/${suffix}` : `${packageName}/iab/${suffix}`;
|
|
412
|
+
}
|
|
413
|
+
function getDesiredImports(packageName, tailwindVersion, includeBase, includeIab) {
|
|
414
|
+
const imports = [];
|
|
415
|
+
if (includeBase) imports.push(getDesiredImportPath(packageName, 'base', tailwindVersion));
|
|
416
|
+
if (includeIab) imports.push(getDesiredImportPath(packageName, 'iab', tailwindVersion));
|
|
417
|
+
return imports;
|
|
418
|
+
}
|
|
419
|
+
function getFrameworkImportRegex(packageNames) {
|
|
420
|
+
const escapedPackages = packageNames.map((packageName)=>packageName.replace('/', '\\/')).join('|');
|
|
421
|
+
return new RegExp(`^\\s*@import\\s+['"](?:${escapedPackages})(?:\\/iab)?\\/styles(?:\\.tw3)?\\.css['"];\\s*$`);
|
|
422
|
+
}
|
|
423
|
+
function findTopInsertionLineIndex(lines) {
|
|
424
|
+
let index = 0;
|
|
425
|
+
if (lines[index]?.trim().startsWith('/*')) {
|
|
426
|
+
while(index < lines.length){
|
|
427
|
+
const line = lines[index];
|
|
428
|
+
index += 1;
|
|
429
|
+
if (line?.includes('*/')) break;
|
|
430
|
+
}
|
|
431
|
+
while(index < lines.length && lines[index]?.trim() === '')index += 1;
|
|
432
|
+
}
|
|
433
|
+
return index;
|
|
434
|
+
}
|
|
435
|
+
function insertImportsIntoCssContent(content, desiredImports, tailwindVersion, managedPackages) {
|
|
436
|
+
const normalizedContent = content.replace(/\r\n/g, '\n');
|
|
437
|
+
const hadTrailingNewline = normalizedContent.endsWith('\n');
|
|
438
|
+
const body = hadTrailingNewline ? normalizedContent.slice(0, -1) : normalizedContent;
|
|
439
|
+
const lines = body.length > 0 ? body.split('\n') : [];
|
|
440
|
+
const importRegex = getFrameworkImportRegex(managedPackages);
|
|
441
|
+
const filteredLines = lines.filter((line)=>!importRegex.test(line));
|
|
442
|
+
const importLines = desiredImports.map((importPath)=>`@import "${importPath}";`);
|
|
443
|
+
let insertionIndex = findTopInsertionLineIndex(filteredLines);
|
|
444
|
+
if (isTailwindV3(tailwindVersion)) {
|
|
445
|
+
const componentsIndex = filteredLines.findIndex((line)=>TAILWIND_COMPONENTS_RE.test(line));
|
|
446
|
+
if (componentsIndex >= 0) insertionIndex = componentsIndex + 1;
|
|
447
|
+
else {
|
|
448
|
+
const utilitiesIndex = filteredLines.findIndex((line)=>TAILWIND_UTILITIES_RE.test(line));
|
|
449
|
+
if (utilitiesIndex >= 0) insertionIndex = utilitiesIndex;
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
const tailwindImportIndex = filteredLines.findIndex((line)=>TAILWIND_V4_IMPORT_RE.test(line));
|
|
453
|
+
if (tailwindImportIndex >= 0) insertionIndex = tailwindImportIndex + 1;
|
|
454
|
+
}
|
|
455
|
+
const nextLines = [
|
|
456
|
+
...filteredLines.slice(0, insertionIndex),
|
|
457
|
+
...importLines,
|
|
458
|
+
...filteredLines.slice(insertionIndex)
|
|
459
|
+
];
|
|
460
|
+
let nextContent = nextLines.join('\n');
|
|
461
|
+
if (hadTrailingNewline) nextContent += '\n';
|
|
462
|
+
if (content.includes('\r\n')) nextContent = nextContent.replace(/\n/g, '\r\n');
|
|
463
|
+
return nextContent;
|
|
464
|
+
}
|
|
465
|
+
function describeImportChange(content, packageName, desiredImportPath) {
|
|
466
|
+
const kind = desiredImportPath.includes('/iab/') ? 'iab' : 'base';
|
|
467
|
+
const variants = getImportVariants(packageName, kind);
|
|
468
|
+
const alternateVariant = variants.find((variant)=>variant !== desiredImportPath && content.includes(variant));
|
|
469
|
+
if (alternateVariant) return `replaced @import "${alternateVariant}"; with @import "${desiredImportPath}";`;
|
|
470
|
+
if (content.includes(desiredImportPath)) return `normalized @import "${desiredImportPath}";`;
|
|
471
|
+
return `added @import "${desiredImportPath}";`;
|
|
472
|
+
}
|
|
473
|
+
async function resolveCssEntrypoint({ projectRoot, entrypointPath }) {
|
|
474
|
+
const searchedPaths = [];
|
|
475
|
+
if (entrypointPath) {
|
|
476
|
+
const resolvedEntrypointPath = normalizePath(projectRoot, entrypointPath);
|
|
477
|
+
if (external_node_fs_existsSync(resolvedEntrypointPath)) {
|
|
478
|
+
const entrypointContent = await readFile(resolvedEntrypointPath, 'utf-8');
|
|
479
|
+
for (const match of entrypointContent.matchAll(LOCAL_CSS_IMPORT_RE)){
|
|
480
|
+
const moduleSpecifier = match[1];
|
|
481
|
+
if (!moduleSpecifier || !isNonModuleLocalCssImport(moduleSpecifier)) continue;
|
|
482
|
+
const candidatePath = external_node_path_resolve(dirname(resolvedEntrypointPath), moduleSpecifier);
|
|
483
|
+
searchedPaths.push(candidatePath);
|
|
484
|
+
if (external_node_fs_existsSync(candidatePath)) return {
|
|
485
|
+
filePath: candidatePath,
|
|
486
|
+
searchedPaths: dedupePaths(searchedPaths)
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
for (const candidate of CSS_ENTRYPOINT_CANDIDATES){
|
|
492
|
+
const candidatePath = external_node_path_join(projectRoot, candidate);
|
|
493
|
+
searchedPaths.push(candidatePath);
|
|
494
|
+
if (external_node_fs_existsSync(candidatePath)) return {
|
|
495
|
+
filePath: candidatePath,
|
|
496
|
+
searchedPaths: dedupePaths(searchedPaths)
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
filePath: null,
|
|
501
|
+
searchedPaths: dedupePaths(searchedPaths)
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function formatSearchedCssPaths(projectRoot, searchedPaths) {
|
|
505
|
+
return searchedPaths.map((filePath)=>relative(projectRoot, filePath) || '.').join(', ');
|
|
506
|
+
}
|
|
507
|
+
function isTailwindV3(version) {
|
|
508
|
+
return null != version && /^(?:\^|~)?3/.test(version);
|
|
509
|
+
}
|
|
510
|
+
async function ensureGlobalCssStylesheetImports(options) {
|
|
511
|
+
const desiredImports = getDesiredImports(options.packageName, options.tailwindVersion, options.includeBase, options.includeIab);
|
|
512
|
+
if (0 === desiredImports.length) return {
|
|
513
|
+
updated: false,
|
|
514
|
+
filePath: null,
|
|
515
|
+
searchedPaths: [],
|
|
516
|
+
changes: []
|
|
517
|
+
};
|
|
518
|
+
const { filePath, searchedPaths } = await resolveCssEntrypoint(options);
|
|
519
|
+
if (!filePath) return {
|
|
520
|
+
updated: false,
|
|
521
|
+
filePath: null,
|
|
522
|
+
searchedPaths,
|
|
523
|
+
changes: []
|
|
524
|
+
};
|
|
525
|
+
const content = await readFile(filePath, 'utf-8');
|
|
526
|
+
const managedPackages = getManagedPackages(options.packageName);
|
|
527
|
+
const nextContent = insertImportsIntoCssContent(content, desiredImports, options.tailwindVersion, managedPackages);
|
|
528
|
+
if (nextContent === content) return {
|
|
529
|
+
updated: false,
|
|
530
|
+
filePath,
|
|
531
|
+
searchedPaths,
|
|
532
|
+
changes: []
|
|
533
|
+
};
|
|
534
|
+
if (!options.dryRun) await writeFile(filePath, nextContent, 'utf-8');
|
|
535
|
+
const changes = desiredImports.map((importPath)=>describeImportChange(content, options.packageName, importPath));
|
|
536
|
+
return {
|
|
537
|
+
updated: true,
|
|
538
|
+
filePath,
|
|
539
|
+
searchedPaths,
|
|
540
|
+
changes
|
|
541
|
+
};
|
|
542
|
+
}
|
|
354
543
|
const add_stylesheet_imports_SUPPORTED_EXTENSIONS = new Set([
|
|
355
544
|
'.ts',
|
|
356
545
|
'.tsx',
|
|
@@ -400,6 +589,17 @@ const NEXTJS_ENTRYPOINTS = [
|
|
|
400
589
|
'pages/_app.tsx',
|
|
401
590
|
'pages/_app.jsx'
|
|
402
591
|
];
|
|
592
|
+
async function detectTailwindVersion(projectRoot) {
|
|
593
|
+
const packageJsonPath = external_node_path_join(projectRoot, 'package.json');
|
|
594
|
+
if (!external_node_fs_existsSync(packageJsonPath)) return null;
|
|
595
|
+
try {
|
|
596
|
+
const content = await readFile(packageJsonPath, 'utf-8');
|
|
597
|
+
const parsed = JSON.parse(content);
|
|
598
|
+
return parsed.dependencies?.tailwindcss ?? parsed.devDependencies?.tailwindcss ?? null;
|
|
599
|
+
} catch {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
403
603
|
async function add_stylesheet_imports_collectSourceFiles(rootDir) {
|
|
404
604
|
const files = [];
|
|
405
605
|
async function walk(currentDir) {
|
|
@@ -492,23 +692,19 @@ function findEntrypoint(projectRoot, framework) {
|
|
|
492
692
|
}
|
|
493
693
|
return null;
|
|
494
694
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if (!sourceFile) return false;
|
|
498
|
-
const importDeclarations = sourceFile.getImportDeclarations();
|
|
499
|
-
return importDeclarations.some((importDecl)=>importDecl.getModuleSpecifierValue() === cssImportPath);
|
|
500
|
-
}
|
|
501
|
-
function addCssImport(project, filePath, cssImportPath) {
|
|
695
|
+
const FRAMEWORK_STYLESHEET_IMPORT_RE = /^@c15t\/(?:react|nextjs)(?:\/iab)?\/styles(?:\.tw3)?\.css$/;
|
|
696
|
+
function removeFrameworkStylesheetImports(project, filePath) {
|
|
502
697
|
const sourceFile = project.addSourceFileAtPathIfExists(filePath);
|
|
503
|
-
if (!sourceFile) return
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
698
|
+
if (!sourceFile) return [];
|
|
699
|
+
const removedImports = [];
|
|
700
|
+
for (const importDeclaration of sourceFile.getImportDeclarations()){
|
|
701
|
+
const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
|
|
702
|
+
if (FRAMEWORK_STYLESHEET_IMPORT_RE.test(moduleSpecifier)) {
|
|
703
|
+
removedImports.push(moduleSpecifier);
|
|
704
|
+
importDeclaration.remove();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return removedImports;
|
|
512
708
|
}
|
|
513
709
|
async function runAddStylesheetImportsCodemod(options) {
|
|
514
710
|
const project = new Project({
|
|
@@ -550,40 +746,42 @@ async function runAddStylesheetImportsCodemod(options) {
|
|
|
550
746
|
};
|
|
551
747
|
}
|
|
552
748
|
const pkg = 'nextjs' === detection.framework ? '@c15t/nextjs' : '@c15t/react';
|
|
553
|
-
const
|
|
554
|
-
const summaries = [];
|
|
749
|
+
const tailwindVersion = await detectTailwindVersion(options.projectRoot);
|
|
555
750
|
try {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
}
|
|
751
|
+
const stylesheetResult = await ensureGlobalCssStylesheetImports({
|
|
752
|
+
projectRoot: options.projectRoot,
|
|
753
|
+
packageName: pkg,
|
|
754
|
+
tailwindVersion,
|
|
755
|
+
entrypointPath: entrypoint,
|
|
756
|
+
includeBase: detection.usesStyledUi || detection.usesIabUi,
|
|
757
|
+
includeIab: detection.usesIabUi,
|
|
758
|
+
dryRun: options.dryRun
|
|
759
|
+
});
|
|
760
|
+
if (!stylesheetResult.filePath) {
|
|
761
|
+
errors.push({
|
|
762
|
+
filePath: options.projectRoot,
|
|
763
|
+
error: `No suitable global CSS entrypoint found. Checked: ${formatSearchedCssPaths(options.projectRoot, stylesheetResult.searchedPaths)}`
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
totalFiles: filePaths.length,
|
|
767
|
+
changedFiles,
|
|
768
|
+
errors
|
|
769
|
+
};
|
|
575
770
|
}
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
771
|
+
if (stylesheetResult.updated) changedFiles.push({
|
|
772
|
+
filePath: stylesheetResult.filePath,
|
|
773
|
+
operations: stylesheetResult.changes.length,
|
|
774
|
+
summaries: stylesheetResult.changes
|
|
775
|
+
});
|
|
776
|
+
for (const filePath of filePaths){
|
|
777
|
+
const removedImports = removeFrameworkStylesheetImports(project, filePath);
|
|
778
|
+
if (0 !== removedImports.length) changedFiles.push({
|
|
779
|
+
filePath,
|
|
780
|
+
operations: removedImports.length,
|
|
781
|
+
summaries: removedImports.map((importPath)=>`removed JS import '${importPath}'`)
|
|
581
782
|
});
|
|
582
|
-
if (!options.dryRun) {
|
|
583
|
-
const sourceFile = project.getSourceFile(entrypoint);
|
|
584
|
-
if (sourceFile) await sourceFile.save();
|
|
585
|
-
}
|
|
586
783
|
}
|
|
784
|
+
if (!options.dryRun) await project.save();
|
|
587
785
|
} catch (error) {
|
|
588
786
|
errors.push({
|
|
589
787
|
filePath: entrypoint,
|
|
@@ -2113,8 +2311,8 @@ const codemods = [
|
|
|
2113
2311
|
},
|
|
2114
2312
|
{
|
|
2115
2313
|
id: 'add-stylesheet-imports',
|
|
2116
|
-
label: '
|
|
2117
|
-
hint: '
|
|
2314
|
+
label: 'configure global CSS for prebuilt UI',
|
|
2315
|
+
hint: 'Moves styled c15t imports into the app CSS entrypoint, including Tailwind 3 and IAB variants when needed.',
|
|
2118
2316
|
run: async (context, dryRun)=>{
|
|
2119
2317
|
const { projectRoot } = context;
|
|
2120
2318
|
const result = await runAddStylesheetImportsCodemod({
|
|
@@ -2173,7 +2371,95 @@ const codemodsCommand = {
|
|
|
2173
2371
|
description: 'Run project codemods (for example translations -> i18n migration).',
|
|
2174
2372
|
action: runCodemods
|
|
2175
2373
|
};
|
|
2176
|
-
const
|
|
2374
|
+
const constants_URLS = {
|
|
2375
|
+
CONSENT_IO: 'https://consent.io',
|
|
2376
|
+
TELEMETRY: 'https://telemetry.c15t.com/c15t/v1/logs',
|
|
2377
|
+
DOCS: 'https://v2.c15t.com/docs',
|
|
2378
|
+
GITHUB: 'https://github.com/c15t/c15t',
|
|
2379
|
+
DISCORD: 'https://v2.c15t.com/discord',
|
|
2380
|
+
API_DOCS: 'https://v2.c15t.com/docs/api',
|
|
2381
|
+
CLI_DOCS: 'https://v2.c15t.com/docs/cli',
|
|
2382
|
+
CHANGELOG: 'https://v2.c15t.com/changelog'
|
|
2383
|
+
};
|
|
2384
|
+
const PATHS = {
|
|
2385
|
+
CONFIG_DIR: '.c15t',
|
|
2386
|
+
CONFIG_FILE: 'config.json',
|
|
2387
|
+
TELEMETRY_STATE_FILE: 'telemetry.json',
|
|
2388
|
+
TELEMETRY_QUEUE_FILE: 'telemetry-queue.json',
|
|
2389
|
+
PROJECT_CONFIG: 'c15t.config.ts',
|
|
2390
|
+
PROJECT_CONFIG_JS: 'c15t.config.js',
|
|
2391
|
+
ENV_FILE: '.env',
|
|
2392
|
+
ENV_LOCAL: '.env.local'
|
|
2393
|
+
};
|
|
2394
|
+
const constants_REGEX = {
|
|
2395
|
+
URL: /^https?:\/\/.+/,
|
|
2396
|
+
C15T_URL: /^https:\/\/[\w-]+\.c15t\.dev$/,
|
|
2397
|
+
DYNAMIC_SEGMENT: /\[[\w-]+\]/,
|
|
2398
|
+
SEMVER: /^\d+\.\d+\.\d+(-[\w.]+)?$/,
|
|
2399
|
+
PACKAGE_NAME: /^(@[\w-]+\/)?[\w-]+$/
|
|
2400
|
+
};
|
|
2401
|
+
const CLI_INFO = {
|
|
2402
|
+
NAME: 'c15t',
|
|
2403
|
+
BIN: 'c15t',
|
|
2404
|
+
CONTROL_PLANE_CLIENT_NAME: 'c15t-cli',
|
|
2405
|
+
VERSION: '2.0.0'
|
|
2406
|
+
};
|
|
2407
|
+
const TIMEOUTS = {
|
|
2408
|
+
DEVICE_FLOW_POLL_INTERVAL: 5,
|
|
2409
|
+
DEVICE_FLOW_EXPIRY: 900,
|
|
2410
|
+
HTTP_REQUEST: 10000,
|
|
2411
|
+
CONTROL_PLANE_CONNECTION: 30000
|
|
2412
|
+
};
|
|
2413
|
+
const ENV_VARS = {
|
|
2414
|
+
V2: 'V2',
|
|
2415
|
+
TELEMETRY_DISABLED: 'C15T_TELEMETRY_DISABLED',
|
|
2416
|
+
TELEMETRY_ENDPOINT: 'C15T_TELEMETRY_ENDPOINT',
|
|
2417
|
+
TELEMETRY_WRITE_KEY: 'C15T_TELEMETRY_WRITE_KEY',
|
|
2418
|
+
TELEMETRY_ORG_ID: 'C15T_TELEMETRY_ORG_ID',
|
|
2419
|
+
CONSENT_URL: 'CONSENT_URL',
|
|
2420
|
+
BACKEND_URL: 'C15T_URL',
|
|
2421
|
+
API_KEY: 'C15T_API_KEY',
|
|
2422
|
+
DEBUG: 'C15T_DEBUG'
|
|
2423
|
+
};
|
|
2424
|
+
const STORAGE_MODES = {
|
|
2425
|
+
HOSTED: 'hosted',
|
|
2426
|
+
C15T: 'c15t',
|
|
2427
|
+
OFFLINE: 'offline',
|
|
2428
|
+
SELF_HOSTED: 'self-hosted',
|
|
2429
|
+
CUSTOM: 'custom'
|
|
2430
|
+
};
|
|
2431
|
+
const LAYOUT_PATTERNS = [
|
|
2432
|
+
'app/layout.tsx',
|
|
2433
|
+
'app/layout.ts',
|
|
2434
|
+
'app/layout.jsx',
|
|
2435
|
+
'app/layout.js',
|
|
2436
|
+
'src/app/layout.tsx',
|
|
2437
|
+
'src/app/layout.ts',
|
|
2438
|
+
'src/app/layout.jsx',
|
|
2439
|
+
'src/app/layout.js',
|
|
2440
|
+
'app/*/layout.tsx',
|
|
2441
|
+
'app/*/layout.ts',
|
|
2442
|
+
'app/*/layout.jsx',
|
|
2443
|
+
'app/*/layout.js',
|
|
2444
|
+
'src/app/*/layout.tsx',
|
|
2445
|
+
'src/app/*/layout.ts',
|
|
2446
|
+
'src/app/*/layout.jsx',
|
|
2447
|
+
'src/app/*/layout.js',
|
|
2448
|
+
'app/*/*/layout.tsx',
|
|
2449
|
+
'app/*/*/layout.ts',
|
|
2450
|
+
'src/app/*/*/layout.tsx',
|
|
2451
|
+
'src/app/*/*/layout.ts'
|
|
2452
|
+
];
|
|
2453
|
+
const PAGES_APP_PATTERNS = [
|
|
2454
|
+
'pages/_app.tsx',
|
|
2455
|
+
'pages/_app.ts',
|
|
2456
|
+
'pages/_app.jsx',
|
|
2457
|
+
'pages/_app.js',
|
|
2458
|
+
'src/pages/_app.tsx',
|
|
2459
|
+
'src/pages/_app.ts',
|
|
2460
|
+
'src/pages/_app.jsx',
|
|
2461
|
+
'src/pages/_app.js'
|
|
2462
|
+
];
|
|
2177
2463
|
const TelemetryEventName = {
|
|
2178
2464
|
CLI_INVOKED: 'cli.invoked',
|
|
2179
2465
|
CLI_COMPLETED: 'cli.completed',
|
|
@@ -2191,6 +2477,7 @@ const TelemetryEventName = {
|
|
|
2191
2477
|
HELP_DISPLAYED: 'help.displayed',
|
|
2192
2478
|
VERSION_DISPLAYED: 'version.displayed',
|
|
2193
2479
|
ONBOARDING_STARTED: 'onboarding.started',
|
|
2480
|
+
ONBOARDING_STAGE: 'onboarding.stage',
|
|
2194
2481
|
ONBOARDING_COMPLETED: 'onboarding.completed',
|
|
2195
2482
|
ONBOARDING_EXITED: 'onboarding.exited',
|
|
2196
2483
|
ONBOARDING_STORAGE_MODE_SELECTED: 'onboarding.storage_mode_selected',
|
|
@@ -2201,6 +2488,13 @@ const TelemetryEventName = {
|
|
|
2201
2488
|
ONBOARDING_DEPENDENCIES_CHOICE: 'onboarding.dependencies_choice',
|
|
2202
2489
|
ONBOARDING_DEPENDENCIES_INSTALLED: 'onboarding.dependencies_installed',
|
|
2203
2490
|
ONBOARDING_GITHUB_STAR: 'onboarding.github_star',
|
|
2491
|
+
AUTH_LOGIN_STARTED: 'auth.login.started',
|
|
2492
|
+
AUTH_LOGIN_SUCCEEDED: 'auth.login.succeeded',
|
|
2493
|
+
AUTH_LOGIN_FAILED: 'auth.login.failed',
|
|
2494
|
+
AUTH_LOGOUT: 'auth.logout',
|
|
2495
|
+
PROJECTS_LISTED: 'projects.listed',
|
|
2496
|
+
PROJECT_SELECTED: 'project.selected',
|
|
2497
|
+
PROJECT_CREATED: 'project.created',
|
|
2204
2498
|
ERROR_OCCURRED: 'error.occurred',
|
|
2205
2499
|
MIGRATION_STARTED: 'migration.started',
|
|
2206
2500
|
MIGRATION_PLANNED: 'migration.planned',
|
|
@@ -2218,178 +2512,408 @@ const TelemetryEventName = {
|
|
|
2218
2512
|
CLI_STATE_CANCELLED: 'cli.state.cancelled',
|
|
2219
2513
|
CLI_STATE_COMPLETE: 'cli.state.complete'
|
|
2220
2514
|
};
|
|
2515
|
+
const DEFAULT_QUEUE_LIMIT = 250;
|
|
2516
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
2517
|
+
const DEFAULT_BATCH_SIZE = 20;
|
|
2518
|
+
const DEFAULT_BATCH_INTERVAL_MS = 1000;
|
|
2519
|
+
const DEFAULT_MAX_BUFFER_SIZE = 250;
|
|
2520
|
+
const MAX_DEPTH = 5;
|
|
2521
|
+
const MAX_ARRAY_LENGTH = 20;
|
|
2522
|
+
const MAX_OBJECT_KEYS = 50;
|
|
2523
|
+
const MAX_STRING_LENGTH = 500;
|
|
2524
|
+
const RESERVED_TOP_LEVEL_KEYS = new Set([
|
|
2525
|
+
'event',
|
|
2526
|
+
'installId',
|
|
2527
|
+
'sessionId',
|
|
2528
|
+
'commandRunId',
|
|
2529
|
+
'sequence',
|
|
2530
|
+
'source'
|
|
2531
|
+
]);
|
|
2532
|
+
const SENSITIVE_KEY_PATTERN = /(^|[-_])(token|secret|password|authorization|cookie|api[-_]?key|access[-_]?token|refresh[-_]?token|config)([-_]|$)/i;
|
|
2533
|
+
const SECRET_VALUE_PATTERN = /^(Bearer\s+[A-Za-z0-9._-]+|[A-Za-z0-9+/=_-]{80,})$/;
|
|
2221
2534
|
class Telemetry {
|
|
2222
|
-
|
|
2223
|
-
|
|
2535
|
+
endpoint;
|
|
2536
|
+
fetchImpl;
|
|
2537
|
+
queuePath;
|
|
2538
|
+
statePath;
|
|
2539
|
+
headers;
|
|
2224
2540
|
defaultProperties;
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2541
|
+
sessionId = node_crypto.randomUUID();
|
|
2542
|
+
installId;
|
|
2543
|
+
isFirstRun;
|
|
2544
|
+
drain;
|
|
2545
|
+
storageDir;
|
|
2228
2546
|
logger;
|
|
2547
|
+
disabled;
|
|
2548
|
+
debug;
|
|
2549
|
+
sequence = 0;
|
|
2550
|
+
activeCommandName;
|
|
2551
|
+
activeCommandRunId;
|
|
2552
|
+
flushPromise = null;
|
|
2553
|
+
queueReplayPromise = Promise.resolve();
|
|
2554
|
+
queueWritePromise = Promise.resolve();
|
|
2229
2555
|
constructor(options){
|
|
2230
|
-
const envDisabled = '1' === process.env[
|
|
2231
|
-
|
|
2232
|
-
this.disabled = options?.disabled ?? envDisabled ?? !hasValidApiKey;
|
|
2233
|
-
this.defaultProperties = options?.defaultProperties ?? {};
|
|
2234
|
-
this.logger = options?.logger;
|
|
2556
|
+
const envDisabled = '1' === process.env[ENV_VARS.TELEMETRY_DISABLED] || process.env[ENV_VARS.TELEMETRY_DISABLED]?.toLowerCase() === 'true';
|
|
2557
|
+
this.disabled = options?.disabled ?? envDisabled ?? false;
|
|
2235
2558
|
this.debug = options?.debug ?? false;
|
|
2236
|
-
this.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2559
|
+
this.logger = options?.logger;
|
|
2560
|
+
this.defaultProperties = this.sanitizeProperties(options?.defaultProperties ?? {});
|
|
2561
|
+
this.endpoint = options?.endpoint ?? process.env[ENV_VARS.TELEMETRY_ENDPOINT] ?? constants_URLS.TELEMETRY;
|
|
2562
|
+
this.fetchImpl = options?.fetch ?? fetch;
|
|
2563
|
+
this.storageDir = options?.storageDir ?? node_path.join(node_os.homedir(), PATHS.CONFIG_DIR);
|
|
2564
|
+
this.statePath = node_path.join(this.storageDir, PATHS.TELEMETRY_STATE_FILE);
|
|
2565
|
+
this.queuePath = node_path.join(this.storageDir, PATHS.TELEMETRY_QUEUE_FILE);
|
|
2566
|
+
this.headers = this.buildHeaders(options?.headers);
|
|
2567
|
+
const identity = this.loadOrCreateInstallIdentity();
|
|
2568
|
+
this.installId = identity.installId;
|
|
2569
|
+
this.isFirstRun = identity.isFirstRun;
|
|
2570
|
+
const userDrainOptions = options?.drainOptions;
|
|
2571
|
+
const onDropped = userDrainOptions?.onDropped;
|
|
2572
|
+
const pipeline = createDrainPipeline({
|
|
2573
|
+
batch: {
|
|
2574
|
+
size: userDrainOptions?.batch?.size ?? DEFAULT_BATCH_SIZE,
|
|
2575
|
+
intervalMs: userDrainOptions?.batch?.intervalMs ?? DEFAULT_BATCH_INTERVAL_MS
|
|
2576
|
+
},
|
|
2577
|
+
retry: {
|
|
2578
|
+
maxAttempts: userDrainOptions?.retry?.maxAttempts ?? 2,
|
|
2579
|
+
backoff: userDrainOptions?.retry?.backoff ?? 'fixed',
|
|
2580
|
+
initialDelayMs: userDrainOptions?.retry?.initialDelayMs ?? 250,
|
|
2581
|
+
maxDelayMs: userDrainOptions?.retry?.maxDelayMs ?? 1000
|
|
2582
|
+
},
|
|
2583
|
+
maxBufferSize: userDrainOptions?.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE,
|
|
2584
|
+
onDropped: (events, error)=>{
|
|
2585
|
+
onDropped?.(events, error);
|
|
2586
|
+
this.persistDroppedEvents(events, error);
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
2589
|
+
this.drain = pipeline(async (batch)=>{
|
|
2590
|
+
await this.sendBatch(batch.map((item)=>item.event));
|
|
2591
|
+
});
|
|
2592
|
+
this.applyLoggerConfig();
|
|
2593
|
+
this.queueReplayPromise = this.flushQueuedEvents();
|
|
2245
2594
|
}
|
|
2246
2595
|
trackEvent(eventName, properties = {}) {
|
|
2247
|
-
if (this.disabled
|
|
2248
|
-
if (this.debug) this.logDebug(`Telemetry event skipped (${eventName}):
|
|
2249
|
-
return;
|
|
2250
|
-
}
|
|
2251
|
-
try {
|
|
2252
|
-
const safeProperties = {};
|
|
2253
|
-
for (const [key, value] of Object.entries(properties))if ('config' !== key && void 0 !== value) safeProperties[key] = value;
|
|
2254
|
-
if (this.debug) this.logDebug(`Sending telemetry event: ${eventName}`);
|
|
2255
|
-
this.client.capture({
|
|
2256
|
-
distinctId: this.distinctId,
|
|
2257
|
-
event: eventName,
|
|
2258
|
-
properties: {
|
|
2259
|
-
...this.defaultProperties,
|
|
2260
|
-
...safeProperties,
|
|
2261
|
-
timestamp: new Date().toISOString()
|
|
2262
|
-
}
|
|
2263
|
-
});
|
|
2264
|
-
this.client.flush();
|
|
2265
|
-
} catch (error) {
|
|
2266
|
-
if (this.debug) this.logDebug(`Error sending telemetry event ${eventName}:`, error);
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
trackEventSync(eventName, properties = {}) {
|
|
2270
|
-
if (this.disabled || !this.client) {
|
|
2271
|
-
if (this.debug) this.logDebug('Telemetry disabled or client not initialized');
|
|
2596
|
+
if (this.disabled) {
|
|
2597
|
+
if (this.debug) this.logDebug(`Telemetry event skipped (${eventName}): telemetry disabled`);
|
|
2272
2598
|
return;
|
|
2273
2599
|
}
|
|
2274
|
-
const safeProperties = {};
|
|
2275
|
-
for (const [key, value] of Object.entries(properties))if ('config' !== key && void 0 !== value) safeProperties[key] = value;
|
|
2276
|
-
if (this.debug) this.logDebug(`Sending telemetry event: ${eventName}`);
|
|
2277
2600
|
try {
|
|
2278
|
-
this.
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
...this.defaultProperties,
|
|
2283
|
-
...safeProperties,
|
|
2284
|
-
timestamp: new Date().toISOString()
|
|
2285
|
-
}
|
|
2286
|
-
});
|
|
2287
|
-
this.client.flush();
|
|
2288
|
-
if (this.debug) this.logDebug(`Flushed telemetry event: ${eventName}`);
|
|
2601
|
+
const log = createLogger(this.buildBaseContext(eventName));
|
|
2602
|
+
log.set(this.sanitizeProperties(properties));
|
|
2603
|
+
log.emit();
|
|
2604
|
+
if (this.debug) this.logDebug(`Queued telemetry event: ${eventName}`);
|
|
2289
2605
|
} catch (error) {
|
|
2290
|
-
if (this.debug) this.logDebug(`
|
|
2606
|
+
if (this.debug) this.logDebug(`Failed to queue telemetry event ${eventName}:`, error);
|
|
2291
2607
|
}
|
|
2292
2608
|
}
|
|
2293
2609
|
trackCommand(command, args = [], flags = {}) {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2610
|
+
this.activeCommandName = command;
|
|
2611
|
+
this.activeCommandRunId = node_crypto.randomUUID();
|
|
2612
|
+
const safeFlags = this.sanitizeProperties(flags);
|
|
2613
|
+
const safeArgs = this.sanitizeValue(args);
|
|
2297
2614
|
this.trackEvent(TelemetryEventName.COMMAND_EXECUTED, {
|
|
2298
2615
|
command,
|
|
2299
|
-
|
|
2300
|
-
|
|
2616
|
+
commandRunId: this.activeCommandRunId,
|
|
2617
|
+
args: safeArgs,
|
|
2618
|
+
argsCount: args.length,
|
|
2619
|
+
flags: safeFlags,
|
|
2620
|
+
flagCount: Object.keys(safeFlags).length,
|
|
2621
|
+
flagNames: Object.keys(safeFlags).sort(),
|
|
2622
|
+
subcommand: 'string' == typeof safeArgs[0] ? safeArgs[0] : void 0
|
|
2301
2623
|
});
|
|
2302
2624
|
}
|
|
2303
2625
|
trackError(error, command) {
|
|
2304
|
-
if (this.disabled
|
|
2305
|
-
this.
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2626
|
+
if (this.disabled) return;
|
|
2627
|
+
const safeCommand = command ?? this.activeCommandName;
|
|
2628
|
+
const safeError = this.sanitizeError(error);
|
|
2629
|
+
const errorMetadata = this.buildErrorMetadata(error);
|
|
2630
|
+
try {
|
|
2631
|
+
const log = createLogger(this.buildBaseContext(TelemetryEventName.ERROR_OCCURRED));
|
|
2632
|
+
log.error(safeError, {
|
|
2633
|
+
command: safeCommand,
|
|
2634
|
+
commandRunId: this.activeCommandRunId,
|
|
2635
|
+
failure: errorMetadata
|
|
2636
|
+
});
|
|
2637
|
+
log.emit();
|
|
2638
|
+
if (this.debug) this.logDebug(`Queued telemetry error event: ${safeCommand ?? 'unknown-command'}`);
|
|
2639
|
+
} catch (trackingError) {
|
|
2640
|
+
if (this.debug) this.logDebug('Failed to queue telemetry error event:', trackingError);
|
|
2641
|
+
}
|
|
2311
2642
|
}
|
|
2312
|
-
|
|
2313
|
-
this.disabled
|
|
2643
|
+
flushSync() {
|
|
2644
|
+
if (this.disabled) return;
|
|
2645
|
+
this.flushPromise = this.flushAll();
|
|
2314
2646
|
}
|
|
2315
|
-
|
|
2316
|
-
this.disabled
|
|
2317
|
-
|
|
2647
|
+
async shutdown() {
|
|
2648
|
+
if (this.disabled) return;
|
|
2649
|
+
await (this.flushPromise ?? this.flushAll());
|
|
2318
2650
|
}
|
|
2319
2651
|
isDisabled() {
|
|
2320
2652
|
return this.disabled;
|
|
2321
2653
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2654
|
+
disable() {
|
|
2655
|
+
this.disabled = true;
|
|
2656
|
+
this.applyLoggerConfig();
|
|
2657
|
+
}
|
|
2658
|
+
enable() {
|
|
2659
|
+
this.disabled = false;
|
|
2660
|
+
this.applyLoggerConfig();
|
|
2661
|
+
this.queueReplayPromise = this.flushQueuedEvents();
|
|
2327
2662
|
}
|
|
2328
2663
|
setLogger(logger) {
|
|
2329
2664
|
this.logger = logger;
|
|
2330
2665
|
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2666
|
+
applyLoggerConfig() {
|
|
2667
|
+
const cliVersion = this.readString(this.defaultProperties.cliVersion);
|
|
2668
|
+
initLogger({
|
|
2669
|
+
enabled: !this.disabled,
|
|
2670
|
+
silent: true,
|
|
2671
|
+
pretty: false,
|
|
2672
|
+
stringify: false,
|
|
2673
|
+
_suppressDrainWarning: this.disabled,
|
|
2674
|
+
env: {
|
|
2675
|
+
service: 'c15t-cli',
|
|
2676
|
+
environment: this.getEnvironmentName(),
|
|
2677
|
+
version: cliVersion
|
|
2678
|
+
},
|
|
2679
|
+
drain: this.disabled ? void 0 : this.drain
|
|
2680
|
+
});
|
|
2334
2681
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2682
|
+
buildBaseContext(eventName) {
|
|
2683
|
+
this.sequence += 1;
|
|
2684
|
+
return {
|
|
2685
|
+
event: eventName,
|
|
2686
|
+
source: 'c15t-cli',
|
|
2687
|
+
installId: this.installId,
|
|
2688
|
+
sessionId: this.sessionId,
|
|
2689
|
+
commandRunId: this.activeCommandRunId,
|
|
2690
|
+
command: this.activeCommandName,
|
|
2691
|
+
sequence: this.sequence,
|
|
2692
|
+
firstRun: this.isFirstRun,
|
|
2693
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
2694
|
+
tty: Boolean(process.stdout.isTTY),
|
|
2695
|
+
ci: this.isCi(),
|
|
2696
|
+
platform: process.platform,
|
|
2697
|
+
arch: process.arch,
|
|
2698
|
+
nodeVersion: process.version,
|
|
2699
|
+
...this.defaultProperties
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
buildHeaders(overrides) {
|
|
2703
|
+
const writeKey = process.env[ENV_VARS.TELEMETRY_WRITE_KEY];
|
|
2704
|
+
const orgId = process.env[ENV_VARS.TELEMETRY_ORG_ID];
|
|
2705
|
+
return {
|
|
2706
|
+
'Content-Type': 'application/json',
|
|
2707
|
+
...writeKey ? {
|
|
2708
|
+
Authorization: `Bearer ${writeKey}`
|
|
2709
|
+
} : {},
|
|
2710
|
+
...orgId ? {
|
|
2711
|
+
'X-Axiom-Org-Id': orgId
|
|
2712
|
+
} : {},
|
|
2713
|
+
...overrides
|
|
2714
|
+
};
|
|
2715
|
+
}
|
|
2716
|
+
async flushAll() {
|
|
2717
|
+
try {
|
|
2718
|
+
await this.queueReplayPromise;
|
|
2719
|
+
await this.drain.flush();
|
|
2720
|
+
await this.queueWritePromise;
|
|
2721
|
+
await this.flushQueuedEvents();
|
|
2722
|
+
if (this.debug) this.logDebug('Flushed telemetry events');
|
|
2723
|
+
} catch (error) {
|
|
2724
|
+
if (this.debug) this.logDebug('Telemetry flush failed:', error);
|
|
2725
|
+
} finally{
|
|
2726
|
+
this.flushPromise = null;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
async sendBatch(events) {
|
|
2730
|
+
if (0 === events.length) return;
|
|
2731
|
+
const controller = new AbortController();
|
|
2732
|
+
const timeout = setTimeout(()=>controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
2733
|
+
try {
|
|
2734
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
2735
|
+
method: 'POST',
|
|
2736
|
+
headers: this.headers,
|
|
2737
|
+
body: JSON.stringify(events),
|
|
2738
|
+
signal: controller.signal
|
|
2739
|
+
});
|
|
2740
|
+
if (!response.ok) throw new Error(`Telemetry ingest failed with status ${response.status}`);
|
|
2741
|
+
} finally{
|
|
2742
|
+
clearTimeout(timeout);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
async persistDroppedEvents(events, error) {
|
|
2746
|
+
this.queueWritePromise = this.queueWritePromise.then(async ()=>{
|
|
2346
2747
|
try {
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
if (this.debug) this.logDebug(
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
if (this.debug) this.logDebug('PostHog client initialized in', initTime, 'ms');
|
|
2357
|
-
} catch (error) {
|
|
2358
|
-
this.disabled = true;
|
|
2359
|
-
const errorDetails = error instanceof Error ? {
|
|
2360
|
-
message: error.message,
|
|
2361
|
-
name: error.name,
|
|
2362
|
-
stack: error.stack
|
|
2363
|
-
} : {
|
|
2364
|
-
rawError: String(error)
|
|
2365
|
-
};
|
|
2366
|
-
if (this.debug) this.logDebug('Telemetry disabled due to initialization error:', JSON.stringify(errorDetails, null, 2));
|
|
2367
|
-
try {
|
|
2368
|
-
if (this.debug) this.logDebug('Attempting fallback PostHog initialization');
|
|
2369
|
-
this.client = new PostHog(this.apiKey);
|
|
2370
|
-
this.disabled = false;
|
|
2371
|
-
if (this.debug) this.logDebug('PostHog client initialized using fallback method');
|
|
2372
|
-
} catch (fallbackError) {
|
|
2373
|
-
this.logDebug('Fallback initialization also failed:', fallbackError instanceof Error ? fallbackError.message : String(fallbackError));
|
|
2374
|
-
}
|
|
2748
|
+
const existing = await this.readQueuedEvents();
|
|
2749
|
+
const next = [
|
|
2750
|
+
...existing,
|
|
2751
|
+
...events.map((item)=>item.event)
|
|
2752
|
+
].slice(-DEFAULT_QUEUE_LIMIT);
|
|
2753
|
+
await this.writeQueuedEvents(next);
|
|
2754
|
+
if (this.debug) this.logDebug(`Persisted ${events.length} dropped telemetry event(s) to disk`, error);
|
|
2755
|
+
} catch (queueError) {
|
|
2756
|
+
if (this.debug) this.logDebug('Failed to persist dropped telemetry events:', queueError);
|
|
2375
2757
|
}
|
|
2758
|
+
});
|
|
2759
|
+
await this.queueWritePromise;
|
|
2760
|
+
}
|
|
2761
|
+
async flushQueuedEvents() {
|
|
2762
|
+
if (this.disabled) return;
|
|
2763
|
+
try {
|
|
2764
|
+
const queuedEvents = await this.readQueuedEvents();
|
|
2765
|
+
if (0 === queuedEvents.length) return;
|
|
2766
|
+
await this.sendBatch(queuedEvents);
|
|
2767
|
+
await promises.unlink(this.queuePath).catch(()=>void 0);
|
|
2768
|
+
if (this.debug) this.logDebug(`Replayed ${queuedEvents.length} queued telemetry event(s)`);
|
|
2769
|
+
} catch (error) {
|
|
2770
|
+
if (this.debug) this.logDebug('Failed to replay queued telemetry events:', error);
|
|
2376
2771
|
}
|
|
2377
2772
|
}
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2773
|
+
async readQueuedEvents() {
|
|
2774
|
+
try {
|
|
2775
|
+
const content = await promises.readFile(this.queuePath, 'utf-8');
|
|
2776
|
+
const parsed = JSON.parse(content);
|
|
2777
|
+
if (!Array.isArray(parsed)) return [];
|
|
2778
|
+
return parsed.filter((item)=>'object' == typeof item && null !== item);
|
|
2779
|
+
} catch {
|
|
2780
|
+
return [];
|
|
2781
|
+
}
|
|
2381
2782
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
2783
|
+
async writeQueuedEvents(events) {
|
|
2784
|
+
await promises.mkdir(this.storageDir, {
|
|
2785
|
+
recursive: true
|
|
2786
|
+
});
|
|
2787
|
+
await promises.writeFile(this.queuePath, JSON.stringify(events, null, 2), {
|
|
2788
|
+
mode: 384
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
loadOrCreateInstallIdentity() {
|
|
2384
2792
|
try {
|
|
2385
|
-
this.
|
|
2386
|
-
|
|
2793
|
+
node_fs.mkdirSync(this.storageDir, {
|
|
2794
|
+
recursive: true
|
|
2795
|
+
});
|
|
2796
|
+
if (node_fs.existsSync(this.statePath)) {
|
|
2797
|
+
const content = node_fs.readFileSync(this.statePath, 'utf-8');
|
|
2798
|
+
const parsed = JSON.parse(content);
|
|
2799
|
+
if ('string' == typeof parsed.installId && parsed.installId.length > 0) return {
|
|
2800
|
+
installId: parsed.installId,
|
|
2801
|
+
isFirstRun: false
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2387
2804
|
} catch (error) {
|
|
2388
|
-
if (this.debug) this.logDebug(
|
|
2805
|
+
if (this.debug) this.logDebug('Failed to read telemetry state file:', error);
|
|
2806
|
+
}
|
|
2807
|
+
const installId = node_crypto.randomUUID();
|
|
2808
|
+
try {
|
|
2809
|
+
node_fs.writeFileSync(this.statePath, JSON.stringify({
|
|
2810
|
+
installId,
|
|
2811
|
+
createdAt: new Date().toISOString()
|
|
2812
|
+
}, null, 2), {
|
|
2813
|
+
mode: 384
|
|
2814
|
+
});
|
|
2815
|
+
} catch (error) {
|
|
2816
|
+
if (this.debug) this.logDebug('Failed to persist telemetry install ID:', error);
|
|
2817
|
+
}
|
|
2818
|
+
return {
|
|
2819
|
+
installId,
|
|
2820
|
+
isFirstRun: true
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
buildErrorMetadata(error) {
|
|
2824
|
+
const eventError = error;
|
|
2825
|
+
return this.sanitizeProperties({
|
|
2826
|
+
name: eventError.name,
|
|
2827
|
+
message: eventError.message,
|
|
2828
|
+
code: 'string' == typeof eventError.code || 'number' == typeof eventError.code ? eventError.code : void 0,
|
|
2829
|
+
status: 'number' == typeof eventError.status ? eventError.status : void 0,
|
|
2830
|
+
statusCode: 'number' == typeof eventError.statusCode ? eventError.statusCode : void 0,
|
|
2831
|
+
cause: eventError.cause instanceof Error ? eventError.cause.message : 'string' == typeof eventError.cause ? eventError.cause : void 0
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2834
|
+
sanitizeError(error) {
|
|
2835
|
+
const safeError = new Error(error.message);
|
|
2836
|
+
safeError.name = error.name;
|
|
2837
|
+
if ('development' === process.env.NODE_ENV) safeError.stack = error.stack;
|
|
2838
|
+
else delete safeError.stack;
|
|
2839
|
+
const sourceError = error;
|
|
2840
|
+
const targetError = safeError;
|
|
2841
|
+
if ('number' == typeof sourceError.status) targetError.status = sourceError.status;
|
|
2842
|
+
if ('number' == typeof sourceError.statusCode) targetError.statusCode = sourceError.statusCode;
|
|
2843
|
+
if ('string' == typeof sourceError.statusText) targetError.statusText = sourceError.statusText;
|
|
2844
|
+
if ('string' == typeof sourceError.statusMessage) targetError.statusMessage = sourceError.statusMessage;
|
|
2845
|
+
if (sourceError.cause instanceof Error) targetError.cause = sourceError.cause.message;
|
|
2846
|
+
else if ('string' == typeof sourceError.cause) targetError.cause = sourceError.cause;
|
|
2847
|
+
return safeError;
|
|
2848
|
+
}
|
|
2849
|
+
sanitizeProperties(properties) {
|
|
2850
|
+
const sanitized = {};
|
|
2851
|
+
for (const [key, value] of Object.entries(properties))if (!RESERVED_TOP_LEVEL_KEYS.has(key)) {
|
|
2852
|
+
if (void 0 !== value) {
|
|
2853
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
2854
|
+
sanitized[key] = '[redacted]';
|
|
2855
|
+
continue;
|
|
2856
|
+
}
|
|
2857
|
+
sanitized[key] = this.sanitizeValue(value, 0, key);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
return sanitized;
|
|
2861
|
+
}
|
|
2862
|
+
sanitizeValue(value, depth = 0, keyHint) {
|
|
2863
|
+
if (depth >= MAX_DEPTH) return '[truncated]';
|
|
2864
|
+
if (null === value) return null;
|
|
2865
|
+
if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value) return this.sanitizePrimitive(value, keyHint);
|
|
2866
|
+
if (value instanceof Date) return value.toISOString();
|
|
2867
|
+
if (value instanceof Error) return this.sanitizeProperties(this.buildErrorMetadata(value));
|
|
2868
|
+
if (Array.isArray(value)) return value.slice(0, MAX_ARRAY_LENGTH).map((item)=>this.sanitizeValue(item, depth + 1));
|
|
2869
|
+
if ('object' == typeof value) {
|
|
2870
|
+
const objectValue = value;
|
|
2871
|
+
const sanitizedObject = {};
|
|
2872
|
+
for (const key of Object.keys(objectValue).slice(0, MAX_OBJECT_KEYS)){
|
|
2873
|
+
const nextValue = objectValue[key];
|
|
2874
|
+
if (void 0 !== nextValue) {
|
|
2875
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
2876
|
+
sanitizedObject[key] = '[redacted]';
|
|
2877
|
+
continue;
|
|
2878
|
+
}
|
|
2879
|
+
sanitizedObject[key] = this.sanitizeValue(nextValue, depth + 1, key);
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
return sanitizedObject;
|
|
2389
2883
|
}
|
|
2884
|
+
return String(value);
|
|
2885
|
+
}
|
|
2886
|
+
sanitizePrimitive(value, keyHint) {
|
|
2887
|
+
if ('string' != typeof value) return value;
|
|
2888
|
+
if (keyHint && SENSITIVE_KEY_PATTERN.test(keyHint)) return '[redacted]';
|
|
2889
|
+
if (SECRET_VALUE_PATTERN.test(value)) return '[redacted]';
|
|
2890
|
+
if (node_path.isAbsolute(value)) return '[absolute-path]';
|
|
2891
|
+
if (value.startsWith('http://') || value.startsWith('https://')) try {
|
|
2892
|
+
const parsed = new URL(value);
|
|
2893
|
+
parsed.username = '';
|
|
2894
|
+
parsed.password = '';
|
|
2895
|
+
parsed.search = '';
|
|
2896
|
+
parsed.hash = '';
|
|
2897
|
+
value = parsed.toString();
|
|
2898
|
+
} catch {}
|
|
2899
|
+
if (value.length > MAX_STRING_LENGTH) return `${value.slice(0, MAX_STRING_LENGTH)}...`;
|
|
2900
|
+
return value;
|
|
2901
|
+
}
|
|
2902
|
+
getEnvironmentName() {
|
|
2903
|
+
return process.env.NODE_ENV ?? (this.isCi() ? 'production' : 'development');
|
|
2904
|
+
}
|
|
2905
|
+
isCi() {
|
|
2906
|
+
return Boolean(process.env.CI || process.env.GITHUB_ACTIONS || process.env.BUILDKITE || process.env.VERCEL);
|
|
2907
|
+
}
|
|
2908
|
+
readString(value) {
|
|
2909
|
+
return 'string' == typeof value ? value : void 0;
|
|
2910
|
+
}
|
|
2911
|
+
logDebug(message, ...args) {
|
|
2912
|
+
if (this.logger) this.logger.debug(message, ...args);
|
|
2913
|
+
else console.debug(message, ...args);
|
|
2390
2914
|
}
|
|
2391
2915
|
}
|
|
2392
|
-
function
|
|
2916
|
+
function telemetry_createTelemetry(options) {
|
|
2393
2917
|
return new Telemetry(options);
|
|
2394
2918
|
}
|
|
2395
2919
|
const DEFAULT_PERSIST_FILENAME = '.c15t-state.json';
|
|
@@ -2484,6 +3008,86 @@ function createPersistenceSubscriber(machineId, persistPath, options = {}) {
|
|
|
2484
3008
|
});
|
|
2485
3009
|
};
|
|
2486
3010
|
}
|
|
3011
|
+
const GENERATE_STAGE_NAMES = {
|
|
3012
|
+
preflight: 'preflight',
|
|
3013
|
+
preflightError: 'preflight',
|
|
3014
|
+
modeSelection: 'mode_selection',
|
|
3015
|
+
hostedMode: 'hosted_mode',
|
|
3016
|
+
offlineMode: 'offline_mode',
|
|
3017
|
+
customMode: 'custom_mode',
|
|
3018
|
+
backendOptions: 'backend_options',
|
|
3019
|
+
frontendOptions: 'frontend_options',
|
|
3020
|
+
scriptsOptions: "scripts_options",
|
|
3021
|
+
fileGeneration: 'file_generation',
|
|
3022
|
+
dependencyCheck: 'dependency_check',
|
|
3023
|
+
dependencyConfirm: 'dependency_confirm',
|
|
3024
|
+
dependencyInstall: 'dependency_install',
|
|
3025
|
+
summary: 'summary',
|
|
3026
|
+
skillsInstall: 'skills_install',
|
|
3027
|
+
githubStar: 'github_star',
|
|
3028
|
+
cancelling: 'cancelling',
|
|
3029
|
+
cleanup: 'cleanup',
|
|
3030
|
+
complete: 'complete',
|
|
3031
|
+
error: 'error',
|
|
3032
|
+
exited: 'exited',
|
|
3033
|
+
cancelled: 'cancelled'
|
|
3034
|
+
};
|
|
3035
|
+
function normalizeGenerateStageName(state) {
|
|
3036
|
+
return GENERATE_STAGE_NAMES[state] ?? state.replace(/[A-Z]/g, (char)=>`_${char.toLowerCase()}`);
|
|
3037
|
+
}
|
|
3038
|
+
function getGenerateContext(snapshot) {
|
|
3039
|
+
return snapshot.context;
|
|
3040
|
+
}
|
|
3041
|
+
function normalizeCancelReason(reason) {
|
|
3042
|
+
if (!reason) return 'user_cancelled';
|
|
3043
|
+
const normalized = reason.toLowerCase();
|
|
3044
|
+
if (normalized.includes('signal')) return 'signal_interrupted';
|
|
3045
|
+
if (normalized.includes('mode selection')) return 'mode_selection_cancelled';
|
|
3046
|
+
if (normalized.includes('hosted setup')) return 'hosted_setup_cancelled';
|
|
3047
|
+
if (normalized.includes('backend options')) return 'backend_options_cancelled';
|
|
3048
|
+
if (normalized.includes('frontend options')) return 'frontend_options_cancelled';
|
|
3049
|
+
if (normalized.includes("scripts option")) return "scripts_options_cancelled";
|
|
3050
|
+
if (normalized.includes('dependency')) return 'dependency_install_cancelled';
|
|
3051
|
+
if (normalized.includes('prompt cancelled at stage:')) return normalized.replace('prompt cancelled at stage:', '').trim().replace(/\s+/g, '_').concat('_cancelled');
|
|
3052
|
+
return 'user_cancelled';
|
|
3053
|
+
}
|
|
3054
|
+
function getStageReason(fromState, toState, context) {
|
|
3055
|
+
if ('preflightError' === toState) return 'preflight_failed';
|
|
3056
|
+
if ('cancelling' === toState || 'cancelled' === toState || 'exited' === toState) return normalizeCancelReason(context?.cancelReason);
|
|
3057
|
+
if ('dependencyInstall' === fromState && context?.installSucceeded === false) return 'dependency_install_failed';
|
|
3058
|
+
if ('error' === toState) {
|
|
3059
|
+
const lastError = context?.errors?.[context.errors.length - 1];
|
|
3060
|
+
if (lastError?.state === 'fileGeneration') return 'file_generation_failed';
|
|
3061
|
+
if (lastError?.error?.name === 'PromptCancelledError') return normalizeCancelReason(lastError.error.message);
|
|
3062
|
+
if (lastError?.state) return `${normalizeGenerateStageName(lastError.state)}_failed`;
|
|
3063
|
+
return 'machine_error';
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
function getStageResult(fromState, toState, context) {
|
|
3067
|
+
if ('preflightError' === toState || 'error' === toState) return 'failed';
|
|
3068
|
+
if ('cancelling' === toState || 'cancelled' === toState || 'exited' === toState) return 'cancelled';
|
|
3069
|
+
if ('dependencyInstall' === fromState && context?.installSucceeded === false) return 'failed';
|
|
3070
|
+
return 'completed';
|
|
3071
|
+
}
|
|
3072
|
+
function buildGenerateStageTelemetry(fromState, toState, durationMs, snapshot) {
|
|
3073
|
+
const context = getGenerateContext(snapshot);
|
|
3074
|
+
return {
|
|
3075
|
+
stage: normalizeGenerateStageName(fromState),
|
|
3076
|
+
nextStage: normalizeGenerateStageName(toState),
|
|
3077
|
+
durationMs,
|
|
3078
|
+
result: getStageResult(fromState, toState, context),
|
|
3079
|
+
reason: getStageReason(fromState, toState, context),
|
|
3080
|
+
selectedMode: context?.selectedMode ?? void 0,
|
|
3081
|
+
hostedProvider: context?.hostedProvider ?? void 0,
|
|
3082
|
+
dependencyCount: context?.dependenciesToAdd?.length ?? 0,
|
|
3083
|
+
filesCreatedCount: context?.filesCreated?.length ?? 0,
|
|
3084
|
+
filesModifiedCount: context?.filesModified?.length ?? 0,
|
|
3085
|
+
installConfirmed: context?.installConfirmed ?? void 0,
|
|
3086
|
+
installAttempted: context?.installAttempted ?? void 0,
|
|
3087
|
+
installSucceeded: context?.installSucceeded ?? void 0,
|
|
3088
|
+
errorsCount: context?.errors?.length ?? 0
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
2487
3091
|
function createTelemetrySubscriber(config) {
|
|
2488
3092
|
const { telemetry, machineId, skipStates = [] } = config;
|
|
2489
3093
|
let lastState = null;
|
|
@@ -2502,6 +3106,7 @@ function createTelemetrySubscriber(config) {
|
|
|
2502
3106
|
toState: currentState,
|
|
2503
3107
|
duration
|
|
2504
3108
|
});
|
|
3109
|
+
if ('generate' === machineId) telemetry.trackEvent(TelemetryEventName.ONBOARDING_STAGE, buildGenerateStageTelemetry(lastState, currentState, duration, snapshot));
|
|
2505
3110
|
}
|
|
2506
3111
|
stateHistory.push({
|
|
2507
3112
|
state: currentState,
|
|
@@ -2556,89 +3161,6 @@ function createDebugSubscriber(machineId, logger) {
|
|
|
2556
3161
|
}
|
|
2557
3162
|
};
|
|
2558
3163
|
}
|
|
2559
|
-
const constants_URLS = {
|
|
2560
|
-
CONSENT_IO: 'https://consent.io',
|
|
2561
|
-
DOCS: 'https://v2.c15t.com/docs',
|
|
2562
|
-
GITHUB: 'https://github.com/c15t/c15t',
|
|
2563
|
-
DISCORD: 'https://v2.c15t.com/discord',
|
|
2564
|
-
API_DOCS: 'https://v2.c15t.com/docs/api',
|
|
2565
|
-
CLI_DOCS: 'https://v2.c15t.com/docs/cli',
|
|
2566
|
-
CHANGELOG: 'https://v2.c15t.com/changelog'
|
|
2567
|
-
};
|
|
2568
|
-
const PATHS = {
|
|
2569
|
-
CONFIG_DIR: '.c15t',
|
|
2570
|
-
CONFIG_FILE: 'config.json',
|
|
2571
|
-
PROJECT_CONFIG: 'c15t.config.ts',
|
|
2572
|
-
PROJECT_CONFIG_JS: 'c15t.config.js',
|
|
2573
|
-
ENV_FILE: '.env',
|
|
2574
|
-
ENV_LOCAL: '.env.local'
|
|
2575
|
-
};
|
|
2576
|
-
const constants_REGEX = {
|
|
2577
|
-
URL: /^https?:\/\/.+/,
|
|
2578
|
-
C15T_URL: /^https:\/\/[\w-]+\.c15t\.dev$/,
|
|
2579
|
-
DYNAMIC_SEGMENT: /\[[\w-]+\]/,
|
|
2580
|
-
SEMVER: /^\d+\.\d+\.\d+(-[\w.]+)?$/,
|
|
2581
|
-
PACKAGE_NAME: /^(@[\w-]+\/)?[\w-]+$/
|
|
2582
|
-
};
|
|
2583
|
-
const CLI_INFO = {
|
|
2584
|
-
NAME: 'c15t',
|
|
2585
|
-
BIN: 'c15t',
|
|
2586
|
-
CONTROL_PLANE_CLIENT_NAME: 'c15t-cli',
|
|
2587
|
-
VERSION: '2.0.0'
|
|
2588
|
-
};
|
|
2589
|
-
const TIMEOUTS = {
|
|
2590
|
-
DEVICE_FLOW_POLL_INTERVAL: 5,
|
|
2591
|
-
DEVICE_FLOW_EXPIRY: 900,
|
|
2592
|
-
HTTP_REQUEST: 10000,
|
|
2593
|
-
CONTROL_PLANE_CONNECTION: 30000
|
|
2594
|
-
};
|
|
2595
|
-
const ENV_VARS = {
|
|
2596
|
-
V2: 'V2',
|
|
2597
|
-
TELEMETRY_DISABLED: 'C15T_TELEMETRY_DISABLED',
|
|
2598
|
-
CONSENT_URL: 'CONSENT_URL',
|
|
2599
|
-
BACKEND_URL: 'C15T_URL',
|
|
2600
|
-
API_KEY: 'C15T_API_KEY',
|
|
2601
|
-
DEBUG: 'C15T_DEBUG'
|
|
2602
|
-
};
|
|
2603
|
-
const STORAGE_MODES = {
|
|
2604
|
-
HOSTED: 'hosted',
|
|
2605
|
-
C15T: 'c15t',
|
|
2606
|
-
OFFLINE: 'offline',
|
|
2607
|
-
SELF_HOSTED: 'self-hosted',
|
|
2608
|
-
CUSTOM: 'custom'
|
|
2609
|
-
};
|
|
2610
|
-
const LAYOUT_PATTERNS = [
|
|
2611
|
-
'app/layout.tsx',
|
|
2612
|
-
'app/layout.ts',
|
|
2613
|
-
'app/layout.jsx',
|
|
2614
|
-
'app/layout.js',
|
|
2615
|
-
'src/app/layout.tsx',
|
|
2616
|
-
'src/app/layout.ts',
|
|
2617
|
-
'src/app/layout.jsx',
|
|
2618
|
-
'src/app/layout.js',
|
|
2619
|
-
'app/*/layout.tsx',
|
|
2620
|
-
'app/*/layout.ts',
|
|
2621
|
-
'app/*/layout.jsx',
|
|
2622
|
-
'app/*/layout.js',
|
|
2623
|
-
'src/app/*/layout.tsx',
|
|
2624
|
-
'src/app/*/layout.ts',
|
|
2625
|
-
'src/app/*/layout.jsx',
|
|
2626
|
-
'src/app/*/layout.js',
|
|
2627
|
-
'app/*/*/layout.tsx',
|
|
2628
|
-
'app/*/*/layout.ts',
|
|
2629
|
-
'src/app/*/*/layout.tsx',
|
|
2630
|
-
'src/app/*/*/layout.ts'
|
|
2631
|
-
];
|
|
2632
|
-
const PAGES_APP_PATTERNS = [
|
|
2633
|
-
'pages/_app.tsx',
|
|
2634
|
-
'pages/_app.ts',
|
|
2635
|
-
'pages/_app.jsx',
|
|
2636
|
-
'pages/_app.js',
|
|
2637
|
-
'src/pages/_app.tsx',
|
|
2638
|
-
'src/pages/_app.ts',
|
|
2639
|
-
'src/pages/_app.jsx',
|
|
2640
|
-
'src/pages/_app.js'
|
|
2641
|
-
];
|
|
2642
3164
|
const ERROR_CATALOG = {
|
|
2643
3165
|
AUTH_FAILED: {
|
|
2644
3166
|
code: 'AUTH_FAILED',
|
|
@@ -2726,17 +3248,17 @@ const ERROR_CATALOG = {
|
|
|
2726
3248
|
URL_INVALID: {
|
|
2727
3249
|
code: 'URL_INVALID',
|
|
2728
3250
|
message: 'Invalid URL format',
|
|
2729
|
-
hint: 'Expected format: https://your-
|
|
3251
|
+
hint: 'Expected format: https://your-project.c15t.dev'
|
|
2730
3252
|
},
|
|
2731
3253
|
INSTANCE_NOT_FOUND: {
|
|
2732
3254
|
code: 'INSTANCE_NOT_FOUND',
|
|
2733
|
-
message: '
|
|
2734
|
-
hint: 'Run `c15t
|
|
3255
|
+
message: 'Project not found',
|
|
3256
|
+
hint: 'Run `c15t projects list` to see available projects'
|
|
2735
3257
|
},
|
|
2736
3258
|
INSTANCE_NAME_INVALID: {
|
|
2737
3259
|
code: 'INSTANCE_NAME_INVALID',
|
|
2738
|
-
message: 'Invalid
|
|
2739
|
-
hint: '
|
|
3260
|
+
message: 'Invalid project slug',
|
|
3261
|
+
hint: 'Project slugs must be alphanumeric with hyphens'
|
|
2740
3262
|
},
|
|
2741
3263
|
FILE_NOT_FOUND: {
|
|
2742
3264
|
code: 'FILE_NOT_FOUND',
|
|
@@ -3255,11 +3777,19 @@ async function getAuthState() {
|
|
|
3255
3777
|
isExpired: isTokenExpired(config)
|
|
3256
3778
|
};
|
|
3257
3779
|
}
|
|
3780
|
+
async function isLoggedIn() {
|
|
3781
|
+
const state = await getAuthState();
|
|
3782
|
+
return state.isLoggedIn && !state.isExpired;
|
|
3783
|
+
}
|
|
3258
3784
|
async function config_store_getAccessToken() {
|
|
3259
3785
|
const config = await loadConfig();
|
|
3260
3786
|
if (!config || isTokenExpired(config)) return null;
|
|
3261
3787
|
return config.accessToken;
|
|
3262
3788
|
}
|
|
3789
|
+
async function getSelectedInstanceId() {
|
|
3790
|
+
const config = await loadConfig();
|
|
3791
|
+
return config?.selectedInstanceId || null;
|
|
3792
|
+
}
|
|
3263
3793
|
async function setSelectedInstanceId(instanceId) {
|
|
3264
3794
|
await updateConfig({
|
|
3265
3795
|
selectedInstanceId: instanceId
|
|
@@ -3668,7 +4198,7 @@ class ControlPlaneClient {
|
|
|
3668
4198
|
const instances = await this.listInstances();
|
|
3669
4199
|
const instance = instances.find((item)=>item.id === id);
|
|
3670
4200
|
if (!instance) throw new CliError('INSTANCE_NOT_FOUND', {
|
|
3671
|
-
details: `
|
|
4201
|
+
details: `Project not found: ${id}`
|
|
3672
4202
|
});
|
|
3673
4203
|
return instance;
|
|
3674
4204
|
}
|
|
@@ -3763,7 +4293,7 @@ function createValidator(validate, errorMessage) {
|
|
|
3763
4293
|
}
|
|
3764
4294
|
createValidator(isValidUrl, 'Please enter a valid URL (e.g., https://example.com)');
|
|
3765
4295
|
createValidator(isValidC15tUrl, 'Please enter a valid c15t URL (e.g., https://my-app.c15t.dev)');
|
|
3766
|
-
const validateInstanceName = createValidator(isValidInstanceName, '
|
|
4296
|
+
const validateInstanceName = createValidator(isValidInstanceName, 'Project slug must be 3-63 lowercase alphanumeric characters with hyphens');
|
|
3767
4297
|
createValidator((value)=>value.trim().length > 0, 'This field is required');
|
|
3768
4298
|
function isCancel(value) {
|
|
3769
4299
|
return __rspack_external__clack_prompts_3cae1695.isCancel(value);
|
|
@@ -3913,7 +4443,7 @@ async function createInstanceInteractively(client, cliContext) {
|
|
|
3913
4443
|
})),
|
|
3914
4444
|
initialValue: organizations[0]?.organizationSlug
|
|
3915
4445
|
});
|
|
3916
|
-
if (isCancel(orgSelection)) throw new PromptCancelledError('
|
|
4446
|
+
if (isCancel(orgSelection)) throw new PromptCancelledError('project_create_org_slug');
|
|
3917
4447
|
const v2Regions = regions.filter((region)=>'v2' === region.family);
|
|
3918
4448
|
if (0 === v2Regions.length) throw new CliError('API_ERROR', {
|
|
3919
4449
|
details: 'No v2 provisioning regions available'
|
|
@@ -3927,15 +4457,15 @@ async function createInstanceInteractively(client, cliContext) {
|
|
|
3927
4457
|
})),
|
|
3928
4458
|
initialValue: v2Regions.find((region)=>'us-east-1' === region.id)?.id
|
|
3929
4459
|
});
|
|
3930
|
-
if (isCancel(regionSelection)) throw new PromptCancelledError('
|
|
4460
|
+
if (isCancel(regionSelection)) throw new PromptCancelledError('project_create_region');
|
|
3931
4461
|
const slugInput = await __rspack_external__clack_prompts_3cae1695.text({
|
|
3932
|
-
message: 'New
|
|
4462
|
+
message: 'New project slug:',
|
|
3933
4463
|
placeholder: 'my-app',
|
|
3934
4464
|
validate: (value)=>validateInstanceName(value?.trim() ?? '')
|
|
3935
4465
|
});
|
|
3936
|
-
if (isCancel(slugInput)) throw new PromptCancelledError('
|
|
4466
|
+
if (isCancel(slugInput)) throw new PromptCancelledError('project_create_name');
|
|
3937
4467
|
const slug = slugInput.trim();
|
|
3938
|
-
const createSpinner = createTaskSpinner(`Creating
|
|
4468
|
+
const createSpinner = createTaskSpinner(`Creating project "${slug}"...`);
|
|
3939
4469
|
createSpinner.start();
|
|
3940
4470
|
try {
|
|
3941
4471
|
const instance = await client.createInstance({
|
|
@@ -3945,17 +4475,17 @@ async function createInstanceInteractively(client, cliContext) {
|
|
|
3945
4475
|
region: regionSelection
|
|
3946
4476
|
}
|
|
3947
4477
|
});
|
|
3948
|
-
createSpinner.success('
|
|
3949
|
-
cliContext.logger.info('Created as a v2 development
|
|
4478
|
+
createSpinner.success('Project created');
|
|
4479
|
+
cliContext.logger.info('Created as a v2 development project. Enable production mode in the dashboard when you are ready.');
|
|
3950
4480
|
return instance;
|
|
3951
4481
|
} catch (error) {
|
|
3952
|
-
createSpinner.error('Failed to create
|
|
4482
|
+
createSpinner.error('Failed to create project');
|
|
3953
4483
|
throw error;
|
|
3954
4484
|
}
|
|
3955
4485
|
}
|
|
3956
4486
|
async function selectOrCreateInstance(cliContext) {
|
|
3957
4487
|
const baseUrl = getControlPlaneBaseUrl();
|
|
3958
|
-
const listSpinner = createTaskSpinner('Fetching your consent.io
|
|
4488
|
+
const listSpinner = createTaskSpinner('Fetching your consent.io projects...');
|
|
3959
4489
|
listSpinner.start();
|
|
3960
4490
|
const client = await createControlPlaneClientFromConfig(baseUrl);
|
|
3961
4491
|
if (!client) {
|
|
@@ -3966,11 +4496,11 @@ async function selectOrCreateInstance(cliContext) {
|
|
|
3966
4496
|
const instances = await client.listInstances();
|
|
3967
4497
|
listSpinner.stop();
|
|
3968
4498
|
if (0 === instances.length) {
|
|
3969
|
-
cliContext.logger.info('No
|
|
4499
|
+
cliContext.logger.info('No projects found. Creating a new project for this local project.');
|
|
3970
4500
|
return await createInstanceInteractively(client, cliContext);
|
|
3971
4501
|
}
|
|
3972
4502
|
const selectedId = await __rspack_external__clack_prompts_3cae1695.select({
|
|
3973
|
-
message: 'Select
|
|
4503
|
+
message: 'Select a project to use:',
|
|
3974
4504
|
options: [
|
|
3975
4505
|
...instances.map((instance)=>({
|
|
3976
4506
|
value: instance.id,
|
|
@@ -3979,12 +4509,12 @@ async function selectOrCreateInstance(cliContext) {
|
|
|
3979
4509
|
})),
|
|
3980
4510
|
{
|
|
3981
4511
|
value: '__create__',
|
|
3982
|
-
label: 'Create new
|
|
3983
|
-
hint: 'Provision a new consent.io
|
|
4512
|
+
label: 'Create new project',
|
|
4513
|
+
hint: 'Provision a new consent.io project now'
|
|
3984
4514
|
}
|
|
3985
4515
|
]
|
|
3986
4516
|
});
|
|
3987
|
-
if (isCancel(selectedId)) throw new PromptCancelledError('
|
|
4517
|
+
if (isCancel(selectedId)) throw new PromptCancelledError('project_select');
|
|
3988
4518
|
if ('__create__' === selectedId) return await createInstanceInteractively(client, cliContext);
|
|
3989
4519
|
const selected = instances.find((instance)=>instance.id === selectedId);
|
|
3990
4520
|
if (!selected) throw new CliError('INSTANCE_NOT_FOUND');
|
|
@@ -4032,10 +4562,10 @@ const hostedModeActor = fromPromise(async ({ input })=>{
|
|
|
4032
4562
|
};
|
|
4033
4563
|
}
|
|
4034
4564
|
if (!isV2ModeEnabled()) {
|
|
4035
|
-
cliContext.logger.info('consent.io sign-in is currently disabled. Set V2=1 to enable sign-in and
|
|
4565
|
+
cliContext.logger.info('consent.io sign-in is currently disabled. Set V2=1 to enable sign-in and project selection.');
|
|
4036
4566
|
const url = await promptBackendURL({
|
|
4037
|
-
message: 'Enter your consent.io
|
|
4038
|
-
placeholder: 'https://your-
|
|
4567
|
+
message: 'Enter your consent.io project URL:',
|
|
4568
|
+
placeholder: 'https://your-project.c15t.dev',
|
|
4039
4569
|
initialURL,
|
|
4040
4570
|
stage: 'consent_manual_url'
|
|
4041
4571
|
});
|
|
@@ -4049,12 +4579,12 @@ const hostedModeActor = fromPromise(async ({ input })=>{
|
|
|
4049
4579
|
options: [
|
|
4050
4580
|
{
|
|
4051
4581
|
value: 'sign-in',
|
|
4052
|
-
label: 'Sign in and pick
|
|
4053
|
-
hint: 'List existing
|
|
4582
|
+
label: 'Sign in and pick a project',
|
|
4583
|
+
hint: 'List existing projects or create a new one'
|
|
4054
4584
|
},
|
|
4055
4585
|
{
|
|
4056
4586
|
value: 'manual-url',
|
|
4057
|
-
label: 'Enter
|
|
4587
|
+
label: 'Enter project URL manually',
|
|
4058
4588
|
hint: 'Use an existing backend URL'
|
|
4059
4589
|
}
|
|
4060
4590
|
],
|
|
@@ -4063,8 +4593,8 @@ const hostedModeActor = fromPromise(async ({ input })=>{
|
|
|
4063
4593
|
if (isCancel(setupMethod)) throw new PromptCancelledError('consent_setup_method');
|
|
4064
4594
|
if ('manual-url' === setupMethod) {
|
|
4065
4595
|
const url = await promptBackendURL({
|
|
4066
|
-
message: 'Enter your consent.io
|
|
4067
|
-
placeholder: 'https://your-
|
|
4596
|
+
message: 'Enter your consent.io project URL:',
|
|
4597
|
+
placeholder: 'https://your-project.c15t.dev',
|
|
4068
4598
|
initialURL,
|
|
4069
4599
|
stage: 'consent_manual_url'
|
|
4070
4600
|
});
|
|
@@ -4076,7 +4606,7 @@ const hostedModeActor = fromPromise(async ({ input })=>{
|
|
|
4076
4606
|
await runConsentLogin(cliContext);
|
|
4077
4607
|
const instance = await selectOrCreateInstance(cliContext);
|
|
4078
4608
|
await setSelectedInstanceId(instance.id);
|
|
4079
|
-
cliContext.logger.info(`Using
|
|
4609
|
+
cliContext.logger.info(`Using project ${picocolors.cyan(instance.name)} (${picocolors.dim(instance.id)})`);
|
|
4080
4610
|
return {
|
|
4081
4611
|
url: instance.url,
|
|
4082
4612
|
provider: 'consent.io'
|
|
@@ -4093,7 +4623,7 @@ const backendOptionsActor = fromPromise(async ({ input })=>{
|
|
|
4093
4623
|
if ('@c15t/nextjs' === cliContext.framework.pkg) {
|
|
4094
4624
|
cliContext.logger.info('Learn more about Next.js Rewrites: https://nextjs.org/docs/app/api-reference/config/next-config-js/rewrites');
|
|
4095
4625
|
const proxyResult = await __rspack_external__clack_prompts_3cae1695.confirm({
|
|
4096
|
-
message: 'Proxy requests to your
|
|
4626
|
+
message: 'Proxy requests to your project with Next.js Rewrites? (Recommended)',
|
|
4097
4627
|
initialValue: true
|
|
4098
4628
|
});
|
|
4099
4629
|
if (isCancel(proxyResult)) throw new PromptCancelledError('proxy_nextjs');
|
|
@@ -5033,6 +5563,29 @@ const generateMachine = setup({
|
|
|
5033
5563
|
}
|
|
5034
5564
|
}
|
|
5035
5565
|
});
|
|
5566
|
+
function getSetupTrigger(modeArg, resumed) {
|
|
5567
|
+
if (resumed) return 'resume';
|
|
5568
|
+
if (modeArg) return 'arg';
|
|
5569
|
+
return 'interactive';
|
|
5570
|
+
}
|
|
5571
|
+
function normalizeSetupReason(finalState, finalContext) {
|
|
5572
|
+
if ('complete' === finalState) return;
|
|
5573
|
+
if ('preflightError' === finalState || !finalContext.preflightPassed && finalContext.preflightChecks.some((check)=>'fail' === check.status)) return 'preflight_failed';
|
|
5574
|
+
if ('exited' === finalState || 'cancelled' === finalState) {
|
|
5575
|
+
const reason = finalContext.cancelReason?.toLowerCase();
|
|
5576
|
+
if (!reason) return 'user_cancelled';
|
|
5577
|
+
if (reason.includes('signal')) return 'signal_interrupted';
|
|
5578
|
+
if (reason.includes('mode selection')) return 'mode_selection_cancelled';
|
|
5579
|
+
if (reason.includes('hosted setup')) return 'hosted_setup_cancelled';
|
|
5580
|
+
if (reason.includes('backend options')) return 'backend_options_cancelled';
|
|
5581
|
+
if (reason.includes('frontend options')) return 'frontend_options_cancelled';
|
|
5582
|
+
if (reason.includes("scripts option")) return "scripts_options_cancelled";
|
|
5583
|
+
return 'user_cancelled';
|
|
5584
|
+
}
|
|
5585
|
+
const lastError = finalContext.errors[finalContext.errors.length - 1];
|
|
5586
|
+
if (lastError?.state) return `${lastError.state}_failed`;
|
|
5587
|
+
return 'machine_error';
|
|
5588
|
+
}
|
|
5036
5589
|
async function runGenerateMachine(options) {
|
|
5037
5590
|
const { context: cliContext, modeArg, resume = false, debug = false, persist = true } = options;
|
|
5038
5591
|
const { logger, telemetry } = cliContext;
|
|
@@ -5069,7 +5622,9 @@ async function runGenerateMachine(options) {
|
|
|
5069
5622
|
const combinedSubscriber = combineSubscribers(...subscribers);
|
|
5070
5623
|
actor.subscribe((snapshot)=>combinedSubscriber(snapshot));
|
|
5071
5624
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_STARTED, {
|
|
5072
|
-
resumed: resume && void 0 !== snapshot
|
|
5625
|
+
resumed: resume && void 0 !== snapshot,
|
|
5626
|
+
trigger: getSetupTrigger(modeArg, resume && void 0 !== snapshot),
|
|
5627
|
+
requestedMode: modeArg ?? void 0
|
|
5073
5628
|
});
|
|
5074
5629
|
telemetry.flushSync();
|
|
5075
5630
|
actor.start();
|
|
@@ -5085,11 +5640,28 @@ async function runGenerateMachine(options) {
|
|
|
5085
5640
|
const duration = Date.now() - startTime;
|
|
5086
5641
|
clearSnapshot(persistPath).catch(()=>{});
|
|
5087
5642
|
const success = 'complete' === finalState;
|
|
5643
|
+
const durationMs = duration;
|
|
5644
|
+
const reason = normalizeSetupReason(finalState, finalContext);
|
|
5645
|
+
const result = success ? 'success' : 'exited' === finalState ? 'cancelled' : 'failed';
|
|
5088
5646
|
telemetry.trackEvent(TelemetryEventName.ONBOARDING_COMPLETED, {
|
|
5089
5647
|
success,
|
|
5648
|
+
result,
|
|
5649
|
+
reason,
|
|
5650
|
+
trigger: getSetupTrigger(modeArg, resume && void 0 !== snapshot),
|
|
5651
|
+
resumed: resume && void 0 !== snapshot,
|
|
5090
5652
|
selectedMode: finalContext.selectedMode ?? void 0,
|
|
5653
|
+
hostedProvider: finalContext.hostedProvider ?? void 0,
|
|
5091
5654
|
installDependencies: finalContext.installSucceeded,
|
|
5655
|
+
installConfirmed: finalContext.installConfirmed,
|
|
5656
|
+
installAttempted: finalContext.installAttempted,
|
|
5657
|
+
installSucceeded: finalContext.installSucceeded,
|
|
5658
|
+
dependencyCount: finalContext.dependenciesToAdd.length,
|
|
5659
|
+
filesCreatedCount: finalContext.filesCreated.length,
|
|
5660
|
+
filesModifiedCount: finalContext.filesModified.length,
|
|
5661
|
+
errorsCount: finalContext.errors.length,
|
|
5662
|
+
cancelReason: finalContext.cancelReason ?? void 0,
|
|
5092
5663
|
duration,
|
|
5664
|
+
durationMs,
|
|
5093
5665
|
finalState
|
|
5094
5666
|
});
|
|
5095
5667
|
resolve({
|
|
@@ -5139,6 +5711,294 @@ async function generate(context, mode) {
|
|
|
5139
5711
|
];
|
|
5140
5712
|
return generateAction(context);
|
|
5141
5713
|
}
|
|
5714
|
+
function instances_formatInstanceLabel(instance) {
|
|
5715
|
+
if (instance.organizationSlug) return `${instance.organizationSlug}/${instance.name}`;
|
|
5716
|
+
return instance.name;
|
|
5717
|
+
}
|
|
5718
|
+
function instances_formatInstanceRegion(instance) {
|
|
5719
|
+
return `(${instance.region ?? 'unknown'})`;
|
|
5720
|
+
}
|
|
5721
|
+
async function requireAuth(context) {
|
|
5722
|
+
if (!await isLoggedIn()) {
|
|
5723
|
+
context.logger.error('You must be logged in to manage projects');
|
|
5724
|
+
context.logger.message(`Run ${picocolors.cyan('c15t login')} to authenticate`);
|
|
5725
|
+
throw new CliError('AUTH_NOT_LOGGED_IN');
|
|
5726
|
+
}
|
|
5727
|
+
}
|
|
5728
|
+
async function listAction(context) {
|
|
5729
|
+
const { logger, telemetry } = context;
|
|
5730
|
+
const baseUrl = getControlPlaneBaseUrl();
|
|
5731
|
+
await requireAuth(context);
|
|
5732
|
+
const spinner = createTaskSpinner('Fetching projects...');
|
|
5733
|
+
spinner.start();
|
|
5734
|
+
try {
|
|
5735
|
+
const client = await createControlPlaneClientFromConfig(baseUrl);
|
|
5736
|
+
if (!client) {
|
|
5737
|
+
spinner.stop();
|
|
5738
|
+
throw new CliError('AUTH_NOT_LOGGED_IN');
|
|
5739
|
+
}
|
|
5740
|
+
const instances = await client.listInstances();
|
|
5741
|
+
await client.close();
|
|
5742
|
+
spinner.stop();
|
|
5743
|
+
telemetry.trackEvent(TelemetryEventName.PROJECTS_LISTED, {
|
|
5744
|
+
count: instances.length
|
|
5745
|
+
});
|
|
5746
|
+
if (0 === instances.length) {
|
|
5747
|
+
logger.message('');
|
|
5748
|
+
logger.message('No projects found.');
|
|
5749
|
+
logger.message('');
|
|
5750
|
+
logger.message(`Run ${picocolors.cyan('c15t projects create')} to create one`);
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
const selectedId = await getSelectedInstanceId();
|
|
5754
|
+
logger.message('');
|
|
5755
|
+
logger.message(picocolors.bold('Your projects:'));
|
|
5756
|
+
logger.message('');
|
|
5757
|
+
for (const instance of instances){
|
|
5758
|
+
const isSelected = instance.id === selectedId;
|
|
5759
|
+
const status = getStatusColor(instance.status);
|
|
5760
|
+
const marker = isSelected ? picocolors.green('▸ ') : ' ';
|
|
5761
|
+
const label = instances_formatInstanceLabel(instance);
|
|
5762
|
+
logger.message(`${marker}${picocolors.bold(label)} ${picocolors.dim(`(${instance.id})`)}`);
|
|
5763
|
+
logger.message(` Region: ${picocolors.cyan(instances_formatInstanceRegion(instance))}`);
|
|
5764
|
+
logger.message(` Status: ${status}`);
|
|
5765
|
+
logger.message('');
|
|
5766
|
+
}
|
|
5767
|
+
if (selectedId) logger.message(picocolors.dim('▸ indicates the currently selected project'));
|
|
5768
|
+
} catch (error) {
|
|
5769
|
+
spinner.stop();
|
|
5770
|
+
throw error;
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5773
|
+
async function selectAction(context) {
|
|
5774
|
+
const { logger, telemetry, commandArgs } = context;
|
|
5775
|
+
const baseUrl = getControlPlaneBaseUrl();
|
|
5776
|
+
await requireAuth(context);
|
|
5777
|
+
const spinner = createTaskSpinner('Fetching projects...');
|
|
5778
|
+
spinner.start();
|
|
5779
|
+
try {
|
|
5780
|
+
const client = await createControlPlaneClientFromConfig(baseUrl);
|
|
5781
|
+
if (!client) {
|
|
5782
|
+
spinner.stop();
|
|
5783
|
+
throw new CliError('AUTH_NOT_LOGGED_IN');
|
|
5784
|
+
}
|
|
5785
|
+
const instances = await client.listInstances();
|
|
5786
|
+
await client.close();
|
|
5787
|
+
spinner.stop();
|
|
5788
|
+
if (0 === instances.length) {
|
|
5789
|
+
logger.message('No projects found.');
|
|
5790
|
+
logger.message(`Run ${picocolors.cyan('c15t projects create')} to create one`);
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
let selectedInstance;
|
|
5794
|
+
if (commandArgs.length > 0) {
|
|
5795
|
+
const query = commandArgs[0];
|
|
5796
|
+
const found = instances.find((i)=>i.id === query || i.name === query || instances_formatInstanceLabel(i) === query);
|
|
5797
|
+
if (!found) throw new CliError('INSTANCE_NOT_FOUND', {
|
|
5798
|
+
details: `No project found with ID, name, or org/name: ${query}`
|
|
5799
|
+
});
|
|
5800
|
+
selectedInstance = found;
|
|
5801
|
+
} else {
|
|
5802
|
+
const currentId = await getSelectedInstanceId();
|
|
5803
|
+
const result = await __rspack_external__clack_prompts_3cae1695.select({
|
|
5804
|
+
message: 'Select a project:',
|
|
5805
|
+
options: instances.map((instance)=>({
|
|
5806
|
+
value: instance.id,
|
|
5807
|
+
label: instances_formatInstanceLabel(instance),
|
|
5808
|
+
hint: instance.id === currentId ? `(currently selected) • ${instances_formatInstanceRegion(instance)}` : instances_formatInstanceRegion(instance)
|
|
5809
|
+
}))
|
|
5810
|
+
});
|
|
5811
|
+
if (__rspack_external__clack_prompts_3cae1695.isCancel(result)) return void logger.info('Selection cancelled');
|
|
5812
|
+
selectedInstance = instances.find((i)=>i.id === result);
|
|
5813
|
+
}
|
|
5814
|
+
await setSelectedInstanceId(selectedInstance.id);
|
|
5815
|
+
telemetry.trackEvent(TelemetryEventName.PROJECT_SELECTED, {
|
|
5816
|
+
projectId: selectedInstance.id
|
|
5817
|
+
});
|
|
5818
|
+
logger.success(`Selected project: ${picocolors.cyan(instances_formatInstanceLabel(selectedInstance))}`);
|
|
5819
|
+
logger.message(`Region: ${instances_formatInstanceRegion(selectedInstance)}`);
|
|
5820
|
+
} catch (error) {
|
|
5821
|
+
spinner.stop();
|
|
5822
|
+
throw error;
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
async function createAction(context) {
|
|
5826
|
+
const { logger, telemetry, commandArgs } = context;
|
|
5827
|
+
const baseUrl = getControlPlaneBaseUrl();
|
|
5828
|
+
await requireAuth(context);
|
|
5829
|
+
const client = await createControlPlaneClientFromConfig(baseUrl);
|
|
5830
|
+
if (!client) throw new CliError('AUTH_NOT_LOGGED_IN');
|
|
5831
|
+
try {
|
|
5832
|
+
const preloadSpinner = createTaskSpinner('Loading organizations and regions...');
|
|
5833
|
+
preloadSpinner.start();
|
|
5834
|
+
let organizations;
|
|
5835
|
+
let regions;
|
|
5836
|
+
try {
|
|
5837
|
+
[organizations, regions] = await Promise.all([
|
|
5838
|
+
client.listOrganizations(),
|
|
5839
|
+
client.listRegions()
|
|
5840
|
+
]);
|
|
5841
|
+
} finally{
|
|
5842
|
+
preloadSpinner.stop();
|
|
5843
|
+
}
|
|
5844
|
+
if (0 === organizations.length) throw new CliError('API_ERROR', {
|
|
5845
|
+
details: 'No organizations available for this account'
|
|
5846
|
+
});
|
|
5847
|
+
if (0 === regions.length) throw new CliError('API_ERROR', {
|
|
5848
|
+
details: 'No provisioning regions available'
|
|
5849
|
+
});
|
|
5850
|
+
let name;
|
|
5851
|
+
if (commandArgs.length > 0) {
|
|
5852
|
+
const providedName = commandArgs[0];
|
|
5853
|
+
if (!providedName) throw new CliError('INSTANCE_NAME_INVALID', {
|
|
5854
|
+
details: 'Project slug is required'
|
|
5855
|
+
});
|
|
5856
|
+
const error = validateInstanceName(providedName);
|
|
5857
|
+
if (error) throw new CliError('INSTANCE_NAME_INVALID', {
|
|
5858
|
+
details: error
|
|
5859
|
+
});
|
|
5860
|
+
name = providedName;
|
|
5861
|
+
} else {
|
|
5862
|
+
const result = await __rspack_external__clack_prompts_3cae1695.text({
|
|
5863
|
+
message: 'Project slug:',
|
|
5864
|
+
placeholder: 'my-app',
|
|
5865
|
+
validate: (value)=>validateInstanceName(value?.trim() ?? '')
|
|
5866
|
+
});
|
|
5867
|
+
if (__rspack_external__clack_prompts_3cae1695.isCancel(result)) return void logger.info('Creation cancelled');
|
|
5868
|
+
name = result;
|
|
5869
|
+
}
|
|
5870
|
+
name = name.trim();
|
|
5871
|
+
const nameValidationError = validateInstanceName(name);
|
|
5872
|
+
if (nameValidationError) throw new CliError('INSTANCE_NAME_INVALID', {
|
|
5873
|
+
details: nameValidationError
|
|
5874
|
+
});
|
|
5875
|
+
const orgSelection = await __rspack_external__clack_prompts_3cae1695.select({
|
|
5876
|
+
message: 'Select organization:',
|
|
5877
|
+
options: organizations.map((org)=>({
|
|
5878
|
+
value: org.organizationSlug,
|
|
5879
|
+
label: org.organizationName,
|
|
5880
|
+
hint: `${org.organizationSlug} • ${org.role}`
|
|
5881
|
+
})),
|
|
5882
|
+
initialValue: organizations[0]?.organizationSlug
|
|
5883
|
+
});
|
|
5884
|
+
if (__rspack_external__clack_prompts_3cae1695.isCancel(orgSelection)) return void logger.info('Creation cancelled');
|
|
5885
|
+
const v2Regions = regions.filter((region)=>'v2' === region.family);
|
|
5886
|
+
if (0 === v2Regions.length) throw new CliError('API_ERROR', {
|
|
5887
|
+
details: 'No v2 provisioning regions available'
|
|
5888
|
+
});
|
|
5889
|
+
const regionSelection = await __rspack_external__clack_prompts_3cae1695.select({
|
|
5890
|
+
message: 'Select V2 region:',
|
|
5891
|
+
options: v2Regions.map((region)=>({
|
|
5892
|
+
value: region.id,
|
|
5893
|
+
label: region.id,
|
|
5894
|
+
hint: region.label
|
|
5895
|
+
})),
|
|
5896
|
+
initialValue: v2Regions.find((region)=>'us-east-1' === region.id)?.id
|
|
5897
|
+
});
|
|
5898
|
+
if (__rspack_external__clack_prompts_3cae1695.isCancel(regionSelection)) return void logger.info('Creation cancelled');
|
|
5899
|
+
const spinner = createTaskSpinner(`Creating project "${name}"...`);
|
|
5900
|
+
spinner.start();
|
|
5901
|
+
let instance;
|
|
5902
|
+
try {
|
|
5903
|
+
instance = await client.createInstance({
|
|
5904
|
+
name,
|
|
5905
|
+
config: {
|
|
5906
|
+
organizationSlug: orgSelection,
|
|
5907
|
+
region: regionSelection
|
|
5908
|
+
}
|
|
5909
|
+
});
|
|
5910
|
+
spinner.success('Project created');
|
|
5911
|
+
} catch (error) {
|
|
5912
|
+
spinner.error('Failed to create project');
|
|
5913
|
+
throw error;
|
|
5914
|
+
}
|
|
5915
|
+
telemetry.trackEvent(TelemetryEventName.PROJECT_CREATED, {
|
|
5916
|
+
projectId: instance.id
|
|
5917
|
+
});
|
|
5918
|
+
logger.message('');
|
|
5919
|
+
logger.message(`Name: ${picocolors.bold(instance.name)}`);
|
|
5920
|
+
logger.message(`ID: ${picocolors.dim(instance.id)}`);
|
|
5921
|
+
logger.message(`URL: ${picocolors.cyan(instance.url)}`);
|
|
5922
|
+
logger.message('');
|
|
5923
|
+
logger.info('Created as a v2 development project. Enable production mode in the dashboard when you are ready.');
|
|
5924
|
+
const shouldSelect = await __rspack_external__clack_prompts_3cae1695.confirm({
|
|
5925
|
+
message: 'Would you like to use this project for your project?',
|
|
5926
|
+
initialValue: true
|
|
5927
|
+
});
|
|
5928
|
+
if (shouldSelect && !__rspack_external__clack_prompts_3cae1695.isCancel(shouldSelect)) {
|
|
5929
|
+
await setSelectedInstanceId(instance.id);
|
|
5930
|
+
logger.info('Project selected');
|
|
5931
|
+
}
|
|
5932
|
+
} finally{
|
|
5933
|
+
await client.close();
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5936
|
+
function getStatusColor(status) {
|
|
5937
|
+
switch(status){
|
|
5938
|
+
case 'active':
|
|
5939
|
+
return picocolors.green('active');
|
|
5940
|
+
case 'inactive':
|
|
5941
|
+
return picocolors.yellow('inactive');
|
|
5942
|
+
case 'pending':
|
|
5943
|
+
return picocolors.blue('pending');
|
|
5944
|
+
default:
|
|
5945
|
+
return status;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
async function projectsAction(context) {
|
|
5949
|
+
const { commandArgs } = context;
|
|
5950
|
+
const subcommand = commandArgs[0];
|
|
5951
|
+
switch(subcommand){
|
|
5952
|
+
case 'list':
|
|
5953
|
+
context.commandArgs = commandArgs.slice(1);
|
|
5954
|
+
return listAction(context);
|
|
5955
|
+
case 'select':
|
|
5956
|
+
context.commandArgs = commandArgs.slice(1);
|
|
5957
|
+
return selectAction(context);
|
|
5958
|
+
case 'create':
|
|
5959
|
+
context.commandArgs = commandArgs.slice(1);
|
|
5960
|
+
return createAction(context);
|
|
5961
|
+
default:
|
|
5962
|
+
return listAction(context);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
const projectsCommand = {
|
|
5966
|
+
name: 'projects',
|
|
5967
|
+
label: 'Projects',
|
|
5968
|
+
hint: 'Manage your c15t projects',
|
|
5969
|
+
description: 'List, select, and create c15t projects',
|
|
5970
|
+
action: projectsAction,
|
|
5971
|
+
subcommands: [
|
|
5972
|
+
{
|
|
5973
|
+
name: 'list',
|
|
5974
|
+
label: 'List',
|
|
5975
|
+
hint: 'List all projects',
|
|
5976
|
+
description: 'List all c15t projects for your account',
|
|
5977
|
+
action: listAction
|
|
5978
|
+
},
|
|
5979
|
+
{
|
|
5980
|
+
name: 'select',
|
|
5981
|
+
label: 'Select',
|
|
5982
|
+
hint: 'Select a project',
|
|
5983
|
+
description: 'Select a project for your local project',
|
|
5984
|
+
action: selectAction
|
|
5985
|
+
},
|
|
5986
|
+
{
|
|
5987
|
+
name: 'create',
|
|
5988
|
+
label: 'Create',
|
|
5989
|
+
hint: 'Create a new project',
|
|
5990
|
+
description: 'Create a new c15t project',
|
|
5991
|
+
action: createAction
|
|
5992
|
+
}
|
|
5993
|
+
]
|
|
5994
|
+
};
|
|
5995
|
+
({
|
|
5996
|
+
...projectsCommand,
|
|
5997
|
+
name: 'instances',
|
|
5998
|
+
label: 'Instances',
|
|
5999
|
+
description: 'Alias for `c15t projects`',
|
|
6000
|
+
hidden: true
|
|
6001
|
+
});
|
|
5142
6002
|
const validLogLevels = [
|
|
5143
6003
|
'error',
|
|
5144
6004
|
'warn',
|
|
@@ -5194,7 +6054,7 @@ const logger_logMessage = (logLevel, message, ...args)=>{
|
|
|
5194
6054
|
}
|
|
5195
6055
|
};
|
|
5196
6056
|
const logger_createCliLogger = (level)=>{
|
|
5197
|
-
const baseLogger =
|
|
6057
|
+
const baseLogger = logger_createLogger({
|
|
5198
6058
|
level,
|
|
5199
6059
|
appName: 'c15t',
|
|
5200
6060
|
log: (logLevel, message, ...args)=>{
|
|
@@ -5205,23 +6065,26 @@ const logger_createCliLogger = (level)=>{
|
|
|
5205
6065
|
extendedLogger.message = (message)=>{
|
|
5206
6066
|
__rspack_external__clack_prompts_3cae1695.log.message(message);
|
|
5207
6067
|
};
|
|
5208
|
-
extendedLogger.note = (
|
|
5209
|
-
|
|
5210
|
-
const title = args.length > 0 && 'string' == typeof args[0] ? args[0] : void 0;
|
|
5211
|
-
__rspack_external__clack_prompts_3cae1695.note(messageStr, title, {
|
|
6068
|
+
extendedLogger.note = (content, title)=>{
|
|
6069
|
+
__rspack_external__clack_prompts_3cae1695.note(content, title, {
|
|
5212
6070
|
format: (line)=>line
|
|
5213
6071
|
});
|
|
5214
6072
|
};
|
|
5215
|
-
extendedLogger.success = (message
|
|
5216
|
-
logger_logMessage('success', message
|
|
6073
|
+
extendedLogger.success = (message)=>{
|
|
6074
|
+
logger_logMessage('success', message);
|
|
5217
6075
|
};
|
|
5218
|
-
extendedLogger.failed = (message
|
|
5219
|
-
logger_logMessage('failed', message
|
|
6076
|
+
extendedLogger.failed = (message)=>{
|
|
6077
|
+
logger_logMessage('failed', message);
|
|
5220
6078
|
process.exit(0);
|
|
5221
6079
|
};
|
|
5222
6080
|
extendedLogger.outro = (message)=>{
|
|
5223
6081
|
__rspack_external__clack_prompts_3cae1695.outro(message);
|
|
5224
6082
|
};
|
|
6083
|
+
extendedLogger.step = (current, total, label)=>{
|
|
6084
|
+
const filled = picocolors.green('█'.repeat(current));
|
|
6085
|
+
const empty = picocolors.dim('░'.repeat(total - current));
|
|
6086
|
+
__rspack_external__clack_prompts_3cae1695.log.step(`[${filled}${empty}] Step ${current}/${total}: ${label}`);
|
|
6087
|
+
};
|
|
5225
6088
|
return extendedLogger;
|
|
5226
6089
|
};
|
|
5227
6090
|
async function addAndInstallDependenciesViaPM(projectRoot, dependencies, packageManager) {
|
|
@@ -6279,10 +7142,13 @@ async function createCliContext(rawArgs, cwd, commands) {
|
|
|
6279
7142
|
const telemetryDisabled = true === parsedFlags['no-telemetry'];
|
|
6280
7143
|
const telemetryDebug = true === parsedFlags['telemetry-debug'];
|
|
6281
7144
|
try {
|
|
6282
|
-
context.telemetry =
|
|
7145
|
+
context.telemetry = telemetry_createTelemetry({
|
|
6283
7146
|
disabled: telemetryDisabled,
|
|
6284
7147
|
debug: telemetryDebug,
|
|
6285
7148
|
defaultProperties: {
|
|
7149
|
+
entryCommand: commandName ?? 'interactive',
|
|
7150
|
+
commandArgsCount: commandArgs.length,
|
|
7151
|
+
enabledFlags: Object.entries(parsedFlags).filter(([, value])=>false !== value && void 0 !== value).map(([key])=>key).sort(),
|
|
6286
7152
|
cliVersion: context.fs.getPackageInfo().version,
|
|
6287
7153
|
framework: context.framework.framework ?? 'unknown',
|
|
6288
7154
|
frameworkVersion: context.framework.frameworkVersion ?? 'unknown',
|
|
@@ -6297,9 +7163,20 @@ async function createCliContext(rawArgs, cwd, commands) {
|
|
|
6297
7163
|
if (telemetryDisabled) logger.debug('Telemetry is disabled by user preference');
|
|
6298
7164
|
else if (telemetryDebug) logger.debug('Telemetry initialized with debug mode enabled');
|
|
6299
7165
|
else logger.debug('Telemetry initialized');
|
|
7166
|
+
context.telemetry.trackEvent(TelemetryEventName.CLI_ENVIRONMENT_DETECTED, {
|
|
7167
|
+
command: commandName ?? 'interactive',
|
|
7168
|
+
projectRootChanged: context.projectRoot !== cwd,
|
|
7169
|
+
framework: context.framework.framework ?? 'unknown',
|
|
7170
|
+
frameworkVersion: context.framework.frameworkVersion ?? 'unknown',
|
|
7171
|
+
packageManager: context.packageManager.name,
|
|
7172
|
+
packageManagerVersion: context.packageManager.version ?? 'unknown',
|
|
7173
|
+
hasReact: context.framework.hasReact,
|
|
7174
|
+
reactVersion: context.framework.reactVersion ?? 'unknown',
|
|
7175
|
+
tailwindVersion: context.framework.tailwindVersion ?? 'unknown'
|
|
7176
|
+
});
|
|
6300
7177
|
} catch {
|
|
6301
7178
|
logger.warn('Failed to initialize telemetry, continuing with telemetry disabled');
|
|
6302
|
-
context.telemetry =
|
|
7179
|
+
context.telemetry = telemetry_createTelemetry({
|
|
6303
7180
|
disabled: true,
|
|
6304
7181
|
logger: context.logger
|
|
6305
7182
|
});
|
|
@@ -6363,6 +7240,21 @@ const src_commands = [
|
|
|
6363
7240
|
await open_0(constants_URLS.GITHUB);
|
|
6364
7241
|
logger.success('Thank you for your support!');
|
|
6365
7242
|
}
|
|
7243
|
+
},
|
|
7244
|
+
{
|
|
7245
|
+
name: 'projects',
|
|
7246
|
+
label: 'Projects',
|
|
7247
|
+
hint: 'Manage your c15t projects',
|
|
7248
|
+
description: 'List, select, and create c15t projects.',
|
|
7249
|
+
action: (context)=>projectsAction(context)
|
|
7250
|
+
},
|
|
7251
|
+
{
|
|
7252
|
+
name: 'instances',
|
|
7253
|
+
label: 'Instances',
|
|
7254
|
+
hint: 'Alias for `projects`',
|
|
7255
|
+
description: 'Alias for `c15t projects`.',
|
|
7256
|
+
action: (context)=>projectsAction(context),
|
|
7257
|
+
hidden: true
|
|
6366
7258
|
}
|
|
6367
7259
|
];
|
|
6368
7260
|
async function main() {
|
|
@@ -6438,7 +7330,7 @@ flag or set ${picocolors.cyan('C15T_TELEMETRY_DISABLED=1')} in your environment.
|
|
|
6438
7330
|
} else {
|
|
6439
7331
|
logger.debug('No command specified, entering interactive selection.');
|
|
6440
7332
|
telemetry.trackEvent(TelemetryEventName.INTERACTIVE_MENU_OPENED, {});
|
|
6441
|
-
const promptOptions = src_commands.map((cmd)=>({
|
|
7333
|
+
const promptOptions = src_commands.filter((cmd)=>!cmd.hidden).map((cmd)=>({
|
|
6442
7334
|
value: cmd.name,
|
|
6443
7335
|
label: cmd.label,
|
|
6444
7336
|
hint: cmd.hint
|
|
@@ -6506,4 +7398,4 @@ flag or set ${picocolors.cyan('C15T_TELEMETRY_DISABLED=1')} in your environment.
|
|
|
6506
7398
|
await telemetry.shutdown();
|
|
6507
7399
|
}
|
|
6508
7400
|
main();
|
|
6509
|
-
export { __webpack_require__temp as __webpack_require__, LAYOUT_PATTERNS, PAGES_APP_PATTERNS, STORAGE_MODES, config_store_getAccessToken as getAccessToken, constants_REGEX, getAuthState, logger_formatLogMessage, main, setSelectedInstanceId, storeTokens };
|
|
7401
|
+
export { __webpack_require__temp as __webpack_require__, LAYOUT_PATTERNS, PAGES_APP_PATTERNS, STORAGE_MODES, config_store_getAccessToken as getAccessToken, constants_REGEX, ensureGlobalCssStylesheetImports, formatSearchedCssPaths, getAuthState, getSelectedInstanceId, isLoggedIn, logger_formatLogMessage, main, setSelectedInstanceId, storeTokens };
|