@korajs/cli 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -1295,62 +1295,24 @@ function resolveKoraVersion() {
1295
1295
  }
1296
1296
 
1297
1297
  // src/commands/deploy/deploy-command.ts
1298
- var import_node_path14 = require("path");
1298
+ var import_node_path16 = require("path");
1299
1299
  var import_citty2 = require("citty");
1300
1300
 
1301
1301
  // src/commands/deploy/adapters/adapter.ts
1302
- var DEPLOY_PLATFORMS = ["fly", "railway", "render", "docker", "kora-cloud"];
1302
+ var DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
1303
1303
  function isDeployPlatform(value) {
1304
1304
  return DEPLOY_PLATFORMS.includes(value);
1305
1305
  }
1306
1306
 
1307
- // src/commands/deploy/adapters/fly-adapter.ts
1307
+ // src/commands/deploy/adapters/aws-ecs-adapter.ts
1308
1308
  var import_node_child_process4 = require("child_process");
1309
- var import_node_path9 = require("path");
1310
-
1311
- // src/commands/deploy/artifacts/fly-toml-generator.ts
1312
- var import_promises5 = require("fs/promises");
1313
- var import_node_path6 = require("path");
1314
- var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
1315
- function generateFlyToml(options) {
1316
- const internalPort = options.internalPort ?? 3e3;
1317
- return [
1318
- GENERATED_HEADER,
1319
- "",
1320
- `app = "${options.appName}"`,
1321
- `primary_region = "${options.region}"`,
1322
- "",
1323
- "[build]",
1324
- ` dockerfile = "Dockerfile"`,
1325
- "",
1326
- "[http_service]",
1327
- ` internal_port = ${String(internalPort)}`,
1328
- " force_https = true",
1329
- ' auto_stop_machines = "stop"',
1330
- " auto_start_machines = true",
1331
- " min_machines_running = 0",
1332
- "",
1333
- " [[http_service.checks]]",
1334
- ' grace_period = "10s"',
1335
- ' interval = "30s"',
1336
- ' method = "GET"',
1337
- ' timeout = "5s"',
1338
- ' path = "/"',
1339
- ""
1340
- ].join("\n");
1341
- }
1342
- async function writeFlyTomlArtifact(deployDirectory, options) {
1343
- await (0, import_promises5.mkdir)(deployDirectory, { recursive: true });
1344
- const filePath = (0, import_node_path6.join)(deployDirectory, "fly.toml");
1345
- await (0, import_promises5.writeFile)(filePath, generateFlyToml(options), "utf-8");
1346
- return filePath;
1347
- }
1309
+ var import_node_path8 = require("path");
1348
1310
 
1349
1311
  // src/commands/deploy/builder/client-builder.ts
1350
1312
  var import_node_child_process3 = require("child_process");
1351
- var import_promises6 = require("fs/promises");
1313
+ var import_promises5 = require("fs/promises");
1352
1314
  var import_node_fs3 = require("fs");
