@korajs/cli 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +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
|
@@ -8,54 +8,16 @@ import {
|
|
|
8
8
|
} from "./chunk-KTSRAPSE.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/deploy/adapters/adapter.ts
|
|
11
|
-
var DEPLOY_PLATFORMS = ["fly", "railway", "render", "docker", "kora-cloud"];
|
|
11
|
+
var DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
|
|
12
12
|
function isDeployPlatform(value) {
|
|
13
13
|
return DEPLOY_PLATFORMS.includes(value);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
// src/commands/deploy/artifacts/fly-toml-generator.ts
|
|
17
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
18
|
-
import { join } from "path";
|
|
19
|
-
var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
|
|
20
|
-
function generateFlyToml(options) {
|
|
21
|
-
const internalPort = options.internalPort ?? 3e3;
|
|
22
|
-
return [
|
|
23
|
-
GENERATED_HEADER,
|
|
24
|
-
"",
|
|
25
|
-
`app = "${options.appName}"`,
|
|
26
|
-
`primary_region = "${options.region}"`,
|
|
27
|
-
"",
|
|
28
|
-
"[build]",
|
|
29
|
-
` dockerfile = "Dockerfile"`,
|
|
30
|
-
"",
|
|
31
|
-
"[http_service]",
|
|
32
|
-
` internal_port = ${String(internalPort)}`,
|
|
33
|
-
" force_https = true",
|
|
34
|
-
' auto_stop_machines = "stop"',
|
|
35
|
-
" auto_start_machines = true",
|
|
36
|
-
" min_machines_running = 0",
|
|
37
|
-
"",
|
|
38
|
-
" [[http_service.checks]]",
|
|
39
|
-
' grace_period = "10s"',
|
|
40
|
-
' interval = "30s"',
|
|
41
|
-
' method = "GET"',
|
|
42
|
-
' timeout = "5s"',
|
|
43
|
-
' path = "/"',
|
|
44
|
-
""
|
|
45
|
-
].join("\n");
|
|
46
|
-
}
|
|
47
|
-
async function writeFlyTomlArtifact(deployDirectory, options) {
|
|
48
|
-
await mkdir(deployDirectory, { recursive: true });
|
|
49
|
-
const filePath = join(deployDirectory, "fly.toml");
|
|
50
|
-
await writeFile(filePath, generateFlyToml(options), "utf-8");
|
|
51
|
-
return filePath;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
16
|
// src/commands/deploy/builder/client-builder.ts
|
|
55
17
|
import { spawn } from "child_process";
|
|
56
18
|
import { copyFile, readdir } from "fs/promises";
|
|
57
19
|
import { existsSync } from "fs";
|
|
58
|
-
import { join
|
|
20
|
+
import { join, resolve } from "path";
|
|
59
21
|
async function buildClient(options) {
|
|
60
22
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
|
|
61
23
|
if (!viteEntryPoint) {
|
|
@@ -76,12 +38,12 @@ async function buildClient(options) {
|
|
|
76
38
|
return { outDir: options.outDir };
|
|
77
39
|
}
|
|
78
40
|
async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
79
|
-
const assetsDir =
|
|
41
|
+
const assetsDir = join(outDir, "assets");
|
|
80
42
|
if (!existsSync(assetsDir)) return;
|
|
81
43
|
const files = await readdir(assetsDir);
|
|
82
44
|
const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
|
|
83
45
|
if (hashedWasm && !files.includes("sqlite3.wasm")) {
|
|
84
|
-
await copyFile(
|
|
46
|
+
await copyFile(join(assetsDir, hashedWasm), join(assetsDir, "sqlite3.wasm"));
|
|
85
47
|
}
|
|
86
48
|
if (!files.includes("sqlite3-opfs-async-proxy.js")) {
|
|
87
49
|
const proxyFile = resolve(
|
|
@@ -94,7 +56,7 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
|
94
56
|
"sqlite3-opfs-async-proxy.js"
|
|
95
57
|
);
|
|
96
58
|
if (existsSync(proxyFile)) {
|
|
97
|
-
await copyFile(proxyFile,
|
|
59
|
+
await copyFile(proxyFile, join(assetsDir, "sqlite3-opfs-async-proxy.js"));
|
|
98
60
|
}
|
|
99
61
|
}
|
|
100
62
|
}
|
|
@@ -119,9 +81,9 @@ async function runProcess(command, args, cwd) {
|
|
|
119
81
|
}
|
|
120
82
|
|
|
121
83
|
// src/commands/deploy/builder/server-bundler.ts
|
|
122
|
-
import { mkdir
|
|
84
|
+
import { mkdir } from "fs/promises";
|
|
123
85
|
import { access } from "fs/promises";
|
|
124
|
-
import { join as
|
|
86
|
+
import { join as join2 } from "path";
|
|
125
87
|
import { build } from "esbuild";
|
|
126
88
|
var DEFAULT_ENTRY_CANDIDATES = [
|
|
127
89
|
"server.ts",
|
|
@@ -137,8 +99,8 @@ async function bundleServer(options) {
|
|
|
137
99
|
`Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
|
|
138
100
|
);
|
|
139
101
|
}
|
|
140
|
-
await
|
|
141
|
-
const outputFilePath =
|
|
102
|
+
await mkdir(options.deployDirectory, { recursive: true });
|
|
103
|
+
const outputFilePath = join2(options.deployDirectory, "server-bundled.js");
|
|
142
104
|
await build({
|
|
143
105
|
entryPoints: [entryFilePath],
|
|
144
106
|
outfile: outputFilePath,
|
|
@@ -160,7 +122,7 @@ async function bundleServer(options) {
|
|
|
160
122
|
}
|
|
161
123
|
async function resolveServerEntry(projectRoot, candidates) {
|
|
162
124
|
for (const candidate of candidates) {
|
|
163
|
-
const fullPath =
|
|
125
|
+
const fullPath = join2(projectRoot, candidate);
|
|
164
126
|
try {
|
|
165
127
|
await access(fullPath);
|
|
166
128
|
return fullPath;
|
|
@@ -170,6 +132,44 @@ async function resolveServerEntry(projectRoot, candidates) {
|
|
|
170
132
|
return null;
|
|
171
133
|
}
|
|
172
134
|
|
|
135
|
+
// src/commands/deploy/artifacts/fly-toml-generator.ts
|
|
136
|
+
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
137
|
+
import { join as join3 } from "path";
|
|
138
|
+
var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
|
|
139
|
+
function generateFlyToml(options) {
|
|
140
|
+
const internalPort = options.internalPort ?? 3e3;
|
|
141
|
+
return [
|
|
142
|
+
GENERATED_HEADER,
|
|
143
|
+
"",
|
|
144
|
+
`app = "${options.appName}"`,
|
|
145
|
+
`primary_region = "${options.region}"`,
|
|
146
|
+
"",
|
|
147
|
+
"[build]",
|
|
148
|
+
` dockerfile = "Dockerfile"`,
|
|
149
|
+
"",
|
|
150
|
+
"[http_service]",
|
|
151
|
+
` internal_port = ${String(internalPort)}`,
|
|
152
|
+
" force_https = true",
|
|
153
|
+
' auto_stop_machines = "stop"',
|
|
154
|
+
" auto_start_machines = true",
|
|
155
|
+
" min_machines_running = 0",
|
|
156
|
+
"",
|
|
157
|
+
" [[http_service.checks]]",
|
|
158
|
+
' grace_period = "10s"',
|
|
159
|
+
' interval = "30s"',
|
|
160
|
+
' method = "GET"',
|
|
161
|
+
' timeout = "5s"',
|
|
162
|
+
' path = "/"',
|
|
163
|
+
""
|
|
164
|
+
].join("\n");
|
|
165
|
+
}
|
|
166
|
+
async function writeFlyTomlArtifact(deployDirectory, options) {
|
|
167
|
+
await mkdir2(deployDirectory, { recursive: true });
|
|
168
|
+
const filePath = join3(deployDirectory, "fly.toml");
|
|
169
|
+
await writeFile(filePath, generateFlyToml(options), "utf-8");
|
|
170
|
+
return filePath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
173
|
// src/commands/deploy/adapters/fly-adapter.ts
|
|
174
174
|
import { spawn as spawn2 } from "child_process";
|
|
175
175
|
import { join as join4 } from "path";
|
|
@@ -896,6 +896,745 @@ var StubDeployAdapter = class {
|
|
|
896
896
|
}
|
|
897
897
|
};
|
|
898
898
|
|
|
899
|
+
// src/commands/deploy/adapters/aws-ecs-adapter.ts
|
|
900
|
+
import { spawn as spawn4 } from "child_process";
|
|
901
|
+
import { join as join7 } from "path";
|
|
902
|
+
var AwsEcsAdapter = class {
|
|
903
|
+
name = "aws-ecs";
|
|
904
|
+
logger = createLogger();
|
|
905
|
+
runner;
|
|
906
|
+
currentContext;
|
|
907
|
+
constructor(options = {}) {
|
|
908
|
+
this.runner = options.runner ?? new NodeAwsCommandRunner();
|
|
909
|
+
this.currentContext = options.context ?? null;
|
|
910
|
+
}
|
|
911
|
+
setContext(context) {
|
|
912
|
+
this.currentContext = context;
|
|
913
|
+
}
|
|
914
|
+
async detect() {
|
|
915
|
+
const result = await this.runner.run("aws", ["--version"], process.cwd());
|
|
916
|
+
return result.exitCode === 0;
|
|
917
|
+
}
|
|
918
|
+
async install() {
|
|
919
|
+
const available = await this.detect();
|
|
920
|
+
if (!available) {
|
|
921
|
+
throw new Error(
|
|
922
|
+
"AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async authenticate() {
|
|
927
|
+
const cwd = this.currentContext?.projectRoot ?? process.cwd();
|
|
928
|
+
const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
|
|
929
|
+
if (result.exitCode !== 0) {
|
|
930
|
+
throw new Error(
|
|
931
|
+
"AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
async provision(config) {
|
|
936
|
+
this.currentContext = {
|
|
937
|
+
projectRoot: config.projectRoot,
|
|
938
|
+
appName: config.appName,
|
|
939
|
+
region: config.region ?? "us-east-1"
|
|
940
|
+
};
|
|
941
|
+
const region = config.region ?? "us-east-1";
|
|
942
|
+
const repoName = `kora/${config.appName}`;
|
|
943
|
+
const createRepo = await this.runner.run("aws", [
|
|
944
|
+
"ecr",
|
|
945
|
+
"create-repository",
|
|
946
|
+
"--repository-name",
|
|
947
|
+
repoName,
|
|
948
|
+
"--region",
|
|
949
|
+
region,
|
|
950
|
+
"--image-scanning-configuration",
|
|
951
|
+
"scanOnPush=true"
|
|
952
|
+
], config.projectRoot);
|
|
953
|
+
if (createRepo.exitCode !== 0 && !createRepo.stderr.includes("RepositoryAlreadyExistsException")) {
|
|
954
|
+
throw new Error(`Failed to create ECR repository: ${createRepo.stderr}`);
|
|
955
|
+
}
|
|
956
|
+
const identity = await this.runner.run("aws", ["sts", "get-caller-identity", "--query", "Account", "--output", "text"], config.projectRoot);
|
|
957
|
+
const accountId = identity.stdout.trim();
|
|
958
|
+
await this.runner.run("aws", [
|
|
959
|
+
"ecs",
|
|
960
|
+
"create-cluster",
|
|
961
|
+
"--cluster-name",
|
|
962
|
+
config.appName,
|
|
963
|
+
"--region",
|
|
964
|
+
region
|
|
965
|
+
], config.projectRoot);
|
|
966
|
+
await this.runner.run("aws", [
|
|
967
|
+
"logs",
|
|
968
|
+
"create-log-group",
|
|
969
|
+
"--log-group-name",
|
|
970
|
+
`/ecs/${config.appName}`,
|
|
971
|
+
"--region",
|
|
972
|
+
region
|
|
973
|
+
], config.projectRoot);
|
|
974
|
+
return {
|
|
975
|
+
applicationId: `${accountId}.dkr.ecr.${region}.amazonaws.com/${repoName}`,
|
|
976
|
+
databaseId: null,
|
|
977
|
+
secretsSet: ["PORT"]
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
async build(config) {
|
|
981
|
+
this.currentContext = {
|
|
982
|
+
projectRoot: config.projectRoot,
|
|
983
|
+
appName: config.appName,
|
|
984
|
+
region: config.region
|
|
985
|
+
};
|
|
986
|
+
const deployDirectory = join7(config.projectRoot, ".kora", "deploy");
|
|
987
|
+
await bundleServer({
|
|
988
|
+
projectRoot: config.projectRoot,
|
|
989
|
+
deployDirectory
|
|
990
|
+
});
|
|
991
|
+
const client = await buildClient({
|
|
992
|
+
projectRoot: config.projectRoot,
|
|
993
|
+
outDir: join7(deployDirectory, "dist"),
|
|
994
|
+
mode: "production"
|
|
995
|
+
});
|
|
996
|
+
return {
|
|
997
|
+
clientDirectory: client.outDir,
|
|
998
|
+
serverBundlePath: join7(deployDirectory, "server-bundled.js"),
|
|
999
|
+
deployDirectory
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
async deploy(artifacts) {
|
|
1003
|
+
const context = this.requireContext();
|
|
1004
|
+
const region = context.region ?? "us-east-1";
|
|
1005
|
+
const repoName = `kora/${context.appName}`;
|
|
1006
|
+
const loginPassword = await this.runner.run("aws", [
|
|
1007
|
+
"ecr",
|
|
1008
|
+
"get-login-password",
|
|
1009
|
+
"--region",
|
|
1010
|
+
region
|
|
1011
|
+
], context.projectRoot);
|
|
1012
|
+
if (loginPassword.exitCode !== 0) {
|
|
1013
|
+
throw new Error(`ECR login failed: ${loginPassword.stderr}`);
|
|
1014
|
+
}
|
|
1015
|
+
const identity = await this.runner.run("aws", [
|
|
1016
|
+
"sts",
|
|
1017
|
+
"get-caller-identity",
|
|
1018
|
+
"--query",
|
|
1019
|
+
"Account",
|
|
1020
|
+
"--output",
|
|
1021
|
+
"text"
|
|
1022
|
+
], context.projectRoot);
|
|
1023
|
+
const accountId = identity.stdout.trim();
|
|
1024
|
+
const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
|
|
1025
|
+
const imageUri = `${ecrUri}/${repoName}:latest`;
|
|
1026
|
+
const dockerLogin = await this.runner.run("docker", [
|
|
1027
|
+
"login",
|
|
1028
|
+
"--username",
|
|
1029
|
+
"AWS",
|
|
1030
|
+
"--password-stdin",
|
|
1031
|
+
ecrUri
|
|
1032
|
+
], artifacts.deployDirectory);
|
|
1033
|
+
this.logger.step("Building Docker image...");
|
|
1034
|
+
const dockerBuild = await this.runner.run("docker", [
|
|
1035
|
+
"build",
|
|
1036
|
+
"--platform",
|
|
1037
|
+
"linux/amd64",
|
|
1038
|
+
"-t",
|
|
1039
|
+
imageUri,
|
|
1040
|
+
"."
|
|
1041
|
+
], artifacts.deployDirectory);
|
|
1042
|
+
if (dockerBuild.exitCode !== 0) {
|
|
1043
|
+
throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
|
|
1044
|
+
}
|
|
1045
|
+
this.logger.step("Pushing image to ECR...");
|
|
1046
|
+
const dockerPush = await this.runner.run("docker", [
|
|
1047
|
+
"push",
|
|
1048
|
+
imageUri
|
|
1049
|
+
], artifacts.deployDirectory);
|
|
1050
|
+
if (dockerPush.exitCode !== 0) {
|
|
1051
|
+
throw new Error(`Docker push failed: ${dockerPush.stderr}`);
|
|
1052
|
+
}
|
|
1053
|
+
const taskDef = JSON.stringify({
|
|
1054
|
+
family: context.appName,
|
|
1055
|
+
networkMode: "awsvpc",
|
|
1056
|
+
requiresCompatibilities: ["FARGATE"],
|
|
1057
|
+
cpu: "256",
|
|
1058
|
+
memory: "512",
|
|
1059
|
+
executionRoleArn: `arn:aws:iam::${accountId}:role/ecsTaskExecutionRole`,
|
|
1060
|
+
containerDefinitions: [{
|
|
1061
|
+
name: context.appName,
|
|
1062
|
+
image: imageUri,
|
|
1063
|
+
essential: true,
|
|
1064
|
+
portMappings: [{ containerPort: 3001, protocol: "tcp" }],
|
|
1065
|
+
logConfiguration: {
|
|
1066
|
+
logDriver: "awslogs",
|
|
1067
|
+
options: {
|
|
1068
|
+
"awslogs-group": `/ecs/${context.appName}`,
|
|
1069
|
+
"awslogs-region": region,
|
|
1070
|
+
"awslogs-stream-prefix": "ecs"
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
healthCheck: {
|
|
1074
|
+
command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
|
|
1075
|
+
interval: 30,
|
|
1076
|
+
timeout: 5,
|
|
1077
|
+
retries: 3,
|
|
1078
|
+
startPeriod: 60
|
|
1079
|
+
}
|
|
1080
|
+
}]
|
|
1081
|
+
});
|
|
1082
|
+
const registerTask = await this.runner.run("aws", [
|
|
1083
|
+
"ecs",
|
|
1084
|
+
"register-task-definition",
|
|
1085
|
+
"--cli-input-json",
|
|
1086
|
+
taskDef,
|
|
1087
|
+
"--region",
|
|
1088
|
+
region
|
|
1089
|
+
], context.projectRoot);
|
|
1090
|
+
if (registerTask.exitCode !== 0) {
|
|
1091
|
+
throw new Error(`Task definition registration failed: ${registerTask.stderr}`);
|
|
1092
|
+
}
|
|
1093
|
+
const updateService = await this.runner.run("aws", [
|
|
1094
|
+
"ecs",
|
|
1095
|
+
"update-service",
|
|
1096
|
+
"--cluster",
|
|
1097
|
+
context.appName,
|
|
1098
|
+
"--service",
|
|
1099
|
+
context.appName,
|
|
1100
|
+
"--task-definition",
|
|
1101
|
+
context.appName,
|
|
1102
|
+
"--force-new-deployment",
|
|
1103
|
+
"--region",
|
|
1104
|
+
region
|
|
1105
|
+
], context.projectRoot);
|
|
1106
|
+
const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1107
|
+
if (updateService.exitCode !== 0) {
|
|
1108
|
+
this.logger.step("Service not found, creating new service...");
|
|
1109
|
+
this.logger.step(
|
|
1110
|
+
`Task definition registered. Create the ECS service with:
|
|
1111
|
+
aws ecs create-service \\
|
|
1112
|
+
--cluster ${context.appName} \\
|
|
1113
|
+
--service-name ${context.appName} \\
|
|
1114
|
+
--task-definition ${context.appName} \\
|
|
1115
|
+
--desired-count 1 \\
|
|
1116
|
+
--launch-type FARGATE \\
|
|
1117
|
+
--network-configuration "awsvpcConfiguration={subnets=[<subnet-id>],securityGroups=[<sg-id>],assignPublicIp=ENABLED}" \\
|
|
1118
|
+
--region ${region}`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
deploymentId,
|
|
1123
|
+
liveUrl: `https://${context.appName}.${region}.amazonaws.com`,
|
|
1124
|
+
syncUrl: `wss://${context.appName}.${region}.amazonaws.com/kora-sync`
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
async rollback(deploymentId) {
|
|
1128
|
+
const context = this.requireContext();
|
|
1129
|
+
const region = context.region ?? "us-east-1";
|
|
1130
|
+
const result = await this.runner.run("aws", [
|
|
1131
|
+
"ecs",
|
|
1132
|
+
"update-service",
|
|
1133
|
+
"--cluster",
|
|
1134
|
+
context.appName,
|
|
1135
|
+
"--service",
|
|
1136
|
+
context.appName,
|
|
1137
|
+
"--task-definition",
|
|
1138
|
+
`${context.appName}:${deploymentId}`,
|
|
1139
|
+
"--force-new-deployment",
|
|
1140
|
+
"--region",
|
|
1141
|
+
region
|
|
1142
|
+
], context.projectRoot);
|
|
1143
|
+
if (result.exitCode !== 0) {
|
|
1144
|
+
throw new Error(`ECS rollback failed: ${result.stderr}`);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
async *logs(options) {
|
|
1148
|
+
const context = this.requireContext();
|
|
1149
|
+
const region = context.region ?? "us-east-1";
|
|
1150
|
+
const args = [
|
|
1151
|
+
"logs",
|
|
1152
|
+
"get-log-events",
|
|
1153
|
+
"--log-group-name",
|
|
1154
|
+
`/ecs/${context.appName}`,
|
|
1155
|
+
"--log-stream-name",
|
|
1156
|
+
"ecs/latest",
|
|
1157
|
+
"--region",
|
|
1158
|
+
region
|
|
1159
|
+
];
|
|
1160
|
+
if (options.tail) {
|
|
1161
|
+
args.push("--limit", String(options.tail));
|
|
1162
|
+
}
|
|
1163
|
+
const result = await this.runner.run("aws", args, context.projectRoot);
|
|
1164
|
+
if (result.exitCode !== 0) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
try {
|
|
1168
|
+
const parsed = JSON.parse(result.stdout);
|
|
1169
|
+
for (const event of parsed.events ?? []) {
|
|
1170
|
+
yield {
|
|
1171
|
+
timestamp: new Date(event.timestamp).toISOString(),
|
|
1172
|
+
level: inferLogLevel3(event.message),
|
|
1173
|
+
message: event.message
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
} catch {
|
|
1177
|
+
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
1178
|
+
yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
async status() {
|
|
1183
|
+
const context = this.requireContext();
|
|
1184
|
+
const region = context.region ?? "us-east-1";
|
|
1185
|
+
const result = await this.runner.run("aws", [
|
|
1186
|
+
"ecs",
|
|
1187
|
+
"describe-services",
|
|
1188
|
+
"--cluster",
|
|
1189
|
+
context.appName,
|
|
1190
|
+
"--services",
|
|
1191
|
+
context.appName,
|
|
1192
|
+
"--region",
|
|
1193
|
+
region
|
|
1194
|
+
], context.projectRoot);
|
|
1195
|
+
if (result.exitCode !== 0) {
|
|
1196
|
+
return { state: "failed", message: result.stderr };
|
|
1197
|
+
}
|
|
1198
|
+
try {
|
|
1199
|
+
const parsed = JSON.parse(result.stdout);
|
|
1200
|
+
const service = parsed.services?.[0];
|
|
1201
|
+
if (!service) {
|
|
1202
|
+
return { state: "unknown", message: "Service not found" };
|
|
1203
|
+
}
|
|
1204
|
+
if (service.status === "ACTIVE" && service.runningCount > 0) {
|
|
1205
|
+
return {
|
|
1206
|
+
state: "healthy",
|
|
1207
|
+
message: `Running ${service.runningCount}/${service.desiredCount} tasks`
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
state: service.runningCount === 0 ? "pending" : "healthy",
|
|
1212
|
+
message: `Status: ${service.status}, running: ${service.runningCount}/${service.desiredCount}`
|
|
1213
|
+
};
|
|
1214
|
+
} catch {
|
|
1215
|
+
return { state: "unknown", message: "Could not parse service status" };
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
requireContext() {
|
|
1219
|
+
if (!this.currentContext) {
|
|
1220
|
+
throw new Error("AWS ECS adapter context is not initialized. Run provision() first.");
|
|
1221
|
+
}
|
|
1222
|
+
return this.currentContext;
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
var NodeAwsCommandRunner = class {
|
|
1226
|
+
async run(command, args, cwd) {
|
|
1227
|
+
return new Promise((resolve2) => {
|
|
1228
|
+
const child = spawn4(command, args, {
|
|
1229
|
+
cwd,
|
|
1230
|
+
env: process.env,
|
|
1231
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1232
|
+
});
|
|
1233
|
+
let stdout = "";
|
|
1234
|
+
let stderr = "";
|
|
1235
|
+
child.stdout?.on("data", (chunk) => {
|
|
1236
|
+
stdout += chunk.toString("utf-8");
|
|
1237
|
+
});
|
|
1238
|
+
child.stderr?.on("data", (chunk) => {
|
|
1239
|
+
stderr += chunk.toString("utf-8");
|
|
1240
|
+
});
|
|
1241
|
+
child.on("error", (error) => {
|
|
1242
|
+
resolve2({ exitCode: 1, stdout, stderr: `${stderr}
|
|
1243
|
+
${error.message}` });
|
|
1244
|
+
});
|
|
1245
|
+
child.on("exit", (code) => {
|
|
1246
|
+
resolve2({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
function inferLogLevel3(line) {
|
|
1252
|
+
const normalized = line.toLowerCase();
|
|
1253
|
+
if (normalized.includes("error")) return "error";
|
|
1254
|
+
if (normalized.includes("warn")) return "warn";
|
|
1255
|
+
if (normalized.includes("debug")) return "debug";
|
|
1256
|
+
return "info";
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/commands/deploy/adapters/aws-lightsail-adapter.ts
|
|
1260
|
+
import { spawn as spawn5 } from "child_process";
|
|
1261
|
+
import { join as join8 } from "path";
|
|
1262
|
+
var AwsLightsailAdapter = class {
|
|
1263
|
+
name = "aws-lightsail";
|
|
1264
|
+
logger = createLogger();
|
|
1265
|
+
runner;
|
|
1266
|
+
currentContext;
|
|
1267
|
+
constructor(options = {}) {
|
|
1268
|
+
this.runner = options.runner ?? new NodeAwsLightsailCommandRunner();
|
|
1269
|
+
this.currentContext = options.context ?? null;
|
|
1270
|
+
}
|
|
1271
|
+
setContext(context) {
|
|
1272
|
+
this.currentContext = context;
|
|
1273
|
+
}
|
|
1274
|
+
async detect() {
|
|
1275
|
+
const result = await this.runner.run("aws", ["--version"], process.cwd());
|
|
1276
|
+
return result.exitCode === 0;
|
|
1277
|
+
}
|
|
1278
|
+
async install() {
|
|
1279
|
+
const available = await this.detect();
|
|
1280
|
+
if (!available) {
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
"AWS CLI is required but not installed. Install from https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
async authenticate() {
|
|
1287
|
+
const cwd = this.currentContext?.projectRoot ?? process.cwd();
|
|
1288
|
+
const result = await this.runner.run("aws", ["sts", "get-caller-identity"], cwd);
|
|
1289
|
+
if (result.exitCode !== 0) {
|
|
1290
|
+
throw new Error(
|
|
1291
|
+
"AWS CLI is not authenticated. Run `aws configure` or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async provision(config) {
|
|
1296
|
+
this.currentContext = {
|
|
1297
|
+
projectRoot: config.projectRoot,
|
|
1298
|
+
appName: config.appName,
|
|
1299
|
+
region: config.region ?? "us-east-1"
|
|
1300
|
+
};
|
|
1301
|
+
const region = config.region ?? "us-east-1";
|
|
1302
|
+
const serviceName = sanitizeLightsailName(config.appName);
|
|
1303
|
+
const createService = await this.runner.run("aws", [
|
|
1304
|
+
"lightsail",
|
|
1305
|
+
"create-container-service",
|
|
1306
|
+
"--service-name",
|
|
1307
|
+
serviceName,
|
|
1308
|
+
"--power",
|
|
1309
|
+
"nano",
|
|
1310
|
+
"--scale",
|
|
1311
|
+
"1",
|
|
1312
|
+
"--region",
|
|
1313
|
+
region
|
|
1314
|
+
], config.projectRoot);
|
|
1315
|
+
if (createService.exitCode !== 0 && !createService.stderr.includes("already exists")) {
|
|
1316
|
+
throw new Error(`Failed to create Lightsail container service: ${createService.stderr}`);
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
applicationId: serviceName,
|
|
1320
|
+
databaseId: null,
|
|
1321
|
+
secretsSet: ["PORT"]
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
async build(config) {
|
|
1325
|
+
this.currentContext = {
|
|
1326
|
+
projectRoot: config.projectRoot,
|
|
1327
|
+
appName: config.appName,
|
|
1328
|
+
region: config.region
|
|
1329
|
+
};
|
|
1330
|
+
const deployDirectory = join8(config.projectRoot, ".kora", "deploy");
|
|
1331
|
+
await bundleServer({
|
|
1332
|
+
projectRoot: config.projectRoot,
|
|
1333
|
+
deployDirectory
|
|
1334
|
+
});
|
|
1335
|
+
const client = await buildClient({
|
|
1336
|
+
projectRoot: config.projectRoot,
|
|
1337
|
+
outDir: join8(deployDirectory, "dist"),
|
|
1338
|
+
mode: "production"
|
|
1339
|
+
});
|
|
1340
|
+
return {
|
|
1341
|
+
clientDirectory: client.outDir,
|
|
1342
|
+
serverBundlePath: join8(deployDirectory, "server-bundled.js"),
|
|
1343
|
+
deployDirectory
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
async deploy(artifacts) {
|
|
1347
|
+
const context = this.requireContext();
|
|
1348
|
+
const region = context.region ?? "us-east-1";
|
|
1349
|
+
const serviceName = sanitizeLightsailName(context.appName);
|
|
1350
|
+
const imageTag = `${serviceName}:latest`;
|
|
1351
|
+
this.logger.step("Building Docker image...");
|
|
1352
|
+
const dockerBuild = await this.runner.run("docker", [
|
|
1353
|
+
"build",
|
|
1354
|
+
"--platform",
|
|
1355
|
+
"linux/amd64",
|
|
1356
|
+
"-t",
|
|
1357
|
+
imageTag,
|
|
1358
|
+
"."
|
|
1359
|
+
], artifacts.deployDirectory);
|
|
1360
|
+
if (dockerBuild.exitCode !== 0) {
|
|
1361
|
+
throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
|
|
1362
|
+
}
|
|
1363
|
+
this.logger.step("Pushing image to Lightsail...");
|
|
1364
|
+
const pushImage = await this.runner.run("aws", [
|
|
1365
|
+
"lightsail",
|
|
1366
|
+
"push-container-image",
|
|
1367
|
+
"--service-name",
|
|
1368
|
+
serviceName,
|
|
1369
|
+
"--label",
|
|
1370
|
+
"latest",
|
|
1371
|
+
"--image",
|
|
1372
|
+
imageTag,
|
|
1373
|
+
"--region",
|
|
1374
|
+
region
|
|
1375
|
+
], artifacts.deployDirectory);
|
|
1376
|
+
if (pushImage.exitCode !== 0) {
|
|
1377
|
+
throw new Error(`Lightsail image push failed: ${pushImage.stderr}`);
|
|
1378
|
+
}
|
|
1379
|
+
const lightsailImage = parseLightsailImageRef(pushImage.stdout) ?? `:${serviceName}.latest.1`;
|
|
1380
|
+
this.logger.step("Creating Lightsail deployment...");
|
|
1381
|
+
const environment = { PORT: "3001" };
|
|
1382
|
+
for (const key of PASSTHROUGH_ENV_VARS) {
|
|
1383
|
+
const value = process.env[key];
|
|
1384
|
+
if (value) {
|
|
1385
|
+
environment[key] = value;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
const containers = JSON.stringify({
|
|
1389
|
+
[serviceName]: {
|
|
1390
|
+
image: lightsailImage,
|
|
1391
|
+
ports: { "3001": "HTTP" },
|
|
1392
|
+
environment
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
const publicEndpoint = JSON.stringify({
|
|
1396
|
+
containerName: serviceName,
|
|
1397
|
+
containerPort: 3001,
|
|
1398
|
+
healthCheck: {
|
|
1399
|
+
path: "/health",
|
|
1400
|
+
intervalSeconds: 30,
|
|
1401
|
+
timeoutSeconds: 5,
|
|
1402
|
+
healthyThreshold: 2,
|
|
1403
|
+
unhealthyThreshold: 3
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
const createDeploy = await this.runner.run("aws", [
|
|
1407
|
+
"lightsail",
|
|
1408
|
+
"create-container-service-deployment",
|
|
1409
|
+
"--service-name",
|
|
1410
|
+
serviceName,
|
|
1411
|
+
"--containers",
|
|
1412
|
+
containers,
|
|
1413
|
+
"--public-endpoint",
|
|
1414
|
+
publicEndpoint,
|
|
1415
|
+
"--region",
|
|
1416
|
+
region
|
|
1417
|
+
], context.projectRoot);
|
|
1418
|
+
if (createDeploy.exitCode !== 0) {
|
|
1419
|
+
throw new Error(`Lightsail deployment failed: ${createDeploy.stderr}`);
|
|
1420
|
+
}
|
|
1421
|
+
const serviceInfo = await this.runner.run("aws", [
|
|
1422
|
+
"lightsail",
|
|
1423
|
+
"get-container-services",
|
|
1424
|
+
"--service-name",
|
|
1425
|
+
serviceName,
|
|
1426
|
+
"--region",
|
|
1427
|
+
region
|
|
1428
|
+
], context.projectRoot);
|
|
1429
|
+
const rawUrl = parseLightsailUrl(serviceInfo.stdout) ?? `https://${serviceName}.${region}.cs.amazonlightsail.com`;
|
|
1430
|
+
const serviceUrl = rawUrl.replace(/\/+$/, "");
|
|
1431
|
+
const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1432
|
+
return {
|
|
1433
|
+
deploymentId,
|
|
1434
|
+
liveUrl: serviceUrl,
|
|
1435
|
+
syncUrl: `${serviceUrl.replace("https://", "wss://")}/kora-sync`
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
async rollback(_deploymentId) {
|
|
1439
|
+
const context = this.requireContext();
|
|
1440
|
+
const region = context.region ?? "us-east-1";
|
|
1441
|
+
const serviceName = sanitizeLightsailName(context.appName);
|
|
1442
|
+
const deployments = await this.runner.run("aws", [
|
|
1443
|
+
"lightsail",
|
|
1444
|
+
"get-container-service-deployments",
|
|
1445
|
+
"--service-name",
|
|
1446
|
+
serviceName,
|
|
1447
|
+
"--region",
|
|
1448
|
+
region
|
|
1449
|
+
], context.projectRoot);
|
|
1450
|
+
if (deployments.exitCode !== 0) {
|
|
1451
|
+
throw new Error(`Lightsail rollback failed: ${deployments.stderr}`);
|
|
1452
|
+
}
|
|
1453
|
+
const previousDeployment = parsePreviousDeployment(deployments.stdout, serviceName);
|
|
1454
|
+
if (!previousDeployment) {
|
|
1455
|
+
throw new Error("No previous deployment found to rollback to.");
|
|
1456
|
+
}
|
|
1457
|
+
const redeploy = await this.runner.run("aws", [
|
|
1458
|
+
"lightsail",
|
|
1459
|
+
"create-container-service-deployment",
|
|
1460
|
+
"--service-name",
|
|
1461
|
+
serviceName,
|
|
1462
|
+
"--containers",
|
|
1463
|
+
JSON.stringify(previousDeployment.containers),
|
|
1464
|
+
"--public-endpoint",
|
|
1465
|
+
JSON.stringify(previousDeployment.publicEndpoint),
|
|
1466
|
+
"--region",
|
|
1467
|
+
region
|
|
1468
|
+
], context.projectRoot);
|
|
1469
|
+
if (redeploy.exitCode !== 0) {
|
|
1470
|
+
throw new Error(`Lightsail rollback deployment failed: ${redeploy.stderr}`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
async *logs(options) {
|
|
1474
|
+
const context = this.requireContext();
|
|
1475
|
+
const region = context.region ?? "us-east-1";
|
|
1476
|
+
const serviceName = sanitizeLightsailName(context.appName);
|
|
1477
|
+
const args = [
|
|
1478
|
+
"lightsail",
|
|
1479
|
+
"get-container-log",
|
|
1480
|
+
"--service-name",
|
|
1481
|
+
serviceName,
|
|
1482
|
+
"--container-name",
|
|
1483
|
+
serviceName,
|
|
1484
|
+
"--region",
|
|
1485
|
+
region
|
|
1486
|
+
];
|
|
1487
|
+
if (options.since) {
|
|
1488
|
+
args.push("--start-time", options.since);
|
|
1489
|
+
}
|
|
1490
|
+
const result = await this.runner.run("aws", args, context.projectRoot);
|
|
1491
|
+
if (result.exitCode !== 0) {
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
try {
|
|
1495
|
+
const parsed = JSON.parse(result.stdout);
|
|
1496
|
+
const events = parsed.logEvents ?? [];
|
|
1497
|
+
const limited = options.tail ? events.slice(-options.tail) : events;
|
|
1498
|
+
for (const event of limited) {
|
|
1499
|
+
yield {
|
|
1500
|
+
timestamp: event.createdAt,
|
|
1501
|
+
level: inferLogLevel4(event.message),
|
|
1502
|
+
message: event.message
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
} catch {
|
|
1506
|
+
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
1507
|
+
yield { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: "info", message: line };
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async status() {
|
|
1512
|
+
const context = this.requireContext();
|
|
1513
|
+
const region = context.region ?? "us-east-1";
|
|
1514
|
+
const serviceName = sanitizeLightsailName(context.appName);
|
|
1515
|
+
const result = await this.runner.run("aws", [
|
|
1516
|
+
"lightsail",
|
|
1517
|
+
"get-container-services",
|
|
1518
|
+
"--service-name",
|
|
1519
|
+
serviceName,
|
|
1520
|
+
"--region",
|
|
1521
|
+
region
|
|
1522
|
+
], context.projectRoot);
|
|
1523
|
+
if (result.exitCode !== 0) {
|
|
1524
|
+
return { state: "failed", message: result.stderr };
|
|
1525
|
+
}
|
|
1526
|
+
try {
|
|
1527
|
+
const parsed = JSON.parse(result.stdout);
|
|
1528
|
+
const service = parsed.containerServices?.[0];
|
|
1529
|
+
if (!service) {
|
|
1530
|
+
return { state: "unknown", message: "Container service not found" };
|
|
1531
|
+
}
|
|
1532
|
+
const deployState = service.currentDeployment?.state ?? "UNKNOWN";
|
|
1533
|
+
if (service.state === "RUNNING" && deployState === "ACTIVE") {
|
|
1534
|
+
return {
|
|
1535
|
+
state: "healthy",
|
|
1536
|
+
message: "Lightsail container service is running",
|
|
1537
|
+
liveUrl: service.url
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
if (service.state === "DEPLOYING" || deployState === "ACTIVATING") {
|
|
1541
|
+
return {
|
|
1542
|
+
state: "pending",
|
|
1543
|
+
message: `Service: ${service.state}, Deployment: ${deployState}`
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
return {
|
|
1547
|
+
state: service.state === "DISABLED" ? "failed" : "unknown",
|
|
1548
|
+
message: `Service: ${service.state}, Deployment: ${deployState}`
|
|
1549
|
+
};
|
|
1550
|
+
} catch {
|
|
1551
|
+
return { state: "unknown", message: "Could not parse service status" };
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
requireContext() {
|
|
1555
|
+
if (!this.currentContext) {
|
|
1556
|
+
throw new Error("AWS Lightsail adapter context is not initialized. Run provision() first.");
|
|
1557
|
+
}
|
|
1558
|
+
return this.currentContext;
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
var NodeAwsLightsailCommandRunner = class {
|
|
1562
|
+
async run(command, args, cwd) {
|
|
1563
|
+
return new Promise((resolve2) => {
|
|
1564
|
+
const child = spawn5(command, args, {
|
|
1565
|
+
cwd,
|
|
1566
|
+
env: process.env,
|
|
1567
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1568
|
+
});
|
|
1569
|
+
let stdout = "";
|
|
1570
|
+
let stderr = "";
|
|
1571
|
+
child.stdout?.on("data", (chunk) => {
|
|
1572
|
+
stdout += chunk.toString("utf-8");
|
|
1573
|
+
});
|
|
1574
|
+
child.stderr?.on("data", (chunk) => {
|
|
1575
|
+
stderr += chunk.toString("utf-8");
|
|
1576
|
+
});
|
|
1577
|
+
child.on("error", (error) => {
|
|
1578
|
+
resolve2({ exitCode: 1, stdout, stderr: `${stderr}
|
|
1579
|
+
${error.message}` });
|
|
1580
|
+
});
|
|
1581
|
+
child.on("exit", (code) => {
|
|
1582
|
+
resolve2({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
1583
|
+
});
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
var PASSTHROUGH_ENV_VARS = [
|
|
1588
|
+
"DATABASE_URL",
|
|
1589
|
+
"AUTH_SECRET",
|
|
1590
|
+
"PUBLIC_URL",
|
|
1591
|
+
"NODE_ENV"
|
|
1592
|
+
];
|
|
1593
|
+
function sanitizeLightsailName(name) {
|
|
1594
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 255) || "kora-app";
|
|
1595
|
+
}
|
|
1596
|
+
function parseLightsailImageRef(output) {
|
|
1597
|
+
const match = output.match(/Refer to this image as\s+"?(:[^"\s]+)"?/i);
|
|
1598
|
+
if (match?.[1]) {
|
|
1599
|
+
return match[1];
|
|
1600
|
+
}
|
|
1601
|
+
const refMatch = output.match(/(:\S+\.\S+\.\d+)/);
|
|
1602
|
+
return refMatch?.[1] ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
function parseLightsailUrl(rawJson) {
|
|
1605
|
+
try {
|
|
1606
|
+
const parsed = JSON.parse(rawJson);
|
|
1607
|
+
const url = parsed.containerServices?.[0]?.url;
|
|
1608
|
+
if (typeof url === "string" && url.length > 0) {
|
|
1609
|
+
return url.startsWith("https://") ? url : `https://${url}`;
|
|
1610
|
+
}
|
|
1611
|
+
return null;
|
|
1612
|
+
} catch {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
function parsePreviousDeployment(rawJson, serviceName) {
|
|
1617
|
+
try {
|
|
1618
|
+
const parsed = JSON.parse(rawJson);
|
|
1619
|
+
const deployments = parsed.deployments ?? [];
|
|
1620
|
+
const previous = deployments.length > 1 ? deployments[1] : null;
|
|
1621
|
+
if (!previous) return null;
|
|
1622
|
+
return {
|
|
1623
|
+
containers: previous.containers,
|
|
1624
|
+
publicEndpoint: previous.publicEndpoint
|
|
1625
|
+
};
|
|
1626
|
+
} catch {
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
function inferLogLevel4(line) {
|
|
1631
|
+
const normalized = line.toLowerCase();
|
|
1632
|
+
if (normalized.includes("error")) return "error";
|
|
1633
|
+
if (normalized.includes("warn")) return "warn";
|
|
1634
|
+
if (normalized.includes("debug")) return "debug";
|
|
1635
|
+
return "info";
|
|
1636
|
+
}
|
|
1637
|
+
|
|
899
1638
|
// src/commands/deploy/adapters/factory.ts
|
|
900
1639
|
function createDeployAdapter(platform) {
|
|
901
1640
|
switch (platform) {
|
|
@@ -903,6 +1642,10 @@ function createDeployAdapter(platform) {
|
|
|
903
1642
|
return new FlyAdapter();
|
|
904
1643
|
case "railway":
|
|
905
1644
|
return new RailwayAdapter();
|
|
1645
|
+
case "aws-ecs":
|
|
1646
|
+
return new AwsEcsAdapter();
|
|
1647
|
+
case "aws-lightsail":
|
|
1648
|
+
return new AwsLightsailAdapter();
|
|
906
1649
|
case "render":
|
|
907
1650
|
case "docker":
|
|
908
1651
|
case "kora-cloud":
|
|
@@ -916,7 +1659,7 @@ function createDeployAdapter(platform) {
|
|
|
916
1659
|
|
|
917
1660
|
// src/commands/deploy/artifacts/dockerfile-generator.ts
|
|
918
1661
|
import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
|
|
919
|
-
import { join as
|
|
1662
|
+
import { join as join9 } from "path";
|
|
920
1663
|
var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
|
|
921
1664
|
function generateDockerfile(options = {}) {
|
|
922
1665
|
const nodeVersion = options.nodeVersion ?? "20-alpine";
|
|
@@ -980,31 +1723,31 @@ function generateDockerIgnore() {
|
|
|
980
1723
|
}
|
|
981
1724
|
async function writeDockerfileArtifact(deployDirectory, options = {}) {
|
|
982
1725
|
await mkdir4(deployDirectory, { recursive: true });
|
|
983
|
-
const dockerfilePath =
|
|
1726
|
+
const dockerfilePath = join9(deployDirectory, "Dockerfile");
|
|
984
1727
|
await writeFile3(dockerfilePath, generateDockerfile(options), "utf-8");
|
|
985
1728
|
if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
|
|
986
|
-
const pkgJsonPath =
|
|
1729
|
+
const pkgJsonPath = join9(deployDirectory, "package.json");
|
|
987
1730
|
await writeFile3(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
|
|
988
1731
|
}
|
|
989
1732
|
return dockerfilePath;
|
|
990
1733
|
}
|
|
991
1734
|
async function writeDockerIgnoreArtifact(deployDirectory) {
|
|
992
1735
|
await mkdir4(deployDirectory, { recursive: true });
|
|
993
|
-
const dockerIgnorePath =
|
|
1736
|
+
const dockerIgnorePath = join9(deployDirectory, ".dockerignore");
|
|
994
1737
|
await writeFile3(dockerIgnorePath, generateDockerIgnore(), "utf-8");
|
|
995
1738
|
return dockerIgnorePath;
|
|
996
1739
|
}
|
|
997
1740
|
|
|
998
1741
|
// src/commands/deploy/state/deploy-state.ts
|
|
999
1742
|
import { mkdir as mkdir5, readFile, rm, writeFile as writeFile4 } from "fs/promises";
|
|
1000
|
-
import { join as
|
|
1001
|
-
var KORA_DEPLOY_DIRECTORY =
|
|
1743
|
+
import { join as join10 } from "path";
|
|
1744
|
+
var KORA_DEPLOY_DIRECTORY = join10(".kora", "deploy");
|
|
1002
1745
|
var DEPLOY_STATE_FILENAME = "deploy.json";
|
|
1003
1746
|
function resolveDeployDirectory(projectRoot) {
|
|
1004
|
-
return
|
|
1747
|
+
return join10(projectRoot, KORA_DEPLOY_DIRECTORY);
|
|
1005
1748
|
}
|
|
1006
1749
|
function resolveDeployStatePath(projectRoot) {
|
|
1007
|
-
return
|
|
1750
|
+
return join10(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
|
|
1008
1751
|
}
|
|
1009
1752
|
async function readDeployState(projectRoot) {
|
|
1010
1753
|
const statePath = resolveDeployStatePath(projectRoot);
|
|
@@ -1290,7 +2033,8 @@ var deployCommand = defineCommand({
|
|
|
1290
2033
|
await writeDockerfileArtifact(deployDirectory, {
|
|
1291
2034
|
nativeDependencies: {
|
|
1292
2035
|
"better-sqlite3": "^11.0.0",
|
|
1293
|
-
"drizzle-orm": "^0.45.2"
|
|
2036
|
+
"drizzle-orm": "^0.45.2",
|
|
2037
|
+
"postgres": "^3.4.0"
|
|
1294
2038
|
}
|
|
1295
2039
|
});
|
|
1296
2040
|
await writeDockerIgnoreArtifact(deployDirectory);
|
|
@@ -1357,6 +2101,14 @@ async function resolvePlatform(options) {
|
|
|
1357
2101
|
label: "Railway",
|
|
1358
2102
|
value: "railway"
|
|
1359
2103
|
},
|
|
2104
|
+
{
|
|
2105
|
+
label: "AWS ECS Fargate (production multi-instance)",
|
|
2106
|
+
value: "aws-ecs"
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
label: "AWS Lightsail Containers (simple, cost-effective)",
|
|
2110
|
+
value: "aws-lightsail"
|
|
2111
|
+
},
|
|
1360
2112
|
{
|
|
1361
2113
|
label: "Render",
|
|
1362
2114
|
value: "render"
|
|
@@ -1521,10 +2273,10 @@ function toPascalCase(name) {
|
|
|
1521
2273
|
export {
|
|
1522
2274
|
DEPLOY_PLATFORMS,
|
|
1523
2275
|
isDeployPlatform,
|
|
1524
|
-
generateFlyToml,
|
|
1525
|
-
writeFlyTomlArtifact,
|
|
1526
2276
|
buildClient,
|
|
1527
2277
|
bundleServer,
|
|
2278
|
+
generateFlyToml,
|
|
2279
|
+
writeFlyTomlArtifact,
|
|
1528
2280
|
FlyAdapter,
|
|
1529
2281
|
NodeFlyCommandRunner,
|
|
1530
2282
|
generateRailwayJson,
|
|
@@ -1547,4 +2299,4 @@ export {
|
|
|
1547
2299
|
deployCommand,
|
|
1548
2300
|
generateTypes
|
|
1549
2301
|
};
|
|
1550
|
-
//# sourceMappingURL=chunk-
|
|
2302
|
+
//# sourceMappingURL=chunk-E7OCVRYL.js.map
|