@korajs/cli 0.3.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/index.cjs CHANGED
@@ -273,7 +273,7 @@ function toPascalCase(name) {
273
273
  }
274
274
 
275
275
  // src/commands/deploy/deploy-command.ts
276
- var import_node_path10 = require("path");
276
+ var import_node_path12 = require("path");
277
277
  var import_citty = require("citty");
278
278
 
279
279
  // src/prompts/prompt-client.ts
@@ -536,58 +536,20 @@ function createLogger(options) {
536
536
  }
537
537
 
538
538
  // src/commands/deploy/adapters/adapter.ts
539
- var DEPLOY_PLATFORMS = ["fly", "railway", "render", "docker", "kora-cloud"];
539
+ var DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
540
540
  function isDeployPlatform(value) {
541
541
  return DEPLOY_PLATFORMS.includes(value);
542
542
  }
543
543
 
544
- // src/commands/deploy/adapters/fly-adapter.ts
544
+ // src/commands/deploy/adapters/aws-ecs-adapter.ts
545
545
  var import_node_child_process2 = require("child_process");
546
- var import_node_path5 = require("path");
547
-
548
- // src/commands/deploy/artifacts/fly-toml-generator.ts
549
- var import_promises2 = require("fs/promises");
550
- var import_node_path2 = require("path");
551
- var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
552
- function generateFlyToml(options) {
553
- const internalPort = options.internalPort ?? 3e3;
554
- return [
555
- GENERATED_HEADER,
556
- "",
557
- `app = "${options.appName}"`,
558
- `primary_region = "${options.region}"`,
559
- "",
560
- "[build]",
561
- ` dockerfile = "Dockerfile"`,
562
- "",
563
- "[http_service]",
564
- ` internal_port = ${String(internalPort)}`,
565
- " force_https = true",
566
- ' auto_stop_machines = "stop"',
567
- " auto_start_machines = true",
568
- " min_machines_running = 0",
569
- "",
570
- " [[http_service.checks]]",
571
- ' grace_period = "10s"',
572
- ' interval = "30s"',
573
- ' method = "GET"',
574
- ' timeout = "5s"',
575
- ' path = "/"',
576
- ""
577
- ].join("\n");
578
- }
579
- async function writeFlyTomlArtifact(deployDirectory, options) {
580
- await (0, import_promises2.mkdir)(deployDirectory, { recursive: true });
581
- const filePath = (0, import_node_path2.join)(deployDirectory, "fly.toml");
582
- await (0, import_promises2.writeFile)(filePath, generateFlyToml(options), "utf-8");
583
- return filePath;
584
- }
546
+ var import_node_path4 = require("path");
585
547
 
586
548
  // src/commands/deploy/builder/client-builder.ts
587
549
  var import_node_child_process = require("child_process");
588
- var import_promises3 = require("fs/promises");
550
+ var import_promises2 = require("fs/promises");
589
551
  var import_node_fs = require("fs");