1353
- var import_node_path7 = require("path");
1315
+ var import_node_path6 = require("path");
1354
1316
  async function buildClient(options) {
1355
1317
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
1356
1318
  if (!viteEntryPoint) {
@@ -1371,15 +1333,15 @@ async function buildClient(options) {
1371
1333
  return { outDir: options.outDir };
1372
1334
  }
1373
1335
  async function patchSqliteWasmAssets(projectRoot, outDir) {
1374
- const assetsDir = (0, import_node_path7.join)(outDir, "assets");
1336
+ const assetsDir = (0, import_node_path6.join)(outDir, "assets");
1375
1337
  if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
1376
- const files = await (0, import_promises6.readdir)(assetsDir);
1338
+ const files = await (0, import_promises5.readdir)(assetsDir);
1377
1339
  const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
1378
1340
  if (hashedWasm && !files.includes("sqlite3.wasm")) {
1379
- await (0, import_promises6.copyFile)((0, import_node_path7.join)(assetsDir, hashedWasm), (0, import_node_path7.join)(assetsDir, "sqlite3.wasm"));
1341
+ await (0, import_promises5.copyFile)((0, import_node_path6.join)(assetsDir, hashedWasm), (0, import_node_path6.join)(assetsDir, "sqlite3.wasm"));
1380
1342
  }
1381
1343
  if (!files.includes("sqlite3-opfs-async-proxy.js")) {
1382
- const proxyFile = (0, import_node_path7.resolve)(
1344
+ const proxyFile = (0, import_node_path6.resolve)(
1383
1345
  projectRoot,
1384
1346
  "node_modules",
1385
1347
  "@sqlite.org",
@@ -1389,7 +1351,7 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
1389
1351
  "sqlite3-opfs-async-proxy.js"
1390
1352
  );
1391
1353
  if ((0, import_node_fs3.existsSync)(proxyFile)) {
1392
- await (0, import_promises6.copyFile)(proxyFile, (0, import_node_path7.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
1354
+ await (0, import_promises5.copyFile)(proxyFile, (0, import_node_path6.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
1393
1355
  }
1394
1356
  }
1395
1357
  }
@@ -1414,9 +1376,9 @@ async function runProcess(command, args, cwd) {
1414
1376
  }
1415
1377
 
1416
1378
  // src/commands/deploy/builder/server-bundler.ts
1379
+ var import_promises6 = require("fs/promises");
1417
1380
  var import_promises7 = require("fs/promises");
1418
- var import_promises8 = require("fs/promises");
1419
- var import_node_path8 = require("path");
1381
+ var import_node_path7 = require("path");
1420
1382
  var import_esbuild = require("esbuild");
1421
1383
  var DEFAULT_ENTRY_CANDIDATES = [
1422
1384
  "server.ts",
@@ -1432,8 +1394,8 @@ async function bundleServer(options) {
1432
1394
  `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
1433
1395
  );
1434
1396
  }
1435
- await (0, import_promises7.mkdir)(options.deployDirectory, { recursive: true });
1436
- const outputFilePath = (0, import_node_path8.join)(options.deployDirectory, "server-bundled.js");
1397
+ await (0, import_promises6.mkdir)(options.deployDirectory, { recursive: true });
1398
+ const outputFilePath = (0, import_node_path7.join)(options.deployDirectory, "server-bundled.js");
1437
1399
  await (0, import_esbuild.build)({
1438
1400
  entryPoints: [entryFilePath],
1439
1401
  outfile: outputFilePath,
@@ -1455,9 +1417,9 @@ async function bundleServer(options) {
1455
1417
  }
1456
1418
  async function resolveServerEntry(projectRoot, candidates) {
1457
1419
  for (const candidate of candidates) {
1458
- const fullPath = (0, import_node_path8.join)(projectRoot, candidate);
1420
+ const fullPath = (0, import_node_path7.join)(projectRoot, candidate);
1459
1421
  try {
1460
- await (0, import_promises8.access)(fullPath);
1422
+ await (0, import_promises7.access)(fullPath);
1461
1423
  return fullPath;
1462
1424
  } catch {
1463
1425
  }
@@ -1465,6 +1427,785 @@ async function resolveServerEntry(projectRoot, candidates) {
1465
1427
  return null;
1466
1428
  }
1467
1429
 
1430
+ // src/commands/deploy/adapters/aws-ecs-adapter.ts
1431
+ var AwsEcsAdapter = class {
1432
+ name = "aws-ecs";
1433
+ logger = createLogger();
1434
+ runner;
1435
+ currentContext;
1436
+ constructor(options = {}) {
1437
+ this.runner = options.runner ?? new NodeAwsCommandRunner();
1438
+ this.currentContext = options.context ?? null;
1439
+ }
1440
+ setContext(context) {
1441
+ this.currentContext = context;
1442
+ }
1443
+ async detect() {
1444
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
1445
+ return result.exitCode === 0;
1446
+ }
1447
+ async install() {
1448
+ const available = await this.detect();
1449
+ if (!available) {
1450
+ throw new Error(
1451
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1452
+ );
1453
+ }
1454
+ }
1455
+ async authenticate() {
1456
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
1457
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
1458
+ if (result.exitCode !== 0) {
1459
+ throw new Error(
1460
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
1461
+ );
1462
+ }
1463
+ }
1464
+ async provision(config) {
1465
+ this.currentContext = {
1466
+ projectRoot: config.projectRoot,
1467
+ appName: config.appName,
1468
+ region: config.region ?? "us-east-1"
1469
+ };
1470
+ const region = config.region ?? "us-east-1";
1471
+ const repoName = `kora/${config.appName}`;
1472
+ const createRepo = await this.runner.run("aws", [
1473
+ "ecr",
1474
+ "create-repository",
1475
+ "--repository-name",
1476
+ repoName,
1477
+ "--region",
1478
+ region,
1479
+ "--image-scanning-configuration",
1480
+ "scanOnPush=true"
1481
+ ], config.projectRoot);
1482
+ if (createRepo.exitCode !== 0 && !createRepo.stderr.includes("RepositoryAlreadyExistsException")) {
1483
+ throw new Error(`Failed to create ECR repository: ${createRepo.stderr}`);
1484
+ }
1485
+ const identity = await this.runner.run("aws", ["sts", "get-caller-identity", "--query", "Account", "--output", "text"], config.projectRoot);
1486
+ const accountId = identity.stdout.trim();
1487
+ await this.runner.run("aws", [
1488
+ "ecs",
1489
+ "create-cluster",
1490
+ "--cluster-name",
1491
+ config.appName,
1492
+ "--region",
1493
+ region
1494
+ ], config.projectRoot);
1495
+ await this.runner.run("aws", [
1496
+ "logs",
1497
+ "create-log-group",
1498
+ "--log-group-name",
1499
+ `/ecs/${config.appName}`,
1500
+ "--region",
1501
+ region
1502
+ ], config.projectRoot);
1503
+ return {
1504
+ applicationId: `${accountId}.dkr.ecr.${region}.amazonaws.com/${repoName}`,
1505
+ databaseId: null,
1506
+ secretsSet: ["PORT"]
1507
+ };
1508
+ }
1509
+ async build(config) {
1510
+ this.currentContext = {
1511
+ projectRoot: config.projectRoot,
1512
+ appName: config.appName,
1513
+ region: config.region
1514
+ };
1515
+ const deployDirectory = (0, import_node_path8.join)(config.projectRoot, ".kora", "deploy");
1516
+ await bundleServer({
1517
+ projectRoot: config.projectRoot,
1518
+ deployDirectory
1519
+ });
1520
+ const client = await buildClient({
1521
+ projectRoot: config.projectRoot,
1522
+ outDir: (0, import_node_path8.join)(deployDirectory, "dist"),
1523
+ mode: "production"
1524
+ });
1525
+ return {
1526
+ clientDirectory: client.outDir,
1527
+ serverBundlePath: (0, import_node_path8.join)(deployDirectory, "server-bundled.js"),
1528
+ deployDirectory
1529
+ };
1530
+ }
1531
+ async deploy(artifacts) {
1532
+ const context = this.requireContext();
1533
+ const region = context.region ?? "us-east-1";
1534
+ const repoName = `kora/${context.appName}`;
1535
+ const loginPassword = await this.runner.run("aws", [
1536
+ "ecr",
1537
+ "get-login-password",
1538
+ "--region",
1539
+ region
1540
+ ], context.projectRoot);
1541
+ if (loginPassword.exitCode !== 0) {
1542
+ throw new Error(`ECR login failed: ${loginPassword.stderr}`);
1543
+ }
1544
+ const identity = await this.runner.run("aws", [
1545
+ "sts",
1546
+ "get-caller-identity",
1547
+ "--query",
1548
+ "Account",
1549
+ "--output",
1550
+ "text"
1551
+ ], context.projectRoot);
1552
+ const accountId = identity.stdout.trim();
1553
+ const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
1554
+ const imageUri = `${ecrUri}/${repoName}:latest`;
1555
+ const dockerLogin = await this.runner.run("docker", [
1556
+ "login",
1557
+ "--username",
1558
+ "AWS",
1559
+ "--password-stdin",
1560
+ ecrUri
1561
+ ], artifacts.deployDirectory);
1562
+ this.logger.step("Building Docker image...");
1563
+ const dockerBuild = await this.runner.run("docker", [
1564
+ "build",
1565
+ "--platform",
1566
+ "linux/amd64",
1567
+ "-t",
1568
+ imageUri,
1569
+ "."
1570
+ ], artifacts.deployDirectory);
1571
+ if (dockerBuild.exitCode !== 0) {
1572
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1573
+ }
1574
+ this.logger.step("Pushing image to ECR...");
1575
+ const dockerPush = await this.runner.run("docker", [
1576
+ "push",
1577
+ imageUri
1578
+ ], artifacts.deployDirectory);
1579
+ if (dockerPush.exitCode !== 0) {
1580
+ throw new Error(`Docker push failed: ${dockerPush.stderr}`);
1581
+ }
1582
+ const taskDef = JSON.stringify({
1583
+ family: context.appName,
1584
+ networkMode: "awsvpc",
1585
+ requiresCompatibilities: ["FARGATE"],
1586
+ cpu: "256",
1587
+ memory: "512",
1588
+ executionRoleArn: `arn:aws:iam::${accountId}:role/ecsTaskExecutionRole`,
1589
+ containerDefinitions: [{
1590
+ name: context.appName,
1591
+ image: imageUri,
1592
+ essential: true,
1593
+ portMappings: [{ containerPort: 3001, protocol: "tcp" }],
1594
+ logConfiguration: {
1595
+ logDriver: "awslogs",
1596
+ options: {
1597
+ "awslogs-group": `/ecs/${context.appName}`,
1598
+ "awslogs-region": region,
1599
+ "awslogs-stream-prefix": "ecs"
1600
+ }
1601
+ },
1602
+ healthCheck: {
1603
+ command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
1604
+ interval: 30,
1605
+ timeout: 5,
1606
+ retries: 3,
1607
+ startPeriod: 60
1608
+ }
1609
+ }]
1610
+ });
1611
+ const registerTask = await this.runner.run("aws", [
1612
+ "ecs",
1613
+ "register-task-definition",
1614
+ "--cli-input-json",
1615
+ taskDef,
1616
+ "--region",
1617
+ region
1618
+ ], context.projectRoot);
1619
+ if (registerTask.exitCode !== 0) {
1620
+ throw new Error(`Task definition registration failed: ${registerTask.stderr}`);
1621
+ }
1622
+ const updateService = await this.runner.run("aws", [
1623
+ "ecs",
1624
+ "update-service",
1625
+ "--cluster",
1626
+ context.appName,
1627
+ "--service",
1628
+ context.appName,
1629
+ "--task-definition",
1630
+ context.appName,
1631
+ "--force-new-deployment",
1632
+ "--region",
1633
+ region
1634
+ ], context.projectRoot);
1635
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1636
+ if (updateService.exitCode !== 0) {
1637
+ this.logger.step("Service not found, creating new service...");
1638
+ this.logger.step(
1639
+ `Task definition registered. Create the ECS service with:
1640
+ aws ecs create-service \\
1641
+ --cluster ${context.appName} \\
1642
+ --service-name ${context.appName} \\
1643
+ --task-definition ${context.appName} \\
1644
+ --desired-count 1 \\
1645
+ --launch-type FARGATE \\
1646
+ --network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<sg-id>],assignPublicIp=ENABLED}" \\
1647
+ --region ${region}`
1648
+ );
1649
+ }
1650
+ return {
1651
+ deploymentId,
1652
+ liveUrl: `https://${context.appName}.${region}.amazonaws.com`,
1653
+ syncUrl: `wss://${context.appName}.${region}.amazonaws.com/kora-sync`
1654
+ };
1655
+ }
1656
+ async rollback(deploymentId) {
1657
+ const context = this.requireContext();
1658
+ const region = context.region ?? "us-east-1";
1659
+ const result = await this.runner.run("aws", [
1660
+ "ecs",
1661
+ "update-service",
1662
+ "--cluster",
1663
+ context.appName,
1664
+ "--service",
1665
+ context.appName,
1666
+ "--task-definition",
1667
+ `${context.appName}:${deploymentId}`,
1668
+ "--force-new-deployment",
1669
+ "--region",
1670
+ region
1671
+ ], context.projectRoot);
1672
+ if (result.exitCode !== 0) {
1673
+ throw new Error(`ECS rollback failed: ${result.stderr}`);
1674
+ }
1675
+ }
1676
+ async *logs(options) {
1677
+ const context = this.requireContext();
1678
+ const region = context.region ?? "us-east-1";
1679
+ const args = [
1680
+ "logs",
1681
+ "get-log-events",
1682
+ "--log-group-name",
1683
+ `/ecs/${context.appName}`,
1684
+ "--log-stream-name",
1685
+ "ecs/latest",
1686
+ "--region",
1687
+ region
1688
+ ];
1689
+ if (options.tail) {
1690
+ args.push("--limit", String(options.tail));
1691
+ }
1692
+ const result = await this.runner.run("aws", args, context.projectRoot);
1693
+ if (result.exitCode !== 0) {
1694
+ return;
1695
+ }
1696
+ try {
1697
+ const parsed = JSON.parse(result.stdout);
1698
+ for (const event of parsed.events ?? []) {
1699
+ yield {
1700
+ timestamp: new Date(event.timestamp).toISOString(),
1701
+ level: inferLogLevel(event.message),
1702
+ message: event.message
1703
+ };
1704
+ }
1705
+ } catch {
1706
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
1707
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
1708
+ }
1709
+ }
1710
+ }
1711
+ async status() {
1712
+ const context = this.requireContext();
1713
+ const region = context.region ?? "us-east-1";
1714
+ const result = await this.runner.run("aws", [
1715
+ "ecs",
1716
+ "describe-services",
1717
+ "--cluster",
1718
+ context.appName,
1719
+ "--services",
1720
+ context.appName,
1721
+ "--region",
1722
+ region
1723
+ ], context.projectRoot);
1724
+ if (result.exitCode !== 0) {
1725
+ return { state: "failed", message: result.stderr };
1726
+ }
1727
+ try {
1728
+ const parsed = JSON.parse(result.stdout);
1729
+ const service = parsed.services?.[0];
1730
+ if (!service) {
1731
+ return { state: "unknown", message: "Service not found" };
1732
+ }
1733
+ if (service.status === "ACTIVE" && service.runningCount > 0) {
1734
+ return {
1735
+ state: "healthy",
1736
+ message: `Running ${service.runningCount}/${service.desiredCount} tasks`
1737
+ };
1738
+ }
1739
+ return {
1740
+ state: service.runningCount === 0 ? "pending" : "healthy",
1741
+ message: `Status: ${service.status}, running: ${service.runningCount}/${service.desiredCount}`
1742
+ };
1743
+ } catch {
1744
+ return { state: "unknown", message: "Could not parse service status" };
1745
+ }
1746
+ }
1747
+ requireContext() {
1748
+ if (!this.currentContext) {
1749
+ throw new Error("AWS ECS adapter context is not initialized. Run provision() first.");
1750
+ }
1751
+ return this.currentContext;
1752
+ }
1753
+ };
1754
+ var NodeAwsCommandRunner = class {
1755
+ async run(command, args, cwd) {
1756
+ return new Promise((resolve9) => {
1757
+ const child = (0, import_node_child_process4.spawn)(command, args, {
1758
+ cwd,
1759
+ env: process.env,
1760
+ stdio: ["ignore", "pipe", "pipe"]
1761
+ });
1762
+ let stdout = "";
1763
+ let stderr = "";
1764
+ child.stdout?.on("data", (chunk) => {
1765
+ stdout += chunk.toString("utf-8");
1766
+ });
1767
+ child.stderr?.on("data", (chunk) => {
1768
+ stderr += chunk.toString("utf-8");
1769
+ });
1770
+ child.on("error", (error) => {
1771
+ resolve9({ exitCode: 1, stdout, stderr: `${stderr}
1772
+ ${error.message}` });
1773
+ });
1774
+ child.on("exit", (code) => {
1775
+ resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1776
+ });
1777
+ });
1778
+ }
1779
+ };
1780
+ function inferLogLevel(line) {
1781
+ const normalized = line.toLowerCase();
1782
+ if (normalized.includes("error")) return "error";
1783
+ if (normalized.includes("warn")) return "warn";
1784
+ if (normalized.includes("debug")) return "debug";
1785
+ return "info";
1786
+ }
1787
+
1788
+ // src/commands/deploy/adapters/aws-lightsail-adapter.ts
1789
+ var import_node_child_process5 = require("child_process");
1790
+ var import_node_path9 = require("path");
1791
+ var AwsLightsailAdapter = class {
1792
+ name = "aws-lightsail";
1793
+ logger = createLogger();
1794
+ runner;
1795
+ currentContext;
1796
+ constructor(options = {}) {
1797
+ this.runner = options.runner ?? new NodeAwsLightsailCommandRunner();
1798
+ this.currentContext = options.context ?? null;
1799
+ }
1800
+ setContext(context) {
1801
+ this.currentContext = context;
1802
+ }
1803
+ async detect() {
1804
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
1805
+ return result.exitCode === 0;
1806
+ }
1807
+ async install() {
1808
+ const available = await this.detect();
1809
+ if (!available) {
1810
+ throw new Error(
1811
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1812
+ );
1813
+ }
1814
+ }
1815
+ async authenticate() {
1816
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
1817
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
1818
+ if (result.exitCode !== 0) {
1819
+ throw new Error(
1820
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
1821
+ );
1822
+ }
1823
+ }
1824
+ async provision(config) {
1825
+ this.currentContext = {
1826
+ projectRoot: config.projectRoot,
1827
+ appName: config.appName,
1828
+ region: config.region ?? "us-east-1"
1829
+ };
1830
+ const region = config.region ?? "us-east-1";
1831
+ const serviceName = sanitizeLightsailName(config.appName);
1832
+ const createService = await this.runner.run("aws", [
1833
+ "lightsail",
1834
+ "create-container-service",
1835
+ "--service-name",
1836
+ serviceName,
1837
+ "--power",
1838
+ "nano",
1839
+ "--scale",
1840
+ "1",
1841
+ "--region",
1842
+ region
1843
+ ], config.projectRoot);
1844
+ if (createService.exitCode !== 0 && !createService.stderr.includes("already exists")) {
1845
+ throw new Error(`Failed to create Lightsail container service: ${createService.stderr}`);
1846
+ }
1847
+ return {
1848
+ applicationId: serviceName,
1849
+ databaseId: null,
1850
+ secretsSet: ["PORT"]
1851
+ };
1852
+ }
1853
+ async build(config) {
1854
+ this.currentContext = {
1855
+ projectRoot: config.projectRoot,
1856
+ appName: config.appName,
1857
+ region: config.region
1858
+ };
1859
+ const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
1860
+ await bundleServer({
1861
+ projectRoot: config.projectRoot,
1862
+ deployDirectory
1863
+ });
1864
+ const client = await buildClient({
1865
+ projectRoot: config.projectRoot,
1866
+ outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
1867
+ mode: "production"
1868
+ });
1869
+ return {
1870
+ clientDirectory: client.outDir,
1871
+ serverBundlePath: (0, import_node_path9.join)(deployDirectory, "server-bundled.js"),
1872
+ deployDirectory
1873
+ };
1874
+ }
1875
+ async deploy(artifacts) {
1876
+ const context = this.requireContext();
1877
+ const region = context.region ?? "us-east-1";
1878
+ const serviceName = sanitizeLightsailName(context.appName);
1879
+ const imageTag = `${serviceName}:latest`;
1880
+ this.logger.step("Building Docker image...");
1881
+ const dockerBuild = await this.runner.run("docker", [
1882
+ "build",
1883
+ "--platform",
1884
+ "linux/amd64",
1885
+ "-t",
1886
+ imageTag,
1887
+ "."
1888
+ ], artifacts.deployDirectory);
1889
+ if (dockerBuild.exitCode !== 0) {
1890
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1891
+ }
1892
+ this.logger.step("Pushing image to Lightsail...");
1893
+ const pushImage = await this.runner.run("aws", [
1894
+ "lightsail",
1895
+ "push-container-image",
1896
+ "--service-name",
1897
+ serviceName,
1898
+ "--label",
1899
+ "latest",
1900
+ "--image",
1901
+ imageTag,
1902
+ "--region",
1903
+ region
1904
+ ], artifacts.deployDirectory);
1905
+ if (pushImage.exitCode !== 0) {
1906
+ throw new Error(`Lightsail image push failed: ${pushImage.stderr}`);
1907
+ }
1908
+ const lightsailImage = parseLightsailImageRef(pushImage.stdout) ?? `:${serviceName}.latest.1`;
1909
+ this.logger.step("Creating Lightsail deployment...");
1910
+ const environment = { PORT: "3001" };
1911
+ for (const key of PASSTHROUGH_ENV_VARS) {
1912
+ const value = process.env[key];
1913
+ if (value) {
1914
+ environment[key] = value;
1915
+ }
1916
+ }
1917
+ const containers = JSON.stringify({
1918
+ [serviceName]: {
1919
+ image: lightsailImage,
1920
+ ports: { "3001": "HTTP" },
1921
+ environment
1922
+ }
1923
+ });
1924
+ const publicEndpoint = JSON.stringify({
1925
+ containerName: serviceName,
1926
+ containerPort: 3001,
1927
+ healthCheck: {
1928
+ path: "/health",
1929
+ intervalSeconds: 30,
1930
+ timeoutSeconds: 5,
1931
+ healthyThreshold: 2,
1932
+ unhealthyThreshold: 3
1933
+ }
1934
+ });
1935
+ const createDeploy = await this.runner.run("aws", [
1936
+ "lightsail",
1937
+ "create-container-service-deployment",
1938
+ "--service-name",
1939
+ serviceName,
1940
+ "--containers",
1941
+ containers,
1942
+ "--public-endpoint",
1943
+ publicEndpoint,
1944
+ "--region",
1945
+ region
1946
+ ], context.projectRoot);
1947
+ if (createDeploy.exitCode !== 0) {
1948
+ throw new Error(`Lightsail deployment failed: ${createDeploy.stderr}`);
1949
+ }
1950
+ const serviceInfo = await this.runner.run("aws", [
1951
+ "lightsail",
1952
+ "get-container-services",
1953
+ "--service-name",
1954
+ serviceName,
1955
+ "--region",
1956
+ region
1957
+ ], context.projectRoot);
1958
+ const rawUrl = parseLightsailUrl(serviceInfo.stdout) ?? `https://${serviceName}.${region}.cs.amazonlightsail.com`;
1959
+ const serviceUrl = rawUrl.replace(/\/+$/, "");
1960
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1961
+ return {
1962
+ deploymentId,
1963
+ liveUrl: serviceUrl,
1964
+ syncUrl: `${serviceUrl.replace("https://", "wss://")}/kora-sync`
1965
+ };
1966
+ }
1967
+ async rollback(_deploymentId) {
1968
+ const context = this.requireContext();
1969
+ const region = context.region ?? "us-east-1";
1970
+ const serviceName = sanitizeLightsailName(context.appName);
1971
+ const deployments = await this.runner.run("aws", [
1972
+ "lightsail",
1973
+ "get-container-service-deployments",
1974
+ "--service-name",
1975
+ serviceName,
1976
+ "--region",
1977
+ region
1978
+ ], context.projectRoot);
1979
+ if (deployments.exitCode !== 0) {
1980
+ throw new Error(`Lightsail rollback failed: ${deployments.stderr}`);
1981
+ }
1982
+ const previousDeployment = parsePreviousDeployment(deployments.stdout, serviceName);
1983
+ if (!previousDeployment) {
1984
+ throw new Error("No previous deployment found to rollback to.");
1985
+ }
1986
+ const redeploy = await this.runner.run("aws", [
1987
+ "lightsail",
1988
+ "create-container-service-deployment",
1989
+ "--service-name",
1990
+ serviceName,
1991
+ "--containers",
1992
+ JSON.stringify(previousDeployment.containers),
1993
+ "--public-endpoint",
1994
+ JSON.stringify(previousDeployment.publicEndpoint),
1995
+ "--region",
1996
+ region
1997
+ ], context.projectRoot);
1998
+ if (redeploy.exitCode !== 0) {
1999
+ throw new Error(`Lightsail rollback deployment failed: ${redeploy.stderr}`);
2000
+ }
2001
+ }
2002
+ async *logs(options) {
2003
+ const context = this.requireContext();
2004
+ const region = context.region ?? "us-east-1";
2005
+ const serviceName = sanitizeLightsailName(context.appName);
2006
+ const args = [
2007
+ "lightsail",
2008
+ "get-container-log",
2009
+ "--service-name",
2010
+ serviceName,
2011
+ "--container-name",
2012
+ serviceName,
2013
+ "--region",
2014
+ region
2015
+ ];
2016
+ if (options.since) {
2017
+ args.push("--start-time", options.since);
2018
+ }
2019
+ const result = await this.runner.run("aws", args, context.projectRoot);
2020
+ if (result.exitCode !== 0) {
2021
+ return;
2022
+ }
2023
+ try {
2024
+ const parsed = JSON.parse(result.stdout);
2025
+ const events = parsed.logEvents ?? [];
2026
+ const limited = options.tail ? events.slice(-options.tail) : events;
2027
+ for (const event of limited) {
2028
+ yield {
2029
+ timestamp: event.createdAt,
2030
+ level: inferLogLevel2(event.message),
2031
+ message: event.message
2032
+ };
2033
+ }
2034
+ } catch {
2035
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
2036
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
2037
+ }
2038
+ }
2039
+ }
2040
+ async status() {
2041
+ const context = this.requireContext();
2042
+ const region = context.region ?? "us-east-1";
2043
+ const serviceName = sanitizeLightsailName(context.appName);
2044
+ const result = await this.runner.run("aws", [
2045
+ "lightsail",
2046
+ "get-container-services",
2047
+ "--service-name",
2048
+ serviceName,
2049
+ "--region",
2050
+ region
2051
+ ], context.projectRoot);
2052
+ if (result.exitCode !== 0) {
2053
+ return { state: "failed", message: result.stderr };
2054
+ }
2055
+ try {
2056
+ const parsed = JSON.parse(result.stdout);
2057
+ const service = parsed.containerServices?.[0];
2058
+ if (!service) {
2059
+ return { state: "unknown", message: "Container service not found" };
2060
+ }
2061
+ const deployState = service.currentDeployment?.state ?? "UNKNOWN";
2062
+ if (service.state === "RUNNING" && deployState === "ACTIVE") {
2063
+ return {
2064
+ state: "healthy",
2065
+ message: "Lightsail container service is running",
2066
+ liveUrl: service.url
2067
+ };
2068
+ }
2069
+ if (service.state === "DEPLOYING" || deployState === "ACTIVATING") {
2070
+ return {
2071
+ state: "pending",
2072
+ message: `Service: ${service.state}, Deployment: ${deployState}`
2073
+ };
2074
+ }
2075
+ return {
2076
+ state: service.state === "DISABLED" ? "failed" : "unknown",
2077
+ message: `Service: ${service.state}, Deployment: ${deployState}`
2078
+ };
2079
+ } catch {
2080
+ return { state: "unknown", message: "Could not parse service status" };
2081
+ }
2082
+ }
2083
+ requireContext() {
2084
+ if (!this.currentContext) {
2085
+ throw new Error("AWS Lightsail adapter context is not initialized. Run provision() first.");
2086
+ }
2087
+ return this.currentContext;
2088
+ }
2089
+ };
2090
+ var NodeAwsLightsailCommandRunner = class {
2091
+ async run(command, args, cwd) {
2092
+ return new Promise((resolve9) => {
2093
+ const child = (0, import_node_child_process5.spawn)(command, args, {
2094
+ cwd,
2095
+ env: process.env,
2096
+ stdio: ["ignore", "pipe", "pipe"]
2097
+ });
2098
+ let stdout = "";
2099
+ let stderr = "";
2100
+ child.stdout?.on("data", (chunk) => {
2101
+ stdout += chunk.toString("utf-8");
2102
+ });
2103
+ child.stderr?.on("data", (chunk) => {
2104
+ stderr += chunk.toString("utf-8");
2105
+ });
2106
+ child.on("error", (error) => {
2107
+ resolve9({ exitCode: 1, stdout, stderr: `${stderr}
2108
+ ${error.message}` });
2109
+ });
2110
+ child.on("exit", (code) => {
2111
+ resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2112
+ });
2113
+ });
2114
+ }
2115
+ };
2116
+ var PASSTHROUGH_ENV_VARS = [
2117
+ "DATABASE_URL",
2118
+ "AUTH_SECRET",
2119
+ "PUBLIC_URL",
2120
+ "NODE_ENV"
2121
+ ];
2122
+ function sanitizeLightsailName(name) {
2123
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 255) || "kora-app";
2124
+ }
2125
+ function parseLightsailImageRef(output) {
2126
+ const match = output.match(/Refer to this image as\s+"?(:[^"\s]+)"?/i);
2127
+ if (match?.[1]) {
2128
+ return match[1];
2129
+ }
2130
+ const refMatch = output.match(/(:\S+\.\S+\.\d+)/);
2131
+ return refMatch?.[1] ?? null;
2132
+ }
2133
+ function parseLightsailUrl(rawJson) {
2134
+ try {
2135
+ const parsed = JSON.parse(rawJson);
2136
+ const url = parsed.containerServices?.[0]?.url;
2137
+ if (typeof url === "string" && url.length > 0) {
2138
+ return url.startsWith("https://") ? url : `https://${url}`;
2139
+ }
2140
+ return null;
2141
+ } catch {
2142
+ return null;
2143
+ }
2144
+ }
2145
+ function parsePreviousDeployment(rawJson, serviceName) {
2146
+ try {
2147
+ const parsed = JSON.parse(rawJson);
2148
+ const deployments = parsed.deployments ?? [];
2149
+ const previous = deployments.length > 1 ? deployments[1] : null;
2150
+ if (!previous) return null;
2151
+ return {
2152
+ containers: previous.containers,
2153
+ publicEndpoint: previous.publicEndpoint
2154
+ };
2155
+ } catch {
2156
+ return null;
2157
+ }
2158
+ }
2159
+ function inferLogLevel2(line) {
2160
+ const normalized = line.toLowerCase();
2161
+ if (normalized.includes("error")) return "error";
2162
+ if (normalized.includes("warn")) return "warn";
2163
+ if (normalized.includes("debug")) return "debug";
2164
+ return "info";
2165
+ }
2166
+
2167
+ // src/commands/deploy/adapters/fly-adapter.ts
2168
+ var import_node_child_process6 = require("child_process");
2169
+ var import_node_path11 = require("path");
2170
+
2171
+ // src/commands/deploy/artifacts/fly-toml-generator.ts
2172
+ var import_promises8 = require("fs/promises");
2173
+ var import_node_path10 = require("path");
2174
+ var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
2175
+ function generateFlyToml(options) {
2176
+ const internalPort = options.internalPort ?? 3e3;
2177
+ return [
2178
+ GENERATED_HEADER,
2179
+ "",
2180
+ `app = "${options.appName}"`,
2181
+ `primary_region = "${options.region}"`,
2182
+ "",
2183
+ "[build]",
2184
+ ` dockerfile = "Dockerfile"`,
2185
+ "",
2186
+ "[http_service]",
2187
+ ` internal_port = ${String(internalPort)}`,
2188
+ " force_https = true",
2189
+ ' auto_stop_machines = "stop"',
2190
+ " auto_start_machines = true",
2191
+ " min_machines_running = 0",
2192
+ "",
2193
+ " [[http_service.checks]]",
2194
+ ' grace_period = "10s"',
2195
+ ' interval = "30s"',
2196
+ ' method = "GET"',
2197
+ ' timeout = "5s"',
2198
+ ' path = "/"',
2199
+ ""
2200
+ ].join("\n");
2201
+ }
2202
+ async function writeFlyTomlArtifact(deployDirectory, options) {
2203
+ await (0, import_promises8.mkdir)(deployDirectory, { recursive: true });
2204
+ const filePath = (0, import_node_path10.join)(deployDirectory, "fly.toml");
2205
+ await (0, import_promises8.writeFile)(filePath, generateFlyToml(options), "utf-8");
2206
+ return filePath;
2207
+ }
2208
+
1468
2209
  // src/commands/deploy/adapters/fly-adapter.ts
