aerocoding 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +423 -196
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1115,8 +1115,12 @@ var projectConfigSchema = z.object({
|
|
|
1115
1115
|
$schema: z.string().optional().default("https://aerocoding.dev/schema.json"),
|
|
1116
1116
|
/** Project UUID from aerocoding.dev */
|
|
1117
1117
|
projectId: z.string().uuid(),
|
|
1118
|
-
/** Template ID for architecture generation */
|
|
1118
|
+
/** Template ID for architecture generation (legacy, prefer backendTemplateId/frontendTemplateId) */
|
|
1119
1119
|
templateId: z.string().min(1),
|
|
1120
|
+
/** Backend template ID */
|
|
1121
|
+
backendTemplateId: z.string().min(1).optional(),
|
|
1122
|
+
/** Frontend template ID */
|
|
1123
|
+
frontendTemplateId: z.string().min(1).optional(),
|
|
1120
1124
|
/** Template version at project creation */
|
|
1121
1125
|
templateVersion: z.string().optional(),
|
|
1122
1126
|
/** Root namespace/package name */
|
|
@@ -1149,6 +1153,8 @@ function createProjectConfig(options) {
|
|
|
1149
1153
|
$schema: "https://aerocoding.dev/schema.json",
|
|
1150
1154
|
projectId: options.projectId,
|
|
1151
1155
|
templateId: options.templateId,
|
|
1156
|
+
backendTemplateId: options.backendTemplateId,
|
|
1157
|
+
frontendTemplateId: options.frontendTemplateId,
|
|
1152
1158
|
templateVersion: options.templateVersion,
|
|
1153
1159
|
namespace: options.namespace,
|
|
1154
1160
|
organizationId: options.organizationId,
|
|
@@ -1316,32 +1322,72 @@ async function createCommand(projectName, options) {
|
|
|
1316
1322
|
}
|
|
1317
1323
|
} catch {
|
|
1318
1324
|
}
|
|
1319
|
-
let
|
|
1320
|
-
|
|
1325
|
+
let backendTemplateId;
|
|
1326
|
+
let frontendTemplateId;
|
|
1327
|
+
const hasBackend = !!project.backendFramework;
|
|
1328
|
+
const hasFrontend = !!project.frontendFramework;
|
|
1329
|
+
if (options.template) {
|
|
1330
|
+
if (hasBackend) {
|
|
1331
|
+
backendTemplateId = options.template;
|
|
1332
|
+
} else if (hasFrontend) {
|
|
1333
|
+
frontendTemplateId = options.template;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (hasBackend && !backendTemplateId) {
|
|
1321
1337
|
const templateSpinner = p.spinner();
|
|
1322
|
-
templateSpinner.start("Loading templates...");
|
|
1338
|
+
templateSpinner.start("Loading backend templates...");
|
|
1323
1339
|
const templateResult = await apiClient.getTemplates({
|
|
1324
1340
|
category: "backend",
|
|
1325
1341
|
language: project.backendFramework || void 0
|
|
1326
1342
|
});
|
|
1327
|
-
templateSpinner.stop("
|
|
1343
|
+
templateSpinner.stop("Backend templates loaded");
|
|
1328
1344
|
if (templateResult.templates.length === 0) {
|
|
1329
|
-
p.
|
|
1330
|
-
|
|
1345
|
+
p.log.warn("No backend templates available for this project's framework.");
|
|
1346
|
+
} else {
|
|
1347
|
+
const selectedTemplate = await p.select({
|
|
1348
|
+
message: "Select backend architecture template",
|
|
1349
|
+
options: templateResult.templates.map((tmpl) => ({
|
|
1350
|
+
value: tmpl.id,
|
|
1351
|
+
label: tmpl.name,
|
|
1352
|
+
hint: tmpl.description || `${tmpl.tier} tier`
|
|
1353
|
+
}))
|
|
1354
|
+
});
|
|
1355
|
+
if (p.isCancel(selectedTemplate)) {
|
|
1356
|
+
p.cancel("Operation cancelled.");
|
|
1357
|
+
process.exit(0);
|
|
1358
|
+
}
|
|
1359
|
+
backendTemplateId = selectedTemplate;
|
|
1331
1360
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1361
|
+
}
|
|
1362
|
+
if (hasFrontend && !frontendTemplateId) {
|
|
1363
|
+
const frontendSpinner = p.spinner();
|
|
1364
|
+
frontendSpinner.start("Loading frontend templates...");
|
|
1365
|
+
const frontendResult = await apiClient.getTemplates({
|
|
1366
|
+
category: "frontend",
|
|
1367
|
+
framework: project.frontendFramework || void 0
|
|
1339
1368
|
});
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1369
|
+
frontendSpinner.stop("Frontend templates loaded");
|
|
1370
|
+
if (frontendResult.templates.length === 0) {
|
|
1371
|
+
p.log.warn("No frontend templates available for this project's framework.");
|
|
1372
|
+
} else {
|
|
1373
|
+
const selectedFrontend = await p.select({
|
|
1374
|
+
message: "Select frontend architecture template",
|
|
1375
|
+
options: frontendResult.templates.map((tmpl) => ({
|
|
1376
|
+
value: tmpl.id,
|
|
1377
|
+
label: tmpl.name,
|
|
1378
|
+
hint: tmpl.description || `${tmpl.tier} tier`
|
|
1379
|
+
}))
|
|
1380
|
+
});
|
|
1381
|
+
if (p.isCancel(selectedFrontend)) {
|
|
1382
|
+
p.cancel("Operation cancelled.");
|
|
1383
|
+
process.exit(0);
|
|
1384
|
+
}
|
|
1385
|
+
frontendTemplateId = selectedFrontend;
|
|
1343
1386
|
}
|
|
1344
|
-
|
|
1387
|
+
}
|
|
1388
|
+
if (!backendTemplateId && !frontendTemplateId) {
|
|
1389
|
+
p.cancel("No templates selected. At least one backend or frontend template is required.");
|
|
1390
|
+
process.exit(1);
|
|
1345
1391
|
}
|
|
1346
1392
|
const diagrams = project.schema?.diagrams || [];
|
|
1347
1393
|
const hasMultipleDiagrams = diagrams.length > 1;
|
|
@@ -1401,50 +1447,73 @@ async function createCommand(projectName, options) {
|
|
|
1401
1447
|
const useContexts = archStyleChoice === "bounded-contexts";
|
|
1402
1448
|
const estimateSpinner = p.spinner();
|
|
1403
1449
|
estimateSpinner.start("Calculating credit cost...");
|
|
1404
|
-
let estimatedCredits = 0;
|
|
1405
1450
|
let creditsRemaining = 0;
|
|
1406
|
-
let
|
|
1407
|
-
let
|
|
1451
|
+
let backendEstimatedCredits = 0;
|
|
1452
|
+
let backendEstimatedFiles = [];
|
|
1453
|
+
let backendEstimatedEntities = 0;
|
|
1454
|
+
let frontendEstimatedCredits = 0;
|
|
1455
|
+
let frontendEstimatedFiles = [];
|
|
1456
|
+
let frontendEstimatedEntities = 0;
|
|
1408
1457
|
const estimateDiagramIds = selectedDiagramIds.length < diagrams.length ? selectedDiagramIds : void 0;
|
|
1458
|
+
const featureFlags = {
|
|
1459
|
+
includeDtos: true,
|
|
1460
|
+
includeUseCases: true,
|
|
1461
|
+
includeMappers: true,
|
|
1462
|
+
includeControllers: true,
|
|
1463
|
+
includeEfConfig: true,
|
|
1464
|
+
includeValidation: true,
|
|
1465
|
+
includeDtoValidation: true,
|
|
1466
|
+
includeUnitTests: true,
|
|
1467
|
+
includeIntegrationTests: true,
|
|
1468
|
+
includeStarterFiles: true,
|
|
1469
|
+
includeReactions: true,
|
|
1470
|
+
includeOutbox: true,
|
|
1471
|
+
includeInbox: true
|
|
1472
|
+
};
|
|
1409
1473
|
try {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1474
|
+
if (backendTemplateId) {
|
|
1475
|
+
const backendEstimate = await apiClient.estimateCreditCost({
|
|
1476
|
+
projectId,
|
|
1477
|
+
templateId: backendTemplateId,
|
|
1478
|
+
options: {
|
|
1479
|
+
featureFlags,
|
|
1480
|
+
useContexts,
|
|
1481
|
+
diagramIds: estimateDiagramIds
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
backendEstimatedCredits = backendEstimate.estimatedCredits;
|
|
1485
|
+
backendEstimatedFiles = backendEstimate.files || [];
|
|
1486
|
+
backendEstimatedEntities = backendEstimate.entities || 0;
|
|
1487
|
+
}
|
|
1488
|
+
if (frontendTemplateId) {
|
|
1489
|
+
const frontendEstimate = await apiClient.estimateCreditCost({
|
|
1490
|
+
projectId,
|
|
1491
|
+
templateId: frontendTemplateId,
|
|
1492
|
+
options: {
|
|
1493
|
+
featureFlags,
|
|
1494
|
+
useContexts,
|
|
1495
|
+
diagramIds: estimateDiagramIds
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
frontendEstimatedCredits = frontendEstimate.estimatedCredits;
|
|
1499
|
+
frontendEstimatedFiles = frontendEstimate.files || [];
|
|
1500
|
+
frontendEstimatedEntities = frontendEstimate.entities || 0;
|
|
1501
|
+
}
|
|
1436
1502
|
const creditUsage = await apiClient.getCreditUsage(organizationId);
|
|
1437
1503
|
creditsRemaining = creditUsage.remaining;
|
|
1438
1504
|
estimateSpinner.stop("Credit estimate calculated");
|
|
1439
1505
|
} catch {
|
|
1440
1506
|
estimateSpinner.stop("Could not estimate credits (will be calculated on generation)");
|
|
1441
1507
|
}
|
|
1442
|
-
const
|
|
1508
|
+
const totalEstimatedCredits = backendEstimatedCredits + frontendEstimatedCredits;
|
|
1509
|
+
const totalEstimatedFiles = backendEstimatedFiles.length + frontendEstimatedFiles.length;
|
|
1510
|
+
const totalEstimatedEntities = backendEstimatedEntities + frontendEstimatedEntities;
|
|
1511
|
+
const hasEnoughCredits = totalEstimatedCredits === 0 || creditsRemaining >= totalEstimatedCredits;
|
|
1512
|
+
const divider = ` ${"-".repeat(33)}`;
|
|
1443
1513
|
console.log("");
|
|
1444
1514
|
console.log(chalk7.bold(" Generation Summary"));
|
|
1445
|
-
console.log(chalk7.gray(
|
|
1515
|
+
console.log(chalk7.gray(divider));
|
|
1446
1516
|
console.log(chalk7.gray(" Project:"), chalk7.white(project.name));
|
|
1447
|
-
console.log(chalk7.gray(" Template:"), chalk7.cyan(templateId));
|
|
1448
1517
|
console.log(chalk7.gray(" Namespace:"), chalk7.cyan(namespace));
|
|
1449
1518
|
console.log(chalk7.gray(" Directory:"), chalk7.cyan(`${safeName}/`));
|
|
1450
1519
|
console.log(
|
|
@@ -1461,36 +1530,89 @@ async function createCommand(projectName, options) {
|
|
|
1461
1530
|
console.log(chalk7.gray(" Bounded Contexts:"), chalk7.cyan(`${selectedCount}/${totalCount} (${selectedNames})`));
|
|
1462
1531
|
}
|
|
1463
1532
|
}
|
|
1464
|
-
|
|
1533
|
+
console.log("");
|
|
1534
|
+
console.log(chalk7.bold(" Templates"));
|
|
1535
|
+
console.log(chalk7.gray(divider));
|
|
1536
|
+
if (backendTemplateId) {
|
|
1537
|
+
console.log(chalk7.gray(" Backend:"), chalk7.cyan(backendTemplateId));
|
|
1538
|
+
}
|
|
1539
|
+
if (frontendTemplateId) {
|
|
1540
|
+
console.log(chalk7.gray(" Frontend:"), chalk7.cyan(frontendTemplateId));
|
|
1541
|
+
}
|
|
1542
|
+
if (backendEstimatedFiles.length > 0) {
|
|
1543
|
+
console.log("");
|
|
1544
|
+
console.log(chalk7.bold(` Backend Files (${backendTemplateId})`));
|
|
1545
|
+
console.log(chalk7.gray(divider));
|
|
1546
|
+
const backendCategories = categorizeFilePaths(backendEstimatedFiles);
|
|
1547
|
+
const backendMaxNameLength = Math.max(...backendCategories.map((c) => c.name.length), 8);
|
|
1548
|
+
for (const category of backendCategories) {
|
|
1549
|
+
const padding = " ".repeat(backendMaxNameLength - category.name.length + 2);
|
|
1550
|
+
const countStr = category.count.toString().padStart(3, " ");
|
|
1551
|
+
console.log(
|
|
1552
|
+
chalk7.gray(` ${category.name}${padding}`),
|
|
1553
|
+
chalk7.cyan(`${countStr} files`)
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
console.log(chalk7.gray(divider));
|
|
1557
|
+
const subtotalPadding = " ".repeat(backendMaxNameLength - 8 + 2);
|
|
1558
|
+
const subtotalStr = backendEstimatedFiles.length.toString().padStart(3, " ");
|
|
1559
|
+
console.log(
|
|
1560
|
+
chalk7.white(` Subtotal${subtotalPadding}`),
|
|
1561
|
+
chalk7.bold.cyan(`${subtotalStr} files`)
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
if (frontendEstimatedFiles.length > 0) {
|
|
1465
1565
|
console.log("");
|
|
1466
|
-
console.log(chalk7.bold(
|
|
1467
|
-
console.log(chalk7.gray(
|
|
1468
|
-
const
|
|
1469
|
-
const
|
|
1470
|
-
for (const category of
|
|
1471
|
-
const padding = " ".repeat(
|
|
1566
|
+
console.log(chalk7.bold(` Frontend Files (${frontendTemplateId})`));
|
|
1567
|
+
console.log(chalk7.gray(divider));
|
|
1568
|
+
const frontendCategories = categorizeFilePaths(frontendEstimatedFiles);
|
|
1569
|
+
const frontendMaxNameLength = Math.max(...frontendCategories.map((c) => c.name.length), 8);
|
|
1570
|
+
for (const category of frontendCategories) {
|
|
1571
|
+
const padding = " ".repeat(frontendMaxNameLength - category.name.length + 2);
|
|
1472
1572
|
const countStr = category.count.toString().padStart(3, " ");
|
|
1473
1573
|
console.log(
|
|
1474
1574
|
chalk7.gray(` ${category.name}${padding}`),
|
|
1475
1575
|
chalk7.cyan(`${countStr} files`)
|
|
1476
1576
|
);
|
|
1477
1577
|
}
|
|
1478
|
-
console.log(chalk7.gray(
|
|
1479
|
-
const
|
|
1480
|
-
const
|
|
1578
|
+
console.log(chalk7.gray(divider));
|
|
1579
|
+
const subtotalPadding = " ".repeat(frontendMaxNameLength - 8 + 2);
|
|
1580
|
+
const subtotalStr = frontendEstimatedFiles.length.toString().padStart(3, " ");
|
|
1581
|
+
console.log(
|
|
1582
|
+
chalk7.white(` Subtotal${subtotalPadding}`),
|
|
1583
|
+
chalk7.bold.cyan(`${subtotalStr} files`)
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
if (backendEstimatedFiles.length > 0 && frontendEstimatedFiles.length > 0) {
|
|
1587
|
+
console.log("");
|
|
1588
|
+
console.log(chalk7.gray(divider));
|
|
1481
1589
|
console.log(
|
|
1482
|
-
chalk7.white(
|
|
1483
|
-
chalk7.bold.
|
|
1590
|
+
chalk7.bold.white(" TOTAL "),
|
|
1591
|
+
chalk7.bold.green(`${totalEstimatedFiles.toString().padStart(3, " ")} files`)
|
|
1484
1592
|
);
|
|
1485
|
-
console.log(chalk7.gray(" Entities:"), chalk7.cyan(
|
|
1593
|
+
console.log(chalk7.gray(" Entities:"), chalk7.cyan(totalEstimatedEntities));
|
|
1594
|
+
} else if (totalEstimatedFiles > 0) {
|
|
1595
|
+
console.log(chalk7.gray(" Entities:"), chalk7.cyan(totalEstimatedEntities));
|
|
1486
1596
|
}
|
|
1487
1597
|
console.log("");
|
|
1488
1598
|
console.log(chalk7.bold(" Credits"));
|
|
1489
|
-
console.log(chalk7.gray(
|
|
1490
|
-
if (
|
|
1599
|
+
console.log(chalk7.gray(divider));
|
|
1600
|
+
if (backendEstimatedCredits > 0) {
|
|
1601
|
+
console.log(
|
|
1602
|
+
chalk7.gray(" Backend:"),
|
|
1603
|
+
chalk7.yellow(`${backendEstimatedCredits} credits`)
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
if (frontendEstimatedCredits > 0) {
|
|
1491
1607
|
console.log(
|
|
1492
|
-
chalk7.
|
|
1493
|
-
|
|
1608
|
+
chalk7.gray(" Frontend:"),
|
|
1609
|
+
chalk7.yellow(`${frontendEstimatedCredits} credits`)
|
|
1610
|
+
);
|
|
1611
|
+
}
|
|
1612
|
+
if (totalEstimatedCredits > 0) {
|
|
1613
|
+
console.log(
|
|
1614
|
+
chalk7.white(" Total Cost:"),
|
|
1615
|
+
hasEnoughCredits ? chalk7.bold.yellow(`${totalEstimatedCredits} credits`) : chalk7.bold.red(`${totalEstimatedCredits} credits`)
|
|
1494
1616
|
);
|
|
1495
1617
|
}
|
|
1496
1618
|
console.log(
|
|
@@ -1499,13 +1621,13 @@ async function createCommand(projectName, options) {
|
|
|
1499
1621
|
);
|
|
1500
1622
|
console.log("");
|
|
1501
1623
|
if (!hasEnoughCredits) {
|
|
1502
|
-
console.log(chalk7.red("
|
|
1503
|
-
console.log(chalk7.gray(` Need ${
|
|
1624
|
+
console.log(chalk7.red(" ! Not enough credits for this generation."));
|
|
1625
|
+
console.log(chalk7.gray(` Need ${totalEstimatedCredits - creditsRemaining} more credits.
|
|
1504
1626
|
`));
|
|
1505
1627
|
process.exit(1);
|
|
1506
1628
|
}
|
|
1507
1629
|
const proceed = await p.confirm({
|
|
1508
|
-
message:
|
|
1630
|
+
message: totalEstimatedCredits > 0 ? `Proceed with generation? (~${totalEstimatedCredits} credits)` : "Proceed with generation?",
|
|
1509
1631
|
initialValue: true
|
|
1510
1632
|
});
|
|
1511
1633
|
if (p.isCancel(proceed) || !proceed) {
|
|
@@ -1516,47 +1638,68 @@ async function createCommand(projectName, options) {
|
|
|
1516
1638
|
dirSpinner.start(`Creating ${safeName}/...`);
|
|
1517
1639
|
await mkdir(projectDir, { recursive: true });
|
|
1518
1640
|
dirSpinner.stop(`Created ${safeName}/`);
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
// Filter by selected bounded contexts
|
|
1641
|
+
const allGeneratedFiles = [];
|
|
1642
|
+
let totalCreditsUsed = 0;
|
|
1643
|
+
let totalCreditsRemaining = creditsRemaining;
|
|
1644
|
+
if (backendTemplateId) {
|
|
1645
|
+
const backendSpinner = ora2({ text: "Generating backend architecture...", color: "cyan" }).start();
|
|
1646
|
+
const backendResult = await apiClient.generateCode({
|
|
1647
|
+
projectId,
|
|
1648
|
+
templateId: backendTemplateId,
|
|
1649
|
+
options: {
|
|
1650
|
+
includeValidations: true,
|
|
1651
|
+
includeComments: true,
|
|
1652
|
+
featureFlags,
|
|
1653
|
+
useContexts,
|
|
1654
|
+
diagramIds: estimateDiagramIds
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
const backendFiles = (backendResult.files || []).map((file) => ({
|
|
1658
|
+
...file,
|
|
1659
|
+
path: `backend/${file.path}`
|
|
1660
|
+
}));
|
|
1661
|
+
allGeneratedFiles.push(...backendFiles);
|
|
1662
|
+
if (backendResult.creditsUsed !== void 0) {
|
|
1663
|
+
totalCreditsUsed += backendResult.creditsUsed;
|
|
1664
|
+
totalCreditsRemaining = backendResult.creditsRemaining ?? totalCreditsRemaining;
|
|
1544
1665
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1666
|
+
backendSpinner.succeed(chalk7.green(`Backend: ${backendFiles.length} files generated`));
|
|
1667
|
+
}
|
|
1668
|
+
if (frontendTemplateId) {
|
|
1669
|
+
const frontendSpinner = ora2({ text: "Generating frontend architecture...", color: "cyan" }).start();
|
|
1670
|
+
const frontendResult = await apiClient.generateCode({
|
|
1671
|
+
projectId,
|
|
1672
|
+
templateId: frontendTemplateId,
|
|
1673
|
+
options: {
|
|
1674
|
+
includeValidations: true,
|
|
1675
|
+
includeComments: true,
|
|
1676
|
+
featureFlags,
|
|
1677
|
+
useContexts,
|
|
1678
|
+
diagramIds: estimateDiagramIds
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
const frontendFiles = (frontendResult.files || []).map((file) => ({
|
|
1682
|
+
...file,
|
|
1683
|
+
path: `frontend/${file.path}`
|
|
1684
|
+
}));
|
|
1685
|
+
allGeneratedFiles.push(...frontendFiles);
|
|
1686
|
+
if (frontendResult.creditsUsed !== void 0) {
|
|
1687
|
+
totalCreditsUsed += frontendResult.creditsUsed;
|
|
1688
|
+
totalCreditsRemaining = frontendResult.creditsRemaining ?? totalCreditsRemaining;
|
|
1689
|
+
}
|
|
1690
|
+
frontendSpinner.succeed(chalk7.green(`Frontend: ${frontendFiles.length} files generated`));
|
|
1691
|
+
}
|
|
1547
1692
|
const writeSpinner = p.spinner();
|
|
1548
1693
|
writeSpinner.start("Writing files...");
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
path: `backend/${file.path}`
|
|
1552
|
-
}));
|
|
1553
|
-
await writeGeneratedFiles(organizedFiles, projectDir, false);
|
|
1554
|
-
writeSpinner.stop(`Wrote ${organizedFiles.length} files`);
|
|
1694
|
+
await writeGeneratedFiles(allGeneratedFiles, projectDir, false);
|
|
1695
|
+
writeSpinner.stop(`Wrote ${allGeneratedFiles.length} files`);
|
|
1555
1696
|
const configSpinner = p.spinner();
|
|
1556
1697
|
configSpinner.start("Creating config files...");
|
|
1557
1698
|
const projectConfig = createProjectConfig({
|
|
1558
1699
|
projectId,
|
|
1559
|
-
templateId,
|
|
1700
|
+
templateId: backendTemplateId || frontendTemplateId || "",
|
|
1701
|
+
backendTemplateId,
|
|
1702
|
+
frontendTemplateId,
|
|
1560
1703
|
templateVersion: "1.0.0",
|
|
1561
1704
|
// TODO: get from template
|
|
1562
1705
|
namespace,
|
|
@@ -1565,7 +1708,7 @@ async function createCommand(projectName, options) {
|
|
|
1565
1708
|
});
|
|
1566
1709
|
await saveProjectConfig(projectConfig, projectDir);
|
|
1567
1710
|
let manifest = createEmptyManifest("1.0.0");
|
|
1568
|
-
for (const file of
|
|
1711
|
+
for (const file of allGeneratedFiles) {
|
|
1569
1712
|
const hash = hashString(file.content);
|
|
1570
1713
|
manifest = setManifestFile(manifest, file.path, {
|
|
1571
1714
|
hash,
|
|
@@ -1580,23 +1723,31 @@ async function createCommand(projectName, options) {
|
|
|
1580
1723
|
const syncResult = await syncToCloud(apiClient, projectId, manifest);
|
|
1581
1724
|
if (syncResult.success) {
|
|
1582
1725
|
console.log(
|
|
1583
|
-
chalk7.gray("
|
|
1726
|
+
chalk7.gray(" OK Manifest synced to cloud") + chalk7.gray(` (${syncResult.fileCount} files)`)
|
|
1584
1727
|
);
|
|
1585
1728
|
}
|
|
1586
1729
|
} catch {
|
|
1587
|
-
console.log(chalk7.yellow("
|
|
1730
|
+
console.log(chalk7.yellow(" ! Could not sync manifest to cloud"));
|
|
1588
1731
|
}
|
|
1589
1732
|
console.log("");
|
|
1590
1733
|
console.log(chalk7.bold(" Project Created Successfully!"));
|
|
1591
|
-
console.log(chalk7.gray(
|
|
1734
|
+
console.log(chalk7.gray(divider));
|
|
1592
1735
|
console.log(chalk7.gray(" Directory:"), chalk7.cyan(safeName + "/"));
|
|
1593
|
-
|
|
1736
|
+
const backendFileCount = allGeneratedFiles.filter((f) => f.path.startsWith("backend/")).length;
|
|
1737
|
+
const frontendFileCount = allGeneratedFiles.filter((f) => f.path.startsWith("frontend/")).length;
|
|
1738
|
+
if (backendFileCount > 0 && frontendFileCount > 0) {
|
|
1739
|
+
console.log(chalk7.gray(" Backend:"), chalk7.cyan(`${backendFileCount} files`));
|
|
1740
|
+
console.log(chalk7.gray(" Frontend:"), chalk7.cyan(`${frontendFileCount} files`));
|
|
1741
|
+
console.log(chalk7.gray(" Total:"), chalk7.bold.cyan(`${allGeneratedFiles.length} files`));
|
|
1742
|
+
} else {
|
|
1743
|
+
console.log(chalk7.gray(" Files:"), chalk7.cyan(allGeneratedFiles.length));
|
|
1744
|
+
}
|
|
1594
1745
|
console.log(chalk7.gray(" Config:"), chalk7.cyan(PROJECT_CONFIG_FILENAME));
|
|
1595
1746
|
console.log(chalk7.gray(" Manifest:"), chalk7.cyan(MANIFEST_FILENAME));
|
|
1596
|
-
if (
|
|
1747
|
+
if (totalCreditsUsed > 0) {
|
|
1597
1748
|
console.log("");
|
|
1598
|
-
console.log(chalk7.gray(" Credits used:"), chalk7.yellow(
|
|
1599
|
-
console.log(chalk7.gray(" Credits remaining:"), chalk7.green(
|
|
1749
|
+
console.log(chalk7.gray(" Credits used:"), chalk7.yellow(totalCreditsUsed));
|
|
1750
|
+
console.log(chalk7.gray(" Credits remaining:"), chalk7.green(totalCreditsRemaining));
|
|
1600
1751
|
}
|
|
1601
1752
|
p.outro(
|
|
1602
1753
|
chalk7.green("Project ready!") + "\n\n" + chalk7.gray(" Next steps:\n") + chalk7.cyan(` cd ${safeName}
|
|
@@ -1714,59 +1865,124 @@ async function updateCommand(options) {
|
|
|
1714
1865
|
projectSpinner.start("Fetching project details...");
|
|
1715
1866
|
const project = await apiClient.getProject(config.projectId);
|
|
1716
1867
|
projectSpinner.stop(`Project: ${project.name}`);
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1868
|
+
const divider = ` ${"-".repeat(33)}`;
|
|
1869
|
+
const hasBackend = !!project.backendFramework;
|
|
1870
|
+
const hasFrontend = !!project.frontendFramework;
|
|
1871
|
+
let backendTemplateId = config.backendTemplateId;
|
|
1872
|
+
let frontendTemplateId = config.frontendTemplateId;
|
|
1873
|
+
if (!backendTemplateId && !frontendTemplateId) {
|
|
1874
|
+
if (hasBackend) {
|
|
1875
|
+
backendTemplateId = config.templateId;
|
|
1876
|
+
} else if (hasFrontend) {
|
|
1877
|
+
frontendTemplateId = config.templateId;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (!backendTemplateId && !frontendTemplateId) {
|
|
1881
|
+
p2.cancel("No templates configured for this project.");
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
const backendOutput = config.output?.backend || "./backend";
|
|
1885
|
+
const frontendOutput = config.output?.frontend || "./frontend";
|
|
1886
|
+
const featureFlags = {
|
|
1887
|
+
includeDtos: true,
|
|
1888
|
+
includeUseCases: true,
|
|
1889
|
+
includeMappers: true,
|
|
1890
|
+
includeControllers: true,
|
|
1891
|
+
includeEfConfig: true,
|
|
1892
|
+
includeValidation: true,
|
|
1893
|
+
includeDtoValidation: true,
|
|
1894
|
+
includeUnitTests: true,
|
|
1895
|
+
includeIntegrationTests: true,
|
|
1896
|
+
includeStarterFiles: false,
|
|
1897
|
+
// Don't regenerate starter files on update
|
|
1898
|
+
includeReactions: true,
|
|
1899
|
+
includeOutbox: true,
|
|
1900
|
+
includeInbox: true
|
|
1901
|
+
};
|
|
1902
|
+
console.log("");
|
|
1903
|
+
console.log(chalk8.bold(" Update Configuration"));
|
|
1904
|
+
console.log(chalk8.gray(divider));
|
|
1905
|
+
console.log(chalk8.gray(" Project:"), chalk8.white(project.name));
|
|
1906
|
+
console.log(chalk8.gray(" Namespace:"), chalk8.cyan(config.namespace));
|
|
1907
|
+
if (backendTemplateId) {
|
|
1908
|
+
console.log(chalk8.gray(" Backend:"), chalk8.cyan(backendTemplateId));
|
|
1909
|
+
console.log(chalk8.gray(" Output:"), chalk8.cyan(backendOutput));
|
|
1910
|
+
}
|
|
1911
|
+
if (frontendTemplateId) {
|
|
1912
|
+
console.log(chalk8.gray(" Frontend:"), chalk8.cyan(frontendTemplateId));
|
|
1913
|
+
console.log(chalk8.gray(" Output:"), chalk8.cyan(frontendOutput));
|
|
1914
|
+
}
|
|
1722
1915
|
if (options.dryRun) {
|
|
1723
|
-
|
|
1916
|
+
console.log(chalk8.yellow(" Mode: DRY RUN (no files will be written)"));
|
|
1724
1917
|
}
|
|
1725
1918
|
if (options.force) {
|
|
1726
|
-
|
|
1919
|
+
console.log(chalk8.yellow(" Mode: FORCE (will overwrite modified files)"));
|
|
1727
1920
|
}
|
|
1728
|
-
let
|
|
1921
|
+
let backendEstimatedCredits = 0;
|
|
1922
|
+
let frontendEstimatedCredits = 0;
|
|
1729
1923
|
let creditsRemaining = 0;
|
|
1730
1924
|
const estimateSpinner = p2.spinner();
|
|
1731
1925
|
estimateSpinner.start("Calculating credit cost...");
|
|
1732
1926
|
try {
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1927
|
+
if (backendTemplateId) {
|
|
1928
|
+
const backendEstimate = await apiClient.estimateCreditCost({
|
|
1929
|
+
projectId: config.projectId,
|
|
1930
|
+
templateId: backendTemplateId,
|
|
1931
|
+
options: {
|
|
1932
|
+
featureFlags,
|
|
1933
|
+
useContexts: true
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
backendEstimatedCredits = backendEstimate.estimatedCredits;
|
|
1937
|
+
}
|
|
1938
|
+
if (frontendTemplateId) {
|
|
1939
|
+
const frontendEstimate = await apiClient.estimateCreditCost({
|
|
1940
|
+
projectId: config.projectId,
|
|
1941
|
+
templateId: frontendTemplateId,
|
|
1942
|
+
options: {
|
|
1943
|
+
featureFlags,
|
|
1944
|
+
useContexts: true
|
|
1945
|
+
}
|
|
1946
|
+
});
|
|
1947
|
+
frontendEstimatedCredits = frontendEstimate.estimatedCredits;
|
|
1948
|
+
}
|
|
1753
1949
|
const creditUsage = await apiClient.getCreditUsage(project.organizationId);
|
|
1754
1950
|
creditsRemaining = creditUsage.remaining;
|
|
1755
1951
|
estimateSpinner.stop("Credit estimate calculated");
|
|
1756
1952
|
} catch {
|
|
1757
1953
|
estimateSpinner.stop("Could not estimate credits (will be calculated on generation)");
|
|
1758
1954
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1955
|
+
const totalEstimatedCredits = backendEstimatedCredits + frontendEstimatedCredits;
|
|
1956
|
+
const hasEnoughCredits = totalEstimatedCredits === 0 || creditsRemaining >= totalEstimatedCredits;
|
|
1957
|
+
console.log("");
|
|
1958
|
+
console.log(chalk8.bold(" Credits"));
|
|
1959
|
+
console.log(chalk8.gray(divider));
|
|
1960
|
+
if (backendEstimatedCredits > 0) {
|
|
1961
|
+
console.log(chalk8.gray(" Backend:"), chalk8.yellow(`${backendEstimatedCredits} credits`));
|
|
1962
|
+
}
|
|
1963
|
+
if (frontendEstimatedCredits > 0) {
|
|
1964
|
+
console.log(chalk8.gray(" Frontend:"), chalk8.yellow(`${frontendEstimatedCredits} credits`));
|
|
1965
|
+
}
|
|
1966
|
+
if (totalEstimatedCredits > 0) {
|
|
1967
|
+
console.log(
|
|
1968
|
+
chalk8.white(" Total Cost:"),
|
|
1969
|
+
hasEnoughCredits ? chalk8.bold.yellow(`${totalEstimatedCredits} credits`) : chalk8.bold.red(`${totalEstimatedCredits} credits`)
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
console.log(
|
|
1973
|
+
chalk8.white(" Available:"),
|
|
1974
|
+
hasEnoughCredits ? chalk8.bold.green(`${creditsRemaining} credits`) : chalk8.bold.red(`${creditsRemaining} credits (insufficient)`)
|
|
1975
|
+
);
|
|
1976
|
+
console.log("");
|
|
1977
|
+
if (!hasEnoughCredits) {
|
|
1978
|
+
console.log(chalk8.red(" ! Not enough credits for this generation."));
|
|
1979
|
+
console.log(chalk8.gray(` Need ${totalEstimatedCredits - creditsRemaining} more credits.
|
|
1980
|
+
`));
|
|
1981
|
+
process.exit(1);
|
|
1766
1982
|
}
|
|
1767
1983
|
if (!options.dryRun) {
|
|
1768
1984
|
const proceed = await p2.confirm({
|
|
1769
|
-
message:
|
|
1985
|
+
message: totalEstimatedCredits > 0 ? `Proceed with update? (~${totalEstimatedCredits} credits)` : "Proceed with update?",
|
|
1770
1986
|
initialValue: true
|
|
1771
1987
|
});
|
|
1772
1988
|
if (p2.isCancel(proceed) || !proceed) {
|
|
@@ -1774,56 +1990,70 @@ async function updateCommand(options) {
|
|
|
1774
1990
|
process.exit(0);
|
|
1775
1991
|
}
|
|
1776
1992
|
}
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
useContexts: true
|
|
1993
|
+
const allGeneratedFiles = [];
|
|
1994
|
+
let totalCreditsUsed = 0;
|
|
1995
|
+
let totalCreditsRemaining = creditsRemaining;
|
|
1996
|
+
if (backendTemplateId) {
|
|
1997
|
+
const backendSpinner = ora3({ text: "Generating backend code...", color: "cyan" }).start();
|
|
1998
|
+
const backendResult = await apiClient.generateCode({
|
|
1999
|
+
projectId: config.projectId,
|
|
2000
|
+
templateId: backendTemplateId,
|
|
2001
|
+
options: {
|
|
2002
|
+
includeValidations: true,
|
|
2003
|
+
includeComments: true,
|
|
2004
|
+
featureFlags,
|
|
2005
|
+
useContexts: true
|
|
2006
|
+
}
|
|
2007
|
+
});
|
|
2008
|
+
const backendFiles = (backendResult.files || []).map((file) => ({
|
|
2009
|
+
...file,
|
|
2010
|
+
path: `${backendOutput.replace(/^\.\//, "")}/${file.path}`
|
|
2011
|
+
}));
|
|
2012
|
+
allGeneratedFiles.push(...backendFiles);
|
|
2013
|
+
if (backendResult.creditsUsed !== void 0) {
|
|
2014
|
+
totalCreditsUsed += backendResult.creditsUsed;
|
|
2015
|
+
totalCreditsRemaining = backendResult.creditsRemaining ?? totalCreditsRemaining;
|
|
1801
2016
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2017
|
+
backendSpinner.succeed(chalk8.green(`Backend: ${backendFiles.length} files generated`));
|
|
2018
|
+
}
|
|
2019
|
+
if (frontendTemplateId) {
|
|
2020
|
+
const frontendSpinner = ora3({ text: "Generating frontend code...", color: "cyan" }).start();
|
|
2021
|
+
const frontendResult = await apiClient.generateCode({
|
|
2022
|
+
projectId: config.projectId,
|
|
2023
|
+
templateId: frontendTemplateId,
|
|
2024
|
+
options: {
|
|
2025
|
+
includeValidations: true,
|
|
2026
|
+
includeComments: true,
|
|
2027
|
+
featureFlags,
|
|
2028
|
+
useContexts: true
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
const frontendFiles = (frontendResult.files || []).map((file) => ({
|
|
2032
|
+
...file,
|
|
2033
|
+
path: `${frontendOutput.replace(/^\.\//, "")}/${file.path}`
|
|
2034
|
+
}));
|
|
2035
|
+
allGeneratedFiles.push(...frontendFiles);
|
|
2036
|
+
if (frontendResult.creditsUsed !== void 0) {
|
|
2037
|
+
totalCreditsUsed += frontendResult.creditsUsed;
|
|
2038
|
+
totalCreditsRemaining = frontendResult.creditsRemaining ?? totalCreditsRemaining;
|
|
2039
|
+
}
|
|
2040
|
+
frontendSpinner.succeed(chalk8.green(`Frontend: ${frontendFiles.length} files generated`));
|
|
2041
|
+
}
|
|
1806
2042
|
if (options.dryRun) {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2043
|
+
console.log("");
|
|
2044
|
+
console.log(chalk8.bold(" Dry Run Results:"));
|
|
2045
|
+
console.log(chalk8.gray(divider));
|
|
2046
|
+
console.log(` Files to process: ${allGeneratedFiles.length}`);
|
|
2047
|
+
console.log(` Tracked files: ${fileCount}`);
|
|
2048
|
+
console.log("");
|
|
2049
|
+
console.log(chalk8.gray(" Run without --dry-run to apply changes."));
|
|
1814
2050
|
p2.outro(chalk8.green("Dry run complete!"));
|
|
1815
2051
|
return;
|
|
1816
2052
|
}
|
|
1817
2053
|
const updateSpinner = p2.spinner();
|
|
1818
2054
|
updateSpinner.start("Applying updates...");
|
|
1819
|
-
const organizedFiles = result.files.map(
|
|
1820
|
-
(file) => ({
|
|
1821
|
-
...file,
|
|
1822
|
-
path: `${config.output.backend.replace(/^\.\//, "")}/${file.path}`
|
|
1823
|
-
})
|
|
1824
|
-
);
|
|
1825
2055
|
const writeResult = await writeGeneratedFilesWithManifest(
|
|
1826
|
-
|
|
2056
|
+
allGeneratedFiles,
|
|
1827
2057
|
process.cwd(),
|
|
1828
2058
|
config.templateVersion || "1.0.0",
|
|
1829
2059
|
{
|
|
@@ -1832,13 +2062,10 @@ async function updateCommand(options) {
|
|
|
1832
2062
|
}
|
|
1833
2063
|
);
|
|
1834
2064
|
updateSpinner.stop("Update complete");
|
|
1835
|
-
if (
|
|
2065
|
+
if (totalCreditsUsed > 0) {
|
|
1836
2066
|
console.log("");
|
|
1837
|
-
console.log(chalk8.gray(" Credits used:"), chalk8.yellow(
|
|
1838
|
-
console.log(
|
|
1839
|
-
chalk8.gray(" Credits remaining:"),
|
|
1840
|
-
chalk8.green(result.creditsRemaining)
|
|
1841
|
-
);
|
|
2067
|
+
console.log(chalk8.gray(" Credits used:"), chalk8.yellow(totalCreditsUsed));
|
|
2068
|
+
console.log(chalk8.gray(" Credits remaining:"), chalk8.green(totalCreditsRemaining));
|
|
1842
2069
|
}
|
|
1843
2070
|
try {
|
|
1844
2071
|
const syncResult = await syncToCloud(
|