590
- var import_node_path3 = require("path");
552
+ var import_node_path2 = require("path");
591
553
  async function buildClient(options) {
592
554
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
593
555
  if (!viteEntryPoint) {
@@ -608,15 +570,15 @@ async function buildClient(options) {
608
570
  return { outDir: options.outDir };
609
571
  }
610
572
  async function patchSqliteWasmAssets(projectRoot, outDir) {
611
- const assetsDir = (0, import_node_path3.join)(outDir, "assets");
573
+ const assetsDir = (0, import_node_path2.join)(outDir, "assets");
612
574
  if (!(0, import_node_fs.existsSync)(assetsDir)) return;
613
- const files = await (0, import_promises3.readdir)(assetsDir);
575
+ const files = await (0, import_promises2.readdir)(assetsDir);
614
576
  const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
615
577
  if (hashedWasm && !files.includes("sqlite3.wasm")) {
616
- await (0, import_promises3.copyFile)((0, import_node_path3.join)(assetsDir, hashedWasm), (0, import_node_path3.join)(assetsDir, "sqlite3.wasm"));
578
+ await (0, import_promises2.copyFile)((0, import_node_path2.join)(assetsDir, hashedWasm), (0, import_node_path2.join)(assetsDir, "sqlite3.wasm"));
617
579
  }
618
580
  if (!files.includes("sqlite3-opfs-async-proxy.js")) {
619
- const proxyFile = (0, import_node_path3.resolve)(
581
+ const proxyFile = (0, import_node_path2.resolve)(
620
582
  projectRoot,
621
583
  "node_modules",
622
584
  "@sqlite.org",
@@ -626,7 +588,7 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
626
588
  "sqlite3-opfs-async-proxy.js"
627
589
  );
628
590
  if ((0, import_node_fs.existsSync)(proxyFile)) {
629
- await (0, import_promises3.copyFile)(proxyFile, (0, import_node_path3.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
591
+ await (0, import_promises2.copyFile)(proxyFile, (0, import_node_path2.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
630
592
  }
631
593
  }
632
594
  }
@@ -651,9 +613,9 @@ async function runProcess(command, args, cwd) {
651
613
  }
652
614
 
653
615
  // src/commands/deploy/builder/server-bundler.ts
616
+ var import_promises3 = require("fs/promises");
654
617
  var import_promises4 = require("fs/promises");
655
- var import_promises5 = require("fs/promises");
656
- var import_node_path4 = require("path");
618
+ var import_node_path3 = require("path");
657
619
  var import_esbuild = require("esbuild");
658
620
  var DEFAULT_ENTRY_CANDIDATES = [
659
621
  "server.ts",
@@ -669,8 +631,8 @@ async function bundleServer(options) {
669
631
  `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
670
632
  );
671
633
  }
672
- await (0, import_promises4.mkdir)(options.deployDirectory, { recursive: true });
673
- const outputFilePath = (0, import_node_path4.join)(options.deployDirectory, "server-bundled.js");
634
+ await (0, import_promises3.mkdir)(options.deployDirectory, { recursive: true });
635
+ const outputFilePath = (0, import_node_path3.join)(options.deployDirectory, "server-bundled.js");
674
636
  await (0, import_esbuild.build)({
675
637
  entryPoints: [entryFilePath],
676
638
  outfile: outputFilePath,
@@ -692,9 +654,9 @@ async function bundleServer(options) {
692
654
  }
693
655
  async function resolveServerEntry(projectRoot, candidates) {
694
656
  for (const candidate of candidates) {
695
- const fullPath = (0, import_node_path4.join)(projectRoot, candidate);
657
+ const fullPath = (0, import_node_path3.join)(projectRoot, candidate);
696
658
  try {
697
- await (0, import_promises5.access)(fullPath);
659
+ await (0, import_promises4.access)(fullPath);
698
660
  return fullPath;
699
661
  } catch {
700
662
  }
@@ -702,6 +664,785 @@ async function resolveServerEntry(projectRoot, candidates) {
702
664
  return null;
703
665
  }
704
666
 
667
+ // src/commands/deploy/adapters/aws-ecs-adapter.ts
668
+ var AwsEcsAdapter = class {
669
+ name = "aws-ecs";
670
+ logger = createLogger();
671
+ runner;
672
+ currentContext;
673
+ constructor(options = {}) {
674
+ this.runner = options.runner ?? new NodeAwsCommandRunner();
675
+ this.currentContext = options.context ?? null;
676
+ }
677
+ setContext(context) {
678
+ this.currentContext = context;
679
+ }
680
+ async detect() {
681
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
682
+ return result.exitCode === 0;
683
+ }
684
+ async install() {
685
+ const available = await this.detect();
686
+ if (!available) {
687
+ throw new Error(
688
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
689
+ );
690
+ }
691
+ }
692
+ async authenticate() {
693
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
694
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
695
+ if (result.exitCode !== 0) {
696
+ throw new Error(
697
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
698
+ );
699
+ }
700
+ }
701
+ async provision(config) {
702
+ this.currentContext = {
703
+ projectRoot: config.projectRoot,
704
+ appName: config.appName,
705
+ region: config.region ?? "us-east-1"
706
+ };
707
+ const region = config.region ?? "us-east-1";
708
+ const repoName = `kora/${config.appName}`;
709
+ const createRepo = await this.runner.run("aws", [
710
+ "ecr",
711
+ "create-repository",
712
+ "--repository-name",
713
+ repoName,
714
+ "--region",
715
+ region,
716
+ "--image-scanning-configuration",
717
+ "scanOnPush=true"
718
+ ], config.projectRoot);
719
+ if (createRepo.exitCode !== 0 && !createRepo.stderr.includes("RepositoryAlreadyExistsException")) {
720
+ throw new Error(`Failed to create ECR repository: ${createRepo.stderr}`);
721
+ }
722
+ const identity = await this.runner.run("aws", ["sts", "get-caller-identity", "--query", "Account", "--output", "text"], config.projectRoot);
723
+ const accountId = identity.stdout.trim();
724
+ await this.runner.run("aws", [
725
+ "ecs",
726
+ "create-cluster",
727
+ "--cluster-name",
728
+ config.appName,
729
+ "--region",
730
+ region
731
+ ], config.projectRoot);
732
+ await this.runner.run("aws", [
733
+ "logs",
734
+ "create-log-group",
735
+ "--log-group-name",
736
+ `/ecs/${config.appName}`,
737
+ "--region",
738
+ region
739
+ ], config.projectRoot);
740
+ return {
741
+ applicationId: `${accountId}.dkr.ecr.${region}.amazonaws.com/${repoName}`,
742
+ databaseId: null,
743
+ secretsSet: ["PORT"]
744
+ };
745
+ }
746
+ async build(config) {
747
+ this.currentContext = {
748
+ projectRoot: config.projectRoot,
749
+ appName: config.appName,
750
+ region: config.region
751
+ };
752
+ const deployDirectory = (0, import_node_path4.join)(config.projectRoot, ".kora", "deploy");
753
+ await bundleServer({
754
+ projectRoot: config.projectRoot,
755
+ deployDirectory
756
+ });
757
+ const client = await buildClient({
758
+ projectRoot: config.projectRoot,
759
+ outDir: (0, import_node_path4.join)(deployDirectory, "dist"),
760
+ mode: "production"
761
+ });
762
+ return {
763
+ clientDirectory: client.outDir,
764
+ serverBundlePath: (0, import_node_path4.join)(deployDirectory, "server-bundled.js"),
765
+ deployDirectory
766
+ };
767
+ }
768
+ async deploy(artifacts) {
769
+ const context = this.requireContext();
770
+ const region = context.region ?? "us-east-1";
771
+ const repoName = `kora/${context.appName}`;
772
+ const loginPassword = await this.runner.run("aws", [
773
+ "ecr",
774
+ "get-login-password",
775
+ "--region",
776
+ region
777
+ ], context.projectRoot);
778
+ if (loginPassword.exitCode !== 0) {
779
+ throw new Error(`ECR login failed: ${loginPassword.stderr}`);
780
+ }
781
+ const identity = await this.runner.run("aws", [
782
+ "sts",
783
+ "get-caller-identity",
784
+ "--query",
785
+ "Account",
786
+ "--output",
787
+ "text"
788
+ ], context.projectRoot);
789
+ const accountId = identity.stdout.trim();
790
+ const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
791
+ const imageUri = `${ecrUri}/${repoName}:latest`;
792
+ const dockerLogin = await this.runner.run("docker", [
793
+ "login",
794
+ "--username",
795
+ "AWS",
796
+ "--password-stdin",
797
+ ecrUri
798
+ ], artifacts.deployDirectory);
799
+ this.logger.step("Building Docker image...");
800
+ const dockerBuild = await this.runner.run("docker", [
801
+ "build",
802
+ "--platform",
803
+ "linux/amd64",
804
+ "-t",
805
+ imageUri,
806
+ "."
807
+ ], artifacts.deployDirectory);
808
+ if (dockerBuild.exitCode !== 0) {
809
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
810
+ }
811
+ this.logger.step("Pushing image to ECR...");
812
+ const dockerPush = await this.runner.run("docker", [
813
+ "push",
814
+ imageUri
815
+ ], artifacts.deployDirectory);
816
+ if (dockerPush.exitCode !== 0) {
817
+ throw new Error(`Docker push failed: ${dockerPush.stderr}`);
818
+ }
819
+ const taskDef = JSON.stringify({
820
+ family: context.appName,
821
+ networkMode: "awsvpc",
822
+ requiresCompatibilities: ["FARGATE"],
823
+ cpu: "256",
824
+ memory: "512",
825
+ executionRoleArn: `arn:aws:iam::${accountId}:role/ecsTaskExecutionRole`,
826
+ containerDefinitions: [{
827
+ name: context.appName,
828
+ image: imageUri,
829
+ essential: true,
830
+ portMappings: [{ containerPort: 3001, protocol: "tcp" }],
831
+ logConfiguration: {
832
+ logDriver: "awslogs",
833
+ options: {
834
+ "awslogs-group": `/ecs/${context.appName}`,
835
+ "awslogs-region": region,
836
+ "awslogs-stream-prefix": "ecs"
837
+ }
838
+ },
839
+ healthCheck: {
840
+ command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
841
+ interval: 30,
842
+ timeout: 5,
843
+ retries: 3,
844
+ startPeriod: 60
845
+ }
846
+ }]
847
+ });
848
+ const registerTask = await this.runner.run("aws", [
849
+ "ecs",
850
+ "register-task-definition",
851
+ "--cli-input-json",
852
+ taskDef,
853
+ "--region",
854
+ region
855
+ ], context.projectRoot);
856
+ if (registerTask.exitCode !== 0) {
857
+ throw new Error(`Task definition registration failed: ${registerTask.stderr}`);
858
+ }
859
+ const updateService = await this.runner.run("aws", [
860
+ "ecs",
861
+ "update-service",
862
+ "--cluster",
863
+ context.appName,
864
+ "--service",
865
+ context.appName,
866
+ "--task-definition",
867
+ context.appName,
868
+ "--force-new-deployment",
869
+ "--region",
870
+ region
871
+ ], context.projectRoot);
872
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
873
+ if (updateService.exitCode !== 0) {
874
+ this.logger.step("Service not found, creating new service...");
875
+ this.logger.step(
876
+ `Task definition registered. Create the ECS service with:
877
+ aws ecs create-service \\
878
+ --cluster ${context.appName} \\
879
+ --service-name ${context.appName} \\
880
+ --task-definition ${context.appName} \\
881
+ --desired-count 1 \\
882
+ --launch-type FARGATE \\
883
+ --network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<sg-id>],assignPublicIp=ENABLED}" \\
884
+ --region ${region}`
885
+ );
886
+ }
887
+ return {
888
+ deploymentId,
889
+ liveUrl: `https://${context.appName}.${region}.amazonaws.com`,
890
+ syncUrl: `wss://${context.appName}.${region}.amazonaws.com/kora-sync`
891
+ };
892
+ }
893
+ async rollback(deploymentId) {
894
+ const context = this.requireContext();
895
+ const region = context.region ?? "us-east-1";
896
+ const result = await this.runner.run("aws", [
897
+ "ecs",
898
+ "update-service",
899
+ "--cluster",
900
+ context.appName,
901
+ "--service",
902
+ context.appName,
903
+ "--task-definition",
904
+ `${context.appName}:${deploymentId}`,
905
+ "--force-new-deployment",
906
+ "--region",
907
+ region
908
+ ], context.projectRoot);
909
+ if (result.exitCode !== 0) {
910
+ throw new Error(`ECS rollback failed: ${result.stderr}`);
911
+ }
912
+ }
913
+ async *logs(options) {
914
+ const context = this.requireContext();
915
+ const region = context.region ?? "us-east-1";
916
+ const args = [
917
+ "logs",
918
+ "get-log-events",
919
+ "--log-group-name",
920
+ `/ecs/${context.appName}`,
921
+ "--log-stream-name",
922
+ "ecs/latest",
923
+ "--region",
924
+ region
925
+ ];
926
+ if (options.tail) {
927
+ args.push("--limit", String(options.tail));
928
+ }
929
+ const result = await this.runner.run("aws", args, context.projectRoot);
930
+ if (result.exitCode !== 0) {
931
+ return;
932
+ }
933
+ try {
934
+ const parsed = JSON.parse(result.stdout);
935
+ for (const event of parsed.events ?? []) {
936
+ yield {
937
+ timestamp: new Date(event.timestamp).toISOString(),
938
+ level: inferLogLevel(event.message),
939
+ message: event.message
940
+ };
941
+ }
942
+ } catch {
943
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
944
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
945
+ }
946
+ }
947
+ }
948
+ async status() {
949
+ const context = this.requireContext();
950
+ const region = context.region ?? "us-east-1";
951
+ const result = await this.runner.run("aws", [
952
+ "ecs",
953
+ "describe-services",
954
+ "--cluster",
955
+ context.appName,
956
+ "--services",
957
+ context.appName,
958
+ "--region",
959
+ region
960
+ ], context.projectRoot);
961
+ if (result.exitCode !== 0) {
962
+ return { state: "failed", message: result.stderr };
963
+ }
964
+ try {
965
+ const parsed = JSON.parse(result.stdout);
966
+ const service = parsed.services?.[0];
967
+ if (!service) {
968
+ return { state: "unknown", message: "Service not found" };
969
+ }
970
+ if (service.status === "ACTIVE" && service.runningCount > 0) {
971
+ return {
972
+ state: "healthy",
973
+ message: `Running ${service.runningCount}/${service.desiredCount} tasks`
974
+ };
975
+ }
976
+ return {
977
+ state: service.runningCount === 0 ? "pending" : "healthy",
978
+ message: `Status: ${service.status}, running: ${service.runningCount}/${service.desiredCount}`
979
+ };
980
+ } catch {
981
+ return { state: "unknown", message: "Could not parse service status" };
982
+ }
983
+ }
984
+ requireContext() {
985
+ if (!this.currentContext) {
986
+ throw new Error("AWS ECS adapter context is not initialized. Run provision() first.");
987
+ }
988
+ return this.currentContext;
989
+ }
990
+ };
991
+ var NodeAwsCommandRunner = class {
992
+ async run(command, args, cwd) {
993
+ return new Promise((resolve3) => {
994
+ const child = (0, import_node_child_process2.spawn)(command, args, {
995
+ cwd,
996
+ env: process.env,
997
+ stdio: ["ignore", "pipe", "pipe"]
998
+ });
999
+ let stdout = "";
1000
+ let stderr = "";
1001
+ child.stdout?.on("data", (chunk) => {
1002
+ stdout += chunk.toString("utf-8");
1003
+ });
1004
+ child.stderr?.on("data", (chunk) => {
1005
+ stderr += chunk.toString("utf-8");
1006
+ });
1007
+ child.on("error", (error) => {
1008
+ resolve3({ exitCode: 1, stdout, stderr: `${stderr}
1009
+ ${error.message}` });
1010
+ });
1011
+ child.on("exit", (code) => {
1012
+ resolve3({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1013
+ });
1014
+ });
1015
+ }
1016
+ };
1017
+ function inferLogLevel(line) {
1018
+ const normalized = line.toLowerCase();
1019
+ if (normalized.includes("error")) return "error";
1020
+ if (normalized.includes("warn")) return "warn";
1021
+ if (normalized.includes("debug")) return "debug";
1022
+ return "info";
1023
+ }
1024
+
1025
+ // src/commands/deploy/adapters/aws-lightsail-adapter.ts
1026
+ var import_node_child_process3 = require("child_process");
1027
+ var import_node_path5 = require("path");
1028
+ var AwsLightsailAdapter = class {
1029
+ name = "aws-lightsail";
1030
+ logger = createLogger();
1031
+ runner;
1032
+ currentContext;
1033
+ constructor(options = {}) {
1034
+ this.runner = options.runner ?? new NodeAwsLightsailCommandRunner();
1035
+ this.currentContext = options.context ?? null;
1036
+ }
1037
+ setContext(context) {
1038
+ this.currentContext = context;
1039
+ }
1040
+ async detect() {
1041
+ const result = await this.runner.run("aws", ["--version"], process.cwd());
1042
+ return result.exitCode === 0;
1043
+ }
1044
+ async install() {
1045
+ const available = await this.detect();
1046
+ if (!available) {
1047
+ throw new Error(
1048
+ "AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
1049
+ );
1050
+ }
1051
+ }
1052
+ async authenticate() {
1053
+ const cwd = this.currentContext?.projectRoot ?? process.cwd();
1054
+ const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
1055
+ if (result.exitCode !== 0) {
1056
+ throw new Error(
1057
+ "AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
1058
+ );
1059
+ }
1060
+ }
1061
+ async provision(config) {
1062
+ this.currentContext = {
1063
+ projectRoot: config.projectRoot,
1064
+ appName: config.appName,
1065
+ region: config.region ?? "us-east-1"
1066
+ };
1067
+ const region = config.region ?? "us-east-1";
1068
+ const serviceName = sanitizeLightsailName(config.appName);
1069
+ const createService = await this.runner.run("aws", [
1070
+ "lightsail",
1071
+ "create-container-service",
1072
+ "--service-name",
1073
+ serviceName,
1074
+ "--power",
1075
+ "nano",
1076
+ "--scale",
1077
+ "1",
1078
+ "--region",
1079
+ region
1080
+ ], config.projectRoot);
1081
+ if (createService.exitCode !== 0 && !createService.stderr.includes("already exists")) {
1082
+ throw new Error(`Failed to create Lightsail container service: ${createService.stderr}`);
1083
+ }
1084
+ return {
1085
+ applicationId: serviceName,
1086
+ databaseId: null,
1087
+ secretsSet: ["PORT"]
1088
+ };
1089
+ }
1090
+ async build(config) {
1091
+ this.currentContext = {
1092
+ projectRoot: config.projectRoot,
1093
+ appName: config.appName,
1094
+ region: config.region
1095
+ };
1096
+ const deployDirectory = (0, import_node_path5.join)(config.projectRoot, ".kora", "deploy");
1097
+ await bundleServer({
1098
+ projectRoot: config.projectRoot,
1099
+ deployDirectory
1100
+ });
1101
+ const client = await buildClient({
1102
+ projectRoot: config.projectRoot,
1103
+ outDir: (0, import_node_path5.join)(deployDirectory, "dist"),
1104
+ mode: "production"
1105
+ });
1106
+ return {
1107
+ clientDirectory: client.outDir,
1108
+ serverBundlePath: (0, import_node_path5.join)(deployDirectory, "server-bundled.js"),
1109
+ deployDirectory
1110
+ };
1111
+ }
1112
+ async deploy(artifacts) {
1113
+ const context = this.requireContext();
1114
+ const region = context.region ?? "us-east-1";
1115
+ const serviceName = sanitizeLightsailName(context.appName);
1116
+ const imageTag = `${serviceName}:latest`;
1117
+ this.logger.step("Building Docker image...");
1118
+ const dockerBuild = await this.runner.run("docker", [
1119
+ "build",
1120
+ "--platform",
1121
+ "linux/amd64",
1122
+ "-t",
1123
+ imageTag,
1124
+ "."
1125
+ ], artifacts.deployDirectory);
1126
+ if (dockerBuild.exitCode !== 0) {
1127
+ throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1128
+ }
1129
+ this.logger.step("Pushing image to Lightsail...");
1130
+ const pushImage = await this.runner.run("aws", [
1131
+ "lightsail",
1132
+ "push-container-image",
1133
+ "--service-name",
1134
+ serviceName,
1135
+ "--label",
1136
+ "latest",
1137
+ "--image",
1138
+ imageTag,
1139
+ "--region",
1140
+ region
1141
+ ], artifacts.deployDirectory);
1142
+ if (pushImage.exitCode !== 0) {
1143
+ throw new Error(`Lightsail image push failed: ${pushImage.stderr}`);
1144
+ }
1145
+ const lightsailImage = parseLightsailImageRef(pushImage.stdout) ?? `:${serviceName}.latest.1`;
1146
+ this.logger.step("Creating Lightsail deployment...");
1147
+ const environment = { PORT: "3001" };
1148
+ for (const key of PASSTHROUGH_ENV_VARS) {
1149
+ const value = process.env[key];
1150
+ if (value) {
1151
+ environment[key] = value;
1152
+ }
1153
+ }
1154
+ const containers = JSON.stringify({
1155
+ [serviceName]: {
1156
+ image: lightsailImage,
1157
+ ports: { "3001": "HTTP" },
1158
+ environment
1159
+ }
1160
+ });
1161
+ const publicEndpoint = JSON.stringify({
1162
+ containerName: serviceName,
1163
+ containerPort: 3001,
1164
+ healthCheck: {
1165
+ path: "/health",
1166
+ intervalSeconds: 30,
1167
+ timeoutSeconds: 5,
1168
+ healthyThreshold: 2,
1169
+ unhealthyThreshold: 3
1170
+ }
1171
+ });
1172
+ const createDeploy = await this.runner.run("aws", [
1173
+ "lightsail",
1174
+ "create-container-service-deployment",
1175
+ "--service-name",
1176
+ serviceName,
1177
+ "--containers",
1178
+ containers,
1179
+ "--public-endpoint",
1180
+ publicEndpoint,
1181
+ "--region",
1182
+ region
1183
+ ], context.projectRoot);
1184
+ if (createDeploy.exitCode !== 0) {
1185
+ throw new Error(`Lightsail deployment failed: ${createDeploy.stderr}`);
1186
+ }
1187
+ const serviceInfo = await this.runner.run("aws", [
1188
+ "lightsail",
1189
+ "get-container-services",
1190
+ "--service-name",
1191
+ serviceName,
1192
+ "--region",
1193
+ region
1194
+ ], context.projectRoot);
1195
+ const rawUrl = parseLightsailUrl(serviceInfo.stdout) ?? `https://${serviceName}.${region}.cs.amazonlightsail.com`;
1196
+ const serviceUrl = rawUrl.replace(/\/+$/, "");
1197
+ const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1198
+ return {
1199
+ deploymentId,
1200
+ liveUrl: serviceUrl,
1201
+ syncUrl: `${serviceUrl.replace("https://", "wss://")}/kora-sync`
1202
+ };
1203
+ }
1204
+ async rollback(_deploymentId) {
1205
+ const context = this.requireContext();
1206
+ const region = context.region ?? "us-east-1";
1207
+ const serviceName = sanitizeLightsailName(context.appName);
1208
+ const deployments = await this.runner.run("aws", [
1209
+ "lightsail",
1210
+ "get-container-service-deployments",
1211
+ "--service-name",
1212
+ serviceName,
1213
+ "--region",
1214
+ region
1215
+ ], context.projectRoot);
1216
+ if (deployments.exitCode !== 0) {
1217
+ throw new Error(`Lightsail rollback failed: ${deployments.stderr}`);
1218
+ }
1219
+ const previousDeployment = parsePreviousDeployment(deployments.stdout, serviceName);
1220
+ if (!previousDeployment) {
1221
+ throw new Error("No previous deployment found to rollback to.");
1222
+ }
1223
+ const redeploy = await this.runner.run("aws", [
1224
+ "lightsail",
1225
+ "create-container-service-deployment",
1226
+ "--service-name",
1227
+ serviceName,
1228
+ "--containers",
1229
+ JSON.stringify(previousDeployment.containers),
1230
+ "--public-endpoint",
1231
+ JSON.stringify(previousDeployment.publicEndpoint),
1232
+ "--region",
1233
+ region
1234
+ ], context.projectRoot);
1235
+ if (redeploy.exitCode !== 0) {
1236
+ throw new Error(`Lightsail rollback deployment failed: ${redeploy.stderr}`);
1237
+ }
1238
+ }
1239
+ async *logs(options) {
1240
+ const context = this.requireContext();
1241
+ const region = context.region ?? "us-east-1";
1242
+ const serviceName = sanitizeLightsailName(context.appName);
1243
+ const args = [
1244
+ "lightsail",
1245
+ "get-container-log",
1246
+ "--service-name",
1247
+ serviceName,
1248
+ "--container-name",
1249
+ serviceName,
1250
+ "--region",
1251
+ region
1252
+ ];
1253
+ if (options.since) {
1254
+ args.push("--start-time", options.since);
1255
+ }
1256
+ const result = await this.runner.run("aws", args, context.projectRoot);
1257
+ if (result.exitCode !== 0) {
1258
+ return;
1259
+ }
1260
+ try {
1261
+ const parsed = JSON.parse(result.stdout);
1262
+ const events = parsed.logEvents ?? [];
1263
+ const limited = options.tail ? events.slice(-options.tail) : events;
1264
+ for (const event of limited) {
1265
+ yield {
1266
+ timestamp: event.createdAt,
1267
+ level: inferLogLevel2(event.message),
1268
+ message: event.message
1269
+ };
1270
+ }
1271
+ } catch {
1272
+ for (const line of result.stdout.split("\n").filter(Boolean)) {
1273
+ yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
1274
+ }
1275
+ }
1276
+ }
1277
+ async status() {
1278
+ const context = this.requireContext();
1279
+ const region = context.region ?? "us-east-1";
1280
+ const serviceName = sanitizeLightsailName(context.appName);
1281
+ const result = await this.runner.run("aws", [
1282
+ "lightsail",
1283
+ "get-container-services",
1284
+ "--service-name",
1285
+ serviceName,
1286
+ "--region",
1287
+ region
1288
+ ], context.projectRoot);
1289
+ if (result.exitCode !== 0) {
1290
+ return { state: "failed", message: result.stderr };
1291
+ }
1292
+ try {
1293
+ const parsed = JSON.parse(result.stdout);
1294
+ const service = parsed.containerServices?.[0];
1295
+ if (!service) {
1296
+ return { state: "unknown", message: "Container service not found" };
1297
+ }
1298
+ const deployState = service.currentDeployment?.state ?? "UNKNOWN";
1299
+ if (service.state === "RUNNING" && deployState === "ACTIVE") {
1300
+ return {
1301
+ state: "healthy",
1302
+ message: "Lightsail container service is running",
1303
+ liveUrl: service.url
1304
+ };
1305
+ }
1306
+ if (service.state === "DEPLOYING" || deployState === "ACTIVATING") {
1307
+ return {
1308
+ state: "pending",
1309
+ message: `Service: ${service.state}, Deployment: ${deployState}`
1310
+ };
1311
+ }
1312
+ return {
1313
+ state: service.state === "DISABLED" ? "failed" : "unknown",
1314
+ message: `Service: ${service.state}, Deployment: ${deployState}`
1315
+ };
1316
+ } catch {
1317
+ return { state: "unknown", message: "Could not parse service status" };
1318
+ }
1319
+ }
1320
+ requireContext() {
1321
+ if (!this.currentContext) {
1322
+ throw new Error("AWS Lightsail adapter context is not initialized. Run provision() first.");
1323
+ }
1324
+ return this.currentContext;
1325
+ }
1326
+ };
1327
+ var NodeAwsLightsailCommandRunner = class {
1328
+ async run(command, args, cwd) {
1329
+ return new Promise((resolve3) => {
1330
+ const child = (0, import_node_child_process3.spawn)(command, args, {
1331
+ cwd,
1332
+ env: process.env,
1333
+ stdio: ["ignore", "pipe", "pipe"]
1334
+ });
1335
+ let stdout = "";
1336
+ let stderr = "";
1337
+ child.stdout?.on("data", (chunk) => {
1338
+ stdout += chunk.toString("utf-8");
1339
+ });
1340
+ child.stderr?.on("data", (chunk) => {
1341
+ stderr += chunk.toString("utf-8");
1342
+ });
1343
+ child.on("error", (error) => {
1344
+ resolve3({ exitCode: 1, stdout, stderr: `${stderr}
1345
+ ${error.message}` });
1346
+ });
1347
+ child.on("exit", (code) => {
1348
+ resolve3({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1349
+ });
1350
+ });
1351
+ }
1352
+ };
1353
+ var PASSTHROUGH_ENV_VARS = [
1354
+ "DATABASE_URL",
1355
+ "AUTH_SECRET",
1356
+ "PUBLIC_URL",
1357
+ "NODE_ENV"
1358
+ ];
1359
+ function sanitizeLightsailName(name) {
1360
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 255) || "kora-app";
1361
+ }
1362
+ function parseLightsailImageRef(output) {
1363
+ const match = output.match(/Refer to this image as\s+"?(:[^"\s]+)"?/i);
1364
+ if (match?.[1]) {
1365
+ return match[1];
1366
+ }
1367
+ const refMatch = output.match(/(:\S+\.\S+\.\d+)/);
1368
+ return refMatch?.[1] ?? null;
1369
+ }
1370
+ function parseLightsailUrl(rawJson) {
1371
+ try {
1372
+ const parsed = JSON.parse(rawJson);
1373
+ const url = parsed.containerServices?.[0]?.url;
1374
+ if (typeof url === "string" && url.length > 0) {
1375
+ return url.startsWith("https://") ? url : `https://${url}`;
1376
+ }
1377
+ return null;
1378
+ } catch {
1379
+ return null;
1380
+ }
1381
+ }
1382
+ function parsePreviousDeployment(rawJson, serviceName) {
1383
+ try {
1384
+ const parsed = JSON.parse(rawJson);
1385
+ const deployments = parsed.deployments ?? [];
1386
+ const previous = deployments.length > 1 ? deployments[1] : null;
1387
+ if (!previous) return null;
1388
+ return {
1389
+ containers: previous.containers,
1390
+ publicEndpoint: previous.publicEndpoint
1391
+ };
1392
+ } catch {
1393
+ return null;
1394
+ }
1395
+ }
1396
+ function inferLogLevel2(line) {
1397
+ const normalized = line.toLowerCase();
1398
+ if (normalized.includes("error")) return "error";
1399
+ if (normalized.includes("warn")) return "warn";
1400
+ if (normalized.includes("debug")) return "debug";
1401
+ return "info";
1402
+ }
1403
+
1404
+ // src/commands/deploy/adapters/fly-adapter.ts
1405
+ var import_node_child_process4 = require("child_process");
1406
+ var import_node_path7 = require("path");
1407
+
1408
+ // src/commands/deploy/artifacts/fly-toml-generator.ts
1409
+ var import_promises5 = require("fs/promises");
1410
+ var import_node_path6 = require("path");
1411
+ var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
1412
+ function generateFlyToml(options) {
1413
+ const internalPort = options.internalPort ?? 3e3;
1414
+ return [
1415
+ GENERATED_HEADER,
1416
+ "",
1417
+ `app = "${options.appName}"`,
1418
+ `primary_region = "${options.region}"`,
1419
+ "",
1420
+ "[build]",
1421
+ ` dockerfile = "Dockerfile"`,
1422
+ "",
1423
+ "[http_service]",
1424
+ ` internal_port = ${String(internalPort)}`,
1425
+ " force_https = true",
1426
+ ' auto_stop_machines = "stop"',
1427
+ " auto_start_machines = true",
1428
+ " min_machines_running = 0",
1429
+ "",
1430
+ " [[http_service.checks]]",
1431
+ ' grace_period = "10s"',
1432
+ ' interval = "30s"',
1433
+ ' method = "GET"',
1434
+ ' timeout = "5s"',
1435
+ ' path = "/"',
1436
+ ""
1437
+ ].join("\n");
1438
+ }
1439
+ async function writeFlyTomlArtifact(deployDirectory, options) {
1440
+ await (0, import_promises5.mkdir)(deployDirectory, { recursive: true });
1441
+ const filePath = (0, import_node_path6.join)(deployDirectory, "fly.toml");
1442
+ await (0, import_promises5.writeFile)(filePath, generateFlyToml(options), "utf-8");
1443
+ return filePath;
1444
+ }
1445
+
705
1446
  // src/commands/deploy/adapters/fly-adapter.ts
706
1447
  var FlyAdapter = class {
707
1448
  name = "fly";
@@ -792,7 +1533,7 @@ var FlyAdapter = class {
792
1533
  appName: config.appName,
793
1534
  region: config.region
794
1535
  };
795
- const deployDirectory = (0, import_node_path5.join)(config.projectRoot, ".kora", "deploy");
1536
+ const deployDirectory = (0, import_node_path7.join)(config.projectRoot, ".kora", "deploy");
796
1537
  await writeFlyTomlArtifact(deployDirectory, {
797
1538
  appName: config.appName,
798
1539
  region: config.region ?? "iad"
@@ -803,12 +1544,12 @@ var FlyAdapter = class {
803
1544
  });
804
1545
  const client = await buildClient({
805
1546
  projectRoot: config.projectRoot,
806
- outDir: (0, import_node_path5.join)(deployDirectory, "dist"),
1547
+ outDir: (0, import_node_path7.join)(deployDirectory, "dist"),
807
1548
  mode: "production"
808
1549
  });
809
1550
  return {
810
1551
  clientDirectory: client.outDir,
811
- serverBundlePath: (0, import_node_path5.join)(deployDirectory, "server-bundled.js"),
1552
+ serverBundlePath: (0, import_node_path7.join)(deployDirectory, "server-bundled.js"),
812
1553
  deployDirectory
813
1554
  };
814
1555
  }
@@ -870,7 +1611,7 @@ var FlyAdapter = class {
870
1611
  for (const line of lines) {
871
1612
  yield {
872
1613
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
873
- level: inferLogLevel(line),
1614
+ level: inferLogLevel3(line),
874
1615
  message: line
875
1616
  };
876
1617
  }
@@ -935,7 +1676,7 @@ var FlyAdapter = class {
935
1676
  var NodeFlyCommandRunner = class {
936
1677
  async run(command, args, cwd) {
937
1678
  return await new Promise((resolve3) => {
938
- const child = (0, import_node_child_process2.spawn)(command, args, {
1679
+ const child = (0, import_node_child_process4.spawn)(command, args, {
939
1680
  cwd,
940
1681
  env: process.env,
941
1682
  stdio: ["ignore", "pipe", "pipe"]
@@ -1010,7 +1751,7 @@ function parseJsonRecord(value) {
1010
1751
  return null;
1011
1752
  }
1012
1753
  }
1013
- function inferLogLevel(line) {
1754
+ function inferLogLevel3(line) {
1014
1755
  const normalized = line.toLowerCase();
1015
1756
  if (normalized.includes("error")) return "error";
1016
1757
  if (normalized.includes("warn")) return "warn";
@@ -1029,12 +1770,12 @@ ${stdout}`.toLowerCase();
1029
1770
  }
1030
1771
 
1031
1772
  // src/commands/deploy/adapters/railway-adapter.ts
1032
- var import_node_child_process3 = require("child_process");
1033
- var import_node_path7 = require("path");
1773
+ var import_node_child_process5 = require("child_process");
1774
+ var import_node_path9 = require("path");
1034
1775
 
1035
1776
  // src/commands/deploy/artifacts/railway-json-generator.ts
1036
1777
  var import_promises6 = require("fs/promises");
1037
- var import_node_path6 = require("path");
1778
+ var import_node_path8 = require("path");
1038
1779
  var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
1039
1780
  function generateRailwayJson(options) {
1040
1781
  const file = {
@@ -1060,7 +1801,7 @@ function generateRailwayJson(options) {
1060
1801
  }
1061
1802
  async function writeRailwayJsonArtifact(deployDirectory, options) {
1062
1803
  await (0, import_promises6.mkdir)(deployDirectory, { recursive: true });
1063
- const filePath = (0, import_node_path6.join)(deployDirectory, "railway.json");
1804
+ const filePath = (0, import_node_path8.join)(deployDirectory, "railway.json");
1064
1805
  await (0, import_promises6.writeFile)(filePath, generateRailwayJson(options), "utf-8");
1065
1806
  return filePath;
1066
1807
  }
@@ -1150,7 +1891,7 @@ var RailwayAdapter = class {
1150
1891
  appName: config.appName,
1151
1892
  region: config.region
1152
1893
  };
1153
- const deployDirectory = (0, import_node_path7.join)(config.projectRoot, ".kora", "deploy");
1894
+ const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
1154
1895
  await writeRailwayJsonArtifact(deployDirectory, {
1155
1896
  appName: config.appName,
1156
1897
  environment: config.environment
@@ -1161,12 +1902,12 @@ var RailwayAdapter = class {
1161
1902
  });
1162
1903
  const client = await buildClient({
1163
1904
  projectRoot: config.projectRoot,
1164
- outDir: (0, import_node_path7.join)(deployDirectory, "dist"),
1905
+ outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
1165
1906
  mode: "production"
1166
1907
  });
1167
1908
  return {
1168
1909
  clientDirectory: client.outDir,
1169
- serverBundlePath: (0, import_node_path7.join)(deployDirectory, "server-bundled.js"),
1910
+ serverBundlePath: (0, import_node_path9.join)(deployDirectory, "server-bundled.js"),
1170
1911
  deployDirectory
1171
1912
  };
1172
1913
  }
@@ -1213,7 +1954,7 @@ var RailwayAdapter = class {
1213
1954
  for (const line of lines) {
1214
1955
  yield {
1215
1956
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1216
- level: inferLogLevel2(line),
1957
+ level: inferLogLevel4(line),
1217
1958
  message: line
1218
1959
  };
1219
1960
  }
@@ -1271,7 +2012,7 @@ var RailwayAdapter = class {
1271
2012
  var NodeRailwayCommandRunner = class {
1272
2013
  async run(command, args, cwd) {
1273
2014
  return await new Promise((resolve3) => {
1274
- const child = (0, import_node_child_process3.spawn)(command, args, {
2015
+ const child = (0, import_node_child_process5.spawn)(command, args, {
1275
2016
  cwd,
1276
2017
  env: process.env,
1277
2018
  stdio: ["ignore", "pipe", "pipe"]
@@ -1362,7 +2103,7 @@ function ensureHttpsUrl(value) {
1362
2103
  }
1363
2104
  return `https://${value}`;
1364
2105
  }
1365
- function inferLogLevel2(line) {
2106
+ function inferLogLevel4(line) {
1366
2107
  const normalized = line.toLowerCase();
1367
2108
  if (normalized.includes("error")) return "error";
1368
2109
  if (normalized.includes("warn")) return "warn";
@@ -1435,6 +2176,10 @@ function createDeployAdapter(platform) {
1435
2176
  return new FlyAdapter();
1436
2177
  case "railway":
1437
2178
  return new RailwayAdapter();
2179
+ case "aws-ecs":
2180
+ return new AwsEcsAdapter();
2181
+ case "aws-lightsail":
2182
+ return new AwsLightsailAdapter();
1438
2183
  case "render":
1439
2184
  case "docker":
1440
2185
  case "kora-cloud":
@@ -1448,7 +2193,7 @@ function createDeployAdapter(platform) {
1448
2193
 
1449
2194
  // src/commands/deploy/artifacts/dockerfile-generator.ts
1450
2195
  var import_promises7 = require("fs/promises");
1451
- var import_node_path8 = require("path");
2196
+ var import_node_path10 = require("path");
1452
2197
  var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
1453
2198
  function generateDockerfile(options = {}) {
1454
2199
  const nodeVersion = options.nodeVersion ?? "20-alpine";
@@ -1512,31 +2257,31 @@ function generateDockerIgnore() {
1512
2257
  }
1513
2258
  async function writeDockerfileArtifact(deployDirectory, options = {}) {
1514
2259
  await (0, import_promises7.mkdir)(deployDirectory, { recursive: true });
1515
- const dockerfilePath = (0, import_node_path8.join)(deployDirectory, "Dockerfile");
2260
+ const dockerfilePath = (0, import_node_path10.join)(deployDirectory, "Dockerfile");
1516
2261
  await (0, import_promises7.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
1517
2262
  if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
1518
- const pkgJsonPath = (0, import_node_path8.join)(deployDirectory, "package.json");
2263
+ const pkgJsonPath = (0, import_node_path10.join)(deployDirectory, "package.json");
1519
2264
  await (0, import_promises7.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
1520
2265
  }
1521
2266
  return dockerfilePath;
1522
2267
  }
1523
2268
  async function writeDockerIgnoreArtifact(deployDirectory) {
1524
2269
  await (0, import_promises7.mkdir)(deployDirectory, { recursive: true });
1525
- const dockerIgnorePath = (0, import_node_path8.join)(deployDirectory, ".dockerignore");
2270
+ const dockerIgnorePath = (0, import_node_path10.join)(deployDirectory, ".dockerignore");
1526
2271
  await (0, import_promises7.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
1527
2272
  return dockerIgnorePath;
1528
2273
  }
1529
2274
 
1530
2275
  // src/commands/deploy/state/deploy-state.ts
1531
2276
  var import_promises8 = require("fs/promises");
1532
- var import_node_path9 = require("path");
1533
- var KORA_DEPLOY_DIRECTORY = (0, import_node_path9.join)(".kora", "deploy");
2277
+ var import_node_path11 = require("path");
2278
+ var KORA_DEPLOY_DIRECTORY = (0, import_node_path11.join)(".kora", "deploy");
1534
2279
  var DEPLOY_STATE_FILENAME = "deploy.json";
1535
2280
  function resolveDeployDirectory(projectRoot) {
1536
- return (0, import_node_path9.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
2281
+ return (0, import_node_path11.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
1537
2282
  }
1538
2283
  function resolveDeployStatePath(projectRoot) {
1539
- return (0, import_node_path9.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
2284
+ return (0, import_node_path11.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
1540
2285
  }
1541
2286
  async function readDeployState(projectRoot) {
1542
2287
  const statePath = resolveDeployStatePath(projectRoot);
@@ -1820,7 +2565,8 @@ var deployCommand = (0, import_citty.defineCommand)({
1820
2565
  await writeDockerfileArtifact(deployDirectory, {
1821
2566
  nativeDependencies: {
1822
2567
  "better-sqlite3": "^11.0.0",
1823
- "drizzle-orm": "^0.45.2"
2568
+ "drizzle-orm": "^0.45.2",
2569
+ "postgres": "^3.4.0"
1824
2570
  }
1825
2571
  });
1826
2572
  await writeDockerIgnoreArtifact(deployDirectory);
@@ -1887,6 +2633,14 @@ async function resolvePlatform(options) {
1887
2633
  label: "Railway",
1888
2634
  value: "railway"
1889
2635
  },
2636
+ {
2637
+ label: "AWS ECS Fargate (production multi-instance)",
2638
+ value: "aws-ecs"
2639
+ },
2640
+ {
2641
+ label: "AWS Lightsail Containers (simple, cost-effective)",
2642
+ value: "aws-lightsail"
2643
+ },
1890
2644
  {
1891
2645
  label: "Render",
1892
2646
  value: "render"
@@ -1913,7 +2667,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
1913
2667
  "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
1914
2668
  );
1915
2669
  }
1916
- return sanitizeAppName((0, import_node_path10.basename)(projectRoot));
2670
+ return sanitizeAppName((0, import_node_path12.basename)(projectRoot));
1917
2671
  }
1918
2672
  function resolveRegion(argValue, storedValue, confirm) {
1919
2673
  if (typeof argValue === "string" && argValue.length > 0) return argValue;
@@ -2168,7 +2922,7 @@ function formatStoredPreferenceLabel(preferences) {
2168
2922
 
2169
2923
  // src/commands/create/sync-provider-preset.ts
2170
2924
  var import_promises9 = require("fs/promises");
2171
- var import_node_path11 = require("path");
2925
+ var import_node_path13 = require("path");
2172
2926
  async function applySyncProviderPreset(options) {
2173
2927
  if (!isSyncTemplate(options.template)) {
2174
2928
  return;
@@ -2178,9 +2932,9 @@ async function applySyncProviderPreset(options) {
2178
2932
  }
2179
2933
  const providerName = getProviderDisplayName(options.dbProvider);
2180
2934
  const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
2181
- const serverPath = (0, import_node_path11.join)(options.targetDir, "server.ts");
2182
- const envPath = (0, import_node_path11.join)(options.targetDir, ".env.example");
2183
- const readmePath = (0, import_node_path11.join)(options.targetDir, "README.md");
2935
+ const serverPath = (0, import_node_path13.join)(options.targetDir, "server.ts");
2936
+ const envPath = (0, import_node_path13.join)(options.targetDir, ".env.example");
2937
+ const readmePath = (0, import_node_path13.join)(options.targetDir, "README.md");
2184
2938
  const serverTemplate = [
2185
2939
  "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
2186
2940
  "",