1469
2210
  var FlyAdapter = class {
1470
2211
  name = "fly";
@@ -1555,7 +2296,7 @@ var FlyAdapter = class {
1555
2296
  appName: config.appName,
1556
2297
  region: config.region
1557
2298
  };
1558
- const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
2299
+ const deployDirectory = (0, import_node_path11.join)(config.projectRoot, ".kora", "deploy");
1559
2300
  await writeFlyTomlArtifact(deployDirectory, {
1560
2301
  appName: config.appName,
1561
2302
  region: config.region ?? "iad"
@@ -1566,12 +2307,12 @@ var FlyAdapter = class {
1566
2307
  });
1567
2308
  const client = await buildClient({
1568
2309
  projectRoot: config.projectRoot,
1569
- outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
2310
+ outDir: (0, import_node_path11.join)(deployDirectory, "dist"),
1570
2311
  mode: "production"
1571
2312
  });
1572
2313
  return {
1573
2314
  clientDirectory: client.outDir,
1574
- serverBundlePath: (0, import_node_path9.join)(deployDirectory, "server-bundled.js"),
2315
+ serverBundlePath: (0, import_node_path11.join)(deployDirectory, "server-bundled.js"),
1575
2316
  deployDirectory
1576
2317
  };
1577
2318
  }
@@ -1633,7 +2374,7 @@ var FlyAdapter = class {
1633
2374
  for (const line of lines) {
1634
2375
  yield {
1635
2376
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1636
- level: inferLogLevel(line),
2377
+ level: inferLogLevel3(line),
1637
2378
  message: line
1638
2379
  };
1639
2380
  }
@@ -1698,7 +2439,7 @@ var FlyAdapter = class {
1698
2439
  var NodeFlyCommandRunner = class {
1699
2440
  async run(command, args, cwd) {
1700
2441
  return await new Promise((resolve9) => {
1701
- const child = (0, import_node_child_process4.spawn)(command, args, {
2442
+ const child = (0, import_node_child_process6.spawn)(command, args, {
1702
2443
  cwd,
1703
2444
  env: process.env,
1704
2445
  stdio: ["ignore", "pipe", "pipe"]
@@ -1773,7 +2514,7 @@ function parseJsonRecord(value) {
1773
2514
  return null;
1774
2515
  }
1775
2516
  }
1776
- function inferLogLevel(line) {
2517
+ function inferLogLevel3(line) {
1777
2518
  const normalized = line.toLowerCase();
1778
2519
  if (normalized.includes("error")) return "error";
1779
2520
  if (normalized.includes("warn")) return "warn";
@@ -1792,12 +2533,12 @@ ${stdout}`.toLowerCase();
1792
2533
  }
1793
2534
 
1794
2535
  // src/commands/deploy/adapters/railway-adapter.ts
1795
- var import_node_child_process5 = require("child_process");
1796
- var import_node_path11 = require("path");
2536
+ var import_node_child_process7 = require("child_process");
2537
+ var import_node_path13 = require("path");
1797
2538
 
1798
2539
  // src/commands/deploy/artifacts/railway-json-generator.ts
1799
2540
  var import_promises9 = require("fs/promises");
1800
- var import_node_path10 = require("path");
2541
+ var import_node_path12 = require("path");
1801
2542
  var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
1802
2543
  function generateRailwayJson(options) {
1803
2544
  const file = {
@@ -1823,7 +2564,7 @@ function generateRailwayJson(options) {
1823
2564
  }
1824
2565
  async function writeRailwayJsonArtifact(deployDirectory, options) {
1825
2566
  await (0, import_promises9.mkdir)(deployDirectory, { recursive: true });
1826
- const filePath = (0, import_node_path10.join)(deployDirectory, "railway.json");
2567
+ const filePath = (0, import_node_path12.join)(deployDirectory, "railway.json");
1827
2568
  await (0, import_promises9.writeFile)(filePath, generateRailwayJson(options), "utf-8");
1828
2569
  return filePath;
1829
2570
  }
@@ -1913,7 +2654,7 @@ var RailwayAdapter = class {
1913
2654
  appName: config.appName,
1914
2655
  region: config.region
1915
2656
  };
1916
- const deployDirectory = (0, import_node_path11.join)(config.projectRoot, ".kora", "deploy");
2657
+ const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
1917
2658
  await writeRailwayJsonArtifact(deployDirectory, {
1918
2659
  appName: config.appName,
1919
2660
  environment: config.environment
@@ -1924,12 +2665,12 @@ var RailwayAdapter = class {
1924
2665
  });
1925
2666
  const client = await buildClient({
1926
2667
  projectRoot: config.projectRoot,
1927
- outDir: (0, import_node_path11.join)(deployDirectory, "dist"),
2668
+ outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
1928
2669
  mode: "production"
1929
2670
  });
1930
2671
  return {
1931
2672
  clientDirectory: client.outDir,
1932
- serverBundlePath: (0, import_node_path11.join)(deployDirectory, "server-bundled.js"),
2673
+ serverBundlePath: (0, import_node_path13.join)(deployDirectory, "server-bundled.js"),
1933
2674
  deployDirectory
1934
2675
  };
1935
2676
  }
@@ -1976,7 +2717,7 @@ var RailwayAdapter = class {
1976
2717
  for (const line of lines) {
1977
2718
  yield {
1978
2719
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1979
- level: inferLogLevel2(line),
2720
+ level: inferLogLevel4(line),
1980
2721
  message: line
1981
2722
  };
1982
2723
  }
@@ -2034,7 +2775,7 @@ var RailwayAdapter = class {
2034
2775
  var NodeRailwayCommandRunner = class {
2035
2776
  async run(command, args, cwd) {
2036
2777
  return await new Promise((resolve9) => {
2037
- const child = (0, import_node_child_process5.spawn)(command, args, {
2778
+ const child = (0, import_node_child_process7.spawn)(command, args, {
2038
2779
  cwd,
2039
2780
  env: process.env,
2040
2781
  stdio: ["ignore", "pipe", "pipe"]
@@ -2125,7 +2866,7 @@ function ensureHttpsUrl(value) {
2125
2866
  }
2126
2867
  return `https://${value}`;
2127
2868
  }
2128
- function inferLogLevel2(line) {
2869
+ function inferLogLevel4(line) {
2129
2870
  const normalized = line.toLowerCase();
2130
2871
  if (normalized.includes("error")) return "error";
2131
2872
  if (normalized.includes("warn")) return "warn";
@@ -2198,6 +2939,10 @@ function createDeployAdapter(platform) {
2198
2939
  return new FlyAdapter();
2199
2940
  case "railway":
2200
2941
  return new RailwayAdapter();
2942
+ case "aws-ecs":
2943
+ return new AwsEcsAdapter();
2944
+ case "aws-lightsail":
2945
+ return new AwsLightsailAdapter();
2201
2946
  case "render":
2202
2947
  case "docker":
2203
2948
  case "kora-cloud":
@@ -2211,7 +2956,7 @@ function createDeployAdapter(platform) {
2211
2956
 
2212
2957
  // src/commands/deploy/artifacts/dockerfile-generator.ts
2213
2958
  var import_promises10 = require("fs/promises");
2214
- var import_node_path12 = require("path");
2959
+ var import_node_path14 = require("path");
2215
2960
  var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
2216
2961
  function generateDockerfile(options = {}) {
2217
2962
  const nodeVersion = options.nodeVersion ?? "20-alpine";
@@ -2275,31 +3020,31 @@ function generateDockerIgnore() {
2275
3020
  }
2276
3021
  async function writeDockerfileArtifact(deployDirectory, options = {}) {
2277
3022
  await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
2278
- const dockerfilePath = (0, import_node_path12.join)(deployDirectory, "Dockerfile");
3023
+ const dockerfilePath = (0, import_node_path14.join)(deployDirectory, "Dockerfile");
2279
3024
  await (0, import_promises10.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
2280
3025
  if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
2281
- const pkgJsonPath = (0, import_node_path12.join)(deployDirectory, "package.json");
3026
+ const pkgJsonPath = (0, import_node_path14.join)(deployDirectory, "package.json");
2282
3027
  await (0, import_promises10.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
2283
3028
  }
2284
3029
  return dockerfilePath;
2285
3030
  }
2286
3031
  async function writeDockerIgnoreArtifact(deployDirectory) {
2287
3032
  await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
2288
- const dockerIgnorePath = (0, import_node_path12.join)(deployDirectory, ".dockerignore");
3033
+ const dockerIgnorePath = (0, import_node_path14.join)(deployDirectory, ".dockerignore");
2289
3034
  await (0, import_promises10.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
2290
3035
  return dockerIgnorePath;
2291
3036
  }
2292
3037
 
2293
3038
  // src/commands/deploy/state/deploy-state.ts
2294
3039
  var import_promises11 = require("fs/promises");
2295
- var import_node_path13 = require("path");
2296
- var KORA_DEPLOY_DIRECTORY = (0, import_node_path13.join)(".kora", "deploy");
3040
+ var import_node_path15 = require("path");
3041
+ var KORA_DEPLOY_DIRECTORY = (0, import_node_path15.join)(".kora", "deploy");
2297
3042
  var DEPLOY_STATE_FILENAME = "deploy.json";
2298
3043
  function resolveDeployDirectory(projectRoot) {
2299
- return (0, import_node_path13.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
3044
+ return (0, import_node_path15.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
2300
3045
  }
2301
3046
  function resolveDeployStatePath(projectRoot) {
2302
- return (0, import_node_path13.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
3047
+ return (0, import_node_path15.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
2303
3048
  }
2304
3049
  async function readDeployState(projectRoot) {
2305
3050
  const statePath = resolveDeployStatePath(projectRoot);
@@ -2583,7 +3328,8 @@ var deployCommand = (0, import_citty2.defineCommand)({
2583
3328
  await writeDockerfileArtifact(deployDirectory, {
2584
3329
  nativeDependencies: {
2585
3330
  "better-sqlite3": "^11.0.0",
2586
- "drizzle-orm": "^0.45.2"
3331
+ "drizzle-orm": "^0.45.2",
3332
+ "postgres": "^3.4.0"
2587
3333
  }
2588
3334
  });
2589
3335
  await writeDockerIgnoreArtifact(deployDirectory);
@@ -2650,6 +3396,14 @@ async function resolvePlatform(options) {
2650
3396
  label: "Railway",
2651
3397
  value: "railway"
2652
3398
  },
3399
+ {
3400
+ label: "AWS ECS Fargate (production multi-instance)",
3401
+ value: "aws-ecs"
3402
+ },
3403
+ {
3404
+ label: "AWS Lightsail Containers (simple, cost-effective)",
3405
+ value: "aws-lightsail"
3406
+ },
2653
3407
  {
2654
3408
  label: "Render",
2655
3409
  value: "render"
@@ -2676,7 +3430,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
2676
3430
  "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
2677
3431
  );
2678
3432
  }
2679
- return sanitizeAppName((0, import_node_path14.basename)(projectRoot));
3433
+ return sanitizeAppName((0, import_node_path16.basename)(projectRoot));
2680
3434
  }
2681
3435
  function resolveRegion(argValue, storedValue, confirm) {
2682
3436
  if (typeof argValue === "string" && argValue.length > 0) return argValue;
@@ -2716,14 +3470,14 @@ function hasContextSetter(adapter) {
2716
3470
 
2717
3471
  // src/commands/dev/dev-command.ts
2718
3472
  var import_promises13 = require("fs/promises");
2719
- var import_node_path17 = require("path");
2720
- var import_node_path18 = require("path");
3473
+ var import_node_path19 = require("path");
3474
+ var import_node_path20 = require("path");
2721
3475
  var import_citty3 = require("citty");
2722
3476
 
2723
3477
  // src/commands/dev/kora-config.ts
2724
- var import_node_child_process6 = require("child_process");
3478
+ var import_node_child_process8 = require("child_process");
2725
3479
  var import_promises12 = require("fs/promises");
2726
- var import_node_path15 = require("path");
3480
+ var import_node_path17 = require("path");
2727
3481
  var import_node_url3 = require("url");
2728
3482
  var CONFIG_CANDIDATES = [
2729
3483
  "kora.config.ts",
@@ -2736,7 +3490,7 @@ var CONFIG_CANDIDATES = [
2736
3490
  async function loadKoraConfig(projectRoot) {
2737
3491
  const configPath = await findKoraConfigFile(projectRoot);
2738
3492
  if (!configPath) return null;
2739
- const ext = (0, import_node_path15.extname)(configPath);
3493
+ const ext = (0, import_node_path17.extname)(configPath);
2740
3494
  if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
2741
3495
  const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
2742
3496
  return toConfigObject(loaded2);
@@ -2746,7 +3500,7 @@ async function loadKoraConfig(projectRoot) {
2746
3500
  }
2747
3501
  async function findKoraConfigFile(projectRoot) {
2748
3502
  for (const file of CONFIG_CANDIDATES) {
2749
- const candidate = (0, import_node_path15.join)(projectRoot, file);
3503
+ const candidate = (0, import_node_path17.join)(projectRoot, file);
2750
3504
  try {
2751
3505
  await (0, import_promises12.access)(candidate);
2752
3506
  return candidate;
@@ -2775,7 +3529,7 @@ async function loadTypeScriptConfig(configPath, projectRoot) {
2775
3529
  }
2776
3530
  async function runCommand(command, args, cwd) {
2777
3531
  return await new Promise((resolve9, reject) => {
2778
- const child = (0, import_node_child_process6.spawn)(command, args, {
3532
+ const child = (0, import_node_child_process8.spawn)(command, args, {
2779
3533
  cwd,
2780
3534
  stdio: ["ignore", "pipe", "pipe"],
2781
3535
  env: process.env
@@ -2812,7 +3566,7 @@ function toConfigObject(mod) {
2812
3566
  }
2813
3567
 
2814
3568
  // src/commands/dev/process-manager.ts
2815
- var import_node_child_process7 = require("child_process");
3569
+ var import_node_child_process9 = require("child_process");
2816
3570
  var ProcessManager = class {
2817
3571
  processes = /* @__PURE__ */ new Map();
2818
3572
  spawn(config) {
@@ -2821,7 +3575,7 @@ var ProcessManager = class {
2821
3575
  env: { ...process.env, ...config.env },
2822
3576
  stdio: ["ignore", "pipe", "pipe"]
2823
3577
  };
2824
- const child = (0, import_node_child_process7.spawn)(config.command, config.args, options);
3578
+ const child = (0, import_node_child_process9.spawn)(config.command, config.args, options);
2825
3579
  let resolveExit;
2826
3580
  const runningProcess = {
2827
3581
  child,
@@ -2899,9 +3653,9 @@ function delay(ms) {
2899
3653
  }
2900
3654
 
2901
3655
  // src/commands/dev/schema-watcher.ts
2902
- var import_node_child_process8 = require("child_process");
3656
+ var import_node_child_process10 = require("child_process");
2903
3657
  var import_node_fs4 = require("fs");
2904
- var import_node_path16 = require("path");
3658
+ var import_node_path18 = require("path");
2905
3659
  var SchemaWatcher = class {
2906
3660
  constructor(config) {
2907
3661
  this.config = config;
@@ -2929,7 +3683,7 @@ var SchemaWatcher = class {
2929
3683
  this.watcher = null;
2930
3684
  }
2931
3685
  async regenerate() {
2932
- const koraBinJs = (0, import_node_path16.join)(this.config.projectRoot, "node_modules", "@korajs", "cli", "dist", "bin.js");
3686
+ const koraBinJs = (0, import_node_path18.join)(this.config.projectRoot, "node_modules", "@korajs", "cli", "dist", "bin.js");
2933
3687
  const hasTsx = await hasTsxInstalled(this.config.projectRoot);
2934
3688
  const command = process.execPath;
2935
3689
  const args = hasTsx ? ["--import", "tsx", koraBinJs, "generate", "types", "--schema", this.config.schemaPath] : [koraBinJs, "generate", "types", "--schema", this.config.schemaPath];
@@ -2950,7 +3704,7 @@ var SchemaWatcher = class {
2950
3704
  };
2951
3705
  async function spawnCommand(command, args, cwd) {
2952
3706
  await new Promise((resolve9, reject) => {
2953
- const child = (0, import_node_child_process8.spawn)(command, args, {
3707
+ const child = (0, import_node_child_process10.spawn)(command, args, {
2954
3708
  cwd,
2955
3709
  stdio: ["ignore", "pipe", "pipe"],
2956
3710
  env: process.env
@@ -3028,7 +3782,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3028
3782
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
3029
3783
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
3030
3784
  if (!viteEntryPoint) {
3031
- throw new DevServerError("vite", (0, import_node_path17.join)(projectRoot, "node_modules", ".bin", "vite"));
3785
+ throw new DevServerError("vite", (0, import_node_path19.join)(projectRoot, "node_modules", ".bin", "vite"));
3032
3786
  }
3033
3787
  const syncServerFile = await findSyncServerFile(projectRoot);
3034
3788
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -3044,7 +3798,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3044
3798
  }
3045
3799
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
3046
3800
  const hasServerPackage = await fileExists2(
3047
- (0, import_node_path17.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
3801
+ (0, import_node_path19.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
3048
3802
  );
3049
3803
  if (!hasServerPackage) {
3050
3804
  logger.warn(
@@ -3061,7 +3815,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3061
3815
  }
3062
3816
  let configuredSchemaPath = null;
3063
3817
  if (typeof config?.schema === "string") {
3064
- const candidate = (0, import_node_path18.resolve)(projectRoot, config.schema);
3818
+ const candidate = (0, import_node_path20.resolve)(projectRoot, config.schema);
3065
3819
  if (await fileExists2(candidate)) {
3066
3820
  configuredSchemaPath = candidate;
3067
3821
  } else {
@@ -3173,7 +3927,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3173
3927
  }
3174
3928
  });
3175
3929
  async function findSyncServerFile(projectRoot) {
3176
- const candidates = [(0, import_node_path17.join)(projectRoot, "server.ts"), (0, import_node_path17.join)(projectRoot, "server.js")];
3930
+ const candidates = [(0, import_node_path19.join)(projectRoot, "server.ts"), (0, import_node_path19.join)(projectRoot, "server.js")];
3177
3931
  for (const candidate of candidates) {
3178
3932
  try {
3179
3933
  await (0, import_promises13.access)(candidate);
@@ -3197,7 +3951,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
3197
3951
  const store = sync.store;
3198
3952
  if (store === void 0) return { type: "memory" };
3199
3953
  if (store === "memory") return { type: "memory" };
3200
- if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path17.join)(projectRoot, "kora-sync.db") };
3954
+ if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path19.join)(projectRoot, "kora-sync.db") };
3201
3955
  if (store === "postgres") {
3202
3956
  const connectionString = process.env.DATABASE_URL;
3203
3957
  if (!connectionString) return null;
@@ -3206,7 +3960,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
3206
3960
  if (typeof store === "object" && store !== null) {
3207
3961
  if (store.type === "memory") return { type: "memory" };
3208
3962
  if (store.type === "sqlite") {
3209
- const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path18.resolve)(projectRoot, store.filename) : (0, import_node_path17.join)(projectRoot, "kora-sync.db");
3963
+ const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path20.resolve)(projectRoot, store.filename) : (0, import_node_path19.join)(projectRoot, "kora-sync.db");
3210
3964
  return { type: "sqlite", filename };
3211
3965
  }
3212
3966
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -3270,7 +4024,7 @@ await new Promise(() => {});
3270
4024
 
3271
4025
  // src/commands/generate/generate-command.ts
3272
4026
  var import_promises14 = require("fs/promises");
3273
- var import_node_path19 = require("path");
4027
+ var import_node_path21 = require("path");
3274
4028
  var import_citty4 = require("citty");
3275
4029
 
3276
4030
  // src/commands/generate/type-generator.ts
@@ -3401,7 +4155,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
3401
4155
  }
3402
4156
  let schemaPath;
3403
4157
  if (args.schema && typeof args.schema === "string") {
3404
- schemaPath = (0, import_node_path19.resolve)(args.schema);
4158
+ schemaPath = (0, import_node_path21.resolve)(args.schema);
3405
4159
  } else {
3406
4160
  const found = await findSchemaFile(projectRoot);
3407
4161
  if (!found) {
@@ -3424,8 +4178,8 @@ var generateCommand = (0, import_citty4.defineCommand)({
3424
4178
  }
3425
4179
  const output = generateTypes(schema);
3426
4180
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
3427
- const outputPath = (0, import_node_path19.resolve)(projectRoot, outputFile);
3428
- await (0, import_promises14.mkdir)((0, import_node_path19.dirname)(outputPath), { recursive: true });
4181
+ const outputPath = (0, import_node_path21.resolve)(projectRoot, outputFile);
4182
+ await (0, import_promises14.mkdir)((0, import_node_path21.dirname)(outputPath), { recursive: true });
3429
4183
  await (0, import_promises14.writeFile)(outputPath, output, "utf-8");
3430
4184
  logger.success(`Generated types at ${outputPath}`);
3431
4185
  }
@@ -3447,7 +4201,7 @@ function isSchemaDefinition(value) {
3447
4201
 
3448
4202
  // src/commands/migrate/migrate-command.ts
3449
4203
  var import_promises15 = require("fs/promises");
3450
- var import_node_path21 = require("path");
4204
+ var import_node_path23 = require("path");
3451
4205
  var import_citty5 = require("citty");
3452
4206
 
3453
4207
  // src/commands/migrate/migration-generator.ts
@@ -4016,11 +4770,11 @@ async function loadPostgresModule() {
4016
4770
  }
4017
4771
 
4018
4772
  // src/commands/migrate/schema-loader.ts
4019
- var import_node_child_process9 = require("child_process");
4020
- var import_node_path20 = require("path");
4773
+ var import_node_child_process11 = require("child_process");
4774
+ var import_node_path22 = require("path");
4021
4775
  var import_node_url4 = require("url");
4022
4776
  async function loadSchemaDefinition(schemaPath, projectRoot) {
4023
- const ext = (0, import_node_path20.extname)(schemaPath);
4777
+ const ext = (0, import_node_path22.extname)(schemaPath);
4024
4778
  const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${(0, import_node_url4.pathToFileURL)(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
4025
4779
  return extractSchema2(moduleValue);
4026
4780
  }
@@ -4060,7 +4814,7 @@ async function loadTypeScriptModule(schemaPath, projectRoot) {
4060
4814
  }
4061
4815
  async function runCommand2(command, args, cwd) {
4062
4816
  return await new Promise((resolve9, reject) => {
4063
- const child = (0, import_node_child_process9.spawn)(command, args, {
4817
+ const child = (0, import_node_child_process11.spawn)(command, args, {
4064
4818
  cwd,
4065
4819
  stdio: ["ignore", "pipe", "pipe"],
4066
4820
  env: process.env
@@ -4133,12 +4887,12 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4133
4887
  throw new InvalidProjectError(process.cwd());
4134
4888
  }
4135
4889
  const config = await loadKoraConfig(projectRoot);
4136
- const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path21.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path21.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
4890
+ const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path23.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path23.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
4137
4891
  if (!resolvedSchemaPath) {
4138
4892
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
4139
4893
  }
4140
4894
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
4141
- const snapshotFile = (0, import_node_path21.join)(projectRoot, SNAPSHOT_PATH);
4895
+ const snapshotFile = (0, import_node_path23.join)(projectRoot, SNAPSHOT_PATH);
4142
4896
  const previousSchema = await readSchemaSnapshot(snapshotFile);
4143
4897
  if (!previousSchema) {
4144
4898
  if (args["dry-run"] === true) {
@@ -4151,7 +4905,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4151
4905
  return;
4152
4906
  }
4153
4907
  const diff = diffSchemas(previousSchema, currentSchema);
4154
- const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path21.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path21.resolve)(projectRoot, MIGRATIONS_DIR);
4908
+ const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path23.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path23.resolve)(projectRoot, MIGRATIONS_DIR);
4155
4909
  if (!diff.hasChanges) {
4156
4910
  logger.success("No schema changes detected.");
4157
4911
  if (args.apply === true) {
@@ -4255,7 +5009,7 @@ function isInteractiveTerminal2() {
4255
5009
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
4256
5010
  }
4257
5011
  async function writeSchemaSnapshot(path, schema) {
4258
- await (0, import_promises15.mkdir)((0, import_node_path21.dirname)(path), { recursive: true });
5012
+ await (0, import_promises15.mkdir)((0, import_node_path23.dirname)(path), { recursive: true });
4259
5013
  await (0, import_promises15.writeFile)(path, `${JSON.stringify(schema, null, 2)}
4260
5014
  `, "utf-8");
4261
5015
  }
@@ -4263,7 +5017,7 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
4263
5017
  const existing = await (0, import_promises15.readdir)(outputDir).catch(() => []);
4264
5018
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
4265
5019
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
4266
- const path = (0, import_node_path21.join)(outputDir, filename);
5020
+ const path = (0, import_node_path23.join)(outputDir, filename);
4267
5021
  const migrationId = filename.replace(/\.ts$/, "");
4268
5022
  const fileContent = [
4269
5023
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -4276,7 +5030,7 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
4276
5030
  ""
4277
5031
  ].join("\n");
4278
5032
  await (0, import_promises15.writeFile)(path, fileContent, "utf-8");
4279
- await writeMigrationManifest((0, import_node_path21.join)(outputDir, `${migrationId}.json`), {
5033
+ await writeMigrationManifest((0, import_node_path23.join)(outputDir, `${migrationId}.json`), {
4280
5034
  id: migrationId,
4281
5035
  fromVersion,
4282
5036
  toVersion,
@@ -4297,13 +5051,13 @@ async function listMigrationManifests(outputDir) {
4297
5051
  const manifests = [];
4298
5052
  for (const file of migrationFiles) {
4299
5053
  const id = file.replace(/\.ts$/, "");
4300
- const manifestPath = (0, import_node_path21.join)(outputDir, `${id}.json`);
5054
+ const manifestPath = (0, import_node_path23.join)(outputDir, `${id}.json`);
4301
5055
  const jsonManifest = await readMigrationManifest(manifestPath);
4302
5056
  if (jsonManifest) {
4303
5057
  manifests.push({ ...jsonManifest, id });
4304
5058
  continue;
4305
5059
  }
4306
- const migrationPath = (0, import_node_path21.join)(outputDir, file);
5060
+ const migrationPath = (0, import_node_path23.join)(outputDir, file);
4307
5061
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
4308
5062
  manifests.push(sourceManifest);
4309
5063
  }
@@ -4379,18 +5133,18 @@ function parseLiteralExport(source, exportName) {
4379
5133
  }
4380
5134
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
4381
5135
  if (typeof dbArg === "string") {
4382
- return (0, import_node_path21.resolve)(projectRoot, dbArg);
5136
+ return (0, import_node_path23.resolve)(projectRoot, dbArg);
4383
5137
  }
4384
5138
  const sync = config?.dev?.sync;
4385
5139
  if (typeof sync === "object" && sync !== null) {
4386
5140
  if (sync.store === "sqlite") {
4387
- return (0, import_node_path21.join)(projectRoot, "kora-sync.db");
5141
+ return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
4388
5142
  }
4389
5143
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
4390
5144
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
4391
- return (0, import_node_path21.resolve)(projectRoot, sync.store.filename);
5145
+ return (0, import_node_path23.resolve)(projectRoot, sync.store.filename);
4392
5146
  }
4393
- return (0, import_node_path21.join)(projectRoot, "kora-sync.db");
5147
+ return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
4394
5148
  }
4395
5149
  }
4396
5150
  return void 0;