@h-ai/cli 0.1.0-alpha.54 → 0.1.0-alpha.56

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.
Files changed (75) hide show
  1. package/README.md +4 -0
  2. package/dist/{chunk-FVDXJYMO.js → chunk-O3ANVICQ.js} +31 -2
  3. package/dist/chunk-O3ANVICQ.js.map +1 -0
  4. package/dist/cli-config-templates-OWBEGBZQ.js +5 -0
  5. package/dist/{cli-config-templates-WGN2I23J.js.map → cli-config-templates-OWBEGBZQ.js.map} +1 -1
  6. package/dist/index.js +3 -3
  7. package/dist/{src-6ZBWDBUB.js → src-TBIET7YU.js} +908 -127
  8. package/dist/src-TBIET7YU.js.map +1 -0
  9. package/package.json +7 -7
  10. package/templates/base/.gitignore.hbs +3 -0
  11. package/templates/base/package.json.hbs +1 -1
  12. package/templates/skills/AGENTS.md +8 -22
  13. package/templates/skills/CLAUDE.md +3 -16
  14. package/templates/skills/bridges/admin/AGENTS.md +3 -16
  15. package/templates/skills/bridges/admin/CLAUDE.md +3 -11
  16. package/templates/skills/bridges/admin/copilot-instructions.md +2 -39
  17. package/templates/skills/bridges/api/AGENTS.md +5 -18
  18. package/templates/skills/bridges/api/CLAUDE.md +3 -11
  19. package/templates/skills/bridges/api/copilot-instructions.md +3 -38
  20. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +24 -59
  21. package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +1 -15
  22. package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +5 -45
  23. package/templates/skills/bridges/h5/AGENTS.md +4 -17
  24. package/templates/skills/bridges/h5/CLAUDE.md +3 -11
  25. package/templates/skills/bridges/h5/copilot-instructions.md +2 -37
  26. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -18
  27. package/templates/skills/bridges/mobile-app/CLAUDE.md +3 -11
  28. package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -38
  29. package/templates/skills/bridges/website/AGENTS.md +4 -17
  30. package/templates/skills/bridges/website/CLAUDE.md +3 -11
  31. package/templates/skills/bridges/website/copilot-instructions.md +2 -37
  32. package/templates/skills/copilot-instructions.md +2 -36
  33. package/templates/skills/hai-ai/SKILL.md +35 -389
  34. package/templates/skills/hai-ai/guide.md +383 -0
  35. package/templates/skills/hai-ai/reference.md +1 -1
  36. package/templates/skills/hai-api-client/SKILL.md +6 -6
  37. package/templates/skills/hai-api-contract/SKILL.md +10 -25
  38. package/templates/skills/hai-app-create/SKILL.md +29 -388
  39. package/templates/skills/hai-app-create/reference.md +350 -0
  40. package/templates/skills/hai-app-review/SKILL.md +9 -23
  41. package/templates/skills/hai-app-tests/SKILL.md +29 -494
  42. package/templates/skills/hai-app-tests/reference.md +442 -0
  43. package/templates/skills/hai-audit/SKILL.md +6 -6
  44. package/templates/skills/hai-build/SKILL.md +19 -338
  45. package/templates/skills/hai-build/reference.md +326 -0
  46. package/templates/skills/hai-cache/SKILL.md +27 -32
  47. package/templates/skills/hai-capacitor/SKILL.md +19 -366
  48. package/templates/skills/hai-capacitor/reference.md +352 -0
  49. package/templates/skills/hai-ci/SKILL.md +6 -21
  50. package/templates/skills/hai-core/SKILL.md +25 -566
  51. package/templates/skills/hai-core/reference.md +533 -0
  52. package/templates/skills/hai-crypto/SKILL.md +6 -20
  53. package/templates/skills/hai-datapipe/SKILL.md +9 -23
  54. package/templates/skills/hai-deploy/SKILL.md +63 -25
  55. package/templates/skills/hai-framework-sync/SKILL.md +8 -20
  56. package/templates/skills/hai-iam/SKILL.md +27 -371
  57. package/templates/skills/hai-iam/reference.md +343 -0
  58. package/templates/skills/hai-kit/SKILL.md +6 -6
  59. package/templates/skills/hai-payment/SKILL.md +6 -26
  60. package/templates/skills/hai-pr-review/SKILL.md +6 -22
  61. package/templates/skills/hai-reach/SKILL.md +19 -317
  62. package/templates/skills/hai-reach/reference.md +303 -0
  63. package/templates/skills/hai-reldb/SKILL.md +19 -472
  64. package/templates/skills/hai-reldb/reference.md +452 -0
  65. package/templates/skills/hai-scheduler/SKILL.md +6 -6
  66. package/templates/skills/hai-serv/SKILL.md +27 -759
  67. package/templates/skills/hai-serv/reference.md +736 -0
  68. package/templates/skills/hai-storage/SKILL.md +7 -25
  69. package/templates/skills/hai-ui/SKILL.md +29 -625
  70. package/templates/skills/hai-ui/reference.md +598 -0
  71. package/templates/skills/hai-vecdb/SKILL.md +18 -303
  72. package/templates/skills/hai-vecdb/reference.md +291 -0
  73. package/dist/chunk-FVDXJYMO.js.map +0 -1
  74. package/dist/cli-config-templates-WGN2I23J.js +0 -5
  75. 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
