@h-ai/cli 0.1.0-alpha.55 → 0.1.0-alpha.57
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/README.en.md +205 -0
- package/README.ja.md +205 -0
- package/README.md +16 -0
- package/dist/chunk-MJGUBOAV.js +391 -0
- package/dist/chunk-MJGUBOAV.js.map +1 -0
- package/dist/{chunk-FVDXJYMO.js → chunk-O3ANVICQ.js} +31 -2
- package/dist/chunk-O3ANVICQ.js.map +1 -0
- package/dist/cli-config-templates-OWBEGBZQ.js +5 -0
- package/dist/{cli-config-templates-WGN2I23J.js.map → cli-config-templates-OWBEGBZQ.js.map} +1 -1
- package/dist/{cli-skill-templates-ZIWYMS4Y.js → cli-skill-templates-ZYXES3NT.js} +3 -3
- package/dist/{cli-skill-templates-ZIWYMS4Y.js.map → cli-skill-templates-ZYXES3NT.js.map} +1 -1
- package/dist/index.js +35 -72
- package/dist/index.js.map +1 -1
- package/dist/{src-6ZBWDBUB.js → src-TBIET7YU.js} +908 -127
- package/dist/src-TBIET7YU.js.map +1 -0
- package/package.json +7 -7
- package/templates/apps/api/.gitignore.hbs +13 -0
- package/templates/apps/api/.npmrc.hbs +2 -0
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +1 -1
- package/templates/apps/api/{eslint.config.js → eslint.config.js.hbs} +6 -8
- package/templates/apps/api/package.json.hbs +1 -1
- package/templates/apps/fullstack/.gitignore.hbs +16 -0
- package/templates/apps/fullstack/.npmrc.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +5 -8
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +1 -1
- package/templates/apps/fullstack/{eslint.config.js → eslint.config.js.hbs} +6 -8
- package/templates/apps/fullstack/package.json.hbs +1 -1
- package/templates/base/.gitignore.hbs +3 -0
- package/templates/base/.npmrc.hbs +15 -0
- package/templates/base/eslint-rules.js +45 -0
- package/templates/base/eslint.config.js.hbs +6 -8
- package/templates/base/package.json.hbs +1 -1
- package/templates/skills/AGENTS.md +9 -8
- package/templates/skills/CLAUDE.md +4 -0
- package/templates/skills/bridges/admin/AGENTS.md +8 -0
- package/templates/skills/bridges/admin/CLAUDE.md +4 -0
- package/templates/skills/bridges/admin/copilot-instructions.md +2 -0
- package/templates/skills/bridges/api/AGENTS.md +8 -1
- package/templates/skills/bridges/api/CLAUDE.md +4 -0
- package/templates/skills/bridges/api/copilot-instructions.md +2 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +8 -2
- package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +4 -0
- package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +2 -0
- package/templates/skills/bridges/h5/AGENTS.md +8 -0
- package/templates/skills/bridges/h5/CLAUDE.md +4 -0
- package/templates/skills/bridges/h5/copilot-instructions.md +2 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +8 -0
- package/templates/skills/bridges/mobile-app/CLAUDE.md +4 -0
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -0
- package/templates/skills/bridges/website/AGENTS.md +8 -0
- package/templates/skills/bridges/website/CLAUDE.md +4 -0
- package/templates/skills/bridges/website/copilot-instructions.md +2 -0
- package/templates/skills/claude-skill.md.hbs +7 -0
- package/templates/skills/copilot-instructions.md +2 -0
- package/templates/skills/hai-api-client/SKILL.md +2 -0
- package/templates/skills/hai-app-create/SKILL.md +9 -7
- package/templates/skills/hai-app-create/development.md +66 -0
- package/templates/skills/hai-app-create/reference.md +44 -32
- package/templates/skills/hai-app-review/SKILL.md +50 -144
- package/templates/skills/hai-app-tests/SKILL.md +3 -1
- package/templates/skills/hai-app-tests/reference.md +47 -27
- package/templates/skills/hai-build/SKILL.md +5 -4
- package/templates/skills/hai-build/reference.md +52 -32
- package/templates/skills/hai-ci/SKILL.md +8 -15
- package/templates/skills/hai-deploy/SKILL.md +63 -7
- package/templates/skills/hai-framework-sync/SKILL.md +8 -1
- package/templates/skills/hai-pr-review/SKILL.md +5 -4
- package/templates/skills/hai-reldb/SKILL.md +1 -0
- package/templates/skills/hai-reldb/reference.md +3 -0
- package/templates/skills/hai-scheduler/SKILL.md +3 -0
- package/templates/skills/hai-serv/SKILL.md +1 -0
- package/templates/skills/hai-serv/reference.md +16 -2
- package/templates/skills/hai-storage/SKILL.md +3 -1
- package/templates/skills/hai-ui/reference.md +1 -1
- package/dist/chunk-FVDXJYMO.js.map +0 -1
- package/dist/chunk-WUYLYCW2.js +0 -237
- package/dist/chunk-WUYLYCW2.js.map +0 -1
- package/dist/cli-config-templates-WGN2I23J.js +0 -5
- package/dist/src-6ZBWDBUB.js.map +0 -1
|
@@ -6,8 +6,8 @@ import { homedir } from 'os';
|
|
|
6
6
|
import { join, dirname, relative, sep } from 'path';
|
|
7
7
|
import process2 from 'process';
|
|
8
8
|
import { parse as parse$1, stringify } from 'yaml';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
import { createHash } from 'crypto';
|
|
9
|
+
import { execSync, spawn } from 'child_process';
|
|
10
|
+
import { randomBytes, createHash } from 'crypto';
|
|
11
11
|
import { Buffer } from 'buffer';
|
|
12
12
|
|
|
13
13
|
// ../../node_modules/zod/v4/classic/external.js
|
|
@@ -14544,7 +14544,15 @@ var en_US_default = {
|
|
|
14544
14544
|
deploy_fileUploadFailed: "File upload failed ({status})",
|
|
14545
14545
|
deploy_provisionNoResult: "No connection info returned from {service}",
|
|
14546
14546
|
deploy_recoveryFailure: "Deployment stopped at {stage}; inspect the recovery details.",
|
|
14547
|
-
deploy_recoverySuggestion: "Inspect listed resources in the provider console before retrying with the same project name. Unknown means the request may have created a resource. Reused resources are never deleted automatically; remove newly created resources manually only after checking their use."
|
|
14547
|
+
deploy_recoverySuggestion: "Inspect listed resources in the provider console before retrying with the same project name. Unknown means the request may have created a resource. Reused resources are never deleted automatically; remove newly created resources manually only after checking their use.",
|
|
14548
|
+
deploy_commandTimeout: "Command timed out: {command}",
|
|
14549
|
+
deploy_commandFailed: "Command failed ({command}): {error}",
|
|
14550
|
+
deploy_containerRuntimeNotFound: "No usable container runtime found: {runtime}",
|
|
14551
|
+
deploy_containerBuildFailed: "Container image build failed: {error}",
|
|
14552
|
+
deploy_remoteConnectFailed: "Cannot connect to remote host {host}: {error}",
|
|
14553
|
+
deploy_remoteRuntimeMissing: "Remote host missing container runtime or compose: {runtime}",
|
|
14554
|
+
deploy_imageTransferFailed: "Image transfer failed: {error}",
|
|
14555
|
+
deploy_healthCheckFailed: "Health check failed, app not ready at {url}"
|
|
14548
14556
|
};
|
|
14549
14557
|
|
|
14550
14558
|
// ../deploy/messages/zh-CN.json
|
|
@@ -14574,7 +14582,15 @@ var zh_CN_default2 = {
|
|
|
14574
14582
|
deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09",
|
|
14575
14583
|
deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F",
|
|
14576
14584
|
deploy_recoveryFailure: "\u90E8\u7F72\u5728 {stage} \u9636\u6BB5\u505C\u6B62\uFF0C\u8BF7\u67E5\u770B\u6062\u590D\u6E05\u5355\u3002",
|
|
14577
|
-
deploy_recoverySuggestion: "\u91CD\u8BD5\u524D\u5728\u670D\u52A1\u5546\u63A7\u5236\u53F0\u6838\u5BF9\u6E05\u5355\uFF0C\u5E76\u4F7F\u7528\u76F8\u540C\u9879\u76EE\u540D\u3002unknown \u8868\u793A\u8BF7\u6C42\u53EF\u80FD\u5DF2\u521B\u5EFA\u8D44\u6E90\u3002\u590D\u7528\u8D44\u6E90\u4E0D\u4F1A\u81EA\u52A8\u5220\u9664\uFF1B\u65B0\u5EFA\u8D44\u6E90\u4EC5\u5728\u786E\u8BA4\u672A\u88AB\u4F7F\u7528\u540E\u4EBA\u5DE5\u6E05\u7406\u3002"
|
|
14585
|
+
deploy_recoverySuggestion: "\u91CD\u8BD5\u524D\u5728\u670D\u52A1\u5546\u63A7\u5236\u53F0\u6838\u5BF9\u6E05\u5355\uFF0C\u5E76\u4F7F\u7528\u76F8\u540C\u9879\u76EE\u540D\u3002unknown \u8868\u793A\u8BF7\u6C42\u53EF\u80FD\u5DF2\u521B\u5EFA\u8D44\u6E90\u3002\u590D\u7528\u8D44\u6E90\u4E0D\u4F1A\u81EA\u52A8\u5220\u9664\uFF1B\u65B0\u5EFA\u8D44\u6E90\u4EC5\u5728\u786E\u8BA4\u672A\u88AB\u4F7F\u7528\u540E\u4EBA\u5DE5\u6E05\u7406\u3002",
|
|
14586
|
+
deploy_commandTimeout: "\u547D\u4EE4\u6267\u884C\u8D85\u65F6\uFF1A{command}",
|
|
14587
|
+
deploy_commandFailed: "\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF08{command}\uFF09\uFF1A{error}",
|
|
14588
|
+
deploy_containerRuntimeNotFound: "\u672A\u627E\u5230\u53EF\u7528\u7684\u5BB9\u5668\u8FD0\u884C\u65F6\uFF1A{runtime}",
|
|
14589
|
+
deploy_containerBuildFailed: "\u5BB9\u5668\u955C\u50CF\u6784\u5EFA\u5931\u8D25\uFF1A{error}",
|
|
14590
|
+
deploy_remoteConnectFailed: "\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u4E3B\u673A {host}\uFF1A{error}",
|
|
14591
|
+
deploy_remoteRuntimeMissing: "\u8FDC\u7A0B\u4E3B\u673A\u7F3A\u5C11\u5BB9\u5668\u8FD0\u884C\u65F6\u6216 compose\uFF1A{runtime}",
|
|
14592
|
+
deploy_imageTransferFailed: "\u955C\u50CF\u4F20\u8F93\u5931\u8D25\uFF1A{error}",
|
|
14593
|
+
deploy_healthCheckFailed: "\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF0C\u5E94\u7528\u672A\u5728 {url} \u5C31\u7EEA"
|
|
14578
14594
|
};
|
|
14579
14595
|
|
|
14580
14596
|
// ../deploy/src/deploy-i18n.ts
|
|
@@ -14588,8 +14604,47 @@ var VercelProviderConfigSchema = external_exports.object({
|
|
|
14588
14604
|
type: external_exports.literal("vercel"),
|
|
14589
14605
|
token: external_exports.string().min(1, deployM("deploy_configTokenRequired"))
|
|
14590
14606
|
});
|
|
14607
|
+
var SshConfigSchema = external_exports.object({
|
|
14608
|
+
/** 远程主机地址或 ~/.ssh/config 中的 Host 别名 */
|
|
14609
|
+
host: external_exports.string().min(1),
|
|
14610
|
+
/** SSH 端口 */
|
|
14611
|
+
port: external_exports.number().int().min(1).max(65535).default(22),
|
|
14612
|
+
/** SSH 用户名 */
|
|
14613
|
+
username: external_exports.string().min(1),
|
|
14614
|
+
/** 私钥文件路径(省略时使用 ssh-agent 或 ~/.ssh 默认密钥) */
|
|
14615
|
+
identityFile: external_exports.string().min(1).optional(),
|
|
14616
|
+
/** known_hosts 文件路径(省略时使用默认;配合 CI/临时环境隔离主机指纹) */
|
|
14617
|
+
knownHostsFile: external_exports.string().min(1).optional(),
|
|
14618
|
+
/** 严格主机密钥校验,默认开启(true→accept-new 首次信任并拒绝变更;false→关闭校验) */
|
|
14619
|
+
strictHostKeyChecking: external_exports.boolean().default(true),
|
|
14620
|
+
/** 连接超时(秒) */
|
|
14621
|
+
connectTimeoutSeconds: external_exports.number().int().positive().default(10)
|
|
14622
|
+
});
|
|
14623
|
+
var PortExposeSchema = external_exports.object({
|
|
14624
|
+
type: external_exports.literal("port").default("port"),
|
|
14625
|
+
/** 容器内监听端口(adapter-node 默认 3000) */
|
|
14626
|
+
containerPort: external_exports.number().int().positive().default(3e3),
|
|
14627
|
+
/** 宿主机映射端口 */
|
|
14628
|
+
hostPort: external_exports.number().int().positive()
|
|
14629
|
+
});
|
|
14630
|
+
var ExposeSchema = PortExposeSchema;
|
|
14631
|
+
var DockerSshProviderConfigSchema = external_exports.object({
|
|
14632
|
+
type: external_exports.literal("docker-ssh"),
|
|
14633
|
+
/** SSH 连接参数 */
|
|
14634
|
+
ssh: SshConfigSchema,
|
|
14635
|
+
/** 远程主机运行参数 */
|
|
14636
|
+
remote: external_exports.object({
|
|
14637
|
+
/** 远程应用根目录 */
|
|
14638
|
+
baseDir: external_exports.string().min(1).default("/opt/hai/apps"),
|
|
14639
|
+
/** 远程容器运行时(auto 优先 podman) */
|
|
14640
|
+
runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto")
|
|
14641
|
+
}).default({ baseDir: "/opt/hai/apps", runtime: "auto" }),
|
|
14642
|
+
/** 应用暴露方式 */
|
|
14643
|
+
expose: ExposeSchema
|
|
14644
|
+
});
|
|
14591
14645
|
var ProviderConfigSchema = external_exports.discriminatedUnion("type", [
|
|
14592
|
-
VercelProviderConfigSchema
|
|
14646
|
+
VercelProviderConfigSchema,
|
|
14647
|
+
DockerSshProviderConfigSchema
|
|
14593
14648
|
]);
|
|
14594
14649
|
var NeonServiceSchema = external_exports.object({
|
|
14595
14650
|
provisioner: external_exports.literal("neon"),
|
|
@@ -14627,7 +14682,11 @@ var ServicesConfigSchema = external_exports.object({
|
|
|
14627
14682
|
}).optional();
|
|
14628
14683
|
var DeployConfigSchema = external_exports.object({
|
|
14629
14684
|
provider: ProviderConfigSchema,
|
|
14630
|
-
services: ServicesConfigSchema
|
|
14685
|
+
services: ServicesConfigSchema,
|
|
14686
|
+
/** 本地容器构建参数(docker-ssh 使用;auto 优先 podman) */
|
|
14687
|
+
container: external_exports.object({
|
|
14688
|
+
runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto")
|
|
14689
|
+
}).optional()
|
|
14631
14690
|
});
|
|
14632
14691
|
var DeployErrorInfo = {
|
|
14633
14692
|
DEPLOY_FAILED: "001:500",
|
|
@@ -14643,7 +14702,13 @@ var DeployErrorInfo = {
|
|
|
14643
14702
|
ENV_VAR_FAILED: "011:500",
|
|
14644
14703
|
UNSUPPORTED_TYPE: "012:400",
|
|
14645
14704
|
CONFIG_ERROR: "013:500",
|
|
14646
|
-
CREDENTIAL_ERROR: "014:500"
|
|
14705
|
+
CREDENTIAL_ERROR: "014:500",
|
|
14706
|
+
CONTAINER_RUNTIME_NOT_FOUND: "015:500",
|
|
14707
|
+
CONTAINER_BUILD_FAILED: "016:500",
|
|
14708
|
+
REMOTE_CONNECT_FAILED: "017:500",
|
|
14709
|
+
REMOTE_COMMAND_FAILED: "018:500",
|
|
14710
|
+
IMAGE_TRANSFER_FAILED: "019:500",
|
|
14711
|
+
HEALTH_CHECK_FAILED: "020:500"
|
|
14647
14712
|
};
|
|
14648
14713
|
var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
|
|
14649
14714
|
|
|
@@ -14861,55 +14926,61 @@ function hasDependency(pkg, name) {
|
|
|
14861
14926
|
}
|
|
14862
14927
|
function detectRequiredServices(configDir) {
|
|
14863
14928
|
const services = [];
|
|
14929
|
+
const backends = {};
|
|
14864
14930
|
if (!existsSync(configDir)) {
|
|
14865
|
-
return services;
|
|
14931
|
+
return { services, backends };
|
|
14866
14932
|
}
|
|
14867
14933
|
const files = readdirSync(configDir);
|
|
14868
|
-
|
|
14869
|
-
|
|
14934
|
+
const dbFile = files.find((f) => f.startsWith("_db."));
|
|
14935
|
+
if (dbFile) {
|
|
14936
|
+
const dbConfig = readYaml(join(configDir, dbFile));
|
|
14870
14937
|
if (dbConfig !== null) {
|
|
14871
14938
|
services.push("db");
|
|
14939
|
+
backends.db = dbConfig.type ?? "postgresql";
|
|
14872
14940
|
}
|
|
14873
14941
|
}
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
}
|
|
14942
|
+
const cacheFile = files.find((f) => f.startsWith("_cache."));
|
|
14943
|
+
if (cacheFile) {
|
|
14944
|
+
const cacheConfig = readYaml(join(configDir, cacheFile));
|
|
14945
|
+
if (cacheConfig !== null && cacheConfig.type === "redis") {
|
|
14946
|
+
services.push("cache");
|
|
14947
|
+
backends.cache = "redis";
|
|
14881
14948
|
}
|
|
14882
14949
|
}
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14950
|
+
const storageFile = files.find((f) => f.startsWith("_storage."));
|
|
14951
|
+
if (storageFile) {
|
|
14952
|
+
const storageConfig = readYaml(join(configDir, storageFile));
|
|
14953
|
+
if (storageConfig !== null && detectStorageIsS3(storageConfig)) {
|
|
14954
|
+
services.push("storage");
|
|
14955
|
+
backends.storage = "s3";
|
|
14956
|
+
}
|
|
14957
|
+
}
|
|
14958
|
+
const vecdbFile = files.find((f) => f.startsWith("_vecdb."));
|
|
14959
|
+
if (vecdbFile) {
|
|
14960
|
+
const vecdbConfig = readYaml(join(configDir, vecdbFile));
|
|
14961
|
+
if (vecdbConfig !== null) {
|
|
14962
|
+
services.push("vecdb");
|
|
14963
|
+
backends.vecdb = vecdbConfig.type ?? "qdrant";
|
|
14887
14964
|
}
|
|
14888
14965
|
}
|
|
14889
|
-
|
|
14890
|
-
|
|
14966
|
+
const reachFile = files.find((f) => f.startsWith("_reach."));
|
|
14967
|
+
if (reachFile) {
|
|
14968
|
+
const reachConfig = readYaml(join(configDir, reachFile));
|
|
14891
14969
|
if (reachConfig !== null) {
|
|
14892
14970
|
detectReachServices(reachConfig, services);
|
|
14893
14971
|
}
|
|
14894
14972
|
}
|
|
14895
|
-
return services;
|
|
14973
|
+
return { services, backends };
|
|
14896
14974
|
}
|
|
14897
|
-
function
|
|
14975
|
+
function detectStorageIsS3(config2) {
|
|
14898
14976
|
if (config2.type === "s3") {
|
|
14899
|
-
|
|
14900
|
-
return;
|
|
14977
|
+
return true;
|
|
14901
14978
|
}
|
|
14902
14979
|
const providers = config2.providers;
|
|
14903
14980
|
if (providers === void 0) {
|
|
14904
|
-
return;
|
|
14905
|
-
}
|
|
14906
|
-
for (const providerConfig of Object.values(providers)) {
|
|
14907
|
-
const typed = providerConfig;
|
|
14908
|
-
if (typed?.type === "s3") {
|
|
14909
|
-
services.push("storage");
|
|
14910
|
-
return;
|
|
14911
|
-
}
|
|
14981
|
+
return false;
|
|
14912
14982
|
}
|
|
14983
|
+
return Object.values(providers).some((p) => p?.type === "s3");
|
|
14913
14984
|
}
|
|
14914
14985
|
function detectReachServices(config2, services) {
|
|
14915
14986
|
const providers = config2.providers;
|
|
@@ -14943,24 +15014,29 @@ async function scanApp(appDir) {
|
|
|
14943
15014
|
const appName = extractAppName(pkg);
|
|
14944
15015
|
const isSvelteKit = existsSync(join(appDir, "svelte.config.js")) || existsSync(join(appDir, "svelte.config.ts"));
|
|
14945
15016
|
const adapterInstalled = hasDependency(pkg, "@sveltejs/adapter-vercel");
|
|
15017
|
+
const nodeAdapterInstalled = hasDependency(pkg, "@sveltejs/adapter-node");
|
|
14946
15018
|
const configDir = join(appDir, "config");
|
|
14947
|
-
const
|
|
15019
|
+
const configDetection = detectRequiredServices(configDir);
|
|
14948
15020
|
const depServices = detectServicesFromDependencies(pkg);
|
|
14949
|
-
const requiredServices = Array.from(/* @__PURE__ */ new Set([...
|
|
15021
|
+
const requiredServices = Array.from(/* @__PURE__ */ new Set([...configDetection.services, ...depServices]));
|
|
14950
15022
|
const scripts = pkg.scripts;
|
|
14951
15023
|
const buildCommand = scripts?.build ?? "pnpm build";
|
|
14952
15024
|
const scanResult = {
|
|
14953
15025
|
appName,
|
|
14954
15026
|
isSvelteKit,
|
|
14955
15027
|
adapterInstalled,
|
|
15028
|
+
nodeAdapterInstalled,
|
|
14956
15029
|
requiredServices,
|
|
15030
|
+
serviceBackends: configDetection.backends,
|
|
14957
15031
|
buildCommand
|
|
14958
15032
|
};
|
|
14959
15033
|
logger3.info("Application scanned", {
|
|
14960
15034
|
appName,
|
|
14961
15035
|
isSvelteKit,
|
|
14962
15036
|
adapterInstalled,
|
|
14963
|
-
|
|
15037
|
+
nodeAdapterInstalled,
|
|
15038
|
+
requiredServices,
|
|
15039
|
+
serviceBackends: configDetection.backends
|
|
14964
15040
|
});
|
|
14965
15041
|
return ok(scanResult);
|
|
14966
15042
|
} catch (error51) {
|
|
@@ -14978,7 +15054,8 @@ function detectServicesFromDependencies(pkg) {
|
|
|
14978
15054
|
const depMap = {
|
|
14979
15055
|
"@h-ai/reldb": "db",
|
|
14980
15056
|
"@h-ai/cache": "cache",
|
|
14981
|
-
"@h-ai/storage": "storage"
|
|
15057
|
+
"@h-ai/storage": "storage",
|
|
15058
|
+
"@h-ai/vecdb": "vecdb"
|
|
14982
15059
|
};
|
|
14983
15060
|
const services = [];
|
|
14984
15061
|
for (const [depName, serviceType] of Object.entries(depMap)) {
|
|
@@ -14996,7 +15073,667 @@ function extractAppName(pkg) {
|
|
|
14996
15073
|
}
|
|
14997
15074
|
return name;
|
|
14998
15075
|
}
|
|
14999
|
-
var
|
|
15076
|
+
var IMAGES = {
|
|
15077
|
+
postgres: "postgres:16-alpine",
|
|
15078
|
+
mysql: "mysql:8.4",
|
|
15079
|
+
redis: "redis:7-alpine",
|
|
15080
|
+
minio: "minio/minio:RELEASE.2025-09-07T16-13-09Z",
|
|
15081
|
+
mc: "minio/mc:RELEASE.2025-08-13T08-35-41Z",
|
|
15082
|
+
qdrant: "qdrant/qdrant:v1.12.4",
|
|
15083
|
+
pgvector: "pgvector/pgvector:pg17",
|
|
15084
|
+
chroma: "chromadb/chroma:0.5.23"
|
|
15085
|
+
};
|
|
15086
|
+
function generatePassword() {
|
|
15087
|
+
return randomBytes(24).toString("base64url");
|
|
15088
|
+
}
|
|
15089
|
+
function resolveSidecarSecrets(appDir, projectName) {
|
|
15090
|
+
const secretFile = join(appDir, ".hai", "deploy", "runtime", `${projectName}.secrets.json`);
|
|
15091
|
+
if (existsSync(secretFile)) {
|
|
15092
|
+
try {
|
|
15093
|
+
return JSON.parse(readFileSync(secretFile, "utf-8"));
|
|
15094
|
+
} catch {
|
|
15095
|
+
}
|
|
15096
|
+
}
|
|
15097
|
+
const secrets = {
|
|
15098
|
+
dbPassword: generatePassword(),
|
|
15099
|
+
storagePassword: generatePassword()
|
|
15100
|
+
};
|
|
15101
|
+
mkdirSync(dirname(secretFile), { recursive: true });
|
|
15102
|
+
writeFileSync(secretFile, JSON.stringify(secrets), { mode: 384 });
|
|
15103
|
+
return secrets;
|
|
15104
|
+
}
|
|
15105
|
+
function addAppDataVolume(plan, projectName) {
|
|
15106
|
+
const mount = `${projectName}-data:/app/data`;
|
|
15107
|
+
if (!plan.appVolumes.includes(mount)) {
|
|
15108
|
+
plan.appVolumes.push(mount);
|
|
15109
|
+
plan.volumes[`${projectName}-data`] = null;
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
function planPostgresDb(plan, projectName, password, net) {
|
|
15113
|
+
plan.services.push("db");
|
|
15114
|
+
plan.dependsOn.push("db");
|
|
15115
|
+
plan.composeServices.db = {
|
|
15116
|
+
image: IMAGES.postgres,
|
|
15117
|
+
container_name: `hai-${projectName}-db`,
|
|
15118
|
+
restart: "unless-stopped",
|
|
15119
|
+
env_file: [".env.runtime"],
|
|
15120
|
+
volumes: [`${projectName}-db-data:/var/lib/postgresql/data`],
|
|
15121
|
+
networks: net
|
|
15122
|
+
};
|
|
15123
|
+
plan.volumes[`${projectName}-db-data`] = null;
|
|
15124
|
+
plan.env.POSTGRES_USER = "hai";
|
|
15125
|
+
plan.env.POSTGRES_PASSWORD = password;
|
|
15126
|
+
plan.env.POSTGRES_DB = "hai";
|
|
15127
|
+
plan.env.HAI_DB = JSON.stringify({ type: "postgresql", url: `postgresql://hai:${password}@db:5432/hai`, database: "hai" });
|
|
15128
|
+
plan.secrets.push(password);
|
|
15129
|
+
}
|
|
15130
|
+
function planMysqlDb(plan, projectName, password, net) {
|
|
15131
|
+
plan.services.push("db");
|
|
15132
|
+
plan.dependsOn.push("db");
|
|
15133
|
+
plan.composeServices.db = {
|
|
15134
|
+
image: IMAGES.mysql,
|
|
15135
|
+
container_name: `hai-${projectName}-db`,
|
|
15136
|
+
restart: "unless-stopped",
|
|
15137
|
+
env_file: [".env.runtime"],
|
|
15138
|
+
volumes: [`${projectName}-db-data:/var/lib/mysql`],
|
|
15139
|
+
networks: net
|
|
15140
|
+
};
|
|
15141
|
+
plan.volumes[`${projectName}-db-data`] = null;
|
|
15142
|
+
plan.env.MYSQL_ROOT_PASSWORD = password;
|
|
15143
|
+
plan.env.MYSQL_DATABASE = "hai";
|
|
15144
|
+
plan.env.MYSQL_USER = "hai";
|
|
15145
|
+
plan.env.MYSQL_PASSWORD = password;
|
|
15146
|
+
plan.env.HAI_DB = JSON.stringify({ type: "mysql", url: `mysql://hai:${password}@db:3306/hai`, database: "hai" });
|
|
15147
|
+
plan.secrets.push(password);
|
|
15148
|
+
}
|
|
15149
|
+
function planRedisCache(plan, projectName, net) {
|
|
15150
|
+
plan.services.push("cache");
|
|
15151
|
+
plan.dependsOn.push("cache");
|
|
15152
|
+
plan.composeServices.cache = {
|
|
15153
|
+
image: IMAGES.redis,
|
|
15154
|
+
container_name: `hai-${projectName}-cache`,
|
|
15155
|
+
restart: "unless-stopped",
|
|
15156
|
+
networks: net
|
|
15157
|
+
};
|
|
15158
|
+
plan.env.HAI_CACHE = JSON.stringify({ type: "redis", url: "redis://cache:6379" });
|
|
15159
|
+
}
|
|
15160
|
+
function planMinioStorage(plan, projectName, password, net) {
|
|
15161
|
+
plan.services.push("storage");
|
|
15162
|
+
plan.dependsOn.push("storage");
|
|
15163
|
+
plan.composeServices.storage = {
|
|
15164
|
+
image: IMAGES.minio,
|
|
15165
|
+
container_name: `hai-${projectName}-storage`,
|
|
15166
|
+
restart: "unless-stopped",
|
|
15167
|
+
command: ["server", "/data"],
|
|
15168
|
+
env_file: [".env.runtime"],
|
|
15169
|
+
volumes: [`${projectName}-storage-data:/data`],
|
|
15170
|
+
networks: net
|
|
15171
|
+
};
|
|
15172
|
+
plan.composeServices["storage-init"] = {
|
|
15173
|
+
image: IMAGES.mc,
|
|
15174
|
+
depends_on: ["storage"],
|
|
15175
|
+
env_file: [".env.runtime"],
|
|
15176
|
+
entrypoint: [
|
|
15177
|
+
"sh",
|
|
15178
|
+
"-c",
|
|
15179
|
+
'until mc alias set s3 http://storage:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; do sleep 1; done; mc mb -p s3/hai; exit 0'
|
|
15180
|
+
],
|
|
15181
|
+
networks: net
|
|
15182
|
+
};
|
|
15183
|
+
plan.volumes[`${projectName}-storage-data`] = null;
|
|
15184
|
+
plan.env.MINIO_ROOT_USER = "hai";
|
|
15185
|
+
plan.env.MINIO_ROOT_PASSWORD = password;
|
|
15186
|
+
plan.env.HAI_STORAGE = JSON.stringify({
|
|
15187
|
+
type: "s3",
|
|
15188
|
+
endpoint: "http://storage:9000",
|
|
15189
|
+
bucket: "hai",
|
|
15190
|
+
region: "us-east-1",
|
|
15191
|
+
accessKeyId: "hai",
|
|
15192
|
+
secretAccessKey: password,
|
|
15193
|
+
forcePathStyle: true
|
|
15194
|
+
});
|
|
15195
|
+
plan.secrets.push(password);
|
|
15196
|
+
}
|
|
15197
|
+
function planQdrantVecdb(plan, projectName, net) {
|
|
15198
|
+
plan.services.push("vecdb");
|
|
15199
|
+
plan.dependsOn.push("vecdb");
|
|
15200
|
+
plan.composeServices.vecdb = {
|
|
15201
|
+
image: IMAGES.qdrant,
|
|
15202
|
+
container_name: `hai-${projectName}-vecdb`,
|
|
15203
|
+
restart: "unless-stopped",
|
|
15204
|
+
volumes: [`${projectName}-vecdb-data:/qdrant/storage`],
|
|
15205
|
+
networks: net
|
|
15206
|
+
};
|
|
15207
|
+
plan.volumes[`${projectName}-vecdb-data`] = null;
|
|
15208
|
+
plan.env.HAI_VECDB = JSON.stringify({ type: "qdrant", url: "http://vecdb:6333" });
|
|
15209
|
+
}
|
|
15210
|
+
function planPgvectorVecdb(plan, projectName, password, net) {
|
|
15211
|
+
plan.services.push("vecdb");
|
|
15212
|
+
plan.dependsOn.push("vecdb");
|
|
15213
|
+
plan.composeServices.vecdb = {
|
|
15214
|
+
image: IMAGES.pgvector,
|
|
15215
|
+
container_name: `hai-${projectName}-vecdb`,
|
|
15216
|
+
restart: "unless-stopped",
|
|
15217
|
+
env_file: [".env.runtime"],
|
|
15218
|
+
volumes: [`${projectName}-vecdb-data:/var/lib/postgresql/data`],
|
|
15219
|
+
networks: net
|
|
15220
|
+
};
|
|
15221
|
+
plan.volumes[`${projectName}-vecdb-data`] = null;
|
|
15222
|
+
plan.env.POSTGRES_USER = "hai";
|
|
15223
|
+
plan.env.POSTGRES_PASSWORD = password;
|
|
15224
|
+
plan.env.POSTGRES_DB = "hai";
|
|
15225
|
+
plan.env.HAI_VECDB = JSON.stringify({ type: "pgvector", url: `postgresql://hai:${password}@vecdb:5432/hai`, database: "hai" });
|
|
15226
|
+
plan.secrets.push(password);
|
|
15227
|
+
}
|
|
15228
|
+
function planChromaVecdb(plan, projectName, net) {
|
|
15229
|
+
plan.services.push("vecdb");
|
|
15230
|
+
plan.dependsOn.push("vecdb");
|
|
15231
|
+
plan.composeServices.vecdb = {
|
|
15232
|
+
image: IMAGES.chroma,
|
|
15233
|
+
container_name: `hai-${projectName}-vecdb`,
|
|
15234
|
+
restart: "unless-stopped",
|
|
15235
|
+
volumes: [`${projectName}-vecdb-data:/data`],
|
|
15236
|
+
networks: net
|
|
15237
|
+
};
|
|
15238
|
+
plan.volumes[`${projectName}-vecdb-data`] = null;
|
|
15239
|
+
plan.env.HAI_VECDB = JSON.stringify({ type: "chroma", url: "http://vecdb:8000" });
|
|
15240
|
+
}
|
|
15241
|
+
function planSidecars(appDir, projectName, scan, provisionEnv) {
|
|
15242
|
+
const plan = {
|
|
15243
|
+
services: [],
|
|
15244
|
+
composeServices: {},
|
|
15245
|
+
volumes: {},
|
|
15246
|
+
appVolumes: [],
|
|
15247
|
+
env: {},
|
|
15248
|
+
dependsOn: [],
|
|
15249
|
+
secrets: []
|
|
15250
|
+
};
|
|
15251
|
+
const secrets = resolveSidecarSecrets(appDir, projectName);
|
|
15252
|
+
const net = ["hai-app"];
|
|
15253
|
+
const backends = scan.serviceBackends;
|
|
15254
|
+
for (const service of scan.requiredServices) {
|
|
15255
|
+
if (service === "db" && !provisionEnv.HAI_DB) {
|
|
15256
|
+
const backend = backends.db ?? "postgresql";
|
|
15257
|
+
if (backend === "mysql")
|
|
15258
|
+
planMysqlDb(plan, projectName, secrets.dbPassword, net);
|
|
15259
|
+
else if (backend === "sqlite")
|
|
15260
|
+
addAppDataVolume(plan, projectName);
|
|
15261
|
+
else
|
|
15262
|
+
planPostgresDb(plan, projectName, secrets.dbPassword, net);
|
|
15263
|
+
} else if (service === "cache" && !provisionEnv.HAI_CACHE) {
|
|
15264
|
+
planRedisCache(plan, projectName, net);
|
|
15265
|
+
} else if (service === "storage" && !provisionEnv.HAI_STORAGE) {
|
|
15266
|
+
planMinioStorage(plan, projectName, secrets.storagePassword, net);
|
|
15267
|
+
} else if (service === "vecdb" && !provisionEnv.HAI_VECDB) {
|
|
15268
|
+
const backend = backends.vecdb ?? "qdrant";
|
|
15269
|
+
if (backend === "pgvector")
|
|
15270
|
+
planPgvectorVecdb(plan, projectName, secrets.dbPassword, net);
|
|
15271
|
+
else if (backend === "chroma")
|
|
15272
|
+
planChromaVecdb(plan, projectName, net);
|
|
15273
|
+
else if (backend === "lancedb")
|
|
15274
|
+
addAppDataVolume(plan, projectName);
|
|
15275
|
+
else
|
|
15276
|
+
planQdrantVecdb(plan, projectName, net);
|
|
15277
|
+
}
|
|
15278
|
+
}
|
|
15279
|
+
return plan;
|
|
15280
|
+
}
|
|
15281
|
+
function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort) {
|
|
15282
|
+
const sidecar = planSidecars(appDir, projectName, scan, provisionEnv);
|
|
15283
|
+
const env = {
|
|
15284
|
+
NODE_ENV: "production",
|
|
15285
|
+
HOST: "0.0.0.0",
|
|
15286
|
+
PORT: String(containerPort),
|
|
15287
|
+
...sidecar.env,
|
|
15288
|
+
...provisionEnv
|
|
15289
|
+
};
|
|
15290
|
+
return { env, sidecar };
|
|
15291
|
+
}
|
|
15292
|
+
function generateComposeFile(options) {
|
|
15293
|
+
const { projectName, imageRef, hostPort, containerPort, sidecar } = options;
|
|
15294
|
+
const app = {
|
|
15295
|
+
image: imageRef,
|
|
15296
|
+
container_name: `hai-${projectName}-app`,
|
|
15297
|
+
restart: "unless-stopped",
|
|
15298
|
+
// 镜像已由 load 注入本地,禁止 compose 再从 registry 拉取
|
|
15299
|
+
pull_policy: "never",
|
|
15300
|
+
env_file: [".env.runtime"],
|
|
15301
|
+
ports: [`${hostPort}:${containerPort}`],
|
|
15302
|
+
networks: ["hai-app"]
|
|
15303
|
+
};
|
|
15304
|
+
if (sidecar.appVolumes.length > 0)
|
|
15305
|
+
app.volumes = sidecar.appVolumes;
|
|
15306
|
+
if (sidecar.dependsOn.length > 0)
|
|
15307
|
+
app.depends_on = sidecar.dependsOn;
|
|
15308
|
+
const compose = {
|
|
15309
|
+
services: {
|
|
15310
|
+
app,
|
|
15311
|
+
...sidecar.composeServices
|
|
15312
|
+
},
|
|
15313
|
+
networks: {
|
|
15314
|
+
"hai-app": { name: `hai-${projectName}` }
|
|
15315
|
+
}
|
|
15316
|
+
};
|
|
15317
|
+
if (Object.keys(sidecar.volumes).length > 0)
|
|
15318
|
+
compose.volumes = sidecar.volumes;
|
|
15319
|
+
return stringify(compose);
|
|
15320
|
+
}
|
|
15321
|
+
function serializeEnvFile(env) {
|
|
15322
|
+
return `${Object.entries(env).map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
15323
|
+
`;
|
|
15324
|
+
}
|
|
15325
|
+
function generateDockerignore() {
|
|
15326
|
+
return `**/node_modules
|
|
15327
|
+
**/dist
|
|
15328
|
+
**/build
|
|
15329
|
+
**/.turbo
|
|
15330
|
+
**/.svelte-kit
|
|
15331
|
+
**/coverage
|
|
15332
|
+
**/test-results
|
|
15333
|
+
**/playwright-report
|
|
15334
|
+
**/.hai/deploy
|
|
15335
|
+
.git
|
|
15336
|
+
`;
|
|
15337
|
+
}
|
|
15338
|
+
function generateFallbackDockerfile(packageName, containerPort) {
|
|
15339
|
+
return `# syntax=docker/dockerfile:1.7
|
|
15340
|
+
FROM node:22-bookworm-slim AS build
|
|
15341
|
+
ENV PNPM_HOME=/pnpm
|
|
15342
|
+
ENV PATH=$PNPM_HOME:$PATH
|
|
15343
|
+
ENV CI=true
|
|
15344
|
+
WORKDIR /workspace
|
|
15345
|
+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
15346
|
+
COPY . .
|
|
15347
|
+
RUN pnpm install --frozen-lockfile --ignore-scripts
|
|
15348
|
+
RUN pnpm --filter ${packageName} build
|
|
15349
|
+
RUN pnpm --filter ${packageName} --prod deploy /prod/app
|
|
15350
|
+
|
|
15351
|
+
FROM node:22-bookworm-slim AS runtime
|
|
15352
|
+
ENV NODE_ENV=production
|
|
15353
|
+
ENV HOST=0.0.0.0
|
|
15354
|
+
ENV PORT=${containerPort}
|
|
15355
|
+
WORKDIR /app
|
|
15356
|
+
COPY --from=build --chown=node:node /prod/app ./
|
|
15357
|
+
USER node
|
|
15358
|
+
EXPOSE ${containerPort}
|
|
15359
|
+
CMD ["node", "build"]
|
|
15360
|
+
`;
|
|
15361
|
+
}
|
|
15362
|
+
var logger4 = core.logger.child({ module: "deploy", scope: "command" });
|
|
15363
|
+
function maskSecrets(text, secrets) {
|
|
15364
|
+
if (!secrets || secrets.length === 0)
|
|
15365
|
+
return text;
|
|
15366
|
+
let masked = text;
|
|
15367
|
+
for (const secret of secrets) {
|
|
15368
|
+
if (secret && secret.length > 0)
|
|
15369
|
+
masked = masked.split(secret).join("***");
|
|
15370
|
+
}
|
|
15371
|
+
return masked;
|
|
15372
|
+
}
|
|
15373
|
+
function runCommand(command, args, options = {}) {
|
|
15374
|
+
return new Promise((resolve) => {
|
|
15375
|
+
const child = spawn(command, args, {
|
|
15376
|
+
cwd: options.cwd,
|
|
15377
|
+
env: options.env ? { ...process2.env, ...options.env } : process2.env,
|
|
15378
|
+
shell: false
|
|
15379
|
+
});
|
|
15380
|
+
let stdout = "";
|
|
15381
|
+
let stderr = "";
|
|
15382
|
+
let settled = false;
|
|
15383
|
+
let timer;
|
|
15384
|
+
const finish = (result) => {
|
|
15385
|
+
if (settled)
|
|
15386
|
+
return;
|
|
15387
|
+
settled = true;
|
|
15388
|
+
if (timer)
|
|
15389
|
+
clearTimeout(timer);
|
|
15390
|
+
resolve(result);
|
|
15391
|
+
};
|
|
15392
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
15393
|
+
timer = setTimeout(() => {
|
|
15394
|
+
child.kill("SIGKILL");
|
|
15395
|
+
logger4.error("Command timed out", { command, timeoutMs: options.timeoutMs });
|
|
15396
|
+
finish(err(
|
|
15397
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
15398
|
+
deployM("deploy_commandTimeout", { params: { command } })
|
|
15399
|
+
));
|
|
15400
|
+
}, options.timeoutMs);
|
|
15401
|
+
}
|
|
15402
|
+
child.stdout.on("data", (chunk) => {
|
|
15403
|
+
stdout += chunk.toString("utf-8");
|
|
15404
|
+
});
|
|
15405
|
+
child.stderr.on("data", (chunk) => {
|
|
15406
|
+
stderr += chunk.toString("utf-8");
|
|
15407
|
+
});
|
|
15408
|
+
child.on("error", (error51) => {
|
|
15409
|
+
logger4.error("Command failed to start", { command, error: maskSecrets(String(error51), options.secrets) });
|
|
15410
|
+
finish(err(
|
|
15411
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
15412
|
+
deployM("deploy_commandFailed", {
|
|
15413
|
+
params: { command, error: maskSecrets(error51.message, options.secrets) }
|
|
15414
|
+
}),
|
|
15415
|
+
error51
|
|
15416
|
+
));
|
|
15417
|
+
});
|
|
15418
|
+
child.on("close", (code) => {
|
|
15419
|
+
const exitCode = code ?? -1;
|
|
15420
|
+
if (exitCode === 0) {
|
|
15421
|
+
finish(ok({ code: exitCode, stdout, stderr }));
|
|
15422
|
+
return;
|
|
15423
|
+
}
|
|
15424
|
+
logger4.error("Command exited with non-zero code", {
|
|
15425
|
+
command,
|
|
15426
|
+
code: exitCode,
|
|
15427
|
+
stderr: maskSecrets(stderr, options.secrets).slice(-500)
|
|
15428
|
+
});
|
|
15429
|
+
finish(err(
|
|
15430
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
15431
|
+
deployM("deploy_commandFailed", {
|
|
15432
|
+
params: { command, error: maskSecrets(stderr || `exit ${exitCode}`, options.secrets).slice(-500) }
|
|
15433
|
+
})
|
|
15434
|
+
));
|
|
15435
|
+
});
|
|
15436
|
+
if (options.input !== void 0) {
|
|
15437
|
+
child.stdin.write(options.input);
|
|
15438
|
+
child.stdin.end();
|
|
15439
|
+
} else {
|
|
15440
|
+
child.stdin.end();
|
|
15441
|
+
}
|
|
15442
|
+
});
|
|
15443
|
+
}
|
|
15444
|
+
|
|
15445
|
+
// ../deploy/src/container/deploy-container-runtime.ts
|
|
15446
|
+
var logger5 = core.logger.child({ module: "deploy", scope: "container-runtime" });
|
|
15447
|
+
async function detectLocalRuntime(preferred = "auto") {
|
|
15448
|
+
const candidates = preferred === "auto" ? ["podman", "docker"] : [preferred];
|
|
15449
|
+
for (const runtime of candidates) {
|
|
15450
|
+
const probe = await runCommand(runtime, ["version"], { timeoutMs: 15e3 });
|
|
15451
|
+
if (probe.success) {
|
|
15452
|
+
logger5.info("Local container runtime detected", { runtime });
|
|
15453
|
+
return ok(runtime);
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
return err(
|
|
15457
|
+
HaiDeployError.CONTAINER_RUNTIME_NOT_FOUND,
|
|
15458
|
+
deployM("deploy_containerRuntimeNotFound", { params: { runtime: candidates.join(" / ") } })
|
|
15459
|
+
);
|
|
15460
|
+
}
|
|
15461
|
+
async function buildImage(runtime, options) {
|
|
15462
|
+
const args = ["build", "-f", options.dockerfilePath, "-t", options.imageRef];
|
|
15463
|
+
if (runtime === "podman")
|
|
15464
|
+
args.push("--format", "docker", "--jobs", "1");
|
|
15465
|
+
args.push(options.contextDir);
|
|
15466
|
+
logger5.info("Building container image", { runtime, imageRef: options.imageRef });
|
|
15467
|
+
const result = await runCommand(runtime, args, { cwd: options.contextDir });
|
|
15468
|
+
if (!result.success) {
|
|
15469
|
+
return err(
|
|
15470
|
+
HaiDeployError.CONTAINER_BUILD_FAILED,
|
|
15471
|
+
deployM("deploy_containerBuildFailed", { params: { error: result.error.message } }),
|
|
15472
|
+
result.error
|
|
15473
|
+
);
|
|
15474
|
+
}
|
|
15475
|
+
return ok(void 0);
|
|
15476
|
+
}
|
|
15477
|
+
async function saveImage(runtime, imageRef, tarPath) {
|
|
15478
|
+
logger5.info("Saving container image", { runtime, imageRef, tarPath });
|
|
15479
|
+
const result = await runCommand(runtime, ["save", "-o", tarPath, imageRef], {});
|
|
15480
|
+
if (!result.success) {
|
|
15481
|
+
return err(
|
|
15482
|
+
HaiDeployError.CONTAINER_BUILD_FAILED,
|
|
15483
|
+
deployM("deploy_containerBuildFailed", { params: { error: result.error.message } }),
|
|
15484
|
+
result.error
|
|
15485
|
+
);
|
|
15486
|
+
}
|
|
15487
|
+
return ok(void 0);
|
|
15488
|
+
}
|
|
15489
|
+
var logger6 = core.logger.child({ module: "deploy", scope: "ssh" });
|
|
15490
|
+
function buildConnectOptions(config2) {
|
|
15491
|
+
const args = ["-o", "BatchMode=yes"];
|
|
15492
|
+
args.push("-o", `ConnectTimeout=${config2.connectTimeoutSeconds}`);
|
|
15493
|
+
args.push("-o", `StrictHostKeyChecking=${config2.strictHostKeyChecking ? "accept-new" : "no"}`);
|
|
15494
|
+
if (config2.knownHostsFile)
|
|
15495
|
+
args.push("-o", `UserKnownHostsFile=${config2.knownHostsFile}`);
|
|
15496
|
+
if (config2.identityFile) {
|
|
15497
|
+
args.push("-i", config2.identityFile);
|
|
15498
|
+
args.push("-o", "IdentitiesOnly=yes");
|
|
15499
|
+
}
|
|
15500
|
+
return args;
|
|
15501
|
+
}
|
|
15502
|
+
function buildSshExecArgs(config2, remoteCommand) {
|
|
15503
|
+
return [
|
|
15504
|
+
...buildConnectOptions(config2),
|
|
15505
|
+
"-p",
|
|
15506
|
+
String(config2.port),
|
|
15507
|
+
`${config2.username}@${config2.host}`,
|
|
15508
|
+
remoteCommand
|
|
15509
|
+
];
|
|
15510
|
+
}
|
|
15511
|
+
function buildScpUploadArgs(config2, localPath, remotePath) {
|
|
15512
|
+
return [
|
|
15513
|
+
...buildConnectOptions(config2),
|
|
15514
|
+
"-P",
|
|
15515
|
+
String(config2.port),
|
|
15516
|
+
localPath,
|
|
15517
|
+
`${config2.username}@${config2.host}:${remotePath}`
|
|
15518
|
+
];
|
|
15519
|
+
}
|
|
15520
|
+
function createSshExecutor(config2) {
|
|
15521
|
+
const exec = async (remoteCommand, options) => {
|
|
15522
|
+
return runCommand("ssh", buildSshExecArgs(config2, remoteCommand), {
|
|
15523
|
+
timeoutMs: options?.timeoutMs,
|
|
15524
|
+
secrets: options?.secrets
|
|
15525
|
+
});
|
|
15526
|
+
};
|
|
15527
|
+
const upload = async (localPath, remotePath) => {
|
|
15528
|
+
const args = buildScpUploadArgs(config2, localPath, remotePath);
|
|
15529
|
+
const result = await runCommand("scp", args, {});
|
|
15530
|
+
if (!result.success) {
|
|
15531
|
+
return err(
|
|
15532
|
+
HaiDeployError.IMAGE_TRANSFER_FAILED,
|
|
15533
|
+
deployM("deploy_imageTransferFailed", { params: { error: result.error.message } }),
|
|
15534
|
+
result.error
|
|
15535
|
+
);
|
|
15536
|
+
}
|
|
15537
|
+
return ok(void 0);
|
|
15538
|
+
};
|
|
15539
|
+
const ping = async () => {
|
|
15540
|
+
logger6.debug("SSH preflight", { host: config2.host, port: config2.port });
|
|
15541
|
+
const result = await exec("true", { timeoutMs: 2e4 });
|
|
15542
|
+
if (!result.success) {
|
|
15543
|
+
return err(
|
|
15544
|
+
HaiDeployError.REMOTE_CONNECT_FAILED,
|
|
15545
|
+
deployM("deploy_remoteConnectFailed", { params: { host: config2.host, error: result.error.message } }),
|
|
15546
|
+
result.error
|
|
15547
|
+
);
|
|
15548
|
+
}
|
|
15549
|
+
return ok(void 0);
|
|
15550
|
+
};
|
|
15551
|
+
return { exec, upload, ping };
|
|
15552
|
+
}
|
|
15553
|
+
|
|
15554
|
+
// ../deploy/src/providers/deploy-provider-docker-ssh.ts
|
|
15555
|
+
var logger7 = core.logger.child({ module: "deploy", scope: "provider-docker-ssh" });
|
|
15556
|
+
var HEALTH_POLL_INTERVAL = 3e3;
|
|
15557
|
+
var HEALTH_MAX_ATTEMPTS = 40;
|
|
15558
|
+
function makeDeploymentId() {
|
|
15559
|
+
const now = /* @__PURE__ */ new Date();
|
|
15560
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
15561
|
+
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
15562
|
+
return `${stamp}-${randomBytes(3).toString("hex")}`;
|
|
15563
|
+
}
|
|
15564
|
+
function parseLoadedImageRef(stdout, fallback) {
|
|
15565
|
+
const match = stdout.match(/Loaded image(?:\(s\))?:\s*(\S+)/);
|
|
15566
|
+
return match ? match[1] : fallback;
|
|
15567
|
+
}
|
|
15568
|
+
function sleep(ms) {
|
|
15569
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
15570
|
+
}
|
|
15571
|
+
function readPackageName(appDir) {
|
|
15572
|
+
try {
|
|
15573
|
+
const pkg = JSON.parse(readFileSync(join(appDir, "package.json"), "utf-8"));
|
|
15574
|
+
return pkg.name ?? "app";
|
|
15575
|
+
} catch {
|
|
15576
|
+
return "app";
|
|
15577
|
+
}
|
|
15578
|
+
}
|
|
15579
|
+
function resolveWorkspaceRoot(appDir) {
|
|
15580
|
+
if (existsSync(join(appDir, "pnpm-workspace.yaml")))
|
|
15581
|
+
return appDir;
|
|
15582
|
+
let current = appDir;
|
|
15583
|
+
let parent = dirname(current);
|
|
15584
|
+
while (parent !== current) {
|
|
15585
|
+
if (existsSync(join(parent, "pnpm-workspace.yaml")))
|
|
15586
|
+
return parent;
|
|
15587
|
+
current = parent;
|
|
15588
|
+
parent = dirname(parent);
|
|
15589
|
+
}
|
|
15590
|
+
return appDir;
|
|
15591
|
+
}
|
|
15592
|
+
function createDockerSshProvider(config2, localRuntimePref = "auto") {
|
|
15593
|
+
const ssh = createSshExecutor(config2.ssh);
|
|
15594
|
+
const baseDir = config2.remote.baseDir;
|
|
15595
|
+
async function detectRemoteRuntime() {
|
|
15596
|
+
const candidates = config2.remote.runtime === "auto" ? ["podman", "docker"] : [config2.remote.runtime];
|
|
15597
|
+
for (const runtime of candidates) {
|
|
15598
|
+
const versionResult = await ssh.exec(`${runtime} version`, { timeoutMs: 2e4 });
|
|
15599
|
+
if (!versionResult.success)
|
|
15600
|
+
continue;
|
|
15601
|
+
const composeResult = await ssh.exec(`${runtime} compose version`, { timeoutMs: 2e4 });
|
|
15602
|
+
if (composeResult.success) {
|
|
15603
|
+
logger7.info("Remote container runtime detected", { runtime });
|
|
15604
|
+
return ok(runtime);
|
|
15605
|
+
}
|
|
15606
|
+
}
|
|
15607
|
+
return err(
|
|
15608
|
+
HaiDeployError.REMOTE_COMMAND_FAILED,
|
|
15609
|
+
deployM("deploy_remoteRuntimeMissing", { params: { runtime: candidates.join(" / ") } })
|
|
15610
|
+
);
|
|
15611
|
+
}
|
|
15612
|
+
async function healthCheck(url2) {
|
|
15613
|
+
for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
|
|
15614
|
+
try {
|
|
15615
|
+
const response = await fetch(url2, { signal: AbortSignal.timeout(5e3) });
|
|
15616
|
+
if (response.ok)
|
|
15617
|
+
return true;
|
|
15618
|
+
} catch {
|
|
15619
|
+
}
|
|
15620
|
+
await sleep(HEALTH_POLL_INTERVAL);
|
|
15621
|
+
}
|
|
15622
|
+
return false;
|
|
15623
|
+
}
|
|
15624
|
+
const deploy2 = async (context) => {
|
|
15625
|
+
const { appDir, projectName, scan, provisionEnv } = context;
|
|
15626
|
+
const deploymentId = makeDeploymentId();
|
|
15627
|
+
const containerPort = config2.expose.containerPort;
|
|
15628
|
+
const hostPort = config2.expose.hostPort;
|
|
15629
|
+
const localImageTag = `hai-${projectName}:${deploymentId}`;
|
|
15630
|
+
const runtimeResult = await detectLocalRuntime(localRuntimePref);
|
|
15631
|
+
if (!runtimeResult.success)
|
|
15632
|
+
return err(runtimeResult.error);
|
|
15633
|
+
const localRuntime = runtimeResult.data;
|
|
15634
|
+
const artifactDir = join(appDir, ".hai", "deploy");
|
|
15635
|
+
mkdirSync(artifactDir, { recursive: true });
|
|
15636
|
+
const appDockerfile = join(appDir, "Dockerfile");
|
|
15637
|
+
let dockerfilePath;
|
|
15638
|
+
let contextDir;
|
|
15639
|
+
if (existsSync(appDockerfile)) {
|
|
15640
|
+
dockerfilePath = appDockerfile;
|
|
15641
|
+
contextDir = appDir;
|
|
15642
|
+
} else {
|
|
15643
|
+
dockerfilePath = join(artifactDir, "Dockerfile");
|
|
15644
|
+
writeFileSync(dockerfilePath, generateFallbackDockerfile(readPackageName(appDir), containerPort));
|
|
15645
|
+
writeFileSync(`${dockerfilePath}.dockerignore`, generateDockerignore());
|
|
15646
|
+
contextDir = resolveWorkspaceRoot(appDir);
|
|
15647
|
+
logger7.info("Generated fallback Dockerfile", { dockerfilePath, contextDir });
|
|
15648
|
+
}
|
|
15649
|
+
const buildResult = await buildImage(localRuntime, {
|
|
15650
|
+
contextDir,
|
|
15651
|
+
dockerfilePath,
|
|
15652
|
+
imageRef: localImageTag
|
|
15653
|
+
});
|
|
15654
|
+
if (!buildResult.success)
|
|
15655
|
+
return err(buildResult.error);
|
|
15656
|
+
const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort);
|
|
15657
|
+
const envPath = join(artifactDir, ".env.runtime");
|
|
15658
|
+
writeFileSync(envPath, serializeEnvFile(env), { mode: 384 });
|
|
15659
|
+
const tarPath = join(artifactDir, `${projectName}.tar`);
|
|
15660
|
+
const saveResult = await saveImage(localRuntime, localImageTag, tarPath);
|
|
15661
|
+
if (!saveResult.success)
|
|
15662
|
+
return err(saveResult.error);
|
|
15663
|
+
const pingResult = await ssh.ping();
|
|
15664
|
+
if (!pingResult.success)
|
|
15665
|
+
return err(pingResult.error);
|
|
15666
|
+
const remoteRuntimeResult = await detectRemoteRuntime();
|
|
15667
|
+
if (!remoteRuntimeResult.success)
|
|
15668
|
+
return err(remoteRuntimeResult.error);
|
|
15669
|
+
const remoteRuntime = remoteRuntimeResult.data;
|
|
15670
|
+
const remoteDir = `${baseDir}/${projectName}`;
|
|
15671
|
+
const prepareResult = await ssh.exec(`mkdir -p ${remoteDir} && chmod 700 ${remoteDir}`);
|
|
15672
|
+
if (!prepareResult.success)
|
|
15673
|
+
return err(prepareResult.error);
|
|
15674
|
+
const remoteTar = `${remoteDir}/image.tar`;
|
|
15675
|
+
const uploadTarResult = await ssh.upload(tarPath, remoteTar);
|
|
15676
|
+
if (!uploadTarResult.success)
|
|
15677
|
+
return err(uploadTarResult.error);
|
|
15678
|
+
const loadResult = await ssh.exec(`${remoteRuntime} load -i ${remoteTar}`, { timeoutMs: 3e5 });
|
|
15679
|
+
if (!loadResult.success)
|
|
15680
|
+
return err(loadResult.error);
|
|
15681
|
+
const loadedRef = parseLoadedImageRef(loadResult.data.stdout, localImageTag);
|
|
15682
|
+
const remoteImageRef = remoteRuntime === "podman" ? `localhost/${localImageTag}` : localImageTag;
|
|
15683
|
+
if (loadedRef !== remoteImageRef) {
|
|
15684
|
+
const tagResult = await ssh.exec(`${remoteRuntime} tag ${loadedRef} ${remoteImageRef}`);
|
|
15685
|
+
if (!tagResult.success)
|
|
15686
|
+
return err(tagResult.error);
|
|
15687
|
+
}
|
|
15688
|
+
const composeContent = generateComposeFile({
|
|
15689
|
+
projectName,
|
|
15690
|
+
imageRef: remoteImageRef,
|
|
15691
|
+
hostPort,
|
|
15692
|
+
containerPort,
|
|
15693
|
+
sidecar
|
|
15694
|
+
});
|
|
15695
|
+
const composePath = join(artifactDir, "compose.yml");
|
|
15696
|
+
writeFileSync(composePath, composeContent);
|
|
15697
|
+
const uploadComposeResult = await ssh.upload(composePath, `${remoteDir}/compose.yml`);
|
|
15698
|
+
if (!uploadComposeResult.success)
|
|
15699
|
+
return err(uploadComposeResult.error);
|
|
15700
|
+
const uploadEnvResult = await ssh.upload(envPath, `${remoteDir}/.env.runtime`);
|
|
15701
|
+
if (!uploadEnvResult.success)
|
|
15702
|
+
return err(uploadEnvResult.error);
|
|
15703
|
+
const secretMask = sidecar.secrets;
|
|
15704
|
+
const chmodResult = await ssh.exec(`chmod 600 ${remoteDir}/.env.runtime`, { secrets: secretMask });
|
|
15705
|
+
if (!chmodResult.success)
|
|
15706
|
+
return err(chmodResult.error);
|
|
15707
|
+
const upResult = await ssh.exec(
|
|
15708
|
+
`cd ${remoteDir} && ${remoteRuntime} compose -p hai-${projectName} up -d`,
|
|
15709
|
+
{ timeoutMs: 3e5, secrets: secretMask }
|
|
15710
|
+
);
|
|
15711
|
+
if (!upResult.success)
|
|
15712
|
+
return err(upResult.error);
|
|
15713
|
+
await ssh.exec(`rm -f ${remoteTar}`);
|
|
15714
|
+
const url2 = `http://${config2.ssh.host}:${hostPort}`;
|
|
15715
|
+
logger7.info("Running health check", { url: url2 });
|
|
15716
|
+
const healthy = await healthCheck(`${url2}/`);
|
|
15717
|
+
if (!healthy) {
|
|
15718
|
+
return err(
|
|
15719
|
+
HaiDeployError.HEALTH_CHECK_FAILED,
|
|
15720
|
+
deployM("deploy_healthCheckFailed", { params: { url: url2 } })
|
|
15721
|
+
);
|
|
15722
|
+
}
|
|
15723
|
+
const result = {
|
|
15724
|
+
url: url2,
|
|
15725
|
+
deploymentId,
|
|
15726
|
+
status: "ready",
|
|
15727
|
+
provider: "docker-ssh",
|
|
15728
|
+
healthCheck: "passed",
|
|
15729
|
+
envVarsSet: Object.keys(env)
|
|
15730
|
+
};
|
|
15731
|
+
logger7.info("docker-ssh deployment complete", { url: url2, deploymentId });
|
|
15732
|
+
return ok(result);
|
|
15733
|
+
};
|
|
15734
|
+
return { name: "docker-ssh", deploy: deploy2 };
|
|
15735
|
+
}
|
|
15736
|
+
var logger8 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
|
|
15000
15737
|
var VERCEL_API = "https://api.vercel.com";
|
|
15001
15738
|
var POLL_INTERVAL = 3e3;
|
|
15002
15739
|
var MAX_POLL_ATTEMPTS = 60;
|
|
@@ -15048,7 +15785,7 @@ function createVercelProvider() {
|
|
|
15048
15785
|
return {
|
|
15049
15786
|
name: "vercel",
|
|
15050
15787
|
async authenticate(apiToken) {
|
|
15051
|
-
|
|
15788
|
+
logger8.debug("Authenticating with Vercel");
|
|
15052
15789
|
try {
|
|
15053
15790
|
const user = await vercelFetch(
|
|
15054
15791
|
apiToken,
|
|
@@ -15056,10 +15793,10 @@ function createVercelProvider() {
|
|
|
15056
15793
|
);
|
|
15057
15794
|
token = apiToken;
|
|
15058
15795
|
const username = user.user.username || user.user.email;
|
|
15059
|
-
|
|
15796
|
+
logger8.info("Vercel authenticated", { user: username });
|
|
15060
15797
|
return ok(username);
|
|
15061
15798
|
} catch (error51) {
|
|
15062
|
-
|
|
15799
|
+
logger8.error("Vercel authentication failed", { error: error51 });
|
|
15063
15800
|
return err(toDeployError(HaiDeployError.AUTH_FAILED, "deploy_authFailed", error51));
|
|
15064
15801
|
}
|
|
15065
15802
|
},
|
|
@@ -15070,11 +15807,11 @@ function createVercelProvider() {
|
|
|
15070
15807
|
deployM("deploy_authRequired")
|
|
15071
15808
|
);
|
|
15072
15809
|
}
|
|
15073
|
-
|
|
15810
|
+
logger8.debug("Creating Vercel project", { projectName });
|
|
15074
15811
|
try {
|
|
15075
15812
|
const existingId = await findExistingProject(token, projectName);
|
|
15076
15813
|
if (existingId !== null) {
|
|
15077
|
-
|
|
15814
|
+
logger8.info("Vercel project found", { projectName, projectId: existingId });
|
|
15078
15815
|
return ok(existingId);
|
|
15079
15816
|
}
|
|
15080
15817
|
try {
|
|
@@ -15089,13 +15826,13 @@ function createVercelProvider() {
|
|
|
15089
15826
|
})
|
|
15090
15827
|
}
|
|
15091
15828
|
);
|
|
15092
|
-
|
|
15829
|
+
logger8.info("Vercel project created", { projectName, projectId: project.id });
|
|
15093
15830
|
return ok(project.id);
|
|
15094
15831
|
} catch (error51) {
|
|
15095
15832
|
if (error51 instanceof VercelApiError && error51.status === 409) {
|
|
15096
15833
|
const concurrentProjectId = await findExistingProject(token, projectName);
|
|
15097
15834
|
if (concurrentProjectId !== null) {
|
|
15098
|
-
|
|
15835
|
+
logger8.info("Vercel project reused after create conflict", {
|
|
15099
15836
|
projectName,
|
|
15100
15837
|
projectId: concurrentProjectId
|
|
15101
15838
|
});
|
|
@@ -15105,7 +15842,7 @@ function createVercelProvider() {
|
|
|
15105
15842
|
throw error51;
|
|
15106
15843
|
}
|
|
15107
15844
|
} catch (error51) {
|
|
15108
|
-
|
|
15845
|
+
logger8.error("Vercel project creation failed", { projectName, error: error51 });
|
|
15109
15846
|
return err(toDeployError(HaiDeployError.PROJECT_CREATE_FAILED, "deploy_projectCreateFailed", error51));
|
|
15110
15847
|
}
|
|
15111
15848
|
},
|
|
@@ -15116,7 +15853,7 @@ function createVercelProvider() {
|
|
|
15116
15853
|
deployM("deploy_authRequired")
|
|
15117
15854
|
);
|
|
15118
15855
|
}
|
|
15119
|
-
|
|
15856
|
+
logger8.debug("Setting Vercel env vars", { projectId, count: Object.keys(envVars).length });
|
|
15120
15857
|
try {
|
|
15121
15858
|
const envEntries = Object.entries(envVars).map(([key, value]) => ({
|
|
15122
15859
|
key,
|
|
@@ -15132,10 +15869,10 @@ function createVercelProvider() {
|
|
|
15132
15869
|
body: JSON.stringify(envEntries)
|
|
15133
15870
|
}
|
|
15134
15871
|
);
|
|
15135
|
-
|
|
15872
|
+
logger8.info("Vercel env vars set", { projectId, count: envEntries.length });
|
|
15136
15873
|
return ok(void 0);
|
|
15137
15874
|
} catch (error51) {
|
|
15138
|
-
|
|
15875
|
+
logger8.error("Vercel env vars setup failed", { projectId, error: error51 });
|
|
15139
15876
|
return err(toDeployError(HaiDeployError.ENV_VAR_FAILED, "deploy_envVarFailed", error51));
|
|
15140
15877
|
}
|
|
15141
15878
|
},
|
|
@@ -15146,7 +15883,7 @@ function createVercelProvider() {
|
|
|
15146
15883
|
deployM("deploy_authRequired")
|
|
15147
15884
|
);
|
|
15148
15885
|
}
|
|
15149
|
-
|
|
15886
|
+
logger8.debug("Deploying to Vercel", { projectId, outputDir });
|
|
15150
15887
|
try {
|
|
15151
15888
|
const filePaths = collectFiles(outputDir, outputDir);
|
|
15152
15889
|
const files = [];
|
|
@@ -15158,7 +15895,7 @@ function createVercelProvider() {
|
|
|
15158
15895
|
await uploadFile(token, content, sha);
|
|
15159
15896
|
files.push({ file: filePath, sha, size });
|
|
15160
15897
|
}
|
|
15161
|
-
|
|
15898
|
+
logger8.debug("Files uploaded", { count: files.length });
|
|
15162
15899
|
const deployment = await vercelFetch(
|
|
15163
15900
|
token,
|
|
15164
15901
|
"/v13/deployments",
|
|
@@ -15178,14 +15915,14 @@ function createVercelProvider() {
|
|
|
15178
15915
|
status: finalStatus,
|
|
15179
15916
|
envVarsSet: []
|
|
15180
15917
|
};
|
|
15181
|
-
|
|
15918
|
+
logger8.info("Vercel deployment complete", {
|
|
15182
15919
|
deploymentId: deployment.id,
|
|
15183
15920
|
url: result.url,
|
|
15184
15921
|
status: finalStatus
|
|
15185
15922
|
});
|
|
15186
15923
|
return ok(result);
|
|
15187
15924
|
} catch (error51) {
|
|
15188
|
-
|
|
15925
|
+
logger8.error("Vercel deployment failed", { projectId, error: error51 });
|
|
15189
15926
|
return err(toDeployError(HaiDeployError.UPLOAD_FAILED, "deploy_uploadFailed", error51));
|
|
15190
15927
|
}
|
|
15191
15928
|
}
|
|
@@ -15234,7 +15971,7 @@ async function pollDeploymentStatus(token, deploymentId) {
|
|
|
15234
15971
|
}
|
|
15235
15972
|
return "building";
|
|
15236
15973
|
}
|
|
15237
|
-
var
|
|
15974
|
+
var logger9 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun" });
|
|
15238
15975
|
function createAliyunProvisioner() {
|
|
15239
15976
|
let accessKey = null;
|
|
15240
15977
|
let secretKey = null;
|
|
@@ -15243,7 +15980,7 @@ function createAliyunProvisioner() {
|
|
|
15243
15980
|
name: "aliyun",
|
|
15244
15981
|
serviceType: "sms",
|
|
15245
15982
|
async authenticate(credentials) {
|
|
15246
|
-
|
|
15983
|
+
logger9.debug("Authenticating with Aliyun");
|
|
15247
15984
|
try {
|
|
15248
15985
|
const akId = credentials.accessKeyId ?? credentials.access_key_id ?? credentials.access_key ?? "";
|
|
15249
15986
|
const akSecret = credentials.accessKeySecret ?? credentials.access_key_secret ?? credentials.secret_key ?? "";
|
|
@@ -15253,10 +15990,10 @@ function createAliyunProvisioner() {
|
|
|
15253
15990
|
accessKey = akId;
|
|
15254
15991
|
secretKey = akSecret;
|
|
15255
15992
|
signName = credentials.signName;
|
|
15256
|
-
|
|
15993
|
+
logger9.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
|
|
15257
15994
|
return ok(akId);
|
|
15258
15995
|
} catch (error51) {
|
|
15259
|
-
|
|
15996
|
+
logger9.error("Aliyun authentication failed", { error: error51 });
|
|
15260
15997
|
return err(
|
|
15261
15998
|
HaiDeployError.AUTH_FAILED,
|
|
15262
15999
|
deployM("deploy_authFailed", {
|
|
@@ -15273,7 +16010,7 @@ function createAliyunProvisioner() {
|
|
|
15273
16010
|
deployM("deploy_authRequired")
|
|
15274
16011
|
);
|
|
15275
16012
|
}
|
|
15276
|
-
|
|
16013
|
+
logger9.debug("Provisioning Aliyun SMS service");
|
|
15277
16014
|
return ok({
|
|
15278
16015
|
serviceType: "sms",
|
|
15279
16016
|
provisionerName: "aliyun",
|
|
@@ -15286,7 +16023,7 @@ function createAliyunProvisioner() {
|
|
|
15286
16023
|
}
|
|
15287
16024
|
};
|
|
15288
16025
|
}
|
|
15289
|
-
var
|
|
16026
|
+
var logger10 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
|
|
15290
16027
|
var NEON_API = "https://console.neon.tech/api/v2";
|
|
15291
16028
|
var DEFAULT_NEON_DATABASE_NAME = "neondb";
|
|
15292
16029
|
var DEFAULT_NEON_ROLE_NAME = "neondb_owner";
|
|
@@ -15345,7 +16082,7 @@ function createNeonProvisioner() {
|
|
|
15345
16082
|
name: "neon",
|
|
15346
16083
|
serviceType: "db",
|
|
15347
16084
|
async authenticate(credentials) {
|
|
15348
|
-
|
|
16085
|
+
logger10.debug("Authenticating with Neon");
|
|
15349
16086
|
try {
|
|
15350
16087
|
const apiToken = credentials.apiKey ?? credentials.token ?? credentials.api_key ?? "";
|
|
15351
16088
|
if (!apiToken) {
|
|
@@ -15353,10 +16090,10 @@ function createNeonProvisioner() {
|
|
|
15353
16090
|
}
|
|
15354
16091
|
await listNeonProjects(apiToken);
|
|
15355
16092
|
token = apiToken;
|
|
15356
|
-
|
|
16093
|
+
logger10.info("Neon authenticated");
|
|
15357
16094
|
return ok("neon-user");
|
|
15358
16095
|
} catch (error51) {
|
|
15359
|
-
|
|
16096
|
+
logger10.error("Neon authentication failed", { error: error51 });
|
|
15360
16097
|
return err(
|
|
15361
16098
|
HaiDeployError.AUTH_FAILED,
|
|
15362
16099
|
deployM("deploy_authFailed", {
|
|
@@ -15373,7 +16110,7 @@ function createNeonProvisioner() {
|
|
|
15373
16110
|
deployM("deploy_authRequired")
|
|
15374
16111
|
);
|
|
15375
16112
|
}
|
|
15376
|
-
|
|
16113
|
+
logger10.debug("Provisioning Neon database", { appName });
|
|
15377
16114
|
let resource;
|
|
15378
16115
|
try {
|
|
15379
16116
|
const projectName = `${appName}-db`;
|
|
@@ -15381,7 +16118,7 @@ function createNeonProvisioner() {
|
|
|
15381
16118
|
if (existingProject !== null) {
|
|
15382
16119
|
resource = { resourceInfo: `neon-project:${existingProject.id}`, resourceStatus: "reused" };
|
|
15383
16120
|
const connectionUri2 = await getNeonConnectionUri(token, existingProject.id);
|
|
15384
|
-
|
|
16121
|
+
logger10.info("Neon project reused", {
|
|
15385
16122
|
projectId: existingProject.id,
|
|
15386
16123
|
projectName
|
|
15387
16124
|
});
|
|
@@ -15403,10 +16140,10 @@ function createNeonProvisioner() {
|
|
|
15403
16140
|
if (!projectId || !connectionUri) {
|
|
15404
16141
|
throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
|
|
15405
16142
|
}
|
|
15406
|
-
|
|
16143
|
+
logger10.info("Neon database provisioned", { projectId });
|
|
15407
16144
|
return ok(buildNeonProvisionResult(projectId, connectionUri, "created"));
|
|
15408
16145
|
} catch (error51) {
|
|
15409
|
-
|
|
16146
|
+
logger10.error("Neon provisioning failed", { appName });
|
|
15410
16147
|
if (resource) {
|
|
15411
16148
|
return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
|
|
15412
16149
|
projectName: appName,
|
|
@@ -15428,7 +16165,7 @@ function createNeonProvisioner() {
|
|
|
15428
16165
|
}
|
|
15429
16166
|
};
|
|
15430
16167
|
}
|
|
15431
|
-
var
|
|
16168
|
+
var logger11 = core.logger.child({ module: "deploy", scope: "provisioner-r2" });
|
|
15432
16169
|
var CF_API = "https://api.cloudflare.com/client/v4";
|
|
15433
16170
|
function createR2Provisioner() {
|
|
15434
16171
|
let token = null;
|
|
@@ -15439,7 +16176,7 @@ function createR2Provisioner() {
|
|
|
15439
16176
|
name: "r2",
|
|
15440
16177
|
serviceType: "storage",
|
|
15441
16178
|
async authenticate(credentials) {
|
|
15442
|
-
|
|
16179
|
+
logger11.debug("Authenticating with Cloudflare");
|
|
15443
16180
|
try {
|
|
15444
16181
|
const acctId = credentials.accountId ?? credentials.account_id ?? "";
|
|
15445
16182
|
const apiTok = credentials.apiToken ?? credentials.api_token ?? credentials.token ?? "";
|
|
@@ -15456,10 +16193,10 @@ function createR2Provisioner() {
|
|
|
15456
16193
|
token = apiTok;
|
|
15457
16194
|
accessKey = credentials.accessKeyId;
|
|
15458
16195
|
secretKey = credentials.secretAccessKey;
|
|
15459
|
-
|
|
16196
|
+
logger11.info("Cloudflare R2 authenticated", { accountId: acctId });
|
|
15460
16197
|
return ok(acctId);
|
|
15461
16198
|
} catch (error51) {
|
|
15462
|
-
|
|
16199
|
+
logger11.error("Cloudflare R2 authentication failed", { error: error51 });
|
|
15463
16200
|
return err(
|
|
15464
16201
|
HaiDeployError.AUTH_FAILED,
|
|
15465
16202
|
deployM("deploy_authFailed", {
|
|
@@ -15477,7 +16214,7 @@ function createR2Provisioner() {
|
|
|
15477
16214
|
);
|
|
15478
16215
|
}
|
|
15479
16216
|
const bucketName = `${appName}-storage`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
15480
|
-
|
|
16217
|
+
logger11.debug("Provisioning R2 bucket", { appName, bucketName });
|
|
15481
16218
|
try {
|
|
15482
16219
|
const createRes = await fetch(
|
|
15483
16220
|
`${CF_API}/accounts/${accountId}/r2/buckets`,
|
|
@@ -15493,7 +16230,7 @@ function createR2Provisioner() {
|
|
|
15493
16230
|
if (!createRes.ok && createRes.status !== 409) {
|
|
15494
16231
|
throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status) } }));
|
|
15495
16232
|
}
|
|
15496
|
-
|
|
16233
|
+
logger11.info("R2 bucket provisioned", { bucketName });
|
|
15497
16234
|
return ok({
|
|
15498
16235
|
serviceType: "storage",
|
|
15499
16236
|
provisionerName: "r2",
|
|
@@ -15511,7 +16248,7 @@ function createR2Provisioner() {
|
|
|
15511
16248
|
resourceInfo: `r2-bucket:${bucketName}`
|
|
15512
16249
|
});
|
|
15513
16250
|
} catch (error51) {
|
|
15514
|
-
|
|
16251
|
+
logger11.error("R2 provisioning failed", { appName, error: error51 });
|
|
15515
16252
|
return err(
|
|
15516
16253
|
HaiDeployError.PROVISION_FAILED,
|
|
15517
16254
|
deployM("deploy_provisionFailed", {
|
|
@@ -15526,7 +16263,7 @@ function createR2Provisioner() {
|
|
|
15526
16263
|
}
|
|
15527
16264
|
};
|
|
15528
16265
|
}
|
|
15529
|
-
var
|
|
16266
|
+
var logger12 = core.logger.child({ module: "deploy", scope: "provisioner-resend" });
|
|
15530
16267
|
var RESEND_API = "https://api.resend.com";
|
|
15531
16268
|
function createResendProvisioner() {
|
|
15532
16269
|
let token = null;
|
|
@@ -15535,7 +16272,7 @@ function createResendProvisioner() {
|
|
|
15535
16272
|
name: "resend",
|
|
15536
16273
|
serviceType: "email",
|
|
15537
16274
|
async authenticate(credentials) {
|
|
15538
|
-
|
|
16275
|
+
logger12.debug("Authenticating with Resend");
|
|
15539
16276
|
try {
|
|
15540
16277
|
const apiToken = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
|
|
15541
16278
|
if (!apiToken || !credentials.from) {
|
|
@@ -15549,10 +16286,10 @@ function createResendProvisioner() {
|
|
|
15549
16286
|
}
|
|
15550
16287
|
token = apiToken;
|
|
15551
16288
|
from = credentials.from;
|
|
15552
|
-
|
|
16289
|
+
logger12.info("Resend authenticated");
|
|
15553
16290
|
return ok("resend-user");
|
|
15554
16291
|
} catch (error51) {
|
|
15555
|
-
|
|
16292
|
+
logger12.error("Resend authentication failed", { error: error51 });
|
|
15556
16293
|
return err(
|
|
15557
16294
|
HaiDeployError.AUTH_FAILED,
|
|
15558
16295
|
deployM("deploy_authFailed", {
|
|
@@ -15569,7 +16306,7 @@ function createResendProvisioner() {
|
|
|
15569
16306
|
deployM("deploy_authRequired")
|
|
15570
16307
|
);
|
|
15571
16308
|
}
|
|
15572
|
-
|
|
16309
|
+
logger12.debug("Provisioning Resend email service");
|
|
15573
16310
|
return ok({
|
|
15574
16311
|
serviceType: "email",
|
|
15575
16312
|
provisionerName: "resend",
|
|
@@ -15582,7 +16319,7 @@ function createResendProvisioner() {
|
|
|
15582
16319
|
}
|
|
15583
16320
|
};
|
|
15584
16321
|
}
|
|
15585
|
-
var
|
|
16322
|
+
var logger13 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
|
|
15586
16323
|
var UPSTASH_API = "https://api.upstash.com";
|
|
15587
16324
|
function createUpstashAuthHeader(email3, apiKey) {
|
|
15588
16325
|
return `Basic ${Buffer.from(`${email3}:${apiKey}`).toString("base64")}`;
|
|
@@ -15633,7 +16370,7 @@ function createUpstashProvisioner() {
|
|
|
15633
16370
|
name: "upstash",
|
|
15634
16371
|
serviceType: "cache",
|
|
15635
16372
|
async authenticate(credentials) {
|
|
15636
|
-
|
|
16373
|
+
logger13.debug("Authenticating with Upstash");
|
|
15637
16374
|
try {
|
|
15638
16375
|
const userEmail = credentials.email ?? "";
|
|
15639
16376
|
const userKey = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
|
|
@@ -15643,10 +16380,10 @@ function createUpstashProvisioner() {
|
|
|
15643
16380
|
await listUpstashDatabases(userEmail, userKey);
|
|
15644
16381
|
email3 = userEmail;
|
|
15645
16382
|
apiKey = userKey;
|
|
15646
|
-
|
|
16383
|
+
logger13.info("Upstash authenticated", { email: userEmail });
|
|
15647
16384
|
return ok(userEmail);
|
|
15648
16385
|
} catch (error51) {
|
|
15649
|
-
|
|
16386
|
+
logger13.error("Upstash authentication failed", { error: error51 });
|
|
15650
16387
|
return err(
|
|
15651
16388
|
HaiDeployError.AUTH_FAILED,
|
|
15652
16389
|
deployM("deploy_authFailed", {
|
|
@@ -15663,7 +16400,7 @@ function createUpstashProvisioner() {
|
|
|
15663
16400
|
deployM("deploy_authRequired")
|
|
15664
16401
|
);
|
|
15665
16402
|
}
|
|
15666
|
-
|
|
16403
|
+
logger13.debug("Provisioning Upstash Redis", { appName });
|
|
15667
16404
|
let resource;
|
|
15668
16405
|
try {
|
|
15669
16406
|
const databaseName = `${appName}-cache`;
|
|
@@ -15675,7 +16412,7 @@ function createUpstashProvisioner() {
|
|
|
15675
16412
|
}
|
|
15676
16413
|
resource = { resourceInfo: `upstash-db:${databaseId}`, resourceStatus: "reused" };
|
|
15677
16414
|
const detail = await getUpstashDatabase(email3, apiKey, databaseId);
|
|
15678
|
-
|
|
16415
|
+
logger13.info("Upstash Redis reused", { databaseId, databaseName });
|
|
15679
16416
|
return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }, "reused"));
|
|
15680
16417
|
}
|
|
15681
16418
|
const data = await upstashFetch(email3, apiKey, "/v2/redis/database", {
|
|
@@ -15688,10 +16425,10 @@ function createUpstashProvisioner() {
|
|
|
15688
16425
|
});
|
|
15689
16426
|
if (data.database_id)
|
|
15690
16427
|
resource = { resourceInfo: `upstash-db:${data.database_id}`, resourceStatus: "created" };
|
|
15691
|
-
|
|
16428
|
+
logger13.info("Upstash Redis provisioned", { databaseId: data.database_id });
|
|
15692
16429
|
return ok(buildUpstashProvisionResult(data, "created"));
|
|
15693
16430
|
} catch (error51) {
|
|
15694
|
-
|
|
16431
|
+
logger13.error("Upstash provisioning failed", { appName });
|
|
15695
16432
|
if (resource) {
|
|
15696
16433
|
return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
|
|
15697
16434
|
projectName: appName,
|
|
@@ -15715,9 +16452,10 @@ function createUpstashProvisioner() {
|
|
|
15715
16452
|
}
|
|
15716
16453
|
|
|
15717
16454
|
// ../deploy/src/deploy-main.ts
|
|
15718
|
-
var
|
|
16455
|
+
var logger14 = core.logger.child({ module: "deploy", scope: "main" });
|
|
15719
16456
|
var currentConfig = null;
|
|
15720
16457
|
var currentProvider = null;
|
|
16458
|
+
var currentContainerProvider = null;
|
|
15721
16459
|
var currentProvisioners = /* @__PURE__ */ new Map();
|
|
15722
16460
|
var initInProgress = false;
|
|
15723
16461
|
var notInitialized = core.module.createNotInitializedKit(
|
|
@@ -15763,11 +16501,27 @@ var deployCredentials = {
|
|
|
15763
16501
|
save: (key, value) => saveCredential(key, value),
|
|
15764
16502
|
saveAll: (entries) => saveCredentials(entries)
|
|
15765
16503
|
};
|
|
16504
|
+
function normalizeProjectName(name) {
|
|
16505
|
+
const normalized = name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16506
|
+
return normalized || "app";
|
|
16507
|
+
}
|
|
16508
|
+
function mergeProvisionEnv(results) {
|
|
16509
|
+
let env = {};
|
|
16510
|
+
const reachProviders = [];
|
|
16511
|
+
for (const prov of results) {
|
|
16512
|
+
if (prov.envVars.HAI_REACH_PROVIDERS)
|
|
16513
|
+
reachProviders.push(...JSON.parse(prov.envVars.HAI_REACH_PROVIDERS));
|
|
16514
|
+
env = { ...env, ...prov.envVars };
|
|
16515
|
+
}
|
|
16516
|
+
if (reachProviders.length > 0)
|
|
16517
|
+
env.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
|
|
16518
|
+
return env;
|
|
16519
|
+
}
|
|
15766
16520
|
var deploy = {
|
|
15767
16521
|
credentials: deployCredentials,
|
|
15768
16522
|
async init(config2) {
|
|
15769
16523
|
if (initInProgress) {
|
|
15770
|
-
|
|
16524
|
+
logger14.warn("Deploy init already in progress, skipping concurrent call");
|
|
15771
16525
|
return err(
|
|
15772
16526
|
HaiDeployError.CONFIG_ERROR,
|
|
15773
16527
|
deployM("deploy_configError", { params: { error: "init already in progress" } })
|
|
@@ -15775,14 +16529,14 @@ var deploy = {
|
|
|
15775
16529
|
}
|
|
15776
16530
|
initInProgress = true;
|
|
15777
16531
|
try {
|
|
15778
|
-
if (currentProvider) {
|
|
15779
|
-
|
|
16532
|
+
if (currentProvider || currentContainerProvider) {
|
|
16533
|
+
logger14.warn("Deploy module is already initialized, reinitializing");
|
|
15780
16534
|
await deploy.close();
|
|
15781
16535
|
}
|
|
15782
|
-
|
|
16536
|
+
logger14.info("Initializing deploy module");
|
|
15783
16537
|
const parseResult = DeployConfigSchema.safeParse(config2);
|
|
15784
16538
|
if (!parseResult.success) {
|
|
15785
|
-
|
|
16539
|
+
logger14.error("Deploy config validation failed", { error: parseResult.error.message });
|
|
15786
16540
|
return err(
|
|
15787
16541
|
HaiDeployError.CONFIG_ERROR,
|
|
15788
16542
|
deployM("deploy_configError", { params: { error: parseResult.error.message } }),
|
|
@@ -15791,12 +16545,19 @@ var deploy = {
|
|
|
15791
16545
|
}
|
|
15792
16546
|
const parsed = parseResult.data;
|
|
15793
16547
|
try {
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
if (
|
|
15797
|
-
|
|
16548
|
+
let provider = null;
|
|
16549
|
+
let containerProvider = null;
|
|
16550
|
+
if (parsed.provider.type === "docker-ssh") {
|
|
16551
|
+
containerProvider = createDockerSshProvider(parsed.provider, parsed.container?.runtime);
|
|
16552
|
+
logger14.info("Container deploy provider ready", { provider: "docker-ssh", host: parsed.provider.ssh.host });
|
|
16553
|
+
} else {
|
|
16554
|
+
provider = createProviderByType(parsed.provider.type);
|
|
16555
|
+
const authResult = await provider.authenticate(parsed.provider.token);
|
|
16556
|
+
if (!authResult.success) {
|
|
16557
|
+
return err(authResult.error);
|
|
16558
|
+
}
|
|
16559
|
+
logger14.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
|
|
15798
16560
|
}
|
|
15799
|
-
logger10.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
|
|
15800
16561
|
currentProvisioners.clear();
|
|
15801
16562
|
if (parsed.services) {
|
|
15802
16563
|
const serviceEntries = Object.entries(parsed.services);
|
|
@@ -15807,14 +16568,14 @@ var deploy = {
|
|
|
15807
16568
|
const credentials = extractCredentials(serviceConfig);
|
|
15808
16569
|
const provAuthResult = await provisioner.authenticate(credentials);
|
|
15809
16570
|
if (!provAuthResult.success) {
|
|
15810
|
-
|
|
16571
|
+
logger14.error("Provisioner authentication failed", {
|
|
15811
16572
|
service: serviceType,
|
|
15812
16573
|
provisioner: serviceConfig.provisioner
|
|
15813
16574
|
});
|
|
15814
16575
|
currentProvisioners.clear();
|
|
15815
16576
|
return err(provAuthResult.error);
|
|
15816
16577
|
}
|
|
15817
|
-
|
|
16578
|
+
logger14.info("Provisioner authenticated", {
|
|
15818
16579
|
service: serviceType,
|
|
15819
16580
|
user: provAuthResult.data
|
|
15820
16581
|
});
|
|
@@ -15822,17 +16583,19 @@ var deploy = {
|
|
|
15822
16583
|
}
|
|
15823
16584
|
}
|
|
15824
16585
|
currentProvider = provider;
|
|
16586
|
+
currentContainerProvider = containerProvider;
|
|
15825
16587
|
currentConfig = parsed;
|
|
15826
|
-
|
|
16588
|
+
logger14.info("Deploy module initialized", {
|
|
15827
16589
|
provider: parsed.provider.type,
|
|
15828
16590
|
provisioners: [...currentProvisioners.keys()]
|
|
15829
16591
|
});
|
|
15830
16592
|
return ok(void 0);
|
|
15831
16593
|
} catch (error51) {
|
|
15832
16594
|
currentProvider = null;
|
|
16595
|
+
currentContainerProvider = null;
|
|
15833
16596
|
currentConfig = null;
|
|
15834
16597
|
currentProvisioners.clear();
|
|
15835
|
-
|
|
16598
|
+
logger14.error("Deploy module initialization failed", { error: error51 });
|
|
15836
16599
|
return err(
|
|
15837
16600
|
HaiDeployError.CONFIG_ERROR,
|
|
15838
16601
|
deployM("deploy_configError", {
|
|
@@ -15846,37 +16609,38 @@ var deploy = {
|
|
|
15846
16609
|
}
|
|
15847
16610
|
},
|
|
15848
16611
|
async close() {
|
|
15849
|
-
if (!currentProvider) {
|
|
15850
|
-
|
|
16612
|
+
if (!currentProvider && !currentContainerProvider) {
|
|
16613
|
+
logger14.info("Deploy module already closed, skipping");
|
|
15851
16614
|
return;
|
|
15852
16615
|
}
|
|
15853
|
-
|
|
16616
|
+
logger14.info("Closing deploy module");
|
|
15854
16617
|
currentProvider = null;
|
|
16618
|
+
currentContainerProvider = null;
|
|
15855
16619
|
currentConfig = null;
|
|
15856
16620
|
currentProvisioners.clear();
|
|
15857
|
-
|
|
16621
|
+
logger14.info("Deploy module closed");
|
|
15858
16622
|
},
|
|
15859
16623
|
get config() {
|
|
15860
16624
|
return currentConfig;
|
|
15861
16625
|
},
|
|
15862
16626
|
get isInitialized() {
|
|
15863
|
-
return currentProvider !== null;
|
|
16627
|
+
return currentProvider !== null || currentContainerProvider !== null;
|
|
15864
16628
|
},
|
|
15865
16629
|
async scan(appDir) {
|
|
15866
|
-
|
|
16630
|
+
logger14.debug("Scanning application", { appDir });
|
|
15867
16631
|
return scanApp(appDir);
|
|
15868
16632
|
},
|
|
15869
16633
|
async provisionAll(projectName) {
|
|
15870
|
-
if (!currentProvider) {
|
|
16634
|
+
if (!currentProvider && !currentContainerProvider) {
|
|
15871
16635
|
return notInitialized.result();
|
|
15872
16636
|
}
|
|
15873
|
-
|
|
16637
|
+
logger14.info("Provisioning all services", { projectName, count: currentProvisioners.size });
|
|
15874
16638
|
const results = [];
|
|
15875
16639
|
for (const [serviceType, provisioner] of currentProvisioners) {
|
|
15876
|
-
|
|
16640
|
+
logger14.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
|
|
15877
16641
|
const provResult = await provisioner.provision(projectName);
|
|
15878
16642
|
if (!provResult.success) {
|
|
15879
|
-
|
|
16643
|
+
logger14.error("Service provisioning failed", {
|
|
15880
16644
|
serviceType,
|
|
15881
16645
|
provisioner: provisioner.name,
|
|
15882
16646
|
code: provResult.error.code
|
|
@@ -15893,21 +16657,27 @@ var deploy = {
|
|
|
15893
16657
|
});
|
|
15894
16658
|
}
|
|
15895
16659
|
results.push(provResult.data);
|
|
15896
|
-
|
|
16660
|
+
logger14.info("Service provisioned", { serviceType, provisioner: provisioner.name });
|
|
15897
16661
|
}
|
|
15898
16662
|
return ok(results);
|
|
15899
16663
|
},
|
|
15900
16664
|
async deployApp(appDir, options) {
|
|
15901
|
-
if (!currentProvider) {
|
|
16665
|
+
if (!currentProvider && !currentContainerProvider) {
|
|
15902
16666
|
return notInitialized.result();
|
|
15903
16667
|
}
|
|
15904
|
-
|
|
16668
|
+
logger14.info("Starting deployment", { appDir, options });
|
|
15905
16669
|
const scanResult = await scanApp(appDir);
|
|
15906
16670
|
if (!scanResult.success) {
|
|
15907
16671
|
return err(scanResult.error);
|
|
15908
16672
|
}
|
|
15909
16673
|
const scan = scanResult.data;
|
|
15910
16674
|
const projectName = options?.projectName ?? scan.appName;
|
|
16675
|
+
if (currentContainerProvider) {
|
|
16676
|
+
return deployContainerApp(currentContainerProvider, appDir, scan, projectName, options);
|
|
16677
|
+
}
|
|
16678
|
+
if (!currentProvider) {
|
|
16679
|
+
return notInitialized.result();
|
|
16680
|
+
}
|
|
15911
16681
|
if (scan.isSvelteKit && !scan.adapterInstalled) {
|
|
15912
16682
|
return err(
|
|
15913
16683
|
HaiDeployError.ADAPTER_MISSING,
|
|
@@ -15924,14 +16694,7 @@ var deploy = {
|
|
|
15924
16694
|
return err(provResults.error);
|
|
15925
16695
|
}
|
|
15926
16696
|
recovery.resources = provResults.data.map(({ envVars: _envVars, ...resource }) => resource);
|
|
15927
|
-
|
|
15928
|
-
for (const prov of provResults.data) {
|
|
15929
|
-
if (prov.envVars.HAI_REACH_PROVIDERS)
|
|
15930
|
-
reachProviders.push(...JSON.parse(prov.envVars.HAI_REACH_PROVIDERS));
|
|
15931
|
-
allEnvVars = { ...allEnvVars, ...prov.envVars };
|
|
15932
|
-
}
|
|
15933
|
-
if (reachProviders.length > 0)
|
|
15934
|
-
allEnvVars.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
|
|
16697
|
+
allEnvVars = mergeProvisionEnv(provResults.data);
|
|
15935
16698
|
}
|
|
15936
16699
|
const projectResult = await currentProvider.createProject(projectName);
|
|
15937
16700
|
if (!projectResult.success) {
|
|
@@ -15960,7 +16723,7 @@ var deploy = {
|
|
|
15960
16723
|
...deployResult.data,
|
|
15961
16724
|
envVarsSet: Object.keys(allEnvVars)
|
|
15962
16725
|
};
|
|
15963
|
-
|
|
16726
|
+
logger14.info("Deployment completed", {
|
|
15964
16727
|
url: result.url,
|
|
15965
16728
|
deploymentId: result.deploymentId,
|
|
15966
16729
|
status: result.status
|
|
@@ -15968,7 +16731,25 @@ var deploy = {
|
|
|
15968
16731
|
return ok(result);
|
|
15969
16732
|
}
|
|
15970
16733
|
};
|
|
16734
|
+
async function deployContainerApp(provider, appDir, scan, rawProjectName, options) {
|
|
16735
|
+
const projectName = normalizeProjectName(rawProjectName);
|
|
16736
|
+
if (scan.isSvelteKit && !scan.nodeAdapterInstalled) {
|
|
16737
|
+
return err(
|
|
16738
|
+
HaiDeployError.ADAPTER_MISSING,
|
|
16739
|
+
deployM("deploy_adapterMissing", { params: { adapter: "@sveltejs/adapter-node" } })
|
|
16740
|
+
);
|
|
16741
|
+
}
|
|
16742
|
+
let provisionEnv = {};
|
|
16743
|
+
if (!options?.skipProvision) {
|
|
16744
|
+
const provResults = await deploy.provisionAll(projectName);
|
|
16745
|
+
if (!provResults.success) {
|
|
16746
|
+
return err(provResults.error);
|
|
16747
|
+
}
|
|
16748
|
+
provisionEnv = mergeProvisionEnv(provResults.data);
|
|
16749
|
+
}
|
|
16750
|
+
return provider.deploy({ appDir, projectName, scan, provisionEnv });
|
|
16751
|
+
}
|
|
15971
16752
|
|
|
15972
16753
|
export { DeployConfigSchema, HaiDeployError, ProviderConfigSchema, deploy, getDeployRecovery };
|
|
15973
|
-
//# sourceMappingURL=src-
|
|
15974
|
-
//# sourceMappingURL=src-
|
|
16754
|
+
//# sourceMappingURL=src-TBIET7YU.js.map
|
|
16755
|
+
//# sourceMappingURL=src-TBIET7YU.js.map
|