@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/bin.cjs +875 -121
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-E4JG7THU.js → chunk-E7OCVRYL.js} +812 -60
- package/dist/chunk-E7OCVRYL.js.map +1 -0
- package/dist/index.cjs +839 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-E4JG7THU.js.map +0 -1
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
|
|
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/
|
|
544
|
+
// src/commands/deploy/adapters/aws-ecs-adapter.ts
|
|
545
545
|
var import_node_child_process2 = require("child_process");
|
|
546
|
-
var
|
|
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
|
|
550
|
+
var import_promises2 = require("fs/promises");
|
|
589
551
|
var import_node_fs = require("fs");
|
|
590
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
673
|
-
const outputFilePath = (0,
|
|
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,
|
|
657
|
+
const fullPath = (0, import_node_path3.join)(projectRoot, candidate);
|
|
696
658
|
try {
|
|
697
|
-
await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
1033
|
-
var
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
1533
|
-
var KORA_DEPLOY_DIRECTORY = (0,
|
|
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,
|
|
2281
|
+
return (0, import_node_path11.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
|
|
1537
2282
|
}
|
|
1538
2283
|
function resolveDeployStatePath(projectRoot) {
|
|
1539
|
-
return (0,
|
|
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,
|
|
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
|
|
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,
|
|
2182
|
-
const envPath = (0,
|
|
2183
|
-
const readmePath = (0,
|
|
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
|
"",
|