- if (files.includes("_db.yml") || files.includes("_db.yaml")) {
14869
- const dbConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_db."))));
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
- if (files.includes("_cache.yml") || files.includes("_cache.yaml")) {
14875
- const cacheConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_cache."))));
14876
- if (cacheConfig !== null) {
14877
- const cacheType = cacheConfig.type;
14878
- if (cacheType === "redis") {
14879
- services.push("cache");
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
- if (files.includes("_storage.yml") || files.includes("_storage.yaml")) {
14884
- const storageConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_storage."))));
14885
- if (storageConfig !== null) {
14886
- detectStorageServices(storageConfig, services);
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
- if (files.includes("_reach.yml") || files.includes("_reach.yaml")) {
14890
- const reachConfig = readYaml(join(configDir, files.find((f) => f.startsWith("_reach."))));
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 detectStorageServices(config2, services) {
14975
+ function detectStorageIsS3(config2) {
14898
14976
  if (config2.type === "s3") {
14899
- services.push("storage");
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 configServices = detectRequiredServices(configDir);
15019
+ const configDetection = detectRequiredServices(configDir);
14948
15020
  const depServices = detectServicesFromDependencies(pkg);
14949
- const requiredServices = Array.from(/* @__PURE__ */ new Set([...configServices, ...depServices]));
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
- requiredServices
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 logger4 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
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
- logger4.debug("Authenticating with Vercel");
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
- logger4.info("Vercel authenticated", { user: username });
15796
+ logger8.info("Vercel authenticated", { user: username });
15060
15797
  return ok(username);
15061
15798
  } catch (error51) {
15062
- logger4.error("Vercel authentication failed", { error: error51 });
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
- logger4.debug("Creating Vercel project", { projectName });
15810
+ logger8.debug("Creating Vercel project", { projectName });
15074
15811
  try {
15075
15812
  const existingId = await findExistingProject(token, projectName);
15076
15813
  if (existingId !== null) {
15077
- logger4.info("Vercel project found", { projectName, projectId: existingId });
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
- logger4.info("Vercel project created", { projectName, projectId: project.id });
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
- logger4.info("Vercel project reused after create conflict", {
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
- logger4.error("Vercel project creation failed", { projectName, error: error51 });
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
- logger4.debug("Setting Vercel env vars", { projectId, count: Object.keys(envVars).length });
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
- logger4.info("Vercel env vars set", { projectId, count: envEntries.length });
15872
+ logger8.info("Vercel env vars set", { projectId, count: envEntries.length });
15136
15873
  return ok(void 0);
15137
15874
  } catch (error51) {
15138
- logger4.error("Vercel env vars setup failed", { projectId, error: error51 });
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
- logger4.debug("Deploying to Vercel", { projectId, outputDir });
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
- logger4.debug("Files uploaded", { count: files.length });
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
- logger4.info("Vercel deployment complete", {
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
- logger4.error("Vercel deployment failed", { projectId, error: error51 });
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 logger5 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun" });
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
- logger5.debug("Authenticating with Aliyun");
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
- logger5.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
15993
+ logger9.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
15257
15994
  return ok(akId);
15258
15995
  } catch (error51) {
15259
- logger5.error("Aliyun authentication failed", { error: error51 });
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
- logger5.debug("Provisioning Aliyun SMS service");
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 logger6 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
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
- logger6.debug("Authenticating with Neon");
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
- logger6.info("Neon authenticated");
16093
+ logger10.info("Neon authenticated");
15357
16094
  return ok("neon-user");
15358
16095
  } catch (error51) {
15359
- logger6.error("Neon authentication failed", { error: error51 });
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
- logger6.debug("Provisioning Neon database", { appName });
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
- logger6.info("Neon project reused", {
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
- logger6.info("Neon database provisioned", { projectId });
16143
+ logger10.info("Neon database provisioned", { projectId });
15407
16144
  return ok(buildNeonProvisionResult(projectId, connectionUri, "created"));
15408
16145
  } catch (error51) {
15409
- logger6.error("Neon provisioning failed", { appName });
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 logger7 = core.logger.child({ module: "deploy", scope: "provisioner-r2" });
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
- logger7.debug("Authenticating with Cloudflare");
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
- logger7.info("Cloudflare R2 authenticated", { accountId: acctId });
16196
+ logger11.info("Cloudflare R2 authenticated", { accountId: acctId });
15460
16197
  return ok(acctId);
15461
16198
  } catch (error51) {
15462
- logger7.error("Cloudflare R2 authentication failed", { error: error51 });
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
- logger7.debug("Provisioning R2 bucket", { appName, bucketName });
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
- logger7.info("R2 bucket provisioned", { bucketName });
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
- logger7.error("R2 provisioning failed", { appName, error: error51 });
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 logger8 = core.logger.child({ module: "deploy", scope: "provisioner-resend" });
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
- logger8.debug("Authenticating with Resend");
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
- logger8.info("Resend authenticated");
16289
+ logger12.info("Resend authenticated");
15553
16290
  return ok("resend-user");
15554
16291
  } catch (error51) {
15555
- logger8.error("Resend authentication failed", { error: error51 });
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
- logger8.debug("Provisioning Resend email service");
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 logger9 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
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
- logger9.debug("Authenticating with Upstash");
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
- logger9.info("Upstash authenticated", { email: userEmail });
16383
+ logger13.info("Upstash authenticated", { email: userEmail });
15647
16384
  return ok(userEmail);
15648
16385
  } catch (error51) {
15649
- logger9.error("Upstash authentication failed", { error: error51 });
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
- logger9.debug("Provisioning Upstash Redis", { appName });
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
- logger9.info("Upstash Redis reused", { databaseId, databaseName });
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
- logger9.info("Upstash Redis provisioned", { databaseId: data.database_id });
16428
+ logger13.info("Upstash Redis provisioned", { databaseId: data.database_id });
15692
16429
  return ok(buildUpstashProvisionResult(data, "created"));
15693
16430
  } catch (error51) {
15694
- logger9.error("Upstash provisioning failed", { appName });
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 logger10 = core.logger.child({ module: "deploy", scope: "main" });
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
- logger10.warn("Deploy init already in progress, skipping concurrent call");
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
- logger10.warn("Deploy module is already initialized, reinitializing");
16532
+ if (currentProvider || currentContainerProvider) {
16533
+ logger14.warn("Deploy module is already initialized, reinitializing");
15780
16534
  await deploy.close();
15781
16535
  }
15782
- logger10.info("Initializing deploy module");
16536
+ logger14.info("Initializing deploy module");
15783
16537
  const parseResult = DeployConfigSchema.safeParse(config2);
15784
16538
  if (!parseResult.success) {
15785
- logger10.error("Deploy config validation failed", { error: parseResult.error.message });
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
- const provider = createProviderByType(parsed.provider.type);
15795
- const authResult = await provider.authenticate(parsed.provider.token);
15796
- if (!authResult.success) {
15797
- return err(authResult.error);
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
- logger10.error("Provisioner authentication failed", {
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
- logger10.info("Provisioner authenticated", {
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
- logger10.info("Deploy module initialized", {
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
- logger10.error("Deploy module initialization failed", { error: error51 });
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
- logger10.info("Deploy module already closed, skipping");
16612
+ if (!currentProvider && !currentContainerProvider) {
16613
+ logger14.info("Deploy module already closed, skipping");
15851
16614
  return;
15852
16615
  }
15853
- logger10.info("Closing deploy module");
16616
+ logger14.info("Closing deploy module");
15854
16617
  currentProvider = null;
16618
+ currentContainerProvider = null;
15855
16619
  currentConfig = null;
15856
16620
  currentProvisioners.clear();
15857
- logger10.info("Deploy module closed");
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
- logger10.debug("Scanning application", { appDir });
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
- logger10.info("Provisioning all services", { projectName, count: currentProvisioners.size });
16637
+ logger14.info("Provisioning all services", { projectName, count: currentProvisioners.size });
15874
16638
  const results = [];
15875
16639
  for (const [serviceType, provisioner] of currentProvisioners) {
15876
- logger10.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
16640
+ logger14.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
15877
16641
  const provResult = await provisioner.provision(projectName);
15878
16642
  if (!provResult.success) {
15879
- logger10.error("Service provisioning failed", {
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
- logger10.info("Service provisioned", { serviceType, provisioner: provisioner.name });
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
- logger10.info("Starting deployment", { appDir, options });
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
- const reachProviders = [];
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
- logger10.info("Deployment completed", {
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-6ZBWDBUB.js.map
15974
- //# sourceMappingURL=src-6ZBWDBUB.js.map
16754
+ //# sourceMappingURL=src-TBIET7YU.js.map
16755
+ //# sourceMappingURL=src-TBIET7YU.js.map