@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68

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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -0,0 +1,3849 @@
1
+ #!/usr/bin/env node
2
+ import { external_exports } from './chunk-FI56GONY.js';
3
+ import './chunk-2H7UOFLK.js';
4
+ import { createHash, randomBytes, randomUUID } from 'crypto';
5
+ import { createReadStream, readFileSync, existsSync, lstatSync, writeFileSync, chmodSync, mkdirSync, mkdtempSync, copyFileSync, renameSync, rmSync, statSync, readdirSync } from 'fs';
6
+ import { resolve, join, basename, dirname, relative, sep } from 'path';
7
+ import { core, err, ok } from '@h-ai/core';
8
+ import process3 from 'process';
9
+ import { parseEnv } from 'util';
10
+ import { LineCounter, parseDocument, parse, stringify, parseAllDocuments } from 'yaml';
11
+ import { homedir } from 'os';
12
+ import { spawn } from 'child_process';
13
+ import { readdir, readFile } from 'fs/promises';
14
+ import { setTimeout as setTimeout$1 } from 'timers/promises';
15
+ import { Buffer } from 'buffer';
16
+
17
+ var DeploymentArtifactSchema = external_exports.object({
18
+ /** 清单格式版本,与应用版本和部署环境无关。 */
19
+ version: external_exports.literal(3),
20
+ /** 应用根 package.json 的版本,用于归档、Compose 与桌面交付;最长 80 字符。 */
21
+ appVersion: external_exports.string().max(80).regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i),
22
+ /** 本次构建标识;同一应用版本允许重复构建。 */
23
+ buildId: external_exports.string().regex(/^[a-z0-9][\w.+-]*$/i),
24
+ /** 只包含所选镜像的构建不能用于首次安装。 */
25
+ partial: external_exports.boolean().default(false),
26
+ /** API 单独构建时仍保留工程的 Web 拓扑。 */
27
+ hasWeb: external_exports.boolean().default(false),
28
+ /** 工程显式配置的应用端口,部分构建未包含 app 镜像时仍保留;缺省由部署配置决定。 */
29
+ appContainerPort: external_exports.number().int().min(1).max(65535).optional(),
30
+ /** 工程显式配置的 Web 端口,部分构建未包含 web 镜像时仍保留;缺省使用部署默认值。 */
31
+ webContainerPort: external_exports.number().int().min(1).max(65535).optional(),
32
+ /** 本次构建选择的镜像归档。 */
33
+ images: external_exports.array(external_exports.object({
34
+ /** app 为服务或独立应用,web 为同源 API 代理的静态前端。 */
35
+ name: external_exports.enum(["app", "web"]),
36
+ /** 相对于清单目录的 tar 文件名。 */
37
+ file: external_exports.string().regex(/^[\w.+-]+\.tar$/),
38
+ /** 归档导入后可直接使用的不可变镜像引用。 */
39
+ imageRef: external_exports.string().regex(/^[a-z0-9][a-z0-9./:_-]*:[a-z0-9_][a-z0-9_.-]*$/).refine((value) => !value.endsWith(":latest")),
40
+ /** tar 内容的 SHA-256,用于部署前校验。 */
41
+ sha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
42
+ /** 容器内部监听端口;服务器公开端口由部署选择。 */
43
+ containerPort: external_exports.number().int().min(1).max(65535).optional()
44
+ })).min(1).max(2).refine((images) => new Set(images.map((image) => image.name)).size === images.length)
45
+ }).refine((artifact) => artifact.images.every((image) => image.file === `${image.name}-${artifact.appVersion}.tar`)).refine((artifact) => artifact.partial || artifact.images.some((image) => image.name === "app")).refine((artifact) => artifact.partial || !artifact.hasWeb || artifact.images.some((image) => image.name === "web"));
46
+ async function artifactChecksum(file) {
47
+ const hash = createHash("sha256");
48
+ for await (const chunk of createReadStream(file))
49
+ hash.update(chunk);
50
+ return hash.digest("hex");
51
+ }
52
+ async function readDeploymentArtifact(directory) {
53
+ const artifact = DeploymentArtifactSchema.parse(JSON.parse(readFileSync(resolve(directory, "manifest.json"), "utf8")));
54
+ for (const image of artifact.images) {
55
+ const file = resolve(directory, image.file);
56
+ if (!existsSync(file) || !lstatSync(file).isFile() || lstatSync(file).isSymbolicLink() || await artifactChecksum(file) !== image.sha256)
57
+ throw new Error(`Invalid artifact: ${image.file}`);
58
+ }
59
+ return artifact;
60
+ }
61
+
62
+ // ../deploy/messages/en-US.json
63
+ var en_US_default = {
64
+ deploy_notInitialized: "Deploy module not initialized, call deploy.init() first",
65
+ deploy_initFailed: "Deploy module initialization failed: {error}",
66
+ deploy_unsupportedProviderType: "Unsupported deploy provider type: {type}",
67
+ deploy_unsupportedProvisionerType: "Unsupported service provisioner type: {type}",
68
+ deploy_configTokenRequired: "Deploy provider token is required",
69
+ deploy_authRequired: "Authentication required",
70
+ deploy_authFailed: "Authentication failed: {error}",
71
+ deploy_projectCreateFailed: "Project creation failed: {error}",
72
+ deploy_uploadFailed: "Build output upload failed: {error}",
73
+ deploy_deployFailed: "Deployment failed: {error}",
74
+ deploy_provisionFailed: "Service provisioning failed: {service} - {error}",
75
+ deploy_adapterMissing: "Target platform adapter not found, please install {adapter}",
76
+ deploy_scanFailed: "Application scan failed: {error}",
77
+ deploy_envVarFailed: "Environment variable setup failed: {error}",
78
+ deploy_credentialError: "Credential read/write failed: {error}",
79
+ deploy_credentialInvalidKey: "Unsupported credential key: {key}. Only HAI_DEPLOY_* keys are allowed",
80
+ deploy_credentialSaved: "Credentials saved",
81
+ deploy_credentialLoaded: "Credentials loaded",
82
+ deploy_configError: "Deploy config error: {error}",
83
+ deploy_credentialMissing: "Missing credential fields: {fields}",
84
+ deploy_apiError: "{service} API error ({status})",
85
+ deploy_fileUploadFailed: "File upload failed ({status})",
86
+ deploy_provisionNoResult: "No connection info returned from {service}",
87
+ deploy_recoveryFailure: "Deployment stopped at {stage}; inspect the recovery details.",
88
+ 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.",
89
+ deploy_commandTimeout: "Command timed out: {command}",
90
+ deploy_commandFailed: "Command failed ({command}): {error}",
91
+ deploy_remoteConnectFailed: "Cannot connect to remote host {host}: {error}",
92
+ deploy_remoteRuntimeMissing: "Remote host missing container runtime or compose: {runtime}",
93
+ deploy_imageTransferFailed: "Image transfer failed: {error}",
94
+ deploy_healthCheckFailed: "Health check failed, app not ready at {url}",
95
+ deploy_invalidEnvironment: "Use letters, numbers, underscores or hyphens for the environment; local/example are reserved.",
96
+ deploy_environmentMissing: "Environment file missing: {path}. Create and configure it before deploying.",
97
+ deploy_environmentReadFailed: "Cannot read deployment environment files; check syntax and permissions.",
98
+ deploy_environmentWriteFailed: "Cannot save deployment configuration; check file syntax and permissions.",
99
+ deploy_multipleApplications: "Multiple service packages found; specify the application directory.",
100
+ deploy_purposeSqlite: "Local relational database persisted in a volume",
101
+ deploy_purposeDatabase: "Relational database service for business data",
102
+ deploy_purposeMemory: "Per-process memory cache; cleared on restart, no external service",
103
+ deploy_purposeLocal: "Local filesystem storage persisted in a volume",
104
+ deploy_purposeCache: "Shared cache, sessions and distributed locks",
105
+ deploy_purposeStorage: "S3-compatible object storage for uploaded files",
106
+ deploy_purposeVector: "Vector indexing and similarity search",
107
+ deploy_purposeDisabled: "LDAP login is disabled; no directory service is created",
108
+ deploy_purposeLdap: "Directory account authentication; the default directory has no business users",
109
+ deploy_purposeConsole: "Messages go to application logs without delivery",
110
+ deploy_purposeEmail: "Email/HTTP delivery; the default SMTP container only captures test messages",
111
+ deploy_purposeSms: "SMS through Aliyun credentials; requires an existing signature and quota",
112
+ deploy_webProxyInvalid: "The API prefix must be an absolute path containing only letters, numbers, underscores, hyphens and slashes.",
113
+ deploy_artifactInvalid: "Image artifacts are missing or invalid. Run pnpm build --target docker, select valid --artifact-dir, or use pnpm run deploy --build.",
114
+ deploy_sshRequired: "Automatic deployment requires SSH settings. Use --bundle-only for manual transfer when the server is unreachable.",
115
+ deploy_bundleInstructions: "Bundle: {file}\nIncludes all images needed by selected services, including self-hosted middleware.\nWith SSH access, first edit deploy-target.json in the adjacent deployment directory using its _comments, then run deploy.ps1 (Windows) or deploy.sh (Linux/macOS) there. Both read this JSON; the sh entry also requires local awk.\nFor isolated servers, copy and extract the tar into a separate directory, then run sh apply.sh (uses the configured target directory).\nLinux requires Docker/Podman, Compose, sh, tar, sha256sum, awk, curl, util-linux flock and filesystem/container permissions on a filesystem supporting flock.\nNever delete or replace .deploy.lock; its lock releases after every holder exits or the server restarts. Inspect execution and recovery state after forced termination.\nPartial updates require a successful full installation with matching ports/layout. Steps return nonzero on failure; result-DEPLOYMENT_ID.json records the outcome.\nThe bundle contains private runtime configuration; transfer it securely.\nIncluded images:\n{images}",
116
+ deploy_bundleMetadata: "Application version: {version}\nBuild ID: {buildId}\nCompose file: {compose}",
117
+ deploy_targetHost: "Required: Linux server address or SSH alias. Replace SERVER_HOST before running either upload script.",
118
+ deploy_targetPort: "SSH port, integer 1-65535; default 22.",
119
+ deploy_targetUsername: "SSH login username; default deploy. Requires write access to directory and container runtime permissions.",
120
+ deploy_targetIdentityFile: "Optional private key path on the computer running the upload script. Prefer an absolute path; empty uses ssh-agent/default keys. HAI_DEPLOY_IDENTITY_FILE overrides it.",
121
+ deploy_targetKnownHostsFile: "Optional known_hosts path on the computer running the upload script. Prefer an absolute path; empty uses OpenSSH defaults. HAI_DEPLOY_KNOWN_HOSTS_FILE overrides it.",
122
+ deploy_targetStrictHostKeyChecking: "Boolean, default true: trust new host keys and reject changed keys. false disables host key checking.",
123
+ deploy_targetConnectTimeoutSeconds: "SSH connection timeout in seconds, integer 1-2147483647; default 10.",
124
+ deploy_targetDirectory: "Required absolute application directory on the Linux server; contains incoming packages, releases and successful current state. Parent-directory traversal is forbidden.",
125
+ deploy_targetBundle: "Required tar filename in the parent of this deployment directory. Keep the tar and this entire directory together when moving them.",
126
+ deploy_bundleOnlyContainer: "Compose bundle generation is supported only by docker-ssh.",
127
+ deploy_selectedContainerOnly: "Selected-service deployment requires a docker-ssh Compose destination.",
128
+ deploy_selectionInvalid: "Cannot deploy {service}: check the service name and build its image first.",
129
+ deploy_imageVersionRequired: "Specify an image version; latest is not allowed: {image}",
130
+ deploy_bundleRuntimeRequired: "Start local Podman/Docker to export middleware images, or prepare this complete offline bundle on the build machine.",
131
+ deploy_bundleFailed: "Package preparation or deployment failed: {reason}",
132
+ deploy_configFileInvalid: "Invalid YAML in {path}, line {line}. Check indentation, colons and quotes.",
133
+ deploy_configFileUnreadable: "Cannot read configuration file {path}. Check its path and permissions.",
134
+ deploy_bundleNameInvalid: "Bundle filenames may contain letters, numbers, dots, plus signs, hyphens and underscores; parent directories may contain spaces.",
135
+ deploy_secretsInvalid: "Middleware credentials are damaged or incomplete: {path}. Restore a backup; passwords are not regenerated because existing data may become inaccessible.",
136
+ deploy_storageArtifactRequired: "Storage initialization requires the application image. Select a complete artifact containing app with --artifact-dir, or run pnpm build --target docker --images app first.",
137
+ deploy_localStopped: "The local command process tree has been confirmed exited.",
138
+ deploy_localUnknown: "Local process tree termination is unconfirmed; inspect local execution state.",
139
+ deploy_localNotStarted: "The local command was not started.",
140
+ deploy_commandOutputFailed: "Command output or callback failed",
141
+ deploy_commandInputFailed: "Command input failed",
142
+ deploy_commandTreeActive: "Child processes remain active after the main command exited",
143
+ deploy_commandExecutionFailed: "Command execution failed",
144
+ deploy_remoteUnknown: "Remote execution is unknown. Local SSH termination does not prove remote deployment stopped; inspect the server result and actual containers.",
145
+ deploy_remoteFinished: "A matching server result confirms the deployment script and recovery finished: stage {stage}, exit {code}. Verify actual service health."
146
+ };
147
+
148
+ // ../deploy/messages/ja-JP.json
149
+ var ja_JP_default = {
150
+ deploy_notInitialized: "\u30C7\u30D7\u30ED\u30A4\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bdeploy.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
151
+ deploy_initFailed: "\u30C7\u30D7\u30ED\u30A4\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
152
+ deploy_unsupportedProviderType: "\u672A\u5BFE\u5FDC\u306E\u30C7\u30D7\u30ED\u30A4\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u7A2E\u985E\u3067\u3059: {type}",
153
+ deploy_unsupportedProvisionerType: "\u672A\u5BFE\u5FDC\u306E\u30B5\u30FC\u30D3\u30B9\u30D7\u30ED\u30D3\u30B8\u30E7\u30CA\u30FC\u306E\u7A2E\u985E\u3067\u3059: {type}",
154
+ deploy_configTokenRequired: "\u30C7\u30D7\u30ED\u30A4\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u30C8\u30FC\u30AF\u30F3\u306F\u5FC5\u9808\u3067\u3059",
155
+ deploy_authRequired: "\u8A8D\u8A3C\u304C\u5FC5\u8981\u3067\u3059",
156
+ deploy_authFailed: "\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
157
+ deploy_projectCreateFailed: "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
158
+ deploy_uploadFailed: "\u30D3\u30EB\u30C9\u6210\u679C\u7269\u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
159
+ deploy_deployFailed: "\u30C7\u30D7\u30ED\u30A4\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
160
+ deploy_provisionFailed: "\u30B5\u30FC\u30D3\u30B9\u306E\u6E96\u5099\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {service} - {error}",
161
+ deploy_adapterMissing: "\u5BFE\u8C61\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0\u306E\u30A2\u30C0\u30D7\u30BF\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002{adapter}\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u3066\u304F\u3060\u3055\u3044",
162
+ deploy_scanFailed: "\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u30B9\u30AD\u30E3\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
163
+ deploy_envVarFailed: "\u74B0\u5883\u5909\u6570\u306E\u8A2D\u5B9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
164
+ deploy_credentialError: "\u8A8D\u8A3C\u60C5\u5831\u306E\u8AAD\u307F\u66F8\u304D\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
165
+ deploy_credentialInvalidKey: "\u672A\u5BFE\u5FDC\u306E\u8A8D\u8A3C\u60C5\u5831\u30AD\u30FC\u3067\u3059: {key}\u3002HAI_DEPLOY_*\u30AD\u30FC\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059",
166
+ deploy_credentialSaved: "\u8A8D\u8A3C\u60C5\u5831\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
167
+ deploy_credentialLoaded: "\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3057\u305F",
168
+ deploy_configError: "\u30C7\u30D7\u30ED\u30A4\u8A2D\u5B9A\u30A8\u30E9\u30FC: {error}",
169
+ deploy_credentialMissing: "\u8A8D\u8A3C\u60C5\u5831\u306E\u9805\u76EE\u304C\u4E0D\u8DB3\u3057\u3066\u3044\u307E\u3059: {fields}",
170
+ deploy_apiError: "{service} API\u30A8\u30E9\u30FC\uFF08{status}\uFF09",
171
+ deploy_fileUploadFailed: "\u30D5\u30A1\u30A4\u30EB\u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08{status}\uFF09",
172
+ deploy_provisionNoResult: "{service}\u304B\u3089\u63A5\u7D9A\u60C5\u5831\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F",
173
+ deploy_recoveryFailure: "{stage}\u3067\u30C7\u30D7\u30ED\u30A4\u3092\u505C\u6B62\u3057\u307E\u3057\u305F\u3002\u5FA9\u65E7\u60C5\u5831\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
174
+ deploy_recoverySuggestion: "\u540C\u3058\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u540D\u3067\u518D\u8A66\u884C\u3059\u308B\u524D\u306B\u3001\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u30B3\u30F3\u30BD\u30FC\u30EB\u3067\u8A18\u8F09\u3055\u308C\u305F\u30EA\u30BD\u30FC\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u300C\u4E0D\u660E\u300D\u306F\u3001\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u3088\u3063\u3066\u30EA\u30BD\u30FC\u30B9\u304C\u4F5C\u6210\u3055\u308C\u305F\u53EF\u80FD\u6027\u3092\u793A\u3057\u307E\u3059\u3002\u518D\u5229\u7528\u3057\u305F\u30EA\u30BD\u30FC\u30B9\u306F\u81EA\u52D5\u524A\u9664\u3055\u308C\u307E\u305B\u3093\u3002\u65B0\u898F\u30EA\u30BD\u30FC\u30B9\u306F\u4F7F\u7528\u72B6\u6CC1\u3092\u78BA\u8A8D\u3057\u3066\u304B\u3089\u624B\u52D5\u3067\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
175
+ deploy_commandTimeout: "\u30B3\u30DE\u30F3\u30C9\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F: {command}",
176
+ deploy_commandFailed: "\u30B3\u30DE\u30F3\u30C9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08{command}\uFF09: {error}",
177
+ deploy_remoteConnectFailed: "\u30EA\u30E2\u30FC\u30C8\u30DB\u30B9\u30C8{host}\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093: {error}",
178
+ deploy_remoteRuntimeMissing: "\u30EA\u30E2\u30FC\u30C8\u30DB\u30B9\u30C8\u306B\u30B3\u30F3\u30C6\u30CA\u30FC\u30E9\u30F3\u30BF\u30A4\u30E0\u307E\u305F\u306Fcompose\u304C\u3042\u308A\u307E\u305B\u3093: {runtime}",
179
+ deploy_imageTransferFailed: "\u30A4\u30E1\u30FC\u30B8\u306E\u8EE2\u9001\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {error}",
180
+ deploy_healthCheckFailed: "\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002{url}\u3067\u30A2\u30D7\u30EA\u306E\u6E96\u5099\u304C\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093",
181
+ deploy_invalidEnvironment: "\u74B0\u5883\u540D\u306B\u306F\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002local/example\u306F\u4E88\u7D04\u3055\u308C\u3066\u3044\u307E\u3059\u3002",
182
+ deploy_environmentMissing: "\u74B0\u5883\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093: {path}\u3002\u30C7\u30D7\u30ED\u30A4\u524D\u306B\u4F5C\u6210\u3057\u3066\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
183
+ deploy_environmentReadFailed: "\u30C7\u30D7\u30ED\u30A4\u7528\u306E\u74B0\u5883\u30D5\u30A1\u30A4\u30EB\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002\u69CB\u6587\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
184
+ deploy_environmentWriteFailed: "\u30C7\u30D7\u30ED\u30A4\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3002\u30D5\u30A1\u30A4\u30EB\u306E\u69CB\u6587\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
185
+ deploy_multipleApplications: "\u30B5\u30FC\u30D3\u30B9\u30D1\u30C3\u30B1\u30FC\u30B8\u304C\u8907\u6570\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
186
+ deploy_purposeSqlite: "\u30DC\u30EA\u30E5\u30FC\u30E0\u306B\u6C38\u7D9A\u5316\u3059\u308B\u30ED\u30FC\u30AB\u30EB\u30EA\u30EC\u30FC\u30B7\u30E7\u30CA\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9",
187
+ deploy_purposeDatabase: "\u696D\u52D9\u30C7\u30FC\u30BF\u7528\u306E\u30EA\u30EC\u30FC\u30B7\u30E7\u30CA\u30EB\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u30B5\u30FC\u30D3\u30B9",
188
+ deploy_purposeMemory: "\u30D7\u30ED\u30BB\u30B9\u5358\u4F4D\u306E\u30E1\u30E2\u30EA\u30FC\u30AD\u30E3\u30C3\u30B7\u30E5\u3002\u518D\u8D77\u52D5\u6642\u306B\u6D88\u53BB\u3055\u308C\u3001\u5916\u90E8\u30B5\u30FC\u30D3\u30B9\u306F\u4F7F\u7528\u3057\u307E\u305B\u3093",
189
+ deploy_purposeLocal: "\u30DC\u30EA\u30E5\u30FC\u30E0\u306B\u6C38\u7D9A\u5316\u3059\u308B\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u30B9\u30C8\u30EC\u30FC\u30B8",
190
+ deploy_purposeCache: "\u5171\u6709\u30AD\u30E3\u30C3\u30B7\u30E5\u3001\u30BB\u30C3\u30B7\u30E7\u30F3\u3001\u5206\u6563\u30ED\u30C3\u30AF",
191
+ deploy_purposeStorage: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30D5\u30A1\u30A4\u30EB\u7528\u306ES3\u4E92\u63DB\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u30B9\u30C8\u30EC\u30FC\u30B8",
192
+ deploy_purposeVector: "\u30D9\u30AF\u30C8\u30EB\u306E\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u4F5C\u6210\u3068\u985E\u4F3C\u691C\u7D22",
193
+ deploy_purposeDisabled: "LDAP\u30ED\u30B0\u30A4\u30F3\u306F\u7121\u52B9\u3067\u3059\u3002\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30B5\u30FC\u30D3\u30B9\u306F\u4F5C\u6210\u3057\u307E\u305B\u3093",
194
+ deploy_purposeLdap: "\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30A2\u30AB\u30A6\u30F3\u30C8\u8A8D\u8A3C\u3002\u65E2\u5B9A\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u306F\u696D\u52D9\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
195
+ deploy_purposeConsole: "\u914D\u4FE1\u305B\u305A\u3001\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30ED\u30B0\u306B\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059",
196
+ deploy_purposeEmail: "\u30E1\u30FC\u30EB/HTTP\u914D\u4FE1\u3002\u65E2\u5B9A\u306ESMTP\u30B3\u30F3\u30C6\u30CA\u30FC\u306F\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u306E\u307F\u53D7\u4FE1\u3057\u307E\u3059",
197
+ deploy_purposeSms: "Aliyun\u306E\u8A8D\u8A3C\u60C5\u5831\u306B\u3088\u308BSMS\u3002\u65E2\u5B58\u306E\u7F72\u540D\u3068\u5229\u7528\u67A0\u304C\u5FC5\u8981\u3067\u3059",
198
+ deploy_webProxyInvalid: "API\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u306F\u3001\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30B9\u30E9\u30C3\u30B7\u30E5\u306E\u307F\u3092\u542B\u3080\u7D76\u5BFE\u30D1\u30B9\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
199
+ deploy_artifactInvalid: "\u30A4\u30E1\u30FC\u30B8\u6210\u679C\u7269\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u7121\u52B9\u3067\u3059\u3002pnpm build --target docker \u3092\u5B9F\u884C\u3059\u308B\u304B\u3001--artifact-dir \u3067\u6709\u52B9\u306A\u6210\u679C\u7269\u3092\u9078\u629E\u3059\u308B\u304B\u3001pnpm run deploy --build \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
200
+ deploy_sshRequired: "\u81EA\u52D5\u30C7\u30D7\u30ED\u30A4\u306B\u306FSSH\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u306A\u3044\u5834\u5408\u306F--bundle-only\u3067\u624B\u52D5\u8EE2\u9001\u7528\u306E\u30D0\u30F3\u30C9\u30EB\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
201
+ deploy_bundleInstructions: "\u30D0\u30F3\u30C9\u30EB\uFF1A{file}\n\u9078\u629E\u3057\u305F\u30B5\u30FC\u30D3\u30B9\u306B\u5FC5\u8981\u306A\u3059\u3079\u3066\u306E\u30A4\u30E1\u30FC\u30B8\u3092\u542B\u307F\u307E\u3059\uFF08\u30BB\u30EB\u30D5\u30DB\u30B9\u30C8\u306E\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u3092\u542B\u3080\uFF09\u3002\nSSH \u3092\u4F7F\u7528\u3059\u308B\u5834\u5408\u3001\u96A3\u63A5\u3059\u308B\u30C7\u30D7\u30ED\u30A4\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E deploy-target.json \u3092 _comments \u306B\u5F93\u3063\u3066\u7DE8\u96C6\u3057\u3001\u540C\u3058\u5834\u6240\u3067 deploy.ps1\uFF08Windows\uFF09\u307E\u305F\u306F deploy.sh\uFF08Linux/macOS\uFF09\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u4E21\u65B9\u3068\u3082\u3053\u306E JSON \u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3059\u3002sh \u306E\u5B9F\u884C\u306B\u306F\u30ED\u30FC\u30AB\u30EB\u306E awk \u3082\u5FC5\u8981\u3067\u3059\u3002\n\u9694\u96E2\u3055\u308C\u305F\u30B5\u30FC\u30D0\u30FC\u3067\u306F\u3001tar \u3092\u5225\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u30B3\u30D4\u30FC\u3057\u3066\u5C55\u958B\u3057\u3001sh apply.sh \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8A2D\u5B9A\u3055\u308C\u305F\u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u4F7F\u7528\u3057\u307E\u3059\uFF09\u3002\nLinux \u306B\u306F Docker/Podman\u3001Compose\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux \u306E flock \u304C\u5FC5\u8981\u3067\u3059\u3002flock \u306B\u5BFE\u5FDC\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u4E0A\u3067\u3001\u30D5\u30A1\u30A4\u30EB\u30B7\u30B9\u30C6\u30E0\u3068\u30B3\u30F3\u30C6\u30CA\u30FC\u306E\u64CD\u4F5C\u6A29\u9650\u3082\u5FC5\u8981\u3067\u3059\u3002\n.deploy.lock \u306F\u524A\u9664\u3082\u7F6E\u63DB\u3082\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u3059\u3079\u3066\u306E\u4FDD\u6301\u30D7\u30ED\u30BB\u30B9\u304C\u7D42\u4E86\u3059\u308B\u304B\u3001\u30B5\u30FC\u30D0\u30FC\u304C\u518D\u8D77\u52D5\u3059\u308B\u3068\u30ED\u30C3\u30AF\u304C\u89E3\u653E\u3055\u308C\u307E\u3059\u3002\u5F37\u5236\u7D42\u4E86\u5F8C\u306F\u5B9F\u884C\u72B6\u614B\u3068\u5FA9\u65E7\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\u90E8\u5206\u66F4\u65B0\u306B\u306F\u30DD\u30FC\u30C8\u3068\u69CB\u6210\u304C\u4E00\u81F4\u3059\u308B\u6B63\u5E38\u306A\u5B8C\u5168\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u304C\u5FC5\u8981\u3067\u3059\u3002\u5404\u624B\u9806\u306F\u5931\u6557\u6642\u306B\u975E\u30BC\u30ED\u3067\u7D42\u4E86\u3057\u3001result-DEPLOYMENT_ID.json \u306B\u7D50\u679C\u3092\u8A18\u9332\u3057\u307E\u3059\u3002\n\u30D0\u30F3\u30C9\u30EB\u306B\u306F\u975E\u516C\u958B\u306E\u5B9F\u884C\u6642\u8A2D\u5B9A\u304C\u542B\u307E\u308C\u307E\u3059\u3002\u5B89\u5168\u306B\u8EE2\u9001\u3057\u3066\u304F\u3060\u3055\u3044\u3002\n\u540C\u68B1\u30A4\u30E1\u30FC\u30B8\uFF1A\n{images}",
202
+ deploy_bundleMetadata: "\u30A2\u30D7\u30EA\u306E\u30D0\u30FC\u30B8\u30E7\u30F3: {version}\n\u30D3\u30EB\u30C9ID: {buildId}\nCompose\u30D5\u30A1\u30A4\u30EB: {compose}",
203
+ deploy_targetHost: "\u5FC5\u9808\uFF1ALinux \u30B5\u30FC\u30D0\u30FC\u306E\u30A2\u30C9\u30EC\u30B9\u307E\u305F\u306F SSH \u30A8\u30A4\u30EA\u30A2\u30B9\u3002\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u524D\u306B SERVER_HOST \u3092\u7F6E\u304D\u63DB\u3048\u3066\u304F\u3060\u3055\u3044\u3002",
204
+ deploy_targetPort: "SSH \u30DD\u30FC\u30C8\u30021\u301C65535\u306E\u6574\u6570\u3002\u65E2\u5B9A\u5024\u306F22\u3067\u3059\u3002",
205
+ deploy_targetUsername: "SSH \u30ED\u30B0\u30A4\u30F3\u30E6\u30FC\u30B6\u30FC\u540D\u3002\u65E2\u5B9A\u5024\u306F deploy \u3067\u3059\u3002\u5BFE\u8C61\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u306E\u66F8\u304D\u8FBC\u307F\u6A29\u9650\u3068\u30B3\u30F3\u30C6\u30CA\u30FC\u30E9\u30F3\u30BF\u30A4\u30E0\u306E\u64CD\u4F5C\u6A29\u9650\u304C\u5FC5\u8981\u3067\u3059\u3002",
206
+ deploy_targetIdentityFile: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u7AEF\u672B\u4E0A\u306E\u79D8\u5BC6\u9375\u30D1\u30B9\uFF08\u7701\u7565\u53EF\uFF09\u3002\u7D76\u5BFE\u30D1\u30B9\u3092\u63A8\u5968\u3057\u307E\u3059\u3002\u7A7A\u6B04\u3067\u306F ssh-agent \u307E\u305F\u306F\u65E2\u5B9A\u306E\u9375\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002HAI_DEPLOY_IDENTITY_FILE \u3067\u4E0A\u66F8\u304D\u3067\u304D\u307E\u3059\u3002",
207
+ deploy_targetKnownHostsFile: "\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u30B9\u30AF\u30EA\u30D7\u30C8\u3092\u5B9F\u884C\u3059\u308B\u7AEF\u672B\u4E0A\u306E known_hosts \u30D1\u30B9\uFF08\u7701\u7565\u53EF\uFF09\u3002\u7D76\u5BFE\u30D1\u30B9\u3092\u63A8\u5968\u3057\u307E\u3059\u3002\u7A7A\u6B04\u3067\u306F OpenSSH \u306E\u65E2\u5B9A\u5024\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002HAI_DEPLOY_KNOWN_HOSTS_FILE \u3067\u4E0A\u66F8\u304D\u3067\u304D\u307E\u3059\u3002",
208
+ deploy_targetStrictHostKeyChecking: "\u771F\u507D\u5024\u3002\u65E2\u5B9A\u5024\u306E true \u306F\u65B0\u3057\u3044\u30DB\u30B9\u30C8\u9375\u3092\u4FE1\u983C\u3057\u3001\u5909\u66F4\u3055\u308C\u305F\u9375\u3092\u62D2\u5426\u3057\u307E\u3059\u3002false \u306F\u30DB\u30B9\u30C8\u9375\u306E\u691C\u8A3C\u3092\u7121\u52B9\u306B\u3057\u307E\u3059\u3002",
209
+ deploy_targetConnectTimeoutSeconds: "SSH \u63A5\u7D9A\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\uFF08\u79D2\uFF09\u30021\u301C2147483647\u306E\u6574\u6570\u3002\u65E2\u5B9A\u5024\u306F10\u3067\u3059\u3002",
210
+ deploy_targetDirectory: "\u5FC5\u9808\uFF1ALinux \u30B5\u30FC\u30D0\u30FC\u4E0A\u306E\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u7D76\u5BFE\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3002\u53D7\u4FE1\u30D1\u30C3\u30B1\u30FC\u30B8\u3001\u30EA\u30EA\u30FC\u30B9\u3001\u6210\u529F\u6642\u306E current \u72B6\u614B\u3092\u4FDD\u5B58\u3057\u307E\u3059\u3002\u89AA\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u306E\u79FB\u52D5\u306F\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3002",
211
+ deploy_targetBundle: "\u5FC5\u9808\uFF1A\u3053\u306E\u30C7\u30D7\u30ED\u30A4\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306E\u89AA\u306B\u3042\u308B tar \u30D5\u30A1\u30A4\u30EB\u540D\u3002\u79FB\u52D5\u6642\u306F tar \u3068\u3053\u306E\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u5168\u4F53\u3092\u4E00\u7DD2\u306B\u79FB\u52D5\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
212
+ deploy_bundleOnlyContainer: "Compose\u30D0\u30F3\u30C9\u30EB\u306E\u751F\u6210\u306Fdocker-ssh\u306E\u307F\u3067\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u307E\u3059\u3002",
213
+ deploy_selectedContainerOnly: "\u30B5\u30FC\u30D3\u30B9\u3092\u9078\u629E\u3057\u305F\u30C7\u30D7\u30ED\u30A4\u306B\u306F docker-ssh Compose \u306E\u30C7\u30D7\u30ED\u30A4\u5148\u304C\u5FC5\u8981\u3067\u3059\u3002",
214
+ deploy_selectionInvalid: "{service} \u3092\u30C7\u30D7\u30ED\u30A4\u3067\u304D\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D3\u30B9\u540D\u3092\u78BA\u8A8D\u3057\u3001\u5148\u306B\u30A4\u30E1\u30FC\u30B8\u3092\u30D3\u30EB\u30C9\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
215
+ deploy_imageVersionRequired: "\u30A4\u30E1\u30FC\u30B8\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002latest \u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\uFF1A{image}",
216
+ deploy_bundleRuntimeRequired: "\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u30A4\u30E1\u30FC\u30B8\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3059\u308B\u306B\u306F\u30ED\u30FC\u30AB\u30EB\u306E Podman/Docker \u3092\u8D77\u52D5\u3059\u308B\u304B\u3001\u30D3\u30EB\u30C9\u7AEF\u672B\u3067\u5B8C\u5168\u306A\u30AA\u30D5\u30E9\u30A4\u30F3\u30D0\u30F3\u30C9\u30EB\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
217
+ deploy_bundleFailed: "\u30D1\u30C3\u30B1\u30FC\u30B8\u306E\u6E96\u5099\u307E\u305F\u306F\u30C7\u30D7\u30ED\u30A4\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{reason}",
218
+ deploy_configFileInvalid: "{path} \u306E {line} \u884C\u76EE\u306E YAML \u304C\u7121\u52B9\u3067\u3059\u3002\u30A4\u30F3\u30C7\u30F3\u30C8\u3001\u30B3\u30ED\u30F3\u3001\u5F15\u7528\u7B26\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
219
+ deploy_configFileUnreadable: "\u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB {path} \u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3002\u30D1\u30B9\u3068\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
220
+ deploy_bundleNameInvalid: "\u30D0\u30F3\u30C9\u30EB\u306E\u30D5\u30A1\u30A4\u30EB\u540D\u306B\u306F\u82F1\u6570\u5B57\u3001\u30C9\u30C3\u30C8\u3001\u30D7\u30E9\u30B9\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002\u89AA\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u306B\u306F\u7A7A\u767D\u3092\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
221
+ deploy_secretsInvalid: "\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u306E\u8A8D\u8A3C\u60C5\u5831\u304C\u7834\u640D\u3057\u3066\u3044\u308B\u304B\u3001\u4E0D\u5B8C\u5168\u3067\u3059\uFF1A{path}\u3002\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3092\u5FA9\u5143\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u65E2\u5B58\u30C7\u30FC\u30BF\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u306A\u304F\u306A\u308B\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u3001\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u518D\u751F\u6210\u3057\u307E\u305B\u3093\u3002",
222
+ deploy_storageArtifactRequired: "\u30B9\u30C8\u30EC\u30FC\u30B8\u306E\u521D\u671F\u5316\u306B\u306F\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u30A4\u30E1\u30FC\u30B8\u304C\u5FC5\u8981\u3067\u3059\u3002--artifact-dir \u3067 app \u3092\u542B\u3080\u5B8C\u5168\u306A\u6210\u679C\u7269\u3092\u9078\u629E\u3059\u308B\u304B\u3001\u5148\u306B pnpm build --target docker --images app \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
223
+ deploy_localStopped: "\u30ED\u30FC\u30AB\u30EB\u30B3\u30DE\u30F3\u30C9\u306E\u30D7\u30ED\u30BB\u30B9\u30C4\u30EA\u30FC\u306E\u7D42\u4E86\u3092\u78BA\u8A8D\u3057\u307E\u3057\u305F\u3002",
224
+ deploy_localUnknown: "\u30ED\u30FC\u30AB\u30EB\u30D7\u30ED\u30BB\u30B9\u30C4\u30EA\u30FC\u306E\u7D42\u4E86\u306F\u672A\u78BA\u8A8D\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u306E\u5B9F\u884C\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
225
+ deploy_localNotStarted: "\u30ED\u30FC\u30AB\u30EB\u30B3\u30DE\u30F3\u30C9\u306F\u8D77\u52D5\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
226
+ deploy_commandOutputFailed: "\u30B3\u30DE\u30F3\u30C9\u51FA\u529B\u307E\u305F\u306F\u30B3\u30FC\u30EB\u30D0\u30C3\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
227
+ deploy_commandInputFailed: "\u30B3\u30DE\u30F3\u30C9\u5165\u529B\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
228
+ deploy_commandTreeActive: "\u30E1\u30A4\u30F3\u30B3\u30DE\u30F3\u30C9\u7D42\u4E86\u5F8C\u3082\u5B50\u30D7\u30ED\u30BB\u30B9\u304C\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059",
229
+ deploy_commandExecutionFailed: "\u30B3\u30DE\u30F3\u30C9\u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
230
+ deploy_remoteUnknown: "\u30EA\u30E2\u30FC\u30C8\u306E\u5B9F\u884C\u72B6\u614B\u306F\u4E0D\u660E\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB SSH \u306E\u7D42\u4E86\u306F\u30EA\u30E2\u30FC\u30C8\u30C7\u30D7\u30ED\u30A4\u306E\u505C\u6B62\u3092\u610F\u5473\u3057\u307E\u305B\u3093\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u7D50\u679C\u3068\u5B9F\u969B\u306E\u30B3\u30F3\u30C6\u30CA\u30FC\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
231
+ deploy_remoteFinished: "\u5BFE\u5FDC\u3059\u308B\u30B5\u30FC\u30D0\u30FC\u306E\u7D50\u679C\u306B\u3088\u308A\u3001\u30C7\u30D7\u30ED\u30A4\u30B9\u30AF\u30EA\u30D7\u30C8\u3068\u5FA9\u65E7\u51E6\u7406\u306E\u7D42\u4E86\u3092\u78BA\u8A8D\u3057\u307E\u3057\u305F\uFF1A\u6BB5\u968E {stage}\u3001\u7D42\u4E86\u30B3\u30FC\u30C9 {code}\u3002\u5B9F\u969B\u306E\u30B5\u30FC\u30D3\u30B9\u306E\u7A3C\u50CD\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
232
+ };
233
+
234
+ // ../deploy/messages/zh-CN.json
235
+ var zh_CN_default = {
236
+ deploy_notInitialized: "Deploy \u6A21\u5757\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 deploy.init()",
237
+ deploy_initFailed: "Deploy \u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25\uFF1A{error}",
238
+ deploy_unsupportedProviderType: "\u4E0D\u652F\u6301\u7684\u90E8\u7F72\u5E73\u53F0\u7C7B\u578B\uFF1A{type}",
239
+ deploy_unsupportedProvisionerType: "\u4E0D\u652F\u6301\u7684\u670D\u52A1 Provisioner \u7C7B\u578B\uFF1A{type}",
240
+ deploy_configTokenRequired: "\u90E8\u7F72\u5E73\u53F0 Token \u4E0D\u80FD\u4E3A\u7A7A",
241
+ deploy_authRequired: "\u8BF7\u5148\u5B8C\u6210\u8BA4\u8BC1",
242
+ deploy_authFailed: "\u8BA4\u8BC1\u5931\u8D25\uFF1A{error}",
243
+ deploy_projectCreateFailed: "\u9879\u76EE\u521B\u5EFA\u5931\u8D25\uFF1A{error}",
244
+ deploy_uploadFailed: "\u6784\u5EFA\u4EA7\u7269\u4E0A\u4F20\u5931\u8D25\uFF1A{error}",
245
+ deploy_deployFailed: "\u90E8\u7F72\u5931\u8D25\uFF1A{error}",
246
+ deploy_provisionFailed: "\u57FA\u7840\u8BBE\u65BD\u5F00\u901A\u5931\u8D25\uFF1A{service} - {error}",
247
+ deploy_adapterMissing: "\u672A\u68C0\u6D4B\u5230\u76EE\u6807\u5E73\u53F0 adapter\uFF0C\u8BF7\u5B89\u88C5 {adapter}",
248
+ deploy_scanFailed: "\u5E94\u7528\u626B\u63CF\u5931\u8D25\uFF1A{error}",
249
+ deploy_envVarFailed: "\u73AF\u5883\u53D8\u91CF\u8BBE\u7F6E\u5931\u8D25\uFF1A{error}",
250
+ deploy_credentialError: "\u51ED\u8BC1\u8BFB\u5199\u5931\u8D25\uFF1A{error}",
251
+ deploy_credentialInvalidKey: "\u4E0D\u652F\u6301\u7684\u51ED\u8BC1\u952E\u540D\uFF1A{key}\u3002\u4EC5\u5141\u8BB8 HAI_DEPLOY_*",
252
+ deploy_credentialSaved: "\u51ED\u8BC1\u5DF2\u4FDD\u5B58",
253
+ deploy_credentialLoaded: "\u51ED\u8BC1\u5DF2\u52A0\u8F7D",
254
+ deploy_configError: "\u90E8\u7F72\u914D\u7F6E\u9519\u8BEF\uFF1A{error}",
255
+ deploy_credentialMissing: "\u7F3A\u5C11\u51ED\u8BC1\u5B57\u6BB5\uFF1A{fields}",
256
+ deploy_apiError: "{service} API \u8BF7\u6C42\u5931\u8D25\uFF08{status}\uFF09",
257
+ deploy_fileUploadFailed: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25\uFF08{status}\uFF09",
258
+ deploy_provisionNoResult: "{service} \u672A\u8FD4\u56DE\u8FDE\u63A5\u4FE1\u606F",
259
+ deploy_recoveryFailure: "\u90E8\u7F72\u5728 {stage} \u9636\u6BB5\u505C\u6B62\uFF0C\u8BF7\u67E5\u770B\u6062\u590D\u6E05\u5355\u3002",
260
+ 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",
261
+ deploy_commandTimeout: "\u547D\u4EE4\u6267\u884C\u8D85\u65F6\uFF1A{command}",
262
+ deploy_commandFailed: "\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF08{command}\uFF09\uFF1A{error}",
263
+ deploy_remoteConnectFailed: "\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B\u4E3B\u673A {host}\uFF1A{error}",
264
+ deploy_remoteRuntimeMissing: "\u8FDC\u7A0B\u4E3B\u673A\u7F3A\u5C11\u5BB9\u5668\u8FD0\u884C\u65F6\u6216 compose\uFF1A{runtime}",
265
+ deploy_imageTransferFailed: "\u955C\u50CF\u4F20\u8F93\u5931\u8D25\uFF1A{error}",
266
+ deploy_healthCheckFailed: "\u5065\u5EB7\u68C0\u67E5\u5931\u8D25\uFF0C\u5E94\u7528\u672A\u5728 {url} \u5C31\u7EEA",
267
+ deploy_invalidEnvironment: "\u73AF\u5883\u540D\u79F0\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26\uFF0C\u4E14\u4E0D\u80FD\u4F7F\u7528 local/example\u3002",
268
+ deploy_environmentMissing: "\u7F3A\u5C11\u73AF\u5883\u914D\u7F6E {path}\uFF1B\u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u5E76\u5B8C\u6210\u914D\u7F6E\uFF0C\u518D\u91CD\u65B0\u90E8\u7F72\u3002",
269
+ deploy_environmentReadFailed: "\u90E8\u7F72\u73AF\u5883\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u683C\u5F0F\u4E0E\u6743\u9650\u3002",
270
+ deploy_environmentWriteFailed: "\u90E8\u7F72\u914D\u7F6E\u5199\u5165\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u683C\u5F0F\u4E0E\u6743\u9650\u3002",
271
+ deploy_multipleApplications: "\u53D1\u73B0\u591A\u4E2A\u670D\u52A1\u5305\uFF0C\u8BF7\u6307\u5B9A\u8981\u90E8\u7F72\u7684\u5E94\u7528\u76EE\u5F55\u3002",
272
+ deploy_purposeSqlite: "\u672C\u5730\u6587\u4EF6\u5173\u7CFB\u6570\u636E\u5E93\uFF0C\u4F7F\u7528\u6301\u4E45\u5316\u5377",
273
+ deploy_purposeDatabase: "\u72EC\u7ACB\u5173\u7CFB\u6570\u636E\u5E93\u670D\u52A1\uFF0C\u4FDD\u5B58\u4E1A\u52A1\u6570\u636E",
274
+ deploy_purposeMemory: "\u5355\u8FDB\u7A0B\u5185\u5B58\u7F13\u5B58\uFF0C\u91CD\u542F\u540E\u6E05\u7A7A\uFF0C\u4E0D\u521B\u5EFA\u5916\u90E8\u670D\u52A1",
275
+ deploy_purposeLocal: "\u672C\u5730\u6587\u4EF6\u5B58\u50A8\uFF0C\u4F7F\u7528\u6301\u4E45\u5316\u5377",
276
+ deploy_purposeCache: "\u5171\u4EAB\u7F13\u5B58\u3001\u4F1A\u8BDD\u4E0E\u5206\u5E03\u5F0F\u9501",
277
+ deploy_purposeStorage: "S3 \u517C\u5BB9\u5BF9\u8C61\u5B58\u50A8\uFF0C\u4FDD\u5B58\u4E0A\u4F20\u6587\u4EF6",
278
+ deploy_purposeVector: "\u5411\u91CF\u7D22\u5F15\u4E0E\u76F8\u4F3C\u5EA6\u641C\u7D22",
279
+ deploy_purposeDisabled: "LDAP \u767B\u5F55\u672A\u542F\u7528\uFF0C\u4E0D\u521B\u5EFA\u76EE\u5F55\u670D\u52A1",
280
+ deploy_purposeLdap: "\u4F01\u4E1A\u76EE\u5F55\u8D26\u53F7\u8BA4\u8BC1\uFF1B\u9ED8\u8BA4\u76EE\u5F55\u4E0D\u5305\u542B\u4E1A\u52A1\u7528\u6237",
281
+ deploy_purposeConsole: "\u6D88\u606F\u8F93\u51FA\u5230\u5E94\u7528\u65E5\u5FD7\uFF0C\u4E0D\u5B9E\u9645\u6295\u9012",
282
+ deploy_purposeEmail: "\u90AE\u4EF6\u6216 HTTP \u901A\u77E5\u6295\u9012\uFF1B\u9ED8\u8BA4 SMTP \u5BB9\u5668\u4EC5\u7528\u4E8E\u6D4B\u8BD5\u63A5\u6536",
283
+ deploy_purposeSms: "\u901A\u8FC7\u963F\u91CC\u4E91\u51ED\u8BC1\u63A5\u5165\u77ED\u4FE1\u670D\u52A1\uFF0C\u9700\u5DF2\u6709\u7B7E\u540D\u4E0E\u989D\u5EA6",
284
+ deploy_webProxyInvalid: "API \u524D\u7F00\u5FC5\u987B\u662F\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u8FDE\u5B57\u7B26\u548C\u659C\u6760\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
285
+ deploy_artifactInvalid: "\u955C\u50CF\u4EA7\u7269\u7F3A\u5931\u6216\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u5148\u6267\u884C pnpm build --target docker\uFF0C\u6216\u7528 --artifact-dir \u6307\u5B9A\u6709\u6548\u4EA7\u7269\uFF1B\u4E5F\u53EF\u7528 pnpm run deploy --build \u4E00\u6B21\u5B8C\u6210\u3002",
286
+ deploy_sshRequired: "\u81EA\u52A8\u90E8\u7F72\u9700\u8981 SSH \u914D\u7F6E\uFF1B\u65E0\u7F51\u7EDC\u8FDE\u63A5\u65F6\u4F7F\u7528 --bundle-only \u751F\u6210\u90E8\u7F72\u5305\u5E76\u624B\u5DE5\u642C\u8FD0\u3002",
287
+ deploy_bundleInstructions: "\u90E8\u7F72\u5305\uFF1A{file}\n\u5305\u4E2D\u5DF2\u5305\u542B\u672C\u6B21\u9009\u62E9\u670D\u52A1\u9700\u8981\u7684\u955C\u50CF\uFF0C\u5305\u62EC\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u3002\n\u6709 SSH \u8FDE\u63A5\u65F6\uFF0C\u5148\u6309 _comments \u8BF4\u660E\u4FEE\u6539\u65C1\u8FB9\u90E8\u7F72\u76EE\u5F55\u4E2D\u7684 deploy-target.json\uFF0C\u518D\u8FD0\u884C\u540C\u76EE\u5F55\u7684 deploy.ps1\uFF08Windows\uFF09\u6216 deploy.sh\uFF08Linux/macOS\uFF09\uFF1B\u4E24\u8005\u5747\u8BFB\u53D6\u6B64 JSON\uFF0Csh \u5165\u53E3\u8FD8\u9700\u672C\u673A\u5B89\u88C5 awk\u3002\n\u5B8C\u5168\u9694\u79BB\u65F6\uFF0C\u590D\u5236 tar \u5230 Linux \u670D\u52A1\u5668\uFF0C\u5728\u72EC\u7ACB\u76EE\u5F55\u89E3\u5305\uFF0C\u518D\u6267\u884C sh apply.sh\uFF08\u4F7F\u7528\u5305\u4E2D\u914D\u7F6E\u7684\u76EE\u6807\u76EE\u5F55\uFF09\u3002\n\u670D\u52A1\u5668\u9700\u8981 Docker/Podman\u3001Compose\u3001sh\u3001tar\u3001sha256sum\u3001awk\u3001curl\u3001util-linux flock\uFF0C\u4EE5\u53CA\u652F\u6301 flock \u7684\u6587\u4EF6\u7CFB\u7EDF\u548C\u76EE\u5F55\u3001\u5BB9\u5668\u8FD0\u884C\u6743\u9650\u3002\n\u56FA\u5B9A\u9501\u6587\u4EF6 .deploy.lock \u7981\u6B62\u5220\u9664\u6216\u66FF\u6362\uFF1B\u5168\u90E8\u6301\u6709\u8FDB\u7A0B\u9000\u51FA\u6216\u91CD\u542F\u540E\u81EA\u52A8\u91CA\u653E\u3002\u5F3A\u6740\u540E\u7684\u6267\u884C\u4E0E\u6062\u590D\u72B6\u6001\u987B\u4EBA\u5DE5\u6838\u5BF9\u3002\n\u90E8\u5206\u66F4\u65B0\u8981\u6C42\u670D\u52A1\u5668\u5DF2\u6709\u6210\u529F\u7684\u5B8C\u6574\u90E8\u7F72\uFF0C\u7AEF\u53E3\u53CA\u5E94\u7528\u5E03\u5C40\u4FDD\u6301\u4E00\u81F4\u3002\u6BCF\u6B65\u5931\u8D25\u4F1A\u8FD4\u56DE\u975E\u96F6\u9000\u51FA\u7801\uFF1B\u7ED3\u679C\u8BB0\u5F55\u5728\u90E8\u7F72\u76EE\u5F55 result-\u90E8\u7F72ID.json\u3002\n\u5305\u5305\u542B\u79C1\u6709\u8FD0\u884C\u914D\u7F6E\uFF0C\u8BF7\u5B89\u5168\u4F20\u8F93\u3002\n\u6240\u542B\u955C\u50CF\uFF1A\n{images}",
288
+ deploy_bundleMetadata: "\u5E94\u7528\u7248\u672C\uFF1A{version}\n\u6784\u5EFA\u7F16\u53F7\uFF1A{buildId}\nCompose \u6587\u4EF6\uFF1A{compose}",
289
+ deploy_targetHost: "\u5FC5\u586B\uFF1ALinux \u670D\u52A1\u5668\u5730\u5740\u6216 SSH \u522B\u540D\u3002\u6267\u884C\u4EFB\u4E00\u4E0A\u4F20\u811A\u672C\u524D\uFF0C\u5C06 SERVER_HOST \u6539\u4E3A\u5B9E\u9645\u5730\u5740\u3002",
290
+ deploy_targetPort: "SSH \u7AEF\u53E3\uFF0C1-65535 \u7684\u6574\u6570\uFF0C\u9ED8\u8BA4 22\u3002",
291
+ deploy_targetUsername: "SSH \u767B\u5F55\u7528\u6237\u540D\uFF0C\u9ED8\u8BA4 deploy\uFF1B\u9700\u8981\u76EE\u6807\u76EE\u5F55\u5199\u5165\u6743\u9650\u53CA\u5BB9\u5668\u8FD0\u884C\u6743\u9650\u3002",
292
+ deploy_targetIdentityFile: "\u53EF\u9009\uFF1A\u8FD0\u884C\u4E0A\u4F20\u811A\u672C\u7684\u7535\u8111\u4E0A\u7684\u79C1\u94A5\u8DEF\u5F84\uFF0C\u5EFA\u8BAE\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u7559\u7A7A\u4F7F\u7528 ssh-agent \u6216\u9ED8\u8BA4\u5BC6\u94A5\u3002HAI_DEPLOY_IDENTITY_FILE \u53EF\u8986\u76D6\u3002",
293
+ deploy_targetKnownHostsFile: "\u53EF\u9009\uFF1A\u8FD0\u884C\u4E0A\u4F20\u811A\u672C\u7684\u7535\u8111\u4E0A\u7684 known_hosts \u8DEF\u5F84\uFF0C\u5EFA\u8BAE\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u7559\u7A7A\u4F7F\u7528 OpenSSH \u9ED8\u8BA4\u6587\u4EF6\u3002HAI_DEPLOY_KNOWN_HOSTS_FILE \u53EF\u8986\u76D6\u3002",
294
+ deploy_targetStrictHostKeyChecking: "\u5E03\u5C14\u503C\uFF0C\u9ED8\u8BA4 true\uFF1A\u9996\u6B21\u4FE1\u4EFB\u65B0\u4E3B\u673A\u5BC6\u94A5\uFF0C\u62D2\u7EDD\u5DF2\u53D8\u66F4\u5BC6\u94A5\uFF1Bfalse \u5173\u95ED\u4E3B\u673A\u5BC6\u94A5\u68C0\u67E5\u3002",
295
+ deploy_targetConnectTimeoutSeconds: "SSH \u8FDE\u63A5\u8D85\u65F6\u79D2\u6570\uFF0C1-2147483647 \u7684\u6574\u6570\uFF0C\u9ED8\u8BA4 10\u3002",
296
+ deploy_targetDirectory: "\u5FC5\u586B\uFF1ALinux \u670D\u52A1\u5668\u4E0A\u7684\u5E94\u7528\u7EDD\u5BF9\u76EE\u5F55\uFF0C\u4FDD\u5B58 incoming \u90E8\u7F72\u5305\u3001releases \u53CA\u6210\u529F\u72B6\u6001 current\uFF1B\u7981\u6B62 .. \u7236\u76EE\u5F55\u8DF3\u8F6C\u3002",
297
+ deploy_targetBundle: "\u5FC5\u586B\uFF1A\u672C\u90E8\u7F72\u64CD\u4F5C\u76EE\u5F55\u4E0A\u4E00\u7EA7\u7684 tar \u6587\u4EF6\u540D\uFF1B\u642C\u8FD0\u65F6\u8BF7\u5C06 tar \u548C\u672C\u76EE\u5F55\u5B8C\u6574\u653E\u5728\u4E00\u8D77\u3002",
298
+ deploy_bundleOnlyContainer: "\u4EC5\u751F\u6210 Compose tar \u53EA\u652F\u6301 docker-ssh\u3002",
299
+ deploy_selectedContainerOnly: "\u6309\u670D\u52A1\u5C40\u90E8\u90E8\u7F72\u4EC5\u9002\u7528\u4E8E docker-ssh Compose \u76EE\u6807\u3002",
300
+ deploy_selectionInvalid: "\u65E0\u6CD5\u90E8\u7F72\u670D\u52A1 {service}\uFF1A\u8BF7\u68C0\u67E5\u670D\u52A1\u540D\u79F0\uFF0C\u5E76\u5148\u6784\u5EFA\u5BF9\u5E94\u955C\u50CF\u3002",
301
+ deploy_storageArtifactRequired: "\u5B58\u50A8\u521D\u59CB\u5316\u9700\u8981\u5E94\u7528\u955C\u50CF\u3002\u8BF7\u7528 --artifact-dir \u9009\u62E9\u542B app \u955C\u50CF\u7684\u5B8C\u6574\u5236\u54C1\uFF0C\u6216\u5148\u6267\u884C pnpm build --target docker --images app\u3002",
302
+ deploy_imageVersionRequired: "\u955C\u50CF\u5FC5\u987B\u6307\u5B9A\u7248\u672C\uFF0C\u4E0D\u80FD\u4F7F\u7528 latest\uFF1A{image}",
303
+ deploy_bundleRuntimeRequired: "\u5B8C\u6574\u79BB\u7EBF\u5305\u9700\u8981\u5BFC\u51FA\u4E2D\u95F4\u4EF6\u955C\u50CF\u3002\u8BF7\u542F\u52A8\u672C\u5730 Podman/Docker\uFF0C\u6216\u5728\u6784\u5EFA\u673A\u6253\u5305\u3002",
304
+ deploy_bundleFailed: "\u4EA4\u4ED8\u51C6\u5907\u6216\u90E8\u7F72\u5931\u8D25\uFF1A{reason}",
305
+ deploy_configFileInvalid: "\u914D\u7F6E\u6587\u4EF6 {path} \u7B2C {line} \u884C\u683C\u5F0F\u6709\u8BEF\u3002\u8BF7\u68C0\u67E5\u7F29\u8FDB\u3001\u5192\u53F7\u548C\u5F15\u53F7\u540E\u91CD\u8BD5\u3002",
306
+ deploy_configFileUnreadable: "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6 {path}\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u8DEF\u5F84\u4E0E\u8BFB\u53D6\u6743\u9650\u3002",
307
+ deploy_bundleNameInvalid: "\u90E8\u7F72\u5305\u6587\u4EF6\u540D\u53EA\u652F\u6301\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u52A0\u53F7\u3001\u77ED\u6A2A\u7EBF\u548C\u4E0B\u5212\u7EBF\uFF1B\u6240\u5728\u76EE\u5F55\u53EF\u4EE5\u5305\u542B\u7A7A\u683C\u3002",
308
+ deploy_secretsInvalid: "\u4E2D\u95F4\u4EF6\u51ED\u636E\u6587\u4EF6\u635F\u574F\u6216\u7F3A\u5C11\u5B57\u6BB5\uFF1A{path}\u3002\u8BF7\u4ECE\u5907\u4EFD\u6062\u590D\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u66F4\u6362\u5BC6\u7801\uFF0C\u4EE5\u514D\u5DF2\u6709\u6570\u636E\u65E0\u6CD5\u8BBF\u95EE\u3002",
309
+ deploy_localStopped: "\u5DF2\u786E\u8BA4\u672C\u5730\u547D\u4EE4\u8FDB\u7A0B\u6811\u5168\u90E8\u9000\u51FA\u3002",
310
+ deploy_localUnknown: "\u65E0\u6CD5\u786E\u8BA4\u672C\u5730\u547D\u4EE4\u8FDB\u7A0B\u6811\u5DF2\u5168\u90E8\u9000\u51FA\uFF1B\u8BF7\u68C0\u67E5\u672C\u673A\u6267\u884C\u72B6\u6001\u3002",
311
+ deploy_localNotStarted: "\u672C\u5730\u547D\u4EE4\u5C1A\u672A\u542F\u52A8\u3002",
312
+ deploy_commandOutputFailed: "\u547D\u4EE4\u8F93\u51FA\u6216\u56DE\u8C03\u5931\u8D25",
313
+ deploy_commandInputFailed: "\u547D\u4EE4\u8F93\u5165\u5931\u8D25",
314
+ deploy_commandTreeActive: "\u4E3B\u547D\u4EE4\u9000\u51FA\u540E\u4ECD\u6709\u6D3B\u8DC3\u7684\u5B50\u8FDB\u7A0B",
315
+ deploy_commandExecutionFailed: "\u547D\u4EE4\u6267\u884C\u5931\u8D25",
316
+ deploy_remoteUnknown: "\u8FDC\u7AEF\u6267\u884C\u72B6\u6001\u672A\u77E5\uFF1B\u672C\u5730 SSH \u7ED3\u675F\u4E0D\u8BC1\u660E\u8FDC\u7AEF\u90E8\u7F72\u5DF2\u505C\u6B62\uFF0C\u8BF7\u6838\u5BF9\u670D\u52A1\u5668\u7ED3\u679C\u4E0E\u5B9E\u9645\u5BB9\u5668\u72B6\u6001\u3002",
317
+ deploy_remoteFinished: "\u5339\u914D\u7684\u670D\u52A1\u5668\u7ED3\u679C\u786E\u8BA4\u90E8\u7F72\u811A\u672C\u53CA\u6062\u590D\u5DF2\u7ED3\u675F\uFF1A\u9636\u6BB5 {stage}\uFF0C\u9000\u51FA\u7801 {code}\u3002\u8BF7\u6838\u5BF9\u5B9E\u9645\u670D\u52A1\u5065\u5EB7\u72B6\u6001\u3002"
318
+ };
319
+
320
+ // ../deploy/src/deploy-i18n.ts
321
+ var deployM = core.i18n.createMessageGetter({
322
+ "zh-CN": zh_CN_default,
323
+ "en-US": en_US_default,
324
+ "ja-JP": ja_JP_default
325
+ });
326
+
327
+ // ../deploy/src/deploy-config.ts
328
+ var VercelProviderConfigSchema = external_exports.object({
329
+ type: external_exports.literal("vercel"),
330
+ token: external_exports.string().min(1, deployM("deploy_configTokenRequired"))
331
+ });
332
+ var SshConfigSchema = external_exports.object({
333
+ /** 远程主机地址或 ~/.ssh/config 中的 Host 别名 */
334
+ host: external_exports.string().regex(/^[a-z0-9][a-z0-9.:-]*$/i),
335
+ /** SSH 端口 */
336
+ port: external_exports.number().int().min(1).max(65535).default(22),
337
+ /** SSH 用户名 */
338
+ username: external_exports.string().regex(/^[a-z_][\w-]*$/i),
339
+ /** 私钥文件路径(省略时使用 ssh-agent 或 ~/.ssh 默认密钥) */
340
+ identityFile: external_exports.string().min(1).optional(),
341
+ /** known_hosts 文件路径(省略时使用默认;配合 CI/临时环境隔离主机指纹) */
342
+ knownHostsFile: external_exports.string().min(1).optional(),
343
+ /** 严格主机密钥校验,默认开启(true→accept-new 首次信任并拒绝变更;false→关闭校验) */
344
+ strictHostKeyChecking: external_exports.boolean().default(true),
345
+ /** 连接超时(秒) */
346
+ connectTimeoutSeconds: external_exports.number().int().positive().default(10)
347
+ });
348
+ var PortExposeSchema = external_exports.object({
349
+ type: external_exports.literal("port").default("port"),
350
+ /** 容器内监听端口(adapter-node 默认 3000) */
351
+ containerPort: external_exports.number().int().min(1).max(65535).default(3e3),
352
+ /** 宿主机映射端口 */
353
+ hostPort: external_exports.number().int().min(1).max(65535)
354
+ });
355
+ var ExposeSchema = PortExposeSchema;
356
+ var DockerSshProviderConfigSchema = external_exports.object({
357
+ type: external_exports.literal("docker-ssh"),
358
+ /** SSH 连接参数 */
359
+ ssh: SshConfigSchema.optional(),
360
+ /** 远程主机运行参数 */
361
+ remote: external_exports.object({
362
+ /** 远程应用根目录 */
363
+ baseDir: external_exports.string().regex(/^\/[\w/. -]+$/).refine((value) => !value.split("/").includes("..")).default("/opt/hai/apps"),
364
+ /** 远程容器运行时(auto 优先 podman) */
365
+ runtime: external_exports.enum(["auto", "docker", "podman"]).default("auto")
366
+ }).default({ baseDir: "/opt/hai/apps", runtime: "auto" }),
367
+ /** 应用暴露方式 */
368
+ expose: ExposeSchema
369
+ });
370
+ var ProviderConfigSchema = external_exports.discriminatedUnion("type", [
371
+ VercelProviderConfigSchema,
372
+ DockerSshProviderConfigSchema
373
+ ]);
374
+ var NeonServiceSchema = external_exports.object({
375
+ provisioner: external_exports.literal("neon"),
376
+ apiKey: external_exports.string().min(1)
377
+ });
378
+ var UpstashServiceSchema = external_exports.object({
379
+ provisioner: external_exports.literal("upstash"),
380
+ email: external_exports.string().email(),
381
+ apiKey: external_exports.string().min(1)
382
+ });
383
+ var R2ServiceSchema = external_exports.object({
384
+ provisioner: external_exports.literal("cloudflare-r2"),
385
+ accessKeyId: external_exports.string().min(1),
386
+ secretAccessKey: external_exports.string().min(1),
387
+ accountId: external_exports.string().min(1),
388
+ apiToken: external_exports.string().min(1)
389
+ });
390
+ var ResendServiceSchema = external_exports.object({
391
+ provisioner: external_exports.literal("resend"),
392
+ from: external_exports.string().email(),
393
+ apiKey: external_exports.string().min(1)
394
+ });
395
+ var AliyunSmsServiceSchema = external_exports.object({
396
+ provisioner: external_exports.literal("aliyun"),
397
+ signName: external_exports.string().min(1),
398
+ accessKeyId: external_exports.string().min(1),
399
+ accessKeySecret: external_exports.string().min(1)
400
+ });
401
+ var ServicesConfigSchema = external_exports.object({
402
+ db: external_exports.discriminatedUnion("provisioner", [NeonServiceSchema]).optional(),
403
+ cache: external_exports.discriminatedUnion("provisioner", [UpstashServiceSchema]).optional(),
404
+ storage: external_exports.discriminatedUnion("provisioner", [R2ServiceSchema]).optional(),
405
+ email: external_exports.discriminatedUnion("provisioner", [ResendServiceSchema]).optional(),
406
+ sms: external_exports.discriminatedUnion("provisioner", [AliyunSmsServiceSchema]).optional()
407
+ }).optional();
408
+ var DeployConfigSchema = external_exports.object({
409
+ /** 部署环境文件模式;dev/test/prod 或自定义模式,禁止 local/example。 */
410
+ environment: external_exports.string().regex(/^[a-z0-9][\w-]*$/i).refine((value) => !["local", "example"].includes(value.toLowerCase())).optional(),
411
+ provider: ProviderConfigSchema,
412
+ services: ServicesConfigSchema,
413
+ /** Docker Compose 部署参数;构建配置位于项目 hai.build.json。 */
414
+ container: external_exports.object({
415
+ /** 自建 S3 服务实现;协议仍为 storage.type=s3。 */
416
+ s3Engine: external_exports.enum(["rustfs", "minio"]).default("rustfs")
417
+ }).optional()
418
+ });
419
+ var DeployErrorInfo = {
420
+ DEPLOY_FAILED: "001:500",
421
+ PROJECT_CREATE_FAILED: "002:500",
422
+ UPLOAD_FAILED: "004:500",
423
+ AUTH_REQUIRED: "005:401",
424
+ AUTH_FAILED: "006:401",
425
+ PROVISION_FAILED: "007:500",
426
+ ADAPTER_MISSING: "008:500",
427
+ SCAN_FAILED: "009:500",
428
+ NOT_INITIALIZED: "010:500",
429
+ ENV_VAR_FAILED: "011:500",
430
+ UNSUPPORTED_TYPE: "012:400",
431
+ CONFIG_ERROR: "013:500",
432
+ CREDENTIAL_ERROR: "014:500",
433
+ REMOTE_CONNECT_FAILED: "017:500",
434
+ REMOTE_COMMAND_FAILED: "018:500",
435
+ IMAGE_TRANSFER_FAILED: "019:500",
436
+ HEALTH_CHECK_FAILED: "020:500"
437
+ };
438
+ var HaiDeployError = core.error.buildHaiErrorsDef("deploy", DeployErrorInfo);
439
+
440
+ // ../deploy/src/deploy-environment.ts
441
+ function environmentValue(value) {
442
+ try {
443
+ return parse(value) ?? value;
444
+ } catch {
445
+ return value;
446
+ }
447
+ }
448
+ function resolveConfig(value, prefix, env) {
449
+ if (env[prefix] !== void 0)
450
+ return environmentValue(env[prefix]);
451
+ if (Array.isArray(value))
452
+ return value.map((item, index) => resolveConfig(item, `${prefix}_${index}`, env));
453
+ if (value && typeof value === "object")
454
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, resolveConfig(nested, `${prefix}_${key.toUpperCase()}`, env)]));
455
+ if (typeof value !== "string")
456
+ return value;
457
+ const resolved = value.replace(/\$\{([^:}]+)(?::([^}]*))?\}/g, (_match, name, fallback) => env[name] ?? fallback ?? "");
458
+ return /^\$\{[^}]+\}$/.test(value) ? environmentValue(resolved) : resolved;
459
+ }
460
+ function loadDeploymentConfig(name, path, values) {
461
+ try {
462
+ const lineCounter = new LineCounter();
463
+ const document = parseDocument(readFileSync(path, "utf8"), { lineCounter, prettyErrors: false });
464
+ if (document.errors.length)
465
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_configFileInvalid", { params: { path, line: String(lineCounter.linePos(document.errors[0].pos[0]).line) } }));
466
+ const config = resolveConfig(document.toJS(), `HAI_${name.toUpperCase()}`, values);
467
+ if (config !== null && (typeof config !== "object" || Array.isArray(config)))
468
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_configFileInvalid", { params: { path, line: "1" } }));
469
+ return ok(config && typeof config === "object" && !Array.isArray(config) ? config : {});
470
+ } catch {
471
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_configFileUnreadable", { params: { path } }));
472
+ }
473
+ }
474
+ function loadDeploymentEnvironment(options) {
475
+ const environment = options.environment === "production" ? "prod" : options.environment === "development" ? "dev" : options.environment;
476
+ if (!/^[a-z0-9][\w-]*$/i.test(environment) || ["local", "example"].includes(environment.toLowerCase()))
477
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_invalidEnvironment"));
478
+ const envDir = resolve(options.envDir);
479
+ const target = join(envDir, `.env.${environment}`);
480
+ if (options.requireFile !== false && !existsSync(target))
481
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_environmentMissing", { params: { path: target } }));
482
+ const loaded = core.config.loadEnvFiles({ mode: environment, envDir });
483
+ if (!loaded.success)
484
+ return loaded;
485
+ try {
486
+ const values = { ...loaded.data };
487
+ for (const [name, value] of Object.entries(process3.env)) {
488
+ if (/^(?:HAI_|PUBLIC_|VITE_)/.test(name) && value !== void 0)
489
+ values[name] = value;
490
+ }
491
+ values.HAI_DEPLOY_ENVIRONMENT = environment;
492
+ values.HAI_CORE_ENV ??= environment === "prod" ? "production" : environment === "dev" ? "development" : environment === "test" ? "test" : "staging";
493
+ const targetValues = {};
494
+ if (existsSync(target)) {
495
+ for (const [name, value] of Object.entries(parseEnv(readFileSync(target, "utf8")))) {
496
+ if (value !== void 0)
497
+ targetValues[name] = value;
498
+ }
499
+ }
500
+ return ok({ environment, envDir, values, targetValues });
501
+ } catch {
502
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_environmentReadFailed"));
503
+ }
504
+ }
505
+ function isSecret(name, value) {
506
+ if (/PASSWORD|SECRET|TOKEN|APIKEY|PRIVATEKEY|ACCESSKEY|AUTHORIZATION/i.test(name.replace(/[^a-z0-9]/gi, "")) || /(?:^|_)PASS$/i.test(name) || value.includes("PRIVATE KEY"))
507
+ return true;
508
+ try {
509
+ const url = new URL(value);
510
+ if (url.username || url.password || [...url.searchParams.keys()].some((key) => /password|secret|token|key|authorization/i.test(key)))
511
+ return true;
512
+ } catch {
513
+ }
514
+ try {
515
+ const parsed = JSON.parse(value);
516
+ if (parsed && typeof parsed === "object")
517
+ return Object.entries(parsed).some(([key, nested]) => isSecret(key, typeof nested === "string" ? nested : JSON.stringify(nested)));
518
+ } catch {
519
+ }
520
+ return false;
521
+ }
522
+ function publicConfig(value) {
523
+ if (Array.isArray(value))
524
+ return value.map(publicConfig);
525
+ if (value && typeof value === "object")
526
+ return Object.fromEntries(Object.entries(value).filter(([name, item]) => !isSecret(name, typeof item === "string" ? item : "")).map(([name, item]) => [name, publicConfig(item)]));
527
+ return value;
528
+ }
529
+ function quoteEnv(value) {
530
+ try {
531
+ const parsed = JSON.parse(value);
532
+ if (parsed && typeof parsed === "object")
533
+ return `'${JSON.stringify(parsed).replaceAll("'", "\\u0027")}'`;
534
+ } catch {
535
+ }
536
+ if (!value.includes("'"))
537
+ return `'${value}'`;
538
+ if (!value.includes('"'))
539
+ return `"${value}"`;
540
+ if (!value.includes("`"))
541
+ return `\`${value}\``;
542
+ if (!/[\r\n#]/.test(value))
543
+ return value;
544
+ throw new Error("Unsupported dotenv value");
545
+ }
546
+ function updateFile(path, values, privateFile) {
547
+ let content = existsSync(path) ? readFileSync(path, "utf8") : "# Local private deployment configuration; do not commit.\n";
548
+ for (const [name, value] of Object.entries(values)) {
549
+ if (!/^[A-Z][A-Z0-9_]*$/.test(name))
550
+ throw new Error("Invalid environment variable name");
551
+ const line = `${name}=${quoteEnv(value)}`;
552
+ const pattern = new RegExp(`^(?:export\\s+)?${name}\\s*=\\s*(?:'[^']*'|"[^"]*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*`, "gm");
553
+ if (pattern.test(content))
554
+ content = content.replace(pattern, () => line);
555
+ else
556
+ content = `${content.trimEnd()}
557
+ ${line}
558
+ `;
559
+ }
560
+ writeFileSync(path, content, { mode: privateFile ? 384 : 420 });
561
+ if (privateFile)
562
+ chmodSync(path, 384);
563
+ }
564
+ function removeOverrides(path, names, privateFile = true) {
565
+ if (!existsSync(path) || names.length === 0)
566
+ return;
567
+ let content = readFileSync(path, "utf8");
568
+ for (const name of names)
569
+ content = content.replace(new RegExp(`^(?:export\\s+)?${name}\\s*=\\s*(?:'[^']*'|"[^"]*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*(?:\\r?\\n|$)`, "gm"), "");
570
+ writeFileSync(path, content, { mode: privateFile ? 384 : 420 });
571
+ chmodSync(path, privateFile ? 384 : 420);
572
+ }
573
+ function saveDeploymentEnvironment(environment, values, options = {}) {
574
+ try {
575
+ const shared = {};
576
+ const privateValues = {};
577
+ for (const [name, value] of Object.entries(values)) {
578
+ if (options.private) {
579
+ privateValues[name] = value;
580
+ } else if (isSecret(name, value)) {
581
+ privateValues[name] = value;
582
+ try {
583
+ const parsed = JSON.parse(value);
584
+ if (parsed && typeof parsed === "object")
585
+ shared[name] = JSON.stringify(publicConfig(parsed));
586
+ } catch {
587
+ }
588
+ } else {
589
+ shared[name] = value;
590
+ }
591
+ }
592
+ if (Object.keys(shared).length)
593
+ updateFile(join(environment.envDir, `.env.${environment.environment}`), shared, false);
594
+ if (Object.keys(privateValues).length)
595
+ updateFile(join(environment.envDir, ".env.local"), privateValues, true);
596
+ removeOverrides(join(environment.envDir, `.env.${environment.environment}`), Object.keys(privateValues).filter((name) => !(name in shared)), false);
597
+ removeOverrides(join(environment.envDir, ".env.local"), Object.keys(shared).filter((name) => !(name in privateValues)));
598
+ if (Object.keys(privateValues).length) {
599
+ const ignorePath = join(environment.envDir, ".gitignore");
600
+ const ignore = existsSync(ignorePath) ? readFileSync(ignorePath, "utf8") : "";
601
+ if (!ignore.split(/\r?\n/).includes(".env.local"))
602
+ writeFileSync(ignorePath, `${ignore.trimEnd()}
603
+ .env.local
604
+ .hai/deploy/
605
+ `);
606
+ }
607
+ return ok(Object.keys(values));
608
+ } catch {
609
+ return err(HaiDeployError.ENV_VAR_FAILED, deployM("deploy_environmentWriteFailed"));
610
+ }
611
+ }
612
+ var logger = core.logger.child({ module: "deploy", scope: "credentials" });
613
+ var DEPLOY_CREDENTIAL_KEY_PATTERN = /^HAI_DEPLOY_[A-Z0-9_]+$/;
614
+ function isAllowedCredentialKey(key) {
615
+ return DEPLOY_CREDENTIAL_KEY_PATTERN.test(key);
616
+ }
617
+ function normalizeCredentialEntries(entries) {
618
+ const normalized = {};
619
+ for (const [key, value] of Object.entries(entries)) {
620
+ if (!isAllowedCredentialKey(key)) {
621
+ logger.warn("Ignoring unsupported deploy credential key", { key });
622
+ continue;
623
+ }
624
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
625
+ normalized[key] = String(value);
626
+ continue;
627
+ }
628
+ logger.warn("Ignoring deploy credential with non-scalar value", { key });
629
+ }
630
+ return normalized;
631
+ }
632
+ function assertAllowedCredentialKey(key) {
633
+ if (!isAllowedCredentialKey(key)) {
634
+ throw new Error(deployM("deploy_credentialInvalidKey", { params: { key } }));
635
+ }
636
+ }
637
+ function assertAllowedCredentialEntries(entries) {
638
+ for (const key of Object.keys(entries)) {
639
+ assertAllowedCredentialKey(key);
640
+ }
641
+ }
642
+ function getCredentialsPath() {
643
+ return join(homedir(), ".hai", "credentials.yml");
644
+ }
645
+ function ensureDir(filePath) {
646
+ const dir = dirname(filePath);
647
+ if (!existsSync(dir)) {
648
+ mkdirSync(dir, { recursive: true });
649
+ }
650
+ }
651
+ function readCredentialsFile() {
652
+ const filePath = getCredentialsPath();
653
+ if (!existsSync(filePath)) {
654
+ return {};
655
+ }
656
+ const content = readFileSync(filePath, "utf-8");
657
+ const parsed = parse(content);
658
+ if (parsed === null || parsed === void 0 || typeof parsed !== "object" || Array.isArray(parsed)) {
659
+ return {};
660
+ }
661
+ return normalizeCredentialEntries(parsed);
662
+ }
663
+ function writeCredentialsFile(data) {
664
+ const filePath = getCredentialsPath();
665
+ ensureDir(filePath);
666
+ const header = "# Managed by hai cli - do not edit manually\n";
667
+ const content = header + stringify(data);
668
+ writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
669
+ }
670
+ function loadCredentials() {
671
+ try {
672
+ const data = readCredentialsFile();
673
+ const keys = Object.keys(data);
674
+ for (const key of keys) {
675
+ process3.env[key] = String(data[key]);
676
+ }
677
+ logger.debug("Credentials loaded", { count: keys.length });
678
+ return ok(keys);
679
+ } catch (error) {
680
+ logger.error("Failed to load credentials", { error });
681
+ return err(
682
+ HaiDeployError.CREDENTIAL_ERROR,
683
+ deployM("deploy_credentialError", {
684
+ params: { error: error instanceof Error ? error.message : String(error) }
685
+ }),
686
+ error
687
+ );
688
+ }
689
+ }
690
+ function saveCredential(key, value) {
691
+ try {
692
+ assertAllowedCredentialKey(key);
693
+ const data = readCredentialsFile();
694
+ data[key] = value;
695
+ writeCredentialsFile(data);
696
+ process3.env[key] = value;
697
+ logger.debug("Credential saved", { key });
698
+ return ok(void 0);
699
+ } catch (error) {
700
+ logger.error("Failed to save credential", { key, error });
701
+ return err(
702
+ HaiDeployError.CREDENTIAL_ERROR,
703
+ deployM("deploy_credentialError", {
704
+ params: { error: error instanceof Error ? error.message : String(error) }
705
+ }),
706
+ error
707
+ );
708
+ }
709
+ }
710
+ function saveCredentials(entries) {
711
+ try {
712
+ assertAllowedCredentialEntries(entries);
713
+ const data = readCredentialsFile();
714
+ for (const [key, value] of Object.entries(entries)) {
715
+ data[key] = value;
716
+ process3.env[key] = value;
717
+ }
718
+ writeCredentialsFile(data);
719
+ logger.debug("Credentials saved", { count: Object.keys(entries).length });
720
+ return ok(void 0);
721
+ } catch (error) {
722
+ logger.error("Failed to save credentials", { error });
723
+ return err(
724
+ HaiDeployError.CREDENTIAL_ERROR,
725
+ deployM("deploy_credentialError", {
726
+ params: { error: error instanceof Error ? error.message : String(error) }
727
+ }),
728
+ error
729
+ );
730
+ }
731
+ }
732
+ function resolveProjectDirectory(appDir) {
733
+ let directory = resolve(appDir);
734
+ while (true) {
735
+ if (existsSync(join(directory, "hai.build.json")) || existsSync(join(directory, "pnpm-lock.yaml")))
736
+ return directory;
737
+ const parent = dirname(directory);
738
+ if (parent === directory)
739
+ return resolve(appDir);
740
+ directory = parent;
741
+ }
742
+ }
743
+ function resolveArtifactDirectory(appDir) {
744
+ return join(resolveProjectDirectory(appDir), ".hai/build/docker");
745
+ }
746
+ function usesDefaultMiddleware(scan, service, options) {
747
+ const requirements = scan.middlewares?.filter((item) => item.service === service) ?? [];
748
+ return requirements.length ? requirements.some((item) => (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[service] ?? "default") === "default") : (options?.serviceModes?.[service] ?? "default") === "default";
749
+ }
750
+ function resolveOutputDir(appDir, providerType) {
751
+ switch (providerType) {
752
+ case "vercel":
753
+ return join(appDir, ".vercel", "output");
754
+ default:
755
+ return join(appDir, "build");
756
+ }
757
+ }
758
+ var RecoverySchema = external_exports.object({
759
+ projectName: external_exports.string(),
760
+ stage: external_exports.string(),
761
+ platformProjectId: external_exports.string().optional(),
762
+ resources: external_exports.array(external_exports.object({
763
+ serviceType: external_exports.enum(["db", "cache", "storage", "email", "sms"]),
764
+ provisionerName: external_exports.string(),
765
+ resourceInfo: external_exports.string(),
766
+ resourceStatus: external_exports.enum(["created", "reused", "unknown"])
767
+ }))
768
+ });
769
+ var CommandStateSchema = external_exports.object({
770
+ reason: external_exports.enum(["timeout", "output", "input", "spawn", "exit", "configuration"]),
771
+ localTermination: external_exports.enum(["confirmed", "unknown", "not-started"]),
772
+ remoteExecution: external_exports.enum(["unknown", "finished"]).optional(),
773
+ deploymentId: external_exports.string().optional(),
774
+ resultFile: external_exports.string().optional(),
775
+ remoteExitCode: external_exports.number().int().optional(),
776
+ remoteStage: external_exports.string().optional()
777
+ });
778
+ function getDeployCommandState(error) {
779
+ const parsed = CommandStateSchema.safeParse(error.ext?.commandExecution);
780
+ return parsed.success ? parsed.data : void 0;
781
+ }
782
+ function getDeployRecovery(error) {
783
+ const parsed = RecoverySchema.safeParse(error.ext?.recovery);
784
+ return parsed.success ? parsed.data : void 0;
785
+ }
786
+ function recoveryFailure(error, recovery) {
787
+ return err({
788
+ code: error.code,
789
+ httpStatus: error.httpStatus,
790
+ system: error.system,
791
+ module: error.module,
792
+ message: deployM("deploy_recoveryFailure", { params: { stage: recovery.stage } }),
793
+ suggestion: deployM("deploy_recoverySuggestion"),
794
+ ext: { recovery }
795
+ });
796
+ }
797
+ var logger2 = core.logger.child({ module: "deploy", scope: "scanner" });
798
+ function record(value) {
799
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
800
+ }
801
+ function runtimeDependencies(appDir) {
802
+ let root = appDir;
803
+ while (!existsSync(join(root, "pnpm-lock.yaml"))) {
804
+ const parent = dirname(root);
805
+ if (parent === root)
806
+ throw new Error("Missing pnpm lockfile; run pnpm install");
807
+ root = parent;
808
+ }
809
+ const importerKey = relative(root, appDir).replaceAll("\\", "/") || ".";
810
+ const documents = parseAllDocuments(readFileSync(join(root, "pnpm-lock.yaml"), "utf8"));
811
+ if (documents.some((document) => document.errors.length))
812
+ throw new Error("Invalid pnpm lockfile");
813
+ const lock = documents.map((document) => record(document.toJS())).reverse().find((document) => importerKey in record(document.importers));
814
+ if (!lock)
815
+ throw new Error("Application is missing from pnpm lockfile; run pnpm install");
816
+ const importers = record(lock.importers);
817
+ const packages = record(lock.packages);
818
+ const snapshots = record(lock.snapshots);
819
+ const names = /* @__PURE__ */ new Set();
820
+ const visited = /* @__PURE__ */ new Set();
821
+ const visit = (id, node, importer) => {
822
+ if (visited.has(id))
823
+ return;
824
+ visited.add(id);
825
+ const metadata = importer === void 0 ? record(packages[id.split("(")[0]]) : {};
826
+ const peers = record(metadata.peerDependencies);
827
+ for (const [name, reference] of Object.entries({ ...record(node.dependencies), ...record(node.optionalDependencies) })) {
828
+ if (name in peers)
829
+ continue;
830
+ const version = typeof reference === "string" ? reference : record(reference).version;
831
+ if (typeof version !== "string")
832
+ throw new Error("Invalid pnpm dependency reference");
833
+ if (version.startsWith("link:")) {
834
+ if (importer === void 0)
835
+ throw new Error("Invalid workspace dependency");
836
+ const target = relative(root, resolve(root, importer, version.slice(5))).replaceAll("\\", "/") || ".";
837
+ const linked = record(importers[target]);
838
+ if (!Object.keys(linked).length)
839
+ throw new Error("Missing linked workspace importer");
840
+ names.add(name);
841
+ visit(`workspace:${target}`, linked, target);
842
+ continue;
843
+ }
844
+ const key = version in snapshots || version in packages ? version : `${name}@${version}`;
845
+ names.add(key.match(/^(@[^/]+\/[^@]+|[^@]+)@/)?.[1] ?? name);
846
+ const snapshot = record(snapshots[key] ?? packages[key]);
847
+ if (!Object.keys(snapshot).length && !(key in snapshots) && !(key in packages))
848
+ throw new Error("Missing pnpm dependency snapshot");
849
+ visit(key, snapshot);
850
+ }
851
+ };
852
+ visit(`workspace:${importerKey}`, record(importers[importerKey]), importerKey);
853
+ return { runtime: names, build: /* @__PURE__ */ new Set([...names, ...Object.keys(record(record(importers[importerKey]).devDependencies))]), root };
854
+ }
855
+ async function scanApp(inputDir, options) {
856
+ try {
857
+ const appDir = resolve(inputDir);
858
+ const graph = runtimeDependencies(appDir);
859
+ const envDir = options?.envDir ? resolve(options.envDir) : existsSync(join(appDir, ".env")) ? appDir : graph.root;
860
+ const loaded = loadDeploymentEnvironment({ environment: options?.environment ?? "prod", envDir, requireFile: options?.environment !== void 0 });
861
+ if (!loaded.success)
862
+ return loaded;
863
+ const env = loaded.data.values;
864
+ const deps = graph.runtime;
865
+ const configs = {};
866
+ for (const [name, owner] of Object.entries({ db: "reldb", cache: "cache", storage: "storage", vecdb: "vecdb", iam: "iam", reach: "reach" })) {
867
+ configs[name] = {};
868
+ if (!deps.has(`@h-ai/${owner}`))
869
+ continue;
870
+ const configFile = ["yml", "yaml"].map((ext) => join(appDir, "config", `_${name}.${ext}`)).find(existsSync);
871
+ const config = configFile ? loadDeploymentConfig(name, configFile, env) : void 0;
872
+ if (config && !config.success)
873
+ return config;
874
+ configs[name] = config?.data ?? {};
875
+ }
876
+ const middlewares = [];
877
+ const add = (service, backend, configName = service, configPath = [], config = configs[configName] ?? {}, source = `_${configName}.yml`) => {
878
+ middlewares.push({ service, backend, source, configName, configPath, envPrefix: ["HAI", configName, ...configPath].map((part) => part.toUpperCase()).join("_"), config });
879
+ };
880
+ const iam = deps.has("@h-ai/iam");
881
+ if (deps.has("@h-ai/reldb"))
882
+ add("db", String(configs.db.type ?? "sqlite"), "db", [], configs.db, "@h-ai/reldb / _db.yml");
883
+ if (deps.has("@h-ai/cache"))
884
+ add("cache", String(configs.cache.type ?? "memory"), "cache", [], configs.cache, "@h-ai/cache / _cache.yml");
885
+ if (deps.has("@h-ai/storage"))
886
+ add("storage", String(configs.storage.type ?? "local"));
887
+ if (deps.has("@h-ai/vecdb"))
888
+ add("vecdb", String(configs.vecdb.type ?? "lancedb"));
889
+ if (iam) {
890
+ const ldap = record(configs.iam.ldap);
891
+ add("ldap", record(configs.iam.login).ldap === true ? "ldap" : "disabled", "iam", ["ldap"], ldap, "@h-ai/iam (LDAP)");
892
+ }
893
+ const providers = configs.reach.providers;
894
+ if (deps.has("@h-ai/reach") && Array.isArray(providers)) {
895
+ for (const [index, item] of providers.entries()) {
896
+ const provider = record(item);
897
+ const type = String(provider.type ?? "");
898
+ const service = type === "aliyun-sms" || ["console", "api"].includes(type) && provider.name === "sms" ? "sms" : ["smtp", "api", "console"].includes(type) ? "email" : void 0;
899
+ if (service)
900
+ add(service, type, "reach", ["providers", String(index)], provider, "@h-ai/reach");
901
+ }
902
+ }
903
+ if (deps.has("@h-ai/reach") && !middlewares.some((item) => item.configName === "reach"))
904
+ add("email", "console", "reach", ["providers", "0"], { name: "email", type: "console" }, "@h-ai/reach");
905
+ const isSvelteKit = graph.build.has("@sveltejs/kit");
906
+ const scan = {
907
+ appDir,
908
+ envDir,
909
+ middlewares,
910
+ haiModules: [...deps].filter((name) => name.startsWith("@h-ai/")).sort(),
911
+ appName: basename(appDir),
912
+ isSvelteKit,
913
+ adapterInstalled: graph.build.has("@sveltejs/adapter-vercel"),
914
+ nodeAdapterInstalled: graph.build.has("@sveltejs/adapter-node"),
915
+ requiredServices: [...new Set(middlewares.map((item) => item.service))],
916
+ serviceBackends: Object.fromEntries(middlewares.map((item) => [item.service, item.backend]))
917
+ };
918
+ logger2.info("Application scanned", { appName: scan.appName, haiModules: scan.haiModules, requiredServices: scan.requiredServices, serviceBackends: scan.serviceBackends });
919
+ return ok(scan);
920
+ } catch {
921
+ return err(HaiDeployError.SCAN_FAILED, deployM("deploy_scanFailed", { params: { error: "Invalid or ambiguous application/configuration/lockfile; specify the application directory and run pnpm install" } }));
922
+ }
923
+ }
924
+ var nativeJob = String.raw`
925
+ using System;
926
+ using System.ComponentModel;
927
+ using System.Runtime.InteropServices;
928
+ using System.Text;
929
+ using System.Threading;
930
+
931
+ public static class HaiCommandJob {
932
+ [StructLayout(LayoutKind.Sequential)] struct BasicLimits {
933
+ public long ProcessTime, JobTime;
934
+ public uint Flags;
935
+ public UIntPtr MinimumWorkingSet, MaximumWorkingSet;
936
+ public uint ActiveProcessLimit;
937
+ public UIntPtr Affinity;
938
+ public uint PriorityClass, SchedulingClass;
939
+ }
940
+ [StructLayout(LayoutKind.Sequential)] struct IoCounters {
941
+ public ulong ReadOperations, WriteOperations, OtherOperations, ReadBytes, WriteBytes, OtherBytes;
942
+ }
943
+ [StructLayout(LayoutKind.Sequential)] struct ExtendedLimits {
944
+ public BasicLimits Basic;
945
+ public IoCounters Io;
946
+ public UIntPtr ProcessMemory, JobMemory, PeakProcessMemory, PeakJobMemory;
947
+ }
948
+ [StructLayout(LayoutKind.Sequential)] struct Accounting {
949
+ public long UserTime, KernelTime, PeriodUserTime, PeriodKernelTime;
950
+ public uint Faults, Total, Active, Terminated;
951
+ }
952
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct Startup {
953
+ public uint Size;
954
+ public string Reserved, Desktop, Title;
955
+ public uint X, Y, Width, Height, XChars, YChars, Fill, Flags;
956
+ public ushort Show, ReservedSize;
957
+ public IntPtr ReservedData, Input, Output, Error;
958
+ }
959
+ [StructLayout(LayoutKind.Sequential)] struct ProcessInfo {
960
+ public IntPtr Process, Thread;
961
+ public uint ProcessId, ThreadId;
962
+ }
963
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr CreateJobObject(IntPtr attributes, string name);
964
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr OpenJobObject(uint access, bool inherit, string name);
965
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetInformationJobObject(IntPtr job, int kind, ref ExtendedLimits information, uint size);
966
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool QueryInformationJobObject(IntPtr job, int kind, out Accounting information, uint size, IntPtr length);
967
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
968
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool CreateProcess(string application, StringBuilder command, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref Startup startup, out ProcessInfo information);
969
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint ResumeThread(IntPtr thread);
970
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateProcess(IntPtr process, uint code);
971
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateJobObject(IntPtr job, uint code);
972
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
973
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeProcess(IntPtr process, out uint code);
974
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle);
975
+ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int kind);
976
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
977
+
978
+ static void Check(bool success) { if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); }
979
+ static uint Active(IntPtr job) {
980
+ Accounting information;
981
+ Check(QueryInformationJobObject(job, 1, out information, (uint)Marshal.SizeOf(typeof(Accounting)), IntPtr.Zero));
982
+ return information.Active;
983
+ }
984
+ // Windows argv 编码:引号前与结尾的反斜杠加倍,其余字符原样传递,不经过命令 shell。
985
+ static string Quote(string value) {
986
+ StringBuilder result = new StringBuilder("\"");
987
+ int slashes = 0;
988
+ foreach (char current in value) {
989
+ if (current == '\\') { slashes++; continue; }
990
+ if (current == '"') result.Append('\\', slashes * 2 + 1);
991
+ else result.Append('\\', slashes);
992
+ result.Append(current);
993
+ slashes = 0;
994
+ }
995
+ result.Append('\\', slashes * 2).Append('"');
996
+ return result.ToString();
997
+ }
998
+ public static int Run(string name, string command, string[] arguments) {
999
+ IntPtr job = CreateJobObject(IntPtr.Zero, name);
1000
+ if (job == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
1001
+ ProcessInfo process = new ProcessInfo();
1002
+ bool assigned = false;
1003
+ try {
1004
+ ExtendedLimits limits = new ExtendedLimits();
1005
+ limits.Basic.Flags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;不允许 breakaway。
1006
+ Check(SetInformationJobObject(job, 9, ref limits, (uint)Marshal.SizeOf(typeof(ExtendedLimits))));
1007
+ StringBuilder line = new StringBuilder(Quote(command));
1008
+ foreach (string argument in arguments) line.Append(' ').Append(Quote(argument));
1009
+ Startup startup = new Startup();
1010
+ startup.Size = (uint)Marshal.SizeOf(typeof(Startup));
1011
+ startup.Flags = 0x100;
1012
+ startup.Input = GetStdHandle(-10); startup.Output = GetStdHandle(-11); startup.Error = GetStdHandle(-12);
1013
+ Check(SetHandleInformation(startup.Input, 1, 1));
1014
+ Check(SetHandleInformation(startup.Output, 1, 1));
1015
+ Check(SetHandleInformation(startup.Error, 1, 1));
1016
+ Check(CreateProcess(null, line, IntPtr.Zero, IntPtr.Zero, true, 0x08000004, IntPtr.Zero, null, ref startup, out process));
1017
+ Check(AssignProcessToJobObject(job, process.Process));
1018
+ assigned = true;
1019
+ if (ResumeThread(process.Thread) == 0xFFFFFFFF) throw new Win32Exception(Marshal.GetLastWin32Error());
1020
+ if (WaitForSingleObject(process.Process, 0xFFFFFFFF) != 0) throw new Win32Exception(Marshal.GetLastWin32Error());
1021
+ uint code;
1022
+ Check(GetExitCodeProcess(process.Process, out code));
1023
+ while (Active(job) != 0) Thread.Sleep(20);
1024
+ return unchecked((int)code);
1025
+ }
1026
+ finally {
1027
+ if (process.Process != IntPtr.Zero) {
1028
+ // 分配失败时进程仍挂起,直接终止,不允许裸进程恢复;Job 在其他异常时同样清空。
1029
+ if (!assigned) TerminateProcess(process.Process, 125);
1030
+ else TerminateJobObject(job, 125);
1031
+ WaitForSingleObject(process.Process, 5000);
1032
+ }
1033
+ if (process.Thread != IntPtr.Zero) CloseHandle(process.Thread);
1034
+ if (process.Process != IntPtr.Zero) CloseHandle(process.Process);
1035
+ CloseHandle(job);
1036
+ }
1037
+ }
1038
+ public static bool Empty(string name) {
1039
+ IntPtr job = OpenJobObject(4, false, name); // JOB_OBJECT_QUERY
1040
+ if (job == IntPtr.Zero) {
1041
+ if (Marshal.GetLastWin32Error() == 2) return true;
1042
+ throw new Win32Exception(Marshal.GetLastWin32Error());
1043
+ }
1044
+ try { return Active(job) == 0; }
1045
+ finally { CloseHandle(job); }
1046
+ }
1047
+ }
1048
+ `;
1049
+ function powershell(script) {
1050
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
1051
+ }
1052
+ function windowsCommand(command, args) {
1053
+ const name = `Local\\hai-deploy-${randomUUID()}`;
1054
+ const key = `__HAI_DEPLOY_JOB_${randomUUID().replaceAll("-", "")}`;
1055
+ const script = `$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1056
+ try {
1057
+ Add-Type -TypeDefinition @'
1058
+ ${nativeJob}
1059
+ '@
1060
+ $inputConfig = [Environment]::GetEnvironmentVariable('${key}') | ConvertFrom-Json
1061
+ [Environment]::SetEnvironmentVariable('${key}', $null)
1062
+ exit ([HaiCommandJob]::Run([string]$inputConfig.name, [string]$inputConfig.command, [string[]]$inputConfig.args))
1063
+ } catch { [Console]::Error.WriteLine('Windows command job failed'); exit 125 }`;
1064
+ return { name, args: powershell(script), env: { [key]: JSON.stringify({ name, command, args }) } };
1065
+ }
1066
+ function windowsJobProbe(name) {
1067
+ return powershell(`$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1068
+ try {
1069
+ Add-Type -TypeDefinition @'
1070
+ ${nativeJob}
1071
+ '@
1072
+ if ([HaiCommandJob]::Empty('${name}')) { exit 0 } else { exit 1 }
1073
+ } catch { exit 2 }`);
1074
+ }
1075
+
1076
+ // ../deploy/src/container/deploy-command-process.ts
1077
+ async function groupExited(pid) {
1078
+ try {
1079
+ process3.kill(-pid, 0);
1080
+ } catch (error) {
1081
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
1082
+ }
1083
+ if (process3.platform !== "linux")
1084
+ return false;
1085
+ try {
1086
+ for (const entry of await readdir("/proc")) {
1087
+ if (!/^\d+$/.test(entry))
1088
+ continue;
1089
+ let stat;
1090
+ try {
1091
+ stat = await readFile(`/proc/${entry}/stat`, "utf8");
1092
+ } catch (error) {
1093
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
1094
+ continue;
1095
+ return false;
1096
+ }
1097
+ const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
1098
+ if (Number(fields[2]) === pid && fields[0] !== "Z" && fields[0] !== "X")
1099
+ return false;
1100
+ }
1101
+ return true;
1102
+ } catch {
1103
+ return false;
1104
+ }
1105
+ }
1106
+ async function helper(command, args, timeoutMs) {
1107
+ if (timeoutMs <= 0)
1108
+ return false;
1109
+ return new Promise((resolve6) => {
1110
+ const child = spawn(command, args, { windowsHide: true, shell: false, stdio: "ignore" });
1111
+ let settled = false;
1112
+ let timer;
1113
+ const finish = (success) => {
1114
+ if (settled)
1115
+ return;
1116
+ settled = true;
1117
+ if (timer)
1118
+ clearTimeout(timer);
1119
+ resolve6(success);
1120
+ };
1121
+ timer = globalThis.setTimeout(() => {
1122
+ child.kill("SIGKILL");
1123
+ child.unref();
1124
+ finish(false);
1125
+ }, timeoutMs);
1126
+ child.once("error", () => finish(false));
1127
+ child.once("close", (code) => finish(code === 0));
1128
+ });
1129
+ }
1130
+ function spawnCommand(command, args, options) {
1131
+ const job = process3.platform === "win32" ? windowsCommand(command, args) : void 0;
1132
+ const child = spawn(job ? "powershell.exe" : command, job?.args ?? args, {
1133
+ ...options,
1134
+ env: job ? { ...options.env, ...job.env } : options.env,
1135
+ windowsHide: true,
1136
+ shell: false,
1137
+ detached: !job
1138
+ });
1139
+ let exited = false;
1140
+ child.once("exit", () => {
1141
+ exited = true;
1142
+ });
1143
+ child.once("close", () => {
1144
+ exited = true;
1145
+ });
1146
+ let termination;
1147
+ const treeExited = async () => {
1148
+ if (!child.pid)
1149
+ return true;
1150
+ if (job)
1151
+ return exited && helper("powershell.exe", windowsJobProbe(job.name), 5e3);
1152
+ return groupExited(child.pid);
1153
+ };
1154
+ const terminate = (timeoutMs) => {
1155
+ termination ??= (async () => {
1156
+ const deadline = performance.now() + timeoutMs;
1157
+ let windowsKillConfirmed = !job || exited;
1158
+ if (child.pid) {
1159
+ if (job) {
1160
+ if (!exited) {
1161
+ windowsKillConfirmed = await helper("taskkill", ["/pid", String(child.pid), "/t", "/f"], Math.max(1, deadline - performance.now()));
1162
+ child.kill("SIGKILL");
1163
+ }
1164
+ } else {
1165
+ try {
1166
+ process3.kill(-child.pid, "SIGKILL");
1167
+ } catch {
1168
+ }
1169
+ }
1170
+ }
1171
+ while (performance.now() < deadline) {
1172
+ if (exited)
1173
+ break;
1174
+ await setTimeout$1(20);
1175
+ }
1176
+ let verified = false;
1177
+ if (exited && job) {
1178
+ verified = await helper("powershell.exe", windowsJobProbe(job.name), deadline - performance.now());
1179
+ verified &&= windowsKillConfirmed || child.exitCode !== null;
1180
+ } else if (exited) {
1181
+ while (performance.now() < deadline) {
1182
+ if (await treeExited()) {
1183
+ verified = true;
1184
+ break;
1185
+ }
1186
+ await setTimeout$1(20);
1187
+ }
1188
+ }
1189
+ child.stdin.destroy();
1190
+ child.stdout.destroy();
1191
+ child.stderr.destroy();
1192
+ if (!verified)
1193
+ child.unref();
1194
+ return verified ? "confirmed" : "unknown";
1195
+ })();
1196
+ return termination;
1197
+ };
1198
+ return { child, treeExited, terminate };
1199
+ }
1200
+
1201
+ // ../deploy/src/container/deploy-secret-masker.ts
1202
+ function createSecretMasker(secrets = []) {
1203
+ const values = [...new Set(secrets.filter(Boolean))].sort((a, b) => b.length - a.length);
1204
+ if (values.reduce((total, value) => total + value.length, 0) > 1048576)
1205
+ throw new RangeError("Secret redaction input is too large");
1206
+ const pattern = values.length ? new RegExp(values.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g") : void 0;
1207
+ const tailLength = Math.max(0, (values[0]?.length ?? 1) - 1);
1208
+ let pending = "";
1209
+ let ended = false;
1210
+ function drain(final) {
1211
+ let end = final ? pending.length : Math.max(0, pending.length - tailLength);
1212
+ if (end > 0 && end < pending.length && /[\uD800-\uDBFF]/.test(pending[end - 1]) && /[\uDC00-\uDFFF]/.test(pending[end]))
1213
+ end--;
1214
+ if (end === 0)
1215
+ return "";
1216
+ let cursor = 0;
1217
+ let output = "";
1218
+ if (pattern) {
1219
+ pattern.lastIndex = 0;
1220
+ let match = pattern.exec(pending);
1221
+ while (match && match.index < end) {
1222
+ output += `${pending.slice(cursor, match.index)}***`;
1223
+ cursor = pattern.lastIndex;
1224
+ end = Math.max(end, cursor);
1225
+ match = pattern.exec(pending);
1226
+ }
1227
+ }
1228
+ output += pending.slice(cursor, end);
1229
+ pending = pending.slice(end);
1230
+ return output;
1231
+ }
1232
+ return {
1233
+ write(text) {
1234
+ if (ended)
1235
+ throw new Error("Secret masker has ended");
1236
+ pending += text;
1237
+ return drain(false);
1238
+ },
1239
+ end() {
1240
+ if (ended)
1241
+ return "";
1242
+ ended = true;
1243
+ return drain(true);
1244
+ }
1245
+ };
1246
+ }
1247
+
1248
+ // ../deploy/src/container/deploy-command.ts
1249
+ var logger3 = core.logger.child({ module: "deploy", scope: "command" });
1250
+ function maskSecrets(text, secrets = []) {
1251
+ const masker = createSecretMasker(secrets);
1252
+ return masker.write(text) + masker.end();
1253
+ }
1254
+ async function runCommand(command, args, options = {}) {
1255
+ const failure = (message, state) => err({
1256
+ ...HaiDeployError.REMOTE_COMMAND_FAILED,
1257
+ message: `${message}
1258
+ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state.localTermination === "not-started" ? "deploy_localNotStarted" : "deploy_localUnknown")}`,
1259
+ ext: { commandExecution: state }
1260
+ });
1261
+ try {
1262
+ return await new Promise((resolve6) => {
1263
+ if (typeof command !== "string" || !command || command.includes("\0") || args.some((argument) => typeof argument !== "string" || argument.includes("\0")))
1264
+ throw new TypeError("Invalid command arguments");
1265
+ if (options.input !== void 0 && typeof options.input !== "string")
1266
+ throw new TypeError("Invalid command input");
1267
+ const maxCapture = options.maxCaptureChars ?? 1048576;
1268
+ if (!Number.isSafeInteger(maxCapture) || maxCapture <= 0 || maxCapture > 16777216)
1269
+ throw new RangeError("Invalid command capture limit");
1270
+ const terminationTimeout = options.terminationTimeoutMs ?? 1e4;
1271
+ if (!Number.isSafeInteger(terminationTimeout) || terminationTimeout <= 0 || terminationTimeout > 6e4)
1272
+ throw new RangeError("Invalid command termination timeout");
1273
+ if (options.timeoutMs !== void 0 && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0 || options.timeoutMs > 2147483647))
1274
+ throw new RangeError("Invalid command timeout");
1275
+ const secrets = [...options.secrets ?? []];
1276
+ const safeCommand = maskSecrets(command, secrets);
1277
+ const outputMasker = createSecretMasker(secrets);
1278
+ const errorMasker = createSecretMasker(secrets);
1279
+ const control = spawnCommand(command, args, { cwd: options.cwd, env: options.env ? { ...process3.env, ...options.env } : process3.env });
1280
+ const child = control.child;
1281
+ child.stdout.setEncoding("utf8");
1282
+ child.stderr.setEncoding("utf8");
1283
+ let stdout = "";
1284
+ let stderr = "";
1285
+ let stdoutTruncated = false;
1286
+ let stderrTruncated = false;
1287
+ let settled = false;
1288
+ let interrupted = false;
1289
+ let interruption;
1290
+ let timer;
1291
+ const finish = (result) => {
1292
+ if (settled)
1293
+ return;
1294
+ settled = true;
1295
+ if (timer)
1296
+ clearTimeout(timer);
1297
+ resolve6(result);
1298
+ };
1299
+ const interrupt = (reason, message) => {
1300
+ interrupted = true;
1301
+ interruption ??= (async () => {
1302
+ if (timer)
1303
+ clearTimeout(timer);
1304
+ const localTermination = child.pid ? await control.terminate(terminationTimeout) : "not-started";
1305
+ logger3.error("Command termination checked", { command: safeCommand, reason, localTermination });
1306
+ finish(failure(message, { reason, localTermination }));
1307
+ })();
1308
+ return interruption;
1309
+ };
1310
+ const publish = async (text) => {
1311
+ if (!text || settled || interrupted)
1312
+ return;
1313
+ stdoutTruncated ||= stdout.length + text.length > maxCapture;
1314
+ stdout = (stdout + text).slice(-maxCapture);
1315
+ try {
1316
+ await options.onOutput?.(text);
1317
+ } catch {
1318
+ await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
1319
+ }
1320
+ };
1321
+ const captureError = (text) => {
1322
+ stderrTruncated ||= stderr.length + text.length > maxCapture;
1323
+ stderr = (stderr + text).slice(-maxCapture);
1324
+ };
1325
+ if (options.timeoutMs && options.timeoutMs > 0) {
1326
+ timer = setTimeout(() => {
1327
+ logger3.error("Command timed out", { command: safeCommand, timeoutMs: options.timeoutMs });
1328
+ void interrupt("timeout", deployM("deploy_commandTimeout", { params: { command: safeCommand } }));
1329
+ }, options.timeoutMs);
1330
+ }
1331
+ const outputTask = (async () => {
1332
+ try {
1333
+ for await (const chunk of child.stdout) {
1334
+ if (settled || interrupted)
1335
+ return;
1336
+ if (typeof chunk !== "string")
1337
+ throw new TypeError("Command output must be UTF-8 decoded");
1338
+ await publish(outputMasker.write(chunk));
1339
+ }
1340
+ if (!settled && !interrupted)
1341
+ await publish(outputMasker.end());
1342
+ } catch {
1343
+ await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
1344
+ }
1345
+ })();
1346
+ const errorTask = (async () => {
1347
+ try {
1348
+ for await (const chunk of child.stderr) {
1349
+ if (settled || interrupted)
1350
+ return;
1351
+ if (typeof chunk !== "string")
1352
+ throw new TypeError("Command error output must be UTF-8 decoded");
1353
+ captureError(errorMasker.write(chunk));
1354
+ }
1355
+ if (!settled && !interrupted)
1356
+ captureError(errorMasker.end());
1357
+ } catch {
1358
+ await interrupt("output", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandOutputFailed") } }));
1359
+ }
1360
+ })();
1361
+ child.stdin.on("error", () => {
1362
+ void interrupt("input", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandInputFailed") } }));
1363
+ });
1364
+ child.on("error", (error) => {
1365
+ const safeError = maskSecrets(error.message, secrets);
1366
+ logger3.error("Command failed to start", { command: safeCommand, error: safeError });
1367
+ void interrupt("spawn", deployM("deploy_commandFailed", { params: { command: safeCommand, error: safeError } }));
1368
+ });
1369
+ child.on("close", async (code) => {
1370
+ await Promise.all([outputTask, errorTask]);
1371
+ if (settled || interrupted)
1372
+ return;
1373
+ if (timer)
1374
+ clearTimeout(timer);
1375
+ if (!await control.treeExited()) {
1376
+ await interrupt("exit", deployM("deploy_commandFailed", { params: { command: safeCommand, error: deployM("deploy_commandTreeActive") } }));
1377
+ return;
1378
+ }
1379
+ const exitCode = code ?? -1;
1380
+ if (exitCode === 0) {
1381
+ finish(ok({ code: exitCode, stdout, stderr, stdoutTruncated, stderrTruncated }));
1382
+ return;
1383
+ }
1384
+ logger3.error("Command exited with non-zero code", { command: safeCommand, code: exitCode, stderr: maskSecrets(stderr, secrets).slice(-500), stdout: maskSecrets(stdout, secrets).slice(-3e3), stdoutTruncated, stderrTruncated });
1385
+ finish(failure(deployM("deploy_commandFailed", { params: { command: safeCommand, error: maskSecrets(`${stdout.slice(-2500)}
1386
+ ${stderr || `exit ${exitCode}`}`, secrets).slice(-3e3) } }), { reason: "exit", localTermination: "confirmed" }));
1387
+ });
1388
+ if (options.input !== void 0)
1389
+ child.stdin.write(options.input);
1390
+ child.stdin.end();
1391
+ });
1392
+ } catch {
1393
+ return failure(deployM("deploy_commandFailed", { params: { command: "<command>", error: deployM("deploy_commandExecutionFailed") } }), { reason: "configuration", localTermination: "not-started" });
1394
+ }
1395
+ }
1396
+
1397
+ // ../deploy/src/container/deploy-target.ts
1398
+ function createTargetReader() {
1399
+ return String.raw`function fail(message) {
1400
+ print "Invalid deploy-target.json: " message > "/dev/stderr"
1401
+ exit 2
1402
+ }
1403
+ function whitespace() {
1404
+ while (substr(input, position, 1) ~ /^[ \t\r\n]$/) position++
1405
+ }
1406
+ function hexadecimal(text, result, offset, digit) {
1407
+ result = 0
1408
+ if (length(text) != 4) fail("invalid Unicode escape")
1409
+ for (offset = 1; offset <= 4; offset++) {
1410
+ digit = index("0123456789abcdef", tolower(substr(text, offset, 1))) - 1
1411
+ if (digit < 0) fail("invalid Unicode escape")
1412
+ result = result * 16 + digit
1413
+ }
1414
+ return result
1415
+ }
1416
+ function utf8(code) {
1417
+ if (code < 128) return sprintf("%c", code)
1418
+ if (code < 2048) return sprintf("%c%c", 192 + int(code / 64), 128 + code % 64)
1419
+ if (code < 65536) return sprintf("%c%c%c", 224 + int(code / 4096), 128 + int(code / 64) % 64, 128 + code % 64)
1420
+ return sprintf("%c%c%c%c", 240 + int(code / 262144), 128 + int(code / 4096) % 64, 128 + int(code / 64) % 64, 128 + code % 64)
1421
+ }
1422
+ function string_value( result, character, escaped, code, low) {
1423
+ if (substr(input, position++, 1) != "\"") fail("expected string")
1424
+ result = ""
1425
+ while (position <= length(input)) {
1426
+ character = substr(input, position++, 1)
1427
+ if (character == "\"") return result
1428
+ if (character ~ /[[:cntrl:]]/) fail("control character in string")
1429
+ if (character != "\\") { result = result character; continue }
1430
+ escaped = substr(input, position++, 1)
1431
+ if (escaped == "\"" || escaped == "\\" || escaped == "/") result = result escaped
1432
+ else if (escaped == "b") result = result sprintf("%c", 8)
1433
+ else if (escaped == "f") result = result sprintf("%c", 12)
1434
+ else if (escaped == "n") result = result "\n"
1435
+ else if (escaped == "r") result = result "\r"
1436
+ else if (escaped == "t") result = result "\t"
1437
+ else if (escaped == "u") {
1438
+ code = hexadecimal(substr(input, position, 4)); position += 4
1439
+ if (code >= 55296 && code <= 56319) {
1440
+ if (substr(input, position, 2) != "\\u") fail("missing low surrogate")
1441
+ position += 2
1442
+ low = hexadecimal(substr(input, position, 4)); position += 4
1443
+ if (low < 56320 || low > 57343) fail("invalid low surrogate")
1444
+ code = 65536 + (code - 55296) * 1024 + low - 56320
1445
+ } else if (code >= 56320 && code <= 57343) fail("unexpected low surrogate")
1446
+ if (code == 0) fail("NUL is not allowed")
1447
+ result = result utf8(code)
1448
+ } else fail("invalid escape")
1449
+ }
1450
+ fail("unterminated string")
1451
+ }
1452
+ function object_value(depth, key, character, value, kind) {
1453
+ if (depth > 16) fail("nesting is too deep")
1454
+ position++
1455
+ whitespace()
1456
+ if (substr(input, position, 1) == "}") { position++; return }
1457
+ while (position <= length(input)) {
1458
+ whitespace(); key = string_value(); whitespace()
1459
+ if (substr(input, position++, 1) != ":") fail("expected colon")
1460
+ whitespace(); character = substr(input, position, 1)
1461
+ if (character == "{") { object_value(depth + 1); value = ""; kind = "object" }
1462
+ else if (character == "\"") { value = string_value(); kind = "string" }
1463
+ else if (substr(input, position, 4) == "true") { value = "true"; kind = "boolean"; position += 4 }
1464
+ else if (substr(input, position, 5) == "false") { value = "false"; kind = "boolean"; position += 5 }
1465
+ else if (substr(input, position, 4) == "null") { value = ""; kind = "null"; position += 4 }
1466
+ else if (match(substr(input, position), /^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/)) {
1467
+ value = substr(input, position, RLENGTH); position += RLENGTH; kind = "number"
1468
+ } else fail("unsupported value")
1469
+ if (depth == 1) {
1470
+ if (key in values) fail("duplicate field: " key)
1471
+ values[key] = value; kinds[key] = kind
1472
+ }
1473
+ whitespace(); character = substr(input, position++, 1)
1474
+ if (character == "}") return
1475
+ if (character != ",") fail("expected comma or closing brace")
1476
+ }
1477
+ fail("unterminated object")
1478
+ }
1479
+ { input = input $0 "\n" }
1480
+ END {
1481
+ position = 1
1482
+ if (substr(input, 1, 3) == sprintf("%c%c%c", 239, 187, 191)) position = 4
1483
+ whitespace()
1484
+ if (substr(input, position, 1) != "{") fail("expected object")
1485
+ object_value(1); whitespace()
1486
+ if (position <= length(input)) fail("trailing content")
1487
+ if (!(field in values) || kinds[field] == "null") { print fallback; exit }
1488
+ if (kinds[field] != expected) fail("wrong type for " field)
1489
+ if (values[field] ~ /[[:cntrl:]]/) fail("control character in " field)
1490
+ print values[field]
1491
+ }
1492
+ `;
1493
+ }
1494
+
1495
+ // ../deploy/src/container/deploy-scripts.ts
1496
+ function shellArgument(value) {
1497
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
1498
+ }
1499
+ function createApplyScript(options) {
1500
+ const values = {
1501
+ ID: shellArgument(options.deploymentId),
1502
+ PROJECT: shellArgument(`hai-${options.projectName}`),
1503
+ ENV: shellArgument(options.environment),
1504
+ RUNTIME: shellArgument(options.runtime),
1505
+ PARTIAL: options.partial ? "true" : "false",
1506
+ SELECTED: shellArgument(options.selected.join(" ")),
1507
+ ROOT: shellArgument(options.directory),
1508
+ PORT: String(options.hostPort),
1509
+ HEALTH: options.healthPaths.map(shellArgument).join(" ")
1510
+ };
1511
+ return String.raw`#!/bin/sh
1512
+ set -eu
1513
+ umask 077
1514
+ source_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
1515
+ destination=@@ROOT@@
1516
+ if [ "$#" -gt 0 ]; then destination=$1; fi
1517
+ root=$(mkdir -p -- "$destination" && CDPATH= cd -- "$destination" && pwd)
1518
+ id=@@ID@@
1519
+ project=@@PROJECT@@
1520
+ export HAI_DEPLOY_ENVIRONMENT=@@ENV@@
1521
+ runtime=@@RUNTIME@@
1522
+ partial=@@PARTIAL@@
1523
+ orphan_option=''
1524
+ if [ "$partial" = false ]; then orphan_option=--remove-orphans; fi
1525
+ recreate_option=--force-recreate
1526
+ if [ "$partial" = true ]; then recreate_option=--no-recreate; fi
1527
+ selected=@@SELECTED@@
1528
+ release="$root/releases/$id"
1529
+ case "$release/" in "$source_dir/"*) printf 'Extract the package outside the deployment directory before applying it.\n' >&2; exit 1;; esac
1530
+ stage=''
1531
+ previous=''
1532
+ restore=''
1533
+ changed=false
1534
+ locked=false
1535
+ interrupted=false
1536
+ started=$(date +%s)
1537
+ step_started=$started
1538
+ step() {
1539
+ if [ -n "$stage" ]; then
1540
+ duration=$(( $(date +%s) - step_started ))
1541
+ printf '[PASS] %s (%ss)\n' "$stage" "$duration"
1542
+ printf '{"stage":"%s","status":"passed","durationSeconds":%s}\n' "$stage" "$duration" >> "$root/steps-$id.jsonl"
1543
+ fi
1544
+ stage="$1"
1545
+ step_started=$(date +%s)
1546
+ printf '\n[%s] %s\n' "$stage" "$2"
1547
+ }
1548
+ compose() {
1549
+ arguments_file="$release/compose-files"
1550
+ if [ -n "$restore" ]; then arguments_file="$root/releases/$restore/compose-files"; fi
1551
+ # -f 按顺序合并,由 Compose 保留未选服务、卷和环境文件,不自行拼装配置。
1552
+ command_args="$release/.compose-command"
1553
+ # 文件列表先反向读取,再向参数头部加入,保留原始合并顺序。
1554
+ awk '{lines[NR]=$0} END {for(i=NR;i>0;i--) print lines[i]}' "$arguments_file" > "$command_args"
1555
+ while IFS= read -r file; do set -- -f "$file" "$@"; done < "$command_args"
1556
+ "$runtime" compose -p "$project" "$@"
1557
+ }
1558
+ remove_selected() {
1559
+ test "$partial" = true || return 0
1560
+ # 直接替换选中容器,避免 Compose Provider 连带重建依赖它的未选服务;不删除卷。
1561
+ for service in "$@"; do
1562
+ containers=$("$runtime" ps -aq --filter "label=com.docker.compose.project=$project" --filter "label=com.docker.compose.service=$service") || return 1
1563
+ for container in $containers; do
1564
+ "$runtime" rm -f "$container" || return 1
1565
+ done
1566
+ done
1567
+ }
1568
+ finish() {
1569
+ code=$?
1570
+ trap - EXIT HUP INT TERM
1571
+ if [ "$code" -ne 0 ]; then
1572
+ printf '[FAIL] %s (exit %s)\n' "$stage" "$code" >&2
1573
+ if [ "$changed" = true ]; then
1574
+ diagnostics="$root/diagnostics-$id.log"
1575
+ : > "$diagnostics"
1576
+ for service in $selected; do
1577
+ containers=$("$runtime" ps -aq --filter "label=com.docker.compose.project=$project" --filter "label=com.docker.compose.service=$service" || true)
1578
+ for container in $containers; do
1579
+ "$runtime" inspect --format '{{.Name}} {{.State.Status}} exit={{.State.ExitCode}}' "$container" >> "$diagnostics" 2>&1 || true
1580
+ "$runtime" logs --tail 80 "$container" >> "$diagnostics" 2>&1 || true
1581
+ done
1582
+ done
1583
+ printf 'Startup diagnostics saved to %s (private server file).\n' "$diagnostics" >&2
1584
+ fi
1585
+ if [ -f "$release/rollback-images" ]; then
1586
+ while IFS=' ' read -r image reference; do
1587
+ test -n "$reference" || continue
1588
+ "$runtime" tag "$image" "$reference" || printf '[FAIL] Cannot restore image %s\n' "$reference" >&2
1589
+ done < "$release/rollback-images"
1590
+ fi
1591
+ if [ "$changed" = true ] && [ -n "$previous" ]; then
1592
+ printf '[RECOVER] Restoring previous selected services\n' >&2
1593
+ recover_selected=$selected
1594
+ if [ "$partial" = false ]; then recover_selected=$(grep -v -- '-init$' "$root/releases/$previous/services"); fi
1595
+ restore="$previous"
1596
+ if remove_selected $recover_selected && compose up -d --no-deps --no-build --pull never "$recreate_option" $orphan_option $recover_selected; then
1597
+ case " $selected " in *' app '*)
1598
+ if grep -qx web "$root/releases/$previous/services"; then compose exec -T web nginx -s reload || true; fi
1599
+ ;;
1600
+ esac
1601
+ printf '[RECOVER] Previous containers restored; verify application health\n' >&2
1602
+ else
1603
+ printf '[FAIL] Recovery failed. Use the previous release and inspect container logs.\n' >&2
1604
+ fi
1605
+ elif [ "$changed" = true ]; then
1606
+ compose stop $selected || true
1607
+ printf '[RECOVER] Failed first installation stopped; data volumes retained\n' >&2
1608
+ fi
1609
+ fi
1610
+ if [ "$locked" = true ]; then
1611
+ elapsed=$(( $(date +%s) - started ))
1612
+ outcome=passed
1613
+ if [ "$code" -ne 0 ]; then outcome=failed; fi
1614
+ printf '{"stage":"%s","status":"%s","durationSeconds":%s}\n' "$stage" "$outcome" "$(( $(date +%s) - step_started ))" >> "$root/steps-$id.jsonl"
1615
+ # 先完整写入再同目录原子发布,SSH 重连核对不能读取到半份终态结果。
1616
+ printf '{"deploymentId":"%s","stage":"%s","exitCode":%s,"durationSeconds":%s}\n' "$id" "$stage" "$code" "$elapsed" > "$root/result-$id.json.next"
1617
+ mv "$root/result-$id.json.next" "$root/result-$id.json"
1618
+ # 前台命令及同步恢复已结束;后台服务可继续运行,但不能使成功终态永久占锁。
1619
+ # 中断时仍可能有活跃子命令,必须由全部描述符持有者退出后释放。
1620
+ if [ "$interrupted" = false ]; then
1621
+ flock -u 9
1622
+ exec 9>&-
1623
+ fi
1624
+ fi
1625
+ exit "$code"
1626
+ }
1627
+ trap finish EXIT
1628
+ trap 'interrupted=true; exit 130' HUP INT TERM
1629
+ step preflight 'Checking deployment tools and package'
1630
+ for tool in sha256sum tar awk curl flock; do command -v "$tool" >/dev/null || { printf 'Required command missing: %s\n' "$tool" >&2; exit 1; }; done
1631
+ if [ "$runtime" = auto ]; then
1632
+ if command -v podman >/dev/null 2>&1 && podman version >/dev/null 2>&1; then runtime=podman; else runtime=docker; fi
1633
+ fi
1634
+ case "$runtime" in docker|podman) ;; *) printf 'Runtime must be docker or podman\n' >&2; exit 1;; esac
1635
+ "$runtime" version >/dev/null
1636
+ "$runtime" compose version >/dev/null
1637
+ "$runtime" compose up --help | grep -q never || { printf 'Compose is too old. Install Docker Compose v2 or podman-compose 1.6.0 or newer (supports --pull never).\n' >&2; exit 1; }
1638
+ # 内核锁绑定同一文件描述符;绝不删除/替换锁文件,避免新 inode 绕过活跃部署。
1639
+ # 子命令继承 FD 9:即使父 shell 被强杀,仍执行的命令继续持锁;全部退出或重启后自动释放。
1640
+ exec 9>"$root/.deploy.lock"
1641
+ if flock -n 9; then
1642
+ :
1643
+ else
1644
+ lock_code=$?
1645
+ if [ "$lock_code" -eq 1 ]; then
1646
+ printf 'Another deployment is running. Wait for its commands to finish; do not remove %s/.deploy.lock.\n' "$root" >&2
1647
+ else
1648
+ printf 'Cannot acquire deployment lock (exit %s). Check filesystem flock support and permissions.\n' "$lock_code" >&2
1649
+ fi
1650
+ exit "$lock_code"
1651
+ fi
1652
+ locked=true
1653
+ cd "$source_dir"
1654
+ sha256sum -c SHA256SUMS
1655
+ if [ -f "$root/current" ]; then
1656
+ previous=$(cat "$root/current")
1657
+ case "$previous" in ''|*[!a-zA-Z0-9._-]*) printf 'Invalid deployment state\n' >&2; exit 1;; esac
1658
+ test -f "$root/releases/$previous/compose-files"
1659
+ fi
1660
+ if [ "$partial" = true ]; then
1661
+ test -n "$previous" || { printf 'Partial deployment requires a successful full installation. Deploy a complete bundle first.\n' >&2; exit 1; }
1662
+ cmp -s layout "$root/releases/$previous/layout" || { printf 'Ports, API routes or application layout changed. Create a full deployment bundle.\n' >&2; exit 1; }
1663
+ fi
1664
+ test ! -e "$release" || { printf 'This deployment ID was already used. Create a new deployment bundle.\n' >&2; exit 1; }
1665
+ mkdir -p "$release"
1666
+ cp -R "$source_dir/." "$release/"
1667
+ # Nginx 以非 root 用户运行;只开放不含凭据的代理配置读取权限。
1668
+ if [ -f "$release/web.conf" ]; then chmod 644 "$release/web.conf"; fi
1669
+ : > "$release/compose-files"
1670
+ if [ "$partial" = true ]; then
1671
+ cp "$root/releases/$previous/compose-files" "$release/compose-files"
1672
+ cp "$root/releases/$previous/middleware.env" "$release/middleware.env"
1673
+ for service in $selected; do
1674
+ grep -qx "$service" "$root/releases/$previous/services" || { printf 'Service %s has no installed baseline. Use a full deployment.\n' "$service" >&2; exit 1; }
1675
+ done
1676
+ fi
1677
+ printf '%s\n' "$release/compose-entry.json" >> "$release/compose-files"
1678
+ compose config >/dev/null
1679
+ compose config --services > "$release/services"
1680
+ : > "$release/rollback-images"
1681
+ if [ -n "$previous" ]; then
1682
+ for service in $selected; do
1683
+ containers=$("$runtime" ps -aq --filter "label=com.docker.compose.project=$project" --filter "label=com.docker.compose.service=$service")
1684
+ for container in $containers; do
1685
+ "$runtime" inspect --format '{{.Image}} {{.Config.Image}}' "$container" >> "$release/rollback-images"
1686
+ done
1687
+ done
1688
+ fi
1689
+ step images 'Loading packaged images (no registry access)'
1690
+ while IFS=' ' read -r archive reference; do
1691
+ test -n "$archive" || continue
1692
+ "$runtime" load -i "$release/images/$archive"
1693
+ "$runtime" image inspect "$reference" >/dev/null
1694
+ architecture=$("$runtime" image inspect --format '{{.Architecture}}' "$reference")
1695
+ host_arch=$(uname -m)
1696
+ case "$host_arch:$architecture" in x86_64:amd64|aarch64:arm64) ;; *) printf 'Image architecture %s does not match server %s\n' "$architecture" "$host_arch" >&2; exit 1;; esac
1697
+ done < "$release/image-list"
1698
+ step dependencies 'Checking dependencies of selected services'
1699
+ if [ "$partial" = true ]; then cat "$release/services" >> "$release/dependencies"; fi
1700
+ while IFS= read -r dependency; do
1701
+ test -n "$dependency" || continue
1702
+ case "$dependency" in *-init) continue;; esac
1703
+ case " $selected " in *" $dependency "*) continue;; esac
1704
+ running=$("$runtime" ps -q --filter "label=com.docker.compose.project=$project" --filter "label=com.docker.compose.service=$dependency")
1705
+ test -n "$running" || { printf 'Dependency %s is not running. Include it in a full deployment.\n' "$dependency" >&2; exit 1; }
1706
+ for container in $running; do
1707
+ test "$("$runtime" inspect --format '{{.State.Running}}' "$container")" = true || { printf 'Dependency %s is stopped. Start it before retrying.\n' "$dependency" >&2; exit 1; }
1708
+ done
1709
+ done < "$release/dependencies"
1710
+ step start 'Updating selected services'
1711
+ changed=true
1712
+ remove_selected $selected
1713
+ if [ -f "$release/initialize-storage" ]; then
1714
+ remove_selected storage-init
1715
+ compose up -d --no-deps --no-build --pull never storage
1716
+ compose up --no-deps --no-build --pull never "$recreate_option" --exit-code-from storage-init storage-init
1717
+ fi
1718
+ compose up -d --no-deps --no-build --pull never "$recreate_option" $orphan_option $selected
1719
+ case " $selected " in
1720
+ *' app '*)
1721
+ case " $selected " in *' web '*) ;; *)
1722
+ if grep -qx web "$release/services"; then compose exec -T web nginx -s reload; fi
1723
+ esac
1724
+ ;;
1725
+ esac
1726
+ step health 'Checking HTTP access'
1727
+ for path in @@HEALTH@@; do
1728
+ ready=false
1729
+ attempt=0
1730
+ while [ "$attempt" -lt 40 ]; do
1731
+ if curl --noproxy '*' -fsS --max-time 5 "http://127.0.0.1:@@PORT@@$path" >/dev/null 2>&1; then ready=true; break; fi
1732
+ attempt=$((attempt + 1))
1733
+ sleep 3
1734
+ done
1735
+ test "$ready" = true || { printf 'HTTP check failed: %s. Inspect compose logs for selected services.\n' "$path" >&2; exit 1; }
1736
+ done
1737
+ step complete 'Deployment passed all checks'
1738
+ printf '%s\n' "$id" > "$root/current.next"
1739
+ mv "$root/current.next" "$root/current"
1740
+ printf '[PASS] %s\n' "$id"
1741
+ `.replace(/@@([A-Z]+)@@/g, (_match, name) => values[name]);
1742
+ }
1743
+ function createUploadScripts() {
1744
+ const sh = String.raw`#!/bin/sh
1745
+ set -eu
1746
+ source_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
1747
+ cd "$source_dir/.."
1748
+ command -v awk >/dev/null || { printf 'Install POSIX awk to read deploy-target.json.\n' >&2; exit 1; }
1749
+ read_target() {
1750
+ LC_ALL=C awk -v field="$1" -v expected="$2" -v fallback="$3" -f "$source_dir/read-target.awk" "$source_dir/deploy-target.json"
1751
+ }
1752
+ host=$(read_target host string SERVER_HOST)
1753
+ user=$(read_target username string deploy)
1754
+ port=$(read_target port number 22)
1755
+ remote=$(read_target directory string '')
1756
+ bundle=$(read_target bundle string '')
1757
+ identity=$(read_target identityFile string '')
1758
+ known_hosts=$(read_target knownHostsFile string '')
1759
+ strict=$(read_target strictHostKeyChecking boolean true)
1760
+ timeout=$(read_target connectTimeoutSeconds number 10)
1761
+ if [ "$#" -ge 1 ]; then host=$1; fi
1762
+ if [ "$#" -ge 2 ]; then port=$2; fi
1763
+ if [ "$#" -ge 3 ]; then user=$3; fi
1764
+ if value=$(printenv HAI_DEPLOY_IDENTITY_FILE) && [ -n "$value" ]; then identity=$value; fi
1765
+ if value=$(printenv HAI_DEPLOY_KNOWN_HOSTS_FILE) && [ -n "$value" ]; then known_hosts=$value; fi
1766
+ invalid_target() { printf 'Invalid deployment target. Edit deploy-target.json before running this script.\n' >&2; exit 1; }
1767
+ test "$host" != SERVER_HOST || { printf 'Specify the server: sh deploy.sh SERVER [PORT] [USER].\n' >&2; exit 1; }
1768
+ case "$host" in ''|*[!a-zA-Z0-9.:-]*|[!a-zA-Z0-9]*) invalid_target;; esac
1769
+ case "$user" in ''|*[!a-zA-Z0-9_-]*|[!a-zA-Z_]*) invalid_target;; esac
1770
+ case "$port" in ''|*[!0-9]*|???????????*) invalid_target;; esac
1771
+ test "$port" -ge 1 && test "$port" -le 65535 || invalid_target
1772
+ case "$timeout" in ''|*[!0-9]*|???????????*) invalid_target;; esac
1773
+ test "$timeout" -ge 1 && test "$timeout" -le 2147483647 || invalid_target
1774
+ case "$remote" in /*) ;; *) invalid_target;; esac
1775
+ case "$remote" in *[!a-zA-Z0-9/_.\ -]*|*/../*|*/..|/..) invalid_target;; esac
1776
+ case "$bundle" in *.tar) ;; *) invalid_target;; esac
1777
+ case "$bundle" in ''|*[!a-zA-Z0-9_.+-]*) invalid_target;; esac
1778
+ case "$strict" in true) strict=accept-new;; false) strict=no;; *) invalid_target;; esac
1779
+ test -f "./$bundle" || { printf 'Bundle missing: %s\n' "$bundle" >&2; exit 1; }
1780
+ for tool in ssh scp; do command -v "$tool" >/dev/null || { printf 'Install OpenSSH (%s).\n' "$tool" >&2; exit 1; }; done
1781
+ set -- -o BatchMode=yes -o "StrictHostKeyChecking=$strict" -o "ConnectTimeout=$timeout"
1782
+ if [ -n "$identity" ]; then set -- "$@" -i "$identity" -o IdentitiesOnly=yes; fi
1783
+ if [ -n "$known_hosts" ]; then
1784
+ known_hosts=$(printf '%s' "$known_hosts" | sed 's/[\\"]/\\&/g')
1785
+ set -- "$@" -o "UserKnownHostsFile=\"$known_hosts\""
1786
+ fi
1787
+ printf '[1/3] Connecting to %s\n' "$host"
1788
+ ssh "$@" -p "$port" "$user@$host" "mkdir -p '$remote/incoming' && chmod 700 '$remote' '$remote/incoming'"
1789
+ printf '[2/3] Uploading %s\n' "$bundle"
1790
+ scp_host=$host
1791
+ case "$host" in *:*) scp_host="[$host]";; esac
1792
+ scp "$@" -P "$port" "./$bundle" "$user@$scp_host:$remote/incoming/$bundle"
1793
+ printf '[3/3] Applying package and checking HTTP access\n'
1794
+ if ssh "$@" -p "$port" "$user@$host" "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"; then
1795
+ :
1796
+ else
1797
+ code=$?
1798
+ printf '[UNKNOWN] Remote execution may continue. Inspect %s/result-<deploymentId>.json (ID in manifest.json), current and containers before retrying.\n' "$remote" >&2
1799
+ exit "$code"
1800
+ fi
1801
+ `;
1802
+ const ps1 = String.raw`param([string]$IdentityFile = $env:HAI_DEPLOY_IDENTITY_FILE)
1803
+ $ErrorActionPreference = 'Stop'
1804
+ Set-Location -LiteralPath (Split-Path -Parent $PSScriptRoot)
1805
+ $target = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'deploy-target.json') -Raw -Encoding UTF8 | ConvertFrom-Json
1806
+ if ($target.host -eq 'SERVER_HOST') { throw 'Set the server host in deploy-target.json.' }
1807
+ if ($target.host -isnot [string] -or $target.host -notmatch '^[a-zA-Z0-9][a-zA-Z0-9.:-]*$' -or $target.username -isnot [string] -or $target.username -notmatch '^[a-zA-Z_][a-zA-Z0-9_-]*$' -or $target.directory -isnot [string] -or $target.directory -notmatch '^/[a-zA-Z0-9/_. -]*$' -or $target.directory -match '(^|/)\.\.(/|$)' -or $target.bundle -isnot [string] -or $target.bundle -notmatch '^[a-zA-Z0-9_.+-]+\.tar$') { throw 'Invalid deployment target.' }
1808
+ $port = if ($null -eq $target.port) { 22 } else { $target.port }
1809
+ $timeout = if ($null -eq $target.connectTimeoutSeconds) { 10 } else { $target.connectTimeoutSeconds }
1810
+ if ($port -is [string] -or [string]$port -notmatch '^[0-9]+$' -or $port -lt 1 -or $port -gt 65535 -or $timeout -is [string] -or [string]$timeout -notmatch '^[0-9]+$' -or $timeout -lt 1 -or $timeout -gt 2147483647) { throw 'Invalid deployment port or timeout.' }
1811
+ if ($null -ne $target.strictHostKeyChecking -and $target.strictHostKeyChecking -isnot [bool]) { throw 'Invalid host key policy.' }
1812
+ if (($null -ne $target.identityFile -and $target.identityFile -isnot [string]) -or ($null -ne $target.knownHostsFile -and $target.knownHostsFile -isnot [string])) { throw 'Invalid SSH file path.' }
1813
+ $strict = if ($target.strictHostKeyChecking -eq $false) { 'no' } else { 'accept-new' }
1814
+ $options = @('-o', 'BatchMode=yes', '-o', "StrictHostKeyChecking=$strict", '-o', "ConnectTimeout=$timeout")
1815
+ if (-not $IdentityFile) { $IdentityFile = $target.identityFile }
1816
+ if ($IdentityFile) { $options += @('-i', $IdentityFile, '-o', 'IdentitiesOnly=yes') }
1817
+ $knownHosts = if ($env:HAI_DEPLOY_KNOWN_HOSTS_FILE) { $env:HAI_DEPLOY_KNOWN_HOSTS_FILE } else { $target.knownHostsFile }
1818
+ if ($knownHosts) {
1819
+ $knownHosts = $knownHosts.Replace('\', '\\').Replace('"', '\"')
1820
+ $options += @('-o', ('UserKnownHostsFile="' + $knownHosts + '"'))
1821
+ }
1822
+ $destination = "$($target.username)@$($target.host)"
1823
+ $scpDestination = if ($target.host.Contains(':')) { "$($target.username)@[$($target.host)]" } else { $destination }
1824
+ $remote = $target.directory
1825
+ $bundle = $target.bundle
1826
+ if (-not (Test-Path -LiteralPath "./$bundle" -PathType Leaf)) { throw "Bundle missing: $bundle" }
1827
+ function ConvertTo-NativeArgument([string]$Value) {
1828
+ if ($Value.Length -gt 0 -and $Value -notmatch '[\s"]') { return $Value }
1829
+ $escaped = [regex]::Replace($Value, '(\\*)"', '$1$1\"')
1830
+ $escaped = [regex]::Replace($escaped, '(\\+)$', '$1$1')
1831
+ return '"' + $escaped + '"'
1832
+ }
1833
+ function Invoke-Step([string]$Name, [string]$Command, [string[]]$Arguments) {
1834
+ Write-Host "[$Name]"
1835
+ $nativeCommand = Get-Command $Command -ErrorAction Stop
1836
+ if ($PSVersionTable.PSVersion.Major -lt 6 -and $nativeCommand.CommandType -eq 'Application') {
1837
+ $start = New-Object System.Diagnostics.ProcessStartInfo
1838
+ $start.FileName = $nativeCommand.Source
1839
+ $start.WorkingDirectory = (Get-Location).ProviderPath
1840
+ $start.UseShellExecute = $false
1841
+ $start.CreateNoWindow = $true
1842
+ $start.Arguments = ($Arguments | ForEach-Object { ConvertTo-NativeArgument $_ }) -join ' '
1843
+ $child = [System.Diagnostics.Process]::Start($start)
1844
+ try { $child.WaitForExit(); $code = $child.ExitCode } finally { $child.Dispose() }
1845
+ } else {
1846
+ & $Command @Arguments
1847
+ $code = $LASTEXITCODE
1848
+ }
1849
+ $global:LASTEXITCODE = $code
1850
+ if ($code -ne 0) { throw "$Name failed (exit $code)." }
1851
+ }
1852
+ $applying = $false
1853
+ try {
1854
+ foreach ($tool in @('ssh', 'scp')) { Get-Command $tool -ErrorAction Stop | Out-Null }
1855
+ Invoke-Step '1/3 Connect' 'ssh' ($options + @('-p', [string]$port, $destination, "mkdir -p '$remote/incoming' && chmod 700 '$remote' '$remote/incoming'"))
1856
+ Invoke-Step '2/3 Upload' 'scp' ($options + @('-P', [string]$port, "./$bundle", "$($scpDestination):$remote/incoming/$bundle"))
1857
+ $applying = $true
1858
+ Invoke-Step '3/3 Apply and verify HTTP' 'ssh' ($options + @('-p', [string]$port, $destination, "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"))
1859
+ } catch {
1860
+ if ($applying) { [Console]::Error.WriteLine("[UNKNOWN] Remote execution may continue. Inspect $remote/result-<deploymentId>.json (ID in manifest.json), current and containers before retrying.") }
1861
+ Write-Error $_
1862
+ exit 1
1863
+ }
1864
+ `;
1865
+ return { sh, ps1, reader: createTargetReader() };
1866
+ }
1867
+
1868
+ // ../deploy/src/container/deploy-bundle.ts
1869
+ async function createDeploymentBundle(options) {
1870
+ const { directory, artifactDir, artifact, projectName, environment, config, selected } = options;
1871
+ const stem = basename(options.output).replace(/\.tar$/i, "");
1872
+ const suffix = `-${artifact.appVersion}`;
1873
+ const output = resolve(dirname(options.output), `${stem.endsWith(suffix) ? stem : `${stem}${suffix}`}.tar`);
1874
+ if (!/^[\w.+-]+\.tar$/.test(basename(output)))
1875
+ throw new Error(deployM("deploy_bundleNameInvalid"));
1876
+ const staging = mkdtempSync(join(directory, ".bundle-"));
1877
+ const temporaryTar = `${output}.partial`;
1878
+ mkdirSync(dirname(output), { recursive: true, mode: 448 });
1879
+ chmodSync(staging, 448);
1880
+ const files = [];
1881
+ const put = (file, content) => {
1882
+ mkdirSync(dirname(join(staging, file)), { recursive: true });
1883
+ writeFileSync(join(staging, file), content, { mode: 384 });
1884
+ files.push(file);
1885
+ };
1886
+ try {
1887
+ mkdirSync(join(staging, "images"));
1888
+ const compose = parse(readFileSync(join(directory, `docker-compose-${artifact.appVersion}.yml`), "utf8"));
1889
+ const dependencies = /* @__PURE__ */ new Set();
1890
+ const references = /* @__PURE__ */ new Set();
1891
+ for (const name of selected) {
1892
+ const service = compose.services[name];
1893
+ if (!service)
1894
+ throw new Error(deployM("deploy_selectionInvalid", { params: { service: name } }));
1895
+ if (typeof service.image === "string")
1896
+ references.add(service.image);
1897
+ if (service.depends_on) {
1898
+ for (const dependency of Array.isArray(service.depends_on) ? service.depends_on : Object.keys(service.depends_on))
1899
+ dependencies.add(dependency);
1900
+ }
1901
+ if (service.env_file)
1902
+ service.env_file = [`../${options.deploymentId}/.env.runtime`, `../${options.deploymentId}/middleware.env`];
1903
+ if (Array.isArray(service.volumes))
1904
+ service.volumes = service.volumes.map((mount) => typeof mount === "string" ? mount.replace("./web.conf:", `../${options.deploymentId}/web.conf:`) : mount);
1905
+ }
1906
+ put("compose.json", JSON.stringify({ ...compose, services: Object.fromEntries(selected.map((name) => [name, compose.services[name]])) }));
1907
+ put("compose-entry.json", JSON.stringify({ ...compose, services: Object.fromEntries(selected.map((name) => [name, { extends: { file: `../${options.deploymentId}/compose.json`, service: name } }])) }));
1908
+ let runtime;
1909
+ const imageList = [];
1910
+ for (const reference of references) {
1911
+ if (!/:[^/:]+$/.test(reference) || /:latest$/i.test(reference))
1912
+ throw new Error(deployM("deploy_imageVersionRequired", { params: { image: reference } }));
1913
+ const built = artifact.images.find((image) => image.imageRef === reference);
1914
+ const file = built?.file ?? `middleware-${imageList.length + 1}.tar`;
1915
+ if (built) {
1916
+ copyFileSync(join(artifactDir, file), join(staging, "images", file));
1917
+ } else {
1918
+ if (!runtime) {
1919
+ const candidates = options.runtime === "auto" ? ["podman", "docker"] : [options.runtime];
1920
+ for (const candidate of candidates) {
1921
+ if ((await runCommand(candidate, ["version"])).success) {
1922
+ runtime = candidate;
1923
+ break;
1924
+ }
1925
+ }
1926
+ if (!runtime)
1927
+ throw new Error(deployM("deploy_bundleRuntimeRequired"));
1928
+ }
1929
+ if (!(await runCommand(runtime, ["image", "inspect", reference])).success) {
1930
+ const pulled = await runCommand(runtime, ["pull", reference], { timeoutMs: 6e5 });
1931
+ if (!pulled.success)
1932
+ throw new Error(pulled.error.message);
1933
+ }
1934
+ const saved = await runCommand(runtime, ["save", ...runtime === "podman" ? ["--format", "docker-archive"] : [], "-o", join(staging, "images", file), reference], { timeoutMs: 6e5 });
1935
+ if (!saved.success)
1936
+ throw new Error(saved.error.message);
1937
+ }
1938
+ files.push(`images/${file}`);
1939
+ imageList.push(`${file} ${reference}`);
1940
+ }
1941
+ put(".env.runtime", readFileSync(join(directory, ".env.runtime"), "utf8"));
1942
+ put("middleware.env", options.middlewareEnv);
1943
+ if (selected.includes("web"))
1944
+ put("web.conf", readFileSync(join(directory, "web.conf"), "utf8"));
1945
+ put("manifest.json", `${JSON.stringify({ ...artifact, images: artifact.images.filter((image) => references.has(image.imageRef)), deploymentId: options.deploymentId, selectedServices: selected, partial: options.partial }, null, 2)}
1946
+ `);
1947
+ put("layout", `${config.expose.hostPort}:${options.containerPort}:${options.hasWeb}:${options.hasWeb ? await artifactChecksum(join(directory, "web.conf")) : ""}
1948
+ `);
1949
+ put("image-list", `${imageList.join("\n")}
1950
+ `);
1951
+ put("dependencies", `${[...dependencies].join("\n")}
1952
+ `);
1953
+ if (selected.includes("storage-init"))
1954
+ put("initialize-storage", "");
1955
+ put("apply.sh", createApplyScript({ deploymentId: options.deploymentId, projectName, environment, directory: `${config.remote.baseDir}/${projectName}`, runtime: config.remote.runtime, partial: options.partial, selected: selected.filter((name) => name !== "storage-init"), hostPort: config.expose.hostPort, healthPaths: options.healthPaths }));
1956
+ const instructionParams = { file: basename(output), start: "apply.sh", images: [...references].join("\n"), compose: "compose.json" };
1957
+ const instructions = deployM("deploy_bundleInstructions", { params: instructionParams });
1958
+ put("README.txt", `${deployM("deploy_bundleMetadata", { params: { version: artifact.appVersion, buildId: artifact.buildId, compose: "compose.json" } })}
1959
+ ${instructions}
1960
+ `);
1961
+ const checksums = [];
1962
+ for (const file of files)
1963
+ checksums.push(`${await artifactChecksum(join(staging, file))} ${file}`);
1964
+ writeFileSync(join(staging, "SHA256SUMS"), `${checksums.join("\n")}
1965
+ `);
1966
+ const tar = await runCommand("tar", ["-cf", temporaryTar, "-C", staging, "."]);
1967
+ if (!tar.success)
1968
+ throw new Error(tar.error.message);
1969
+ chmodSync(temporaryTar, 384);
1970
+ renameSync(temporaryTar, output);
1971
+ const upload = createUploadScripts();
1972
+ const scripts = `${output.slice(0, -4)}-deploy`;
1973
+ mkdirSync(scripts, { recursive: true, mode: 448 });
1974
+ writeFileSync(join(scripts, "deploy.sh"), upload.sh, { mode: 448 });
1975
+ writeFileSync(join(scripts, "deploy.ps1"), upload.ps1);
1976
+ writeFileSync(join(scripts, "read-target.awk"), upload.reader);
1977
+ const target = {
1978
+ host: config.ssh?.host ?? "SERVER_HOST",
1979
+ port: config.ssh?.port ?? 22,
1980
+ username: config.ssh?.username ?? "deploy",
1981
+ identityFile: config.ssh?.identityFile ?? "",
1982
+ knownHostsFile: config.ssh?.knownHostsFile ?? "",
1983
+ strictHostKeyChecking: config.ssh?.strictHostKeyChecking ?? true,
1984
+ connectTimeoutSeconds: config.ssh?.connectTimeoutSeconds ?? 10,
1985
+ directory: `${config.remote.baseDir}/${projectName}`,
1986
+ bundle: basename(output)
1987
+ };
1988
+ const comments = {
1989
+ host: deployM("deploy_targetHost"),
1990
+ port: deployM("deploy_targetPort"),
1991
+ username: deployM("deploy_targetUsername"),
1992
+ identityFile: deployM("deploy_targetIdentityFile"),
1993
+ knownHostsFile: deployM("deploy_targetKnownHostsFile"),
1994
+ strictHostKeyChecking: deployM("deploy_targetStrictHostKeyChecking"),
1995
+ connectTimeoutSeconds: deployM("deploy_targetConnectTimeoutSeconds"),
1996
+ directory: deployM("deploy_targetDirectory"),
1997
+ bundle: deployM("deploy_targetBundle")
1998
+ };
1999
+ writeFileSync(join(scripts, "deploy-target.json"), `${JSON.stringify({ ...target, _comments: comments }, null, 2)}
2000
+ `);
2001
+ writeFileSync(join(scripts, "README.txt"), instructions);
2002
+ return { bundlePath: output, instructions: `${deployM("deploy_bundleInstructions", { params: { ...instructionParams, file: output } })}
2003
+ ${scripts}`, startName: "apply.sh" };
2004
+ } finally {
2005
+ rmSync(staging, { recursive: true, force: true });
2006
+ rmSync(temporaryTar, { force: true });
2007
+ }
2008
+ }
2009
+ var IMAGES = {
2010
+ postgres: "docker.io/library/postgres:16.15-alpine",
2011
+ mysql: "docker.io/library/mysql:8.4.11",
2012
+ redis: "docker.io/library/redis:7.4.11-alpine",
2013
+ rustfs: "ghcr.io/rustfs/rustfs:1.0.1",
2014
+ minio: "ghcr.io/coollabsio/minio:RELEASE.2025-10-15T17-29-55Z",
2015
+ qdrant: "docker.io/qdrant/qdrant:v1.19.2",
2016
+ pgvector: "docker.io/pgvector/pgvector:0.8.0-pg17",
2017
+ chroma: "docker.io/chromadb/chroma:1.5.5",
2018
+ ldap: "docker.io/osixia/openldap:1.5.0",
2019
+ smtp: "docker.io/axllent/mailpit:v1.27.4"
2020
+ };
2021
+ function generatePassword() {
2022
+ return randomBytes(24).toString("base64url");
2023
+ }
2024
+ function resolveSidecarSecrets(appDir, projectName) {
2025
+ const secretFile = join(appDir, ".hai", "deploy", "runtime", `${projectName}.secrets.json`);
2026
+ if (existsSync(secretFile)) {
2027
+ try {
2028
+ const saved = JSON.parse(readFileSync(secretFile, "utf-8"));
2029
+ if (!saved || !["dbPassword", "storagePassword", "ldapPassword"].every((key) => typeof saved[key] === "string" && saved[key].length > 0))
2030
+ throw new Error("Invalid credentials");
2031
+ return saved;
2032
+ } catch {
2033
+ throw new Error(deployM("deploy_secretsInvalid", { params: { path: secretFile } }));
2034
+ }
2035
+ }
2036
+ const secrets = {
2037
+ dbPassword: generatePassword(),
2038
+ storagePassword: generatePassword(),
2039
+ ldapPassword: generatePassword()
2040
+ };
2041
+ mkdirSync(dirname(secretFile), { recursive: true });
2042
+ writeFileSync(secretFile, JSON.stringify(secrets), { mode: 384 });
2043
+ return secrets;
2044
+ }
2045
+ function addAppDataVolume(plan, projectName) {
2046
+ const mount = `${projectName}-data:/app/data`;
2047
+ if (!plan.appVolumes.includes(mount)) {
2048
+ plan.appVolumes.push(mount);
2049
+ plan.volumes[`${projectName}-data`] = null;
2050
+ }
2051
+ }
2052
+ function planPostgresDb(plan, projectName, password, net) {
2053
+ plan.services.push("db");
2054
+ plan.dependsOn.push("db");
2055
+ plan.composeServices.db = {
2056
+ image: IMAGES.postgres,
2057
+ container_name: `hai-${projectName}-db`,
2058
+ restart: "unless-stopped",
2059
+ env_file: [".env.runtime"],
2060
+ volumes: [`${projectName}-db-data:/var/lib/postgresql/data`],
2061
+ networks: net
2062
+ };
2063
+ plan.volumes[`${projectName}-db-data`] = null;
2064
+ plan.env.POSTGRES_USER = "hai";
2065
+ plan.env.POSTGRES_PASSWORD = password;
2066
+ plan.env.POSTGRES_DB = "hai";
2067
+ plan.env.HAI_DB = JSON.stringify({ type: "postgresql", url: `postgresql://hai:${password}@db:5432/hai`, database: "hai" });
2068
+ plan.secrets.push(password);
2069
+ }
2070
+ function planMysqlDb(plan, projectName, password, net) {
2071
+ plan.services.push("db");
2072
+ plan.dependsOn.push("db");
2073
+ plan.composeServices.db = {
2074
+ image: IMAGES.mysql,
2075
+ container_name: `hai-${projectName}-db`,
2076
+ restart: "unless-stopped",
2077
+ env_file: [".env.runtime"],
2078
+ volumes: [`${projectName}-db-data:/var/lib/mysql`],
2079
+ networks: net
2080
+ };
2081
+ plan.volumes[`${projectName}-db-data`] = null;
2082
+ plan.env.MYSQL_ROOT_PASSWORD = password;
2083
+ plan.env.MYSQL_DATABASE = "hai";
2084
+ plan.env.MYSQL_USER = "hai";
2085
+ plan.env.MYSQL_PASSWORD = password;
2086
+ plan.env.HAI_DB = JSON.stringify({ type: "mysql", url: `mysql://hai:${password}@db:3306/hai`, database: "hai" });
2087
+ plan.secrets.push(password);
2088
+ }
2089
+ function planRedisCache(plan, projectName, net) {
2090
+ plan.services.push("cache");
2091
+ plan.dependsOn.push("cache");
2092
+ plan.composeServices.cache = {
2093
+ image: IMAGES.redis,
2094
+ container_name: `hai-${projectName}-cache`,
2095
+ restart: "unless-stopped",
2096
+ networks: net
2097
+ };
2098
+ plan.env.HAI_CACHE = JSON.stringify({ type: "redis", url: "redis://cache:6379" });
2099
+ }
2100
+ function planS3Storage(plan, projectName, password, net, engine) {
2101
+ plan.services.push("storage");
2102
+ plan.dependsOn.push("storage");
2103
+ plan.composeServices.storage = {
2104
+ image: IMAGES[engine],
2105
+ container_name: `hai-${projectName}-storage`,
2106
+ restart: "unless-stopped",
2107
+ ...engine === "minio" ? { command: ["server", "/data"] } : {},
2108
+ env_file: [".env.runtime"],
2109
+ volumes: [`${projectName}-storage-data:/data`],
2110
+ networks: net
2111
+ };
2112
+ plan.composeServices["storage-init"] = {
2113
+ depends_on: ["storage"],
2114
+ env_file: [".env.runtime"],
2115
+ entrypoint: [
2116
+ "node",
2117
+ "--input-type=module",
2118
+ "-e",
2119
+ // 复用应用 @h-ai/storage 已依赖的 SDK;不额外拉取失效的 mc 镜像,不把密钥拼进命令。
2120
+ `import { S3Client, HeadBucketCommand, CreateBucketCommand } from '@aws-sdk/client-s3';
2121
+ const config = JSON.parse(process.env.HAI_STORAGE);
2122
+ const client = new S3Client({ endpoint: config.endpoint, region: config.region, forcePathStyle: config.forcePathStyle, credentials: { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey } });
2123
+ let ready = false;
2124
+ for (let attempt = 0; attempt < 120; attempt++) {
2125
+ try {
2126
+ try { await client.send(new HeadBucketCommand({ Bucket: config.bucket })); }
2127
+ catch (error) {
2128
+ if (error.$$metadata?.httpStatusCode !== 404) throw error;
2129
+ await client.send(new CreateBucketCommand({ Bucket: config.bucket }));
2130
+ }
2131
+ ready = true; break;
2132
+ } catch { await new Promise(resolve => setTimeout(resolve, 1000)); }
2133
+ }
2134
+ client.destroy();
2135
+ if (!ready) throw new Error('S3 bucket initialization failed');`
2136
+ ],
2137
+ networks: net
2138
+ };
2139
+ plan.volumes[`${projectName}-storage-data`] = null;
2140
+ if (engine === "rustfs") {
2141
+ Object.assign(plan.env, { RUSTFS_ACCESS_KEY: "hai-storage", RUSTFS_SECRET_KEY: password, RUSTFS_ADDRESS: "0.0.0.0:9000", RUSTFS_VOLUMES: "/data", RUSTFS_CONSOLE_ENABLE: "false" });
2142
+ } else {
2143
+ Object.assign(plan.env, { MINIO_ROOT_USER: "hai-storage", MINIO_ROOT_PASSWORD: password });
2144
+ }
2145
+ plan.env.HAI_STORAGE = JSON.stringify({
2146
+ type: "s3",
2147
+ endpoint: "http://storage:9000",
2148
+ bucket: "hai",
2149
+ region: "us-east-1",
2150
+ accessKeyId: "hai-storage",
2151
+ secretAccessKey: password,
2152
+ forcePathStyle: true
2153
+ });
2154
+ plan.secrets.push(password);
2155
+ }
2156
+ function planQdrantVecdb(plan, projectName, net) {
2157
+ plan.services.push("vecdb");
2158
+ plan.dependsOn.push("vecdb");
2159
+ plan.composeServices.vecdb = {
2160
+ image: IMAGES.qdrant,
2161
+ container_name: `hai-${projectName}-vecdb`,
2162
+ restart: "unless-stopped",
2163
+ volumes: [`${projectName}-vecdb-data:/qdrant/storage`],
2164
+ networks: net
2165
+ };
2166
+ plan.volumes[`${projectName}-vecdb-data`] = null;
2167
+ plan.env.HAI_VECDB = JSON.stringify({ type: "qdrant", url: "http://vecdb:6333" });
2168
+ }
2169
+ function planPgvectorVecdb(plan, projectName, password, net) {
2170
+ plan.services.push("vecdb");
2171
+ plan.dependsOn.push("vecdb");
2172
+ plan.composeServices.vecdb = {
2173
+ image: IMAGES.pgvector,
2174
+ container_name: `hai-${projectName}-vecdb`,
2175
+ restart: "unless-stopped",
2176
+ env_file: [".env.runtime"],
2177
+ volumes: [`${projectName}-vecdb-data:/var/lib/postgresql/data`],
2178
+ networks: net
2179
+ };
2180
+ plan.volumes[`${projectName}-vecdb-data`] = null;
2181
+ plan.env.POSTGRES_USER = "hai";
2182
+ plan.env.POSTGRES_PASSWORD = password;
2183
+ plan.env.POSTGRES_DB = "hai";
2184
+ plan.env.HAI_VECDB = JSON.stringify({ type: "pgvector", url: `postgresql://hai:${password}@vecdb:5432/hai`, database: "hai" });
2185
+ plan.secrets.push(password);
2186
+ }
2187
+ function planChromaVecdb(plan, projectName, net) {
2188
+ plan.services.push("vecdb");
2189
+ plan.dependsOn.push("vecdb");
2190
+ plan.composeServices.vecdb = {
2191
+ image: IMAGES.chroma,
2192
+ container_name: `hai-${projectName}-vecdb`,
2193
+ restart: "unless-stopped",
2194
+ volumes: [`${projectName}-vecdb-data:/data`],
2195
+ networks: net
2196
+ };
2197
+ plan.volumes[`${projectName}-vecdb-data`] = null;
2198
+ plan.env.HAI_VECDB = JSON.stringify({ type: "chroma", url: "http://vecdb:8000" });
2199
+ }
2200
+ function planSidecars(appDir, projectName, scan, provisionEnv, options, s3Engine = "rustfs") {
2201
+ const serviceModes = { ...options?.serviceModes };
2202
+ for (const requirement of scan.middlewares ?? []) {
2203
+ if (options?.selectedServices && !options.selectedServices.includes(requirement.service))
2204
+ continue;
2205
+ const mode = serviceModes[requirement.envPrefix] ?? serviceModes[requirement.service];
2206
+ if (mode !== "keep")
2207
+ continue;
2208
+ const endpoint = requirement.config.url ?? requirement.config.endpoint;
2209
+ let host = requirement.config.host;
2210
+ if (typeof endpoint === "string") {
2211
+ try {
2212
+ host = new URL(endpoint).hostname;
2213
+ } catch {
2214
+ continue;
2215
+ }
2216
+ }
2217
+ if (host === requirement.service || ["sqlite", "local", "memory", "lancedb"].includes(requirement.backend)) {
2218
+ serviceModes[requirement.envPrefix] = "default";
2219
+ serviceModes[requirement.service] = "default";
2220
+ }
2221
+ }
2222
+ options = { ...options, serviceModes };
2223
+ const plan = {
2224
+ services: [],
2225
+ composeServices: {},
2226
+ volumes: {},
2227
+ appVolumes: [],
2228
+ env: {},
2229
+ dependsOn: [],
2230
+ secrets: []
2231
+ };
2232
+ if (!scan.requiredServices.some((service) => usesDefaultMiddleware(scan, service, options)))
2233
+ return plan;
2234
+ const secrets = resolveSidecarSecrets(appDir, projectName);
2235
+ const net = ["hai-app"];
2236
+ const backends = scan.serviceBackends;
2237
+ for (const service of scan.requiredServices) {
2238
+ if (!usesDefaultMiddleware(scan, service, options))
2239
+ continue;
2240
+ if (service === "db" && !provisionEnv.HAI_DB) {
2241
+ const backend = backends.db ?? "postgresql";
2242
+ if (backend === "mysql")
2243
+ planMysqlDb(plan, projectName, secrets.dbPassword, net);
2244
+ else if (backend === "sqlite")
2245
+ addAppDataVolume(plan, projectName);
2246
+ else
2247
+ planPostgresDb(plan, projectName, secrets.dbPassword, net);
2248
+ } else if (service === "cache" && !provisionEnv.HAI_CACHE) {
2249
+ if ((backends.cache ?? "redis") === "redis")
2250
+ planRedisCache(plan, projectName, net);
2251
+ } else if (service === "storage" && !provisionEnv.HAI_STORAGE) {
2252
+ if ((backends.storage ?? "s3") === "s3")
2253
+ planS3Storage(plan, projectName, secrets.storagePassword, net, s3Engine);
2254
+ else
2255
+ addAppDataVolume(plan, projectName);
2256
+ } else if (service === "vecdb" && !provisionEnv.HAI_VECDB) {
2257
+ const backend = backends.vecdb ?? "qdrant";
2258
+ if (backend === "pgvector")
2259
+ planPgvectorVecdb(plan, projectName, secrets.dbPassword, net);
2260
+ else if (backend === "chroma")
2261
+ planChromaVecdb(plan, projectName, net);
2262
+ else if (backend === "lancedb")
2263
+ addAppDataVolume(plan, projectName);
2264
+ else
2265
+ planQdrantVecdb(plan, projectName, net);
2266
+ } else if (service === "ldap" && backends.ldap === "ldap" && !provisionEnv.HAI_IAM_LDAP) {
2267
+ const password = secrets.ldapPassword ?? secrets.dbPassword;
2268
+ plan.services.push("ldap");
2269
+ plan.dependsOn.push("ldap");
2270
+ plan.composeServices.ldap = { image: IMAGES.ldap, restart: "unless-stopped", env_file: [".env.runtime"], networks: net, volumes: [`${projectName}-ldap-data:/var/lib/ldap`, `${projectName}-ldap-config:/etc/ldap/slapd.d`] };
2271
+ plan.volumes[`${projectName}-ldap-data`] = null;
2272
+ plan.volumes[`${projectName}-ldap-config`] = null;
2273
+ Object.assign(plan.env, { LDAP_DOMAIN: "example.org", LDAP_ORGANISATION: "HAI", LDAP_ADMIN_PASSWORD: password, LDAP_TLS: "false", HAI_IAM_LOGIN_LDAP: "true", HAI_IAM_LDAP: JSON.stringify({ url: "ldap://ldap:389", bindDn: "cn=admin,dc=example,dc=org", bindPassword: password, searchBase: "dc=example,dc=org" }) });
2274
+ plan.secrets.push(password);
2275
+ } else if (service === "email" && (scan.middlewares ? scan.middlewares.some((item) => item.service === "email" && item.backend === "smtp" && (options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.email ?? "default") === "default") : backends.email === "smtp") && !provisionEnv.HAI_REACH_PROVIDERS) {
2276
+ plan.services.push("email");
2277
+ plan.dependsOn.push("email");
2278
+ plan.composeServices.email = { image: IMAGES.smtp, restart: "unless-stopped", networks: net };
2279
+ const requirements = scan.middlewares?.filter((item) => item.service === "email" && item.backend === "smtp" && ["default", void 0].includes(options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.email));
2280
+ if (requirements?.length) {
2281
+ for (const requirement of requirements)
2282
+ plan.env[requirement.envPrefix] = JSON.stringify({ name: requirement.config.name ?? "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
2283
+ } else if (!scan.middlewares) {
2284
+ plan.env.HAI_REACH_PROVIDERS_0 = JSON.stringify({ name: "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
2285
+ }
2286
+ }
2287
+ }
2288
+ return plan;
2289
+ }
2290
+ function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv = {}, s3Engine = "rustfs") {
2291
+ const sidecar = planSidecars(appDir, projectName, scan, provisionEnv, options, s3Engine);
2292
+ const env = {
2293
+ NODE_ENV: "production",
2294
+ HOST: "0.0.0.0",
2295
+ PORT: String(containerPort),
2296
+ HAI_SERV_SERVER_HOST: "0.0.0.0",
2297
+ HAI_SERV_SERVER_PORT: String(containerPort),
2298
+ ...appEnv,
2299
+ ...sidecar.env,
2300
+ ...provisionEnv
2301
+ };
2302
+ return { env, sidecar };
2303
+ }
2304
+ function generateComposeFile(options) {
2305
+ const { projectName, imageRef, hostPort, containerPort, sidecar } = options;
2306
+ const app = {
2307
+ image: imageRef,
2308
+ labels: { "org.opencontainers.image.version": options.appVersion },
2309
+ container_name: `hai-${projectName}-app`,
2310
+ restart: "unless-stopped",
2311
+ // 镜像已由 load 注入本地,禁止 compose 再从 registry 拉取
2312
+ pull_policy: "never",
2313
+ env_file: [".env.runtime"],
2314
+ ports: [`${hostPort}:${containerPort}`],
2315
+ networks: ["hai-app"]
2316
+ };
2317
+ if (sidecar.appVolumes.length > 0)
2318
+ app.volumes = sidecar.appVolumes;
2319
+ if (sidecar.dependsOn.length > 0)
2320
+ app.depends_on = sidecar.dependsOn;
2321
+ const services = { app, ...sidecar.composeServices };
2322
+ if (options.web) {
2323
+ delete app.ports;
2324
+ services.web = {
2325
+ image: options.web.imageRef,
2326
+ labels: { "org.opencontainers.image.version": options.appVersion },
2327
+ container_name: `hai-${projectName}-web`,
2328
+ restart: "unless-stopped",
2329
+ pull_policy: "never",
2330
+ ports: [`${hostPort}:${options.web.containerPort}`],
2331
+ environment: { PUBLIC_API_BASE: "" },
2332
+ healthcheck: { test: ["CMD", "wget", "-Y", "off", "-q", "-O", "/dev/null", `http://127.0.0.1:${options.web.containerPort}/_hai/web-health`], interval: "30s", timeout: "5s", retries: 3 },
2333
+ volumes: ["./web.conf:/etc/nginx/conf.d/default.conf:ro"],
2334
+ depends_on: ["app"],
2335
+ networks: ["hai-app"]
2336
+ };
2337
+ }
2338
+ if (services["storage-init"])
2339
+ services["storage-init"] = { ...services["storage-init"], image: imageRef };
2340
+ const compose = {
2341
+ "x-hai-version": options.appVersion,
2342
+ services,
2343
+ "networks": {
2344
+ "hai-app": { name: `hai-${projectName}` }
2345
+ }
2346
+ };
2347
+ if (Object.keys(sidecar.volumes).length > 0)
2348
+ compose.volumes = sidecar.volumes;
2349
+ if (services["storage-init"]) {
2350
+ app.depends_on = {
2351
+ ...Object.fromEntries((app.depends_on ?? []).map((name) => [name, { condition: "service_started" }])),
2352
+ "storage-init": { condition: "service_completed_successfully" }
2353
+ };
2354
+ }
2355
+ for (const service of Object.values(services))
2356
+ service.pull_policy = "never";
2357
+ return stringify(compose);
2358
+ }
2359
+ function generateWebProxyConfig(port, apiPrefix, webPort = 8080) {
2360
+ if (!/^\/[\w/-]+$/.test(apiPrefix) || ![port, webPort].every((value) => Number.isInteger(value) && value >= 1 && value <= 65535))
2361
+ throw new Error("Invalid web proxy target");
2362
+ return `server {
2363
+ listen ${webPort};
2364
+ server_name _;
2365
+ root /usr/share/nginx/html;
2366
+ large_client_header_buffers 4 32k;
2367
+ location / {
2368
+ try_files $uri $uri/ /index.html;
2369
+ }
2370
+ location = /runtime-config.js {
2371
+ add_header Cache-Control "no-store";
2372
+ default_type application/javascript;
2373
+ return 200 'globalThis.__HAI_PUBLIC_CONFIG__={apiBase:globalThis.location.origin+"${apiPrefix}"};';
2374
+ }
2375
+ location = /_hai/web-health {
2376
+ access_log off;
2377
+ return 200 'ok';
2378
+ }
2379
+ location = /health {
2380
+ proxy_pass http://app:${port};
2381
+ proxy_set_header Host $http_host;
2382
+ }
2383
+ location ${apiPrefix.replace(/\/$/, "")}/ {
2384
+ client_max_body_size 2129920;
2385
+ proxy_pass http://app:${port};
2386
+ proxy_http_version 1.1;
2387
+ proxy_set_header Host $http_host;
2388
+ proxy_set_header X-Forwarded-Proto $scheme;
2389
+ proxy_request_buffering off;
2390
+ proxy_buffering off;
2391
+ proxy_buffer_size 32k;
2392
+ proxy_buffers 8 32k;
2393
+ }
2394
+ }
2395
+ `;
2396
+ }
2397
+ function serializeEnvFile(env) {
2398
+ return `${Object.entries(env).map(([key, value]) => {
2399
+ let literal = value;
2400
+ if (/[\s#$'"]/.test(value)) {
2401
+ if (value.startsWith("{") || value.startsWith("["))
2402
+ literal = `'${value.replaceAll("'", "\\u0027")}'`;
2403
+ else if (!value.includes("'"))
2404
+ literal = `'${value}'`;
2405
+ else
2406
+ literal = JSON.stringify(value).replaceAll("$", "$$");
2407
+ }
2408
+ return `${key}=${literal}`;
2409
+ }).join("\n")}
2410
+ `;
2411
+ }
2412
+ var logger4 = core.logger.child({ module: "deploy", scope: "ssh" });
2413
+ function buildConnectOptions(config) {
2414
+ const args = ["-o", "BatchMode=yes"];
2415
+ args.push("-o", `ConnectTimeout=${config.connectTimeoutSeconds}`);
2416
+ args.push("-o", `StrictHostKeyChecking=${config.strictHostKeyChecking ? "accept-new" : "no"}`);
2417
+ if (config.knownHostsFile) {
2418
+ const path = config.knownHostsFile.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
2419
+ args.push("-o", `UserKnownHostsFile="${path}"`);
2420
+ }
2421
+ if (config.identityFile) {
2422
+ args.push("-i", config.identityFile);
2423
+ args.push("-o", "IdentitiesOnly=yes");
2424
+ }
2425
+ return args;
2426
+ }
2427
+ function buildSshExecArgs(config, remoteCommand) {
2428
+ return [
2429
+ ...buildConnectOptions(config),
2430
+ "-p",
2431
+ String(config.port),
2432
+ `${config.username}@${config.host}`,
2433
+ remoteCommand
2434
+ ];
2435
+ }
2436
+ function buildScpUploadArgs(config, localPath, remotePath) {
2437
+ return [
2438
+ ...buildConnectOptions(config),
2439
+ "-P",
2440
+ String(config.port),
2441
+ localPath,
2442
+ `${config.username}@${config.host.includes(":") ? `[${config.host}]` : config.host}:${remotePath}`
2443
+ ];
2444
+ }
2445
+ function createSshExecutor(config) {
2446
+ const exec = async (remoteCommand, options) => {
2447
+ const result = await runCommand("ssh", buildSshExecArgs(config, remoteCommand), {
2448
+ timeoutMs: options?.timeoutMs,
2449
+ terminationTimeoutMs: options?.terminationTimeoutMs,
2450
+ maxCaptureChars: options?.maxCaptureChars,
2451
+ secrets: options?.secrets,
2452
+ onOutput: options?.onOutput
2453
+ });
2454
+ if (result.success)
2455
+ return result;
2456
+ const state = getDeployCommandState(result.error);
2457
+ return err({ ...result.error, suggestion: deployM("deploy_remoteUnknown"), ext: { ...result.error.ext, commandExecution: { ...state, reason: state?.reason ?? "exit", localTermination: state?.localTermination ?? "unknown", remoteExecution: "unknown" } } });
2458
+ };
2459
+ const upload = async (localPath, remotePath) => {
2460
+ const args = buildScpUploadArgs(config, localPath, remotePath);
2461
+ const result = await runCommand("scp", args, { timeoutMs: 6e5 });
2462
+ if (!result.success) {
2463
+ return err({ ...HaiDeployError.IMAGE_TRANSFER_FAILED, message: deployM("deploy_imageTransferFailed", { params: { error: result.error.message } }), ext: result.error.ext });
2464
+ }
2465
+ return ok(void 0);
2466
+ };
2467
+ const ping = async () => {
2468
+ logger4.debug("SSH preflight", { host: config.host, port: config.port });
2469
+ const result = await exec("true", { timeoutMs: 2e4 });
2470
+ if (!result.success) {
2471
+ return err({ ...HaiDeployError.REMOTE_CONNECT_FAILED, message: deployM("deploy_remoteConnectFailed", { params: { host: config.host, error: result.error.message } }), ext: result.error.ext });
2472
+ }
2473
+ return ok(void 0);
2474
+ };
2475
+ return { exec, upload, ping };
2476
+ }
2477
+
2478
+ // ../deploy/src/providers/deploy-provider-docker-ssh.ts
2479
+ var logger5 = core.logger.child({ module: "deploy", scope: "provider-docker-ssh" });
2480
+ var HEALTH_POLL_INTERVAL = 3e3;
2481
+ var HEALTH_MAX_ATTEMPTS = 40;
2482
+ var ServerResultSchema = external_exports.object({
2483
+ deploymentId: external_exports.string(),
2484
+ stage: external_exports.enum(["preflight", "images", "dependencies", "start", "health", "complete"]),
2485
+ exitCode: external_exports.number().int().min(0).max(255),
2486
+ durationSeconds: external_exports.number().int().nonnegative()
2487
+ });
2488
+ function makeDeploymentId() {
2489
+ const now = /* @__PURE__ */ new Date();
2490
+ const pad = (n) => String(n).padStart(2, "0");
2491
+ const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
2492
+ return `${stamp}-${randomBytes(3).toString("hex")}`;
2493
+ }
2494
+ function sleep(ms) {
2495
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
2496
+ }
2497
+ function createDockerSshProvider(config, container) {
2498
+ const ssh = config.ssh ? createSshExecutor(config.ssh) : void 0;
2499
+ const baseDir = config.remote.baseDir;
2500
+ async function detectRemoteRuntime() {
2501
+ if (!ssh)
2502
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
2503
+ const candidates = config.remote.runtime === "auto" ? ["podman", "docker"] : [config.remote.runtime];
2504
+ for (const runtime of candidates) {
2505
+ const versionResult = await ssh.exec(`${runtime} version`, { timeoutMs: 2e4 });
2506
+ if (!versionResult.success)
2507
+ continue;
2508
+ const composeResult = await ssh.exec(`${runtime} compose version`, { timeoutMs: 2e4 });
2509
+ if (composeResult.success) {
2510
+ logger5.info("Remote container runtime detected", { runtime });
2511
+ return ok(runtime);
2512
+ }
2513
+ }
2514
+ return err(
2515
+ HaiDeployError.REMOTE_COMMAND_FAILED,
2516
+ deployM("deploy_remoteRuntimeMissing", { params: { runtime: candidates.join(" / ") } })
2517
+ );
2518
+ }
2519
+ async function healthCheck(url) {
2520
+ for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
2521
+ try {
2522
+ const response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
2523
+ if (response.ok)
2524
+ return true;
2525
+ } catch {
2526
+ }
2527
+ await sleep(HEALTH_POLL_INTERVAL);
2528
+ }
2529
+ return false;
2530
+ }
2531
+ const deploy2 = async (context) => {
2532
+ const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
2533
+ const deploymentId = makeDeploymentId();
2534
+ const hostPort = config.expose.hostPort;
2535
+ const artifactDir = join(resolveProjectDirectory(appDir), ".hai", "deploy", options?.environment ?? "prod");
2536
+ mkdirSync(artifactDir, { recursive: true, mode: 448 });
2537
+ chmodSync(artifactDir, 448);
2538
+ const artifact = context.artifact;
2539
+ const resolvedBuildDir = context.artifactDir;
2540
+ const images = artifact.images;
2541
+ const web = images.find((image) => image.name === "web");
2542
+ const app = images.find((image) => image.name === "app");
2543
+ if (options?.selectedServices) {
2544
+ const available = [...images.map((image) => image.name), ...scan.requiredServices];
2545
+ const invalid = options.selectedServices.find((name) => !available.includes(name));
2546
+ if (!options.selectedServices.length || new Set(options.selectedServices).size !== options.selectedServices.length || invalid)
2547
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectionInvalid", { params: { service: invalid ?? options.selectedServices.join(", ") } }));
2548
+ }
2549
+ const hasWeb = artifact.hasWeb || !!web;
2550
+ const partial = artifact.partial || options?.selectedServices !== void 0;
2551
+ const containerPort = app?.containerPort ?? artifact.appContainerPort ?? config.expose.containerPort;
2552
+ const webContainerPort = web?.containerPort ?? artifact.webContainerPort ?? 8080;
2553
+ const host = config.ssh?.host;
2554
+ const url = host ? `http://${host.includes(":") ? `[${host}]` : host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
2555
+ const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv, container?.s3Engine);
2556
+ if (sidecar.composeServices["storage-init"] && !app && options?.selectedServices?.includes("storage"))
2557
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageArtifactRequired"));
2558
+ let webProxy;
2559
+ if (hasWeb) {
2560
+ const server = loadDeploymentConfig("serv", join(appDir, "config", "_serv.yml"), env);
2561
+ if (!server.success)
2562
+ return server;
2563
+ const http = server.data.http;
2564
+ const cors = server.data.cors;
2565
+ const apiPrefix = http?.apiPrefix ?? "/api/v1";
2566
+ if (typeof apiPrefix !== "string" || !/^\/[\w/-]+$/.test(apiPrefix))
2567
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
2568
+ webProxy = generateWebProxyConfig(containerPort, apiPrefix, webContainerPort);
2569
+ env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: (!cors?.origin || cors.origin === "*") && config.ssh ? url : cors?.origin ?? "*" } });
2570
+ }
2571
+ const envPath = join(artifactDir, ".env.runtime");
2572
+ writeFileSync(envPath, serializeEnvFile(env), { mode: 384 });
2573
+ if (options?.environment) {
2574
+ const environment = loadDeploymentEnvironment({ environment: options.environment, envDir: options.envDir ?? scan.envDir ?? appDir });
2575
+ if (!environment.success)
2576
+ return environment;
2577
+ const saved = saveDeploymentEnvironment(environment.data, { HAI_DEPLOY_ENVIRONMENT: environment.data.environment, HAI_CORE_ENV: environment.data.values.HAI_CORE_ENV, ...sidecar.env, ...provisionEnv });
2578
+ if (!saved.success)
2579
+ return saved;
2580
+ }
2581
+ const composeName = `docker-compose-${artifact.appVersion}.yml`;
2582
+ const composeContent = generateComposeFile({
2583
+ projectName,
2584
+ appVersion: artifact.appVersion,
2585
+ imageRef: app?.imageRef ?? "localhost/hai-unselected:0",
2586
+ hostPort,
2587
+ containerPort,
2588
+ sidecar,
2589
+ web: hasWeb ? { imageRef: web?.imageRef ?? "localhost/hai-unselected:0", containerPort: webContainerPort } : void 0
2590
+ });
2591
+ const composePath = join(artifactDir, composeName);
2592
+ writeFileSync(composePath, composeContent);
2593
+ if (webProxy)
2594
+ writeFileSync(join(artifactDir, "web.conf"), webProxy);
2595
+ const selected = options?.selectedServices ? [...options.selectedServices] : artifact.partial ? images.map((image) => image.name) : [...images.map((image) => image.name), ...Object.keys(sidecar.composeServices)];
2596
+ if (!selected.length || selected.some((name) => !/^[a-z][a-z0-9-]*$/.test(name)) || new Set(selected).size !== selected.length)
2597
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectionInvalid", { params: { service: selected.join(", ") } }));
2598
+ for (const name of selected) {
2599
+ if (["app", "web"].includes(name) && !images.some((image) => image.name === name))
2600
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectionInvalid", { params: { service: name } }));
2601
+ }
2602
+ if (selected.includes("storage") && sidecar.composeServices["storage-init"] && !selected.includes("storage-init"))
2603
+ selected.push("storage-init");
2604
+ options?.onProgress?.("packaging");
2605
+ const bundle = await createDeploymentBundle({
2606
+ directory: artifactDir,
2607
+ artifactDir: resolvedBuildDir,
2608
+ artifact,
2609
+ projectName,
2610
+ environment: options?.environment ?? "prod",
2611
+ deploymentId,
2612
+ config,
2613
+ runtime: options?.runtime ?? "auto",
2614
+ selected,
2615
+ partial,
2616
+ hasWeb,
2617
+ containerPort,
2618
+ middlewareEnv: serializeEnvFile(sidecar.env),
2619
+ healthPaths: hasWeb ? ["/health", "/_hai/web-health", "/"] : [scan.isSvelteKit ? "/" : "/health"],
2620
+ output: options?.output ?? join(artifactDir, `bundle-${projectName}-${deploymentId}-${artifact.appVersion}.tar`)
2621
+ });
2622
+ if (options?.bundleOnly)
2623
+ return ok({ url: "", deploymentId, status: "packaged", provider: "docker-ssh", healthCheck: "skipped", envVarsSet: Object.keys(env), ...bundle });
2624
+ if (!ssh)
2625
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
2626
+ const ping = await ssh.ping();
2627
+ if (!ping.success)
2628
+ return ping;
2629
+ const remoteRuntime = await detectRemoteRuntime();
2630
+ if (!remoteRuntime.success)
2631
+ return remoteRuntime;
2632
+ const remoteDir = `${baseDir}/${projectName}`;
2633
+ const remoteTar = `${remoteDir}/incoming/${basename(bundle.bundlePath)}`;
2634
+ const prepared = await ssh.exec(`mkdir -p -- ${shellArgument(`${remoteDir}/incoming`)} && chmod 700 -- ${shellArgument(remoteDir)} ${shellArgument(`${remoteDir}/incoming`)}`);
2635
+ if (!prepared.success)
2636
+ return prepared;
2637
+ const uploaded = await ssh.upload(bundle.bundlePath, remoteTar);
2638
+ if (!uploaded.success)
2639
+ return uploaded;
2640
+ let pendingOutput = "";
2641
+ const started = await ssh.exec(
2642
+ `mkdir -p ${shellArgument(`${remoteTar}.contents`)} && chmod 600 ${shellArgument(remoteTar)} && tar -xf ${shellArgument(remoteTar)} -C ${shellArgument(`${remoteTar}.contents`)} && sh ${shellArgument(`${remoteTar}.contents/${bundle.startName}`)} ${shellArgument(remoteDir)}`,
2643
+ { timeoutMs: 6e5, secrets: sidecar.secrets, onOutput: (output) => {
2644
+ const parts = output.split("\n");
2645
+ for (let index = 0; index < parts.length; index++) {
2646
+ pendingOutput += parts[index].slice(0, 512 - pendingOutput.length);
2647
+ if (index === parts.length - 1)
2648
+ break;
2649
+ const line = pendingOutput;
2650
+ pendingOutput = "";
2651
+ const stage = /^\[(preflight|images|dependencies|start|health|complete)\]/.exec(line)?.[1];
2652
+ if (stage)
2653
+ options?.onProgress?.(stage);
2654
+ }
2655
+ } }
2656
+ );
2657
+ if (!started.success) {
2658
+ const resultFile = `${remoteDir}/result-${deploymentId}.json`;
2659
+ const state = getDeployCommandState(started.error);
2660
+ const checked = await ssh.exec(`if [ -f ${shellArgument(resultFile)} ]; then cat -- ${shellArgument(resultFile)}; fi`, { timeoutMs: 2e4, maxCaptureChars: 8192 });
2661
+ let terminal;
2662
+ if (checked.success && !checked.data.stdoutTruncated) {
2663
+ try {
2664
+ const parsed = ServerResultSchema.safeParse(JSON.parse(checked.data.stdout));
2665
+ if (parsed.success && parsed.data.deploymentId === deploymentId)
2666
+ terminal = parsed.data;
2667
+ } catch {
2668
+ }
2669
+ }
2670
+ return err({ ...started.error, message: `${started.error.message}
2671
+ ${terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage, code: String(terminal.exitCode) } }) : deployM("deploy_remoteUnknown")}`, suggestion: terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage, code: String(terminal.exitCode) } }) : deployM("deploy_remoteUnknown"), ext: { ...started.error.ext, commandExecution: {
2672
+ ...state,
2673
+ reason: state?.reason ?? "exit",
2674
+ localTermination: state?.localTermination ?? "unknown",
2675
+ remoteExecution: terminal ? "finished" : "unknown",
2676
+ deploymentId,
2677
+ resultFile,
2678
+ ...terminal ? { remoteExitCode: terminal.exitCode, remoteStage: terminal.stage } : {}
2679
+ } } });
2680
+ }
2681
+ logger5.info("Running health check", { url });
2682
+ const healthy = await healthCheck(`${url}${!hasWeb && scan.isSvelteKit ? "/" : "/health"}`) && (!hasWeb || await healthCheck(`${url}/_hai/web-health`) && await healthCheck(`${url}/`));
2683
+ if (!healthy) {
2684
+ return err(
2685
+ HaiDeployError.HEALTH_CHECK_FAILED,
2686
+ deployM("deploy_healthCheckFailed", { params: { url } })
2687
+ );
2688
+ }
2689
+ const result = {
2690
+ url,
2691
+ deploymentId,
2692
+ status: "ready",
2693
+ provider: "docker-ssh",
2694
+ healthCheck: "passed",
2695
+ envVarsSet: Object.keys(env),
2696
+ bundlePath: bundle.bundlePath
2697
+ };
2698
+ logger5.info("docker-ssh deployment complete", { url, deploymentId });
2699
+ return ok(result);
2700
+ };
2701
+ const deploySafely = async (context) => {
2702
+ try {
2703
+ return await deploy2(context);
2704
+ } catch (error) {
2705
+ const reason = error instanceof Error ? error.message : String(error);
2706
+ let safeReason = "Deployment bundle failed";
2707
+ try {
2708
+ safeReason = maskSecrets(reason, Object.values(context.appEnv ?? {}).filter(Boolean));
2709
+ } catch {
2710
+ }
2711
+ logger5.error("Deployment bundle failed", { reason: safeReason });
2712
+ return err(HaiDeployError.DEPLOY_FAILED, deployM("deploy_bundleFailed", { params: { reason: safeReason } }));
2713
+ }
2714
+ };
2715
+ return { name: "docker-ssh", deploy: deploySafely };
2716
+ }
2717
+ var logger6 = core.logger.child({ module: "deploy", scope: "provider-vercel" });
2718
+ var VERCEL_API = "https://api.vercel.com";
2719
+ var POLL_INTERVAL = 3e3;
2720
+ var MAX_POLL_ATTEMPTS = 60;
2721
+ var VercelApiError = class extends Error {
2722
+ constructor(status) {
2723
+ super(deployM("deploy_apiError", { params: { service: "Vercel", status: String(status) } }));
2724
+ this.status = status;
2725
+ }
2726
+ status;
2727
+ };
2728
+ function toDeployError(codeDef, messageKey, error) {
2729
+ return {
2730
+ ...codeDef,
2731
+ message: deployM(messageKey, {
2732
+ params: { error: error instanceof Error ? error.message : String(error) }
2733
+ }),
2734
+ cause: error
2735
+ };
2736
+ }
2737
+ async function vercelFetch(token, path, options) {
2738
+ const url = `${VERCEL_API}${path}`;
2739
+ const response = await fetch(url, {
2740
+ ...options,
2741
+ headers: {
2742
+ "Authorization": `Bearer ${token}`,
2743
+ "Content-Type": "application/json",
2744
+ ...options?.headers
2745
+ }
2746
+ });
2747
+ if (!response.ok) {
2748
+ throw new VercelApiError(response.status);
2749
+ }
2750
+ return response.json();
2751
+ }
2752
+ function collectFiles(dir, baseDir) {
2753
+ const results = [];
2754
+ const entries = readdirSync(dir, { withFileTypes: true });
2755
+ for (const entry of entries) {
2756
+ const fullPath = join(dir, entry.name);
2757
+ if (entry.isDirectory()) {
2758
+ results.push(...collectFiles(fullPath, baseDir));
2759
+ } else {
2760
+ results.push(relative(baseDir, fullPath).split(sep).join("/"));
2761
+ }
2762
+ }
2763
+ return results;
2764
+ }
2765
+ function createVercelProvider() {
2766
+ let token = null;
2767
+ return {
2768
+ name: "vercel",
2769
+ async authenticate(apiToken) {
2770
+ logger6.debug("Authenticating with Vercel");
2771
+ try {
2772
+ const user = await vercelFetch(
2773
+ apiToken,
2774
+ "/v2/user"
2775
+ );
2776
+ token = apiToken;
2777
+ const username = user.user.username || user.user.email;
2778
+ logger6.info("Vercel authenticated", { user: username });
2779
+ return ok(username);
2780
+ } catch (error) {
2781
+ logger6.error("Vercel authentication failed", { error });
2782
+ return err(toDeployError(HaiDeployError.AUTH_FAILED, "deploy_authFailed", error));
2783
+ }
2784
+ },
2785
+ async createProject(projectName) {
2786
+ if (!token) {
2787
+ return err(
2788
+ HaiDeployError.AUTH_REQUIRED,
2789
+ deployM("deploy_authRequired")
2790
+ );
2791
+ }
2792
+ logger6.debug("Creating Vercel project", { projectName });
2793
+ try {
2794
+ const existingId = await findExistingProject(token, projectName);
2795
+ if (existingId !== null) {
2796
+ logger6.info("Vercel project found", { projectName, projectId: existingId });
2797
+ return ok(existingId);
2798
+ }
2799
+ try {
2800
+ const project = await vercelFetch(
2801
+ token,
2802
+ "/v11/projects",
2803
+ {
2804
+ method: "POST",
2805
+ body: JSON.stringify({
2806
+ name: projectName,
2807
+ framework: "sveltekit"
2808
+ })
2809
+ }
2810
+ );
2811
+ logger6.info("Vercel project created", { projectName, projectId: project.id });
2812
+ return ok(project.id);
2813
+ } catch (error) {
2814
+ if (error instanceof VercelApiError && error.status === 409) {
2815
+ const concurrentProjectId = await findExistingProject(token, projectName);
2816
+ if (concurrentProjectId !== null) {
2817
+ logger6.info("Vercel project reused after create conflict", {
2818
+ projectName,
2819
+ projectId: concurrentProjectId
2820
+ });
2821
+ return ok(concurrentProjectId);
2822
+ }
2823
+ }
2824
+ throw error;
2825
+ }
2826
+ } catch (error) {
2827
+ logger6.error("Vercel project creation failed", { projectName, error });
2828
+ return err(toDeployError(HaiDeployError.PROJECT_CREATE_FAILED, "deploy_projectCreateFailed", error));
2829
+ }
2830
+ },
2831
+ async setEnvVars(projectId, envVars) {
2832
+ if (!token) {
2833
+ return err(
2834
+ HaiDeployError.AUTH_REQUIRED,
2835
+ deployM("deploy_authRequired")
2836
+ );
2837
+ }
2838
+ logger6.debug("Setting Vercel env vars", { projectId, count: Object.keys(envVars).length });
2839
+ try {
2840
+ const envEntries = Object.entries(envVars).map(([key, value]) => ({
2841
+ key,
2842
+ value,
2843
+ type: "encrypted",
2844
+ target: ["production", "preview", "development"]
2845
+ }));
2846
+ await vercelFetch(
2847
+ token,
2848
+ `/v10/projects/${projectId}/env?upsert=true`,
2849
+ {
2850
+ method: "POST",
2851
+ body: JSON.stringify(envEntries)
2852
+ }
2853
+ );
2854
+ logger6.info("Vercel env vars set", { projectId, count: envEntries.length });
2855
+ return ok(void 0);
2856
+ } catch (error) {
2857
+ logger6.error("Vercel env vars setup failed", { projectId, error });
2858
+ return err(toDeployError(HaiDeployError.ENV_VAR_FAILED, "deploy_envVarFailed", error));
2859
+ }
2860
+ },
2861
+ async deploy(projectId, outputDir) {
2862
+ if (!token) {
2863
+ return err(
2864
+ HaiDeployError.AUTH_REQUIRED,
2865
+ deployM("deploy_authRequired")
2866
+ );
2867
+ }
2868
+ logger6.debug("Deploying to Vercel", { projectId, outputDir });
2869
+ try {
2870
+ const outputConfig = JSON.parse(readFileSync(join(outputDir, "config.json"), "utf8"));
2871
+ if (outputConfig.version !== 3)
2872
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
2873
+ const filePaths = collectFiles(outputDir, outputDir);
2874
+ const files = [];
2875
+ for (const filePath of filePaths) {
2876
+ const fullPath = join(outputDir, filePath);
2877
+ const content = readFileSync(fullPath);
2878
+ const sha = createHash("sha1").update(content).digest("hex");
2879
+ const size = statSync(fullPath).size;
2880
+ await uploadFile(token, content, sha);
2881
+ files.push({ file: `.vercel/output/${filePath}`, sha, size });
2882
+ }
2883
+ logger6.debug("Files uploaded", { count: files.length });
2884
+ const deployment = await vercelFetch(
2885
+ token,
2886
+ "/v13/deployments?prebuilt=1",
2887
+ {
2888
+ method: "POST",
2889
+ body: JSON.stringify({
2890
+ name: projectId,
2891
+ files,
2892
+ version: 2,
2893
+ project: projectId,
2894
+ projectSettings: { framework: null, buildCommand: "", installCommand: "" }
2895
+ })
2896
+ }
2897
+ );
2898
+ const finalStatus = await pollDeploymentStatus(token, deployment.id);
2899
+ const result = {
2900
+ url: `https://${deployment.url}`,
2901
+ deploymentId: deployment.id,
2902
+ status: finalStatus,
2903
+ envVarsSet: []
2904
+ };
2905
+ logger6.info("Vercel deployment complete", {
2906
+ deploymentId: deployment.id,
2907
+ url: result.url,
2908
+ status: finalStatus
2909
+ });
2910
+ return ok(result);
2911
+ } catch (error) {
2912
+ logger6.error("Vercel deployment failed", { projectId, error });
2913
+ return err(toDeployError(HaiDeployError.UPLOAD_FAILED, "deploy_uploadFailed", error));
2914
+ }
2915
+ }
2916
+ };
2917
+ }
2918
+ async function findExistingProject(token, projectName) {
2919
+ try {
2920
+ const project = await vercelFetch(token, `/v10/projects/${projectName}`);
2921
+ return project.id;
2922
+ } catch (error) {
2923
+ if (error instanceof VercelApiError && error.status === 404) {
2924
+ return null;
2925
+ }
2926
+ throw error;
2927
+ }
2928
+ }
2929
+ async function uploadFile(token, content, sha) {
2930
+ const url = `${VERCEL_API}/v2/files`;
2931
+ const uploadContent = new Uint8Array(content);
2932
+ const response = await fetch(url, {
2933
+ method: "POST",
2934
+ headers: {
2935
+ "Authorization": `Bearer ${token}`,
2936
+ "Content-Type": "application/octet-stream",
2937
+ "x-vercel-digest": sha
2938
+ },
2939
+ body: new Blob([uploadContent])
2940
+ });
2941
+ if (!response.ok && response.status !== 409) {
2942
+ throw new Error(deployM("deploy_fileUploadFailed", { params: { status: String(response.status) } }));
2943
+ }
2944
+ }
2945
+ async function pollDeploymentStatus(token, deploymentId) {
2946
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
2947
+ const deployment = await vercelFetch(
2948
+ token,
2949
+ `/v13/deployments/${deploymentId}`
2950
+ );
2951
+ if (deployment.readyState === "READY") {
2952
+ return "ready";
2953
+ }
2954
+ if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
2955
+ return "error";
2956
+ }
2957
+ await new Promise((resolve6) => setTimeout(resolve6, POLL_INTERVAL));
2958
+ }
2959
+ return "building";
2960
+ }
2961
+ var logger7 = core.logger.child({ module: "deploy", scope: "provisioner-aliyun" });
2962
+ function createAliyunProvisioner() {
2963
+ let accessKey = null;
2964
+ let secretKey = null;
2965
+ let signName = "";
2966
+ return {
2967
+ name: "aliyun",
2968
+ serviceType: "sms",
2969
+ async authenticate(credentials) {
2970
+ logger7.debug("Authenticating with Aliyun");
2971
+ try {
2972
+ const akId = credentials.accessKeyId ?? credentials.access_key_id ?? credentials.access_key ?? "";
2973
+ const akSecret = credentials.accessKeySecret ?? credentials.access_key_secret ?? credentials.secret_key ?? "";
2974
+ if (!akId || !akSecret || !credentials.signName) {
2975
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "accessKeyId, accessKeySecret, signName" } }));
2976
+ }
2977
+ accessKey = akId;
2978
+ secretKey = akSecret;
2979
+ signName = credentials.signName;
2980
+ logger7.info("Aliyun authenticated", { accessKeyId: `${akId.slice(0, 6)}***` });
2981
+ return ok(akId);
2982
+ } catch (error) {
2983
+ logger7.error("Aliyun authentication failed", { error });
2984
+ return err(
2985
+ HaiDeployError.AUTH_FAILED,
2986
+ deployM("deploy_authFailed", {
2987
+ params: { error: error instanceof Error ? error.message : String(error) }
2988
+ }),
2989
+ error
2990
+ );
2991
+ }
2992
+ },
2993
+ async provision(_appName) {
2994
+ if (!accessKey || !secretKey) {
2995
+ return err(
2996
+ HaiDeployError.AUTH_REQUIRED,
2997
+ deployM("deploy_authRequired")
2998
+ );
2999
+ }
3000
+ logger7.debug("Provisioning Aliyun SMS service");
3001
+ return ok({
3002
+ serviceType: "sms",
3003
+ provisionerName: "aliyun",
3004
+ resourceStatus: "reused",
3005
+ envVars: {
3006
+ HAI_REACH_PROVIDERS: JSON.stringify([{ name: "sms", type: "aliyun-sms", accessKeyId: accessKey, accessKeySecret: secretKey, signName }])
3007
+ },
3008
+ resourceInfo: "aliyun:verify-only"
3009
+ });
3010
+ }
3011
+ };
3012
+ }
3013
+ var logger8 = core.logger.child({ module: "deploy", scope: "provisioner-neon" });
3014
+ var NEON_API = "https://console.neon.tech/api/v2";
3015
+ var DEFAULT_NEON_DATABASE_NAME = "neondb";
3016
+ var DEFAULT_NEON_ROLE_NAME = "neondb_owner";
3017
+ async function neonFetch(token, path, options) {
3018
+ const response = await fetch(`${NEON_API}${path}`, {
3019
+ ...options,
3020
+ headers: {
3021
+ "Accept": "application/json",
3022
+ "Authorization": `Bearer ${token}`,
3023
+ "Content-Type": "application/json",
3024
+ ...options?.headers
3025
+ }
3026
+ });
3027
+ if (!response.ok) {
3028
+ throw new Error(deployM("deploy_apiError", { params: { service: "Neon", status: String(response.status) } }));
3029
+ }
3030
+ return response.json();
3031
+ }
3032
+ async function listNeonProjects(token) {
3033
+ const data = await neonFetch(token, "/projects");
3034
+ return data.projects ?? [];
3035
+ }
3036
+ async function findExistingNeonProject(token, projectName) {
3037
+ const projects = await listNeonProjects(token);
3038
+ return projects.find((project) => project.name === projectName) ?? null;
3039
+ }
3040
+ async function getNeonConnectionUri(token, projectId) {
3041
+ const query = new URLSearchParams({
3042
+ database_name: DEFAULT_NEON_DATABASE_NAME,
3043
+ role_name: DEFAULT_NEON_ROLE_NAME
3044
+ });
3045
+ const data = await neonFetch(
3046
+ token,
3047
+ `/projects/${projectId}/connection_uri?${query.toString()}`,
3048
+ { headers: { "Content-Type": "application/json" } }
3049
+ );
3050
+ if (!data.uri) {
3051
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
3052
+ }
3053
+ return data.uri;
3054
+ }
3055
+ function buildNeonProvisionResult(projectId, connectionUri, resourceStatus) {
3056
+ return {
3057
+ serviceType: "db",
3058
+ provisionerName: "neon",
3059
+ resourceStatus,
3060
+ envVars: {
3061
+ HAI_DB: JSON.stringify({ type: "postgresql", url: connectionUri, database: decodeURIComponent(new URL(connectionUri).pathname.slice(1)) })
3062
+ },
3063
+ resourceInfo: `neon-project:${projectId}`
3064
+ };
3065
+ }
3066
+ function createNeonProvisioner() {
3067
+ let token = null;
3068
+ return {
3069
+ name: "neon",
3070
+ serviceType: "db",
3071
+ async authenticate(credentials) {
3072
+ logger8.debug("Authenticating with Neon");
3073
+ try {
3074
+ const apiToken = credentials.apiKey ?? credentials.token ?? credentials.api_key ?? "";
3075
+ if (!apiToken) {
3076
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "token" } }));
3077
+ }
3078
+ await listNeonProjects(apiToken);
3079
+ token = apiToken;
3080
+ logger8.info("Neon authenticated");
3081
+ return ok("neon-user");
3082
+ } catch (error) {
3083
+ logger8.error("Neon authentication failed", { error });
3084
+ return err(
3085
+ HaiDeployError.AUTH_FAILED,
3086
+ deployM("deploy_authFailed", {
3087
+ params: { error: error instanceof Error ? error.message : String(error) }
3088
+ }),
3089
+ error
3090
+ );
3091
+ }
3092
+ },
3093
+ async provision(appName) {
3094
+ if (!token) {
3095
+ return err(
3096
+ HaiDeployError.AUTH_REQUIRED,
3097
+ deployM("deploy_authRequired")
3098
+ );
3099
+ }
3100
+ logger8.debug("Provisioning Neon database", { appName });
3101
+ let resource;
3102
+ try {
3103
+ const projectName = `${appName}-db`;
3104
+ const existingProject = await findExistingNeonProject(token, projectName);
3105
+ if (existingProject !== null) {
3106
+ resource = { resourceInfo: `neon-project:${existingProject.id}`, resourceStatus: "reused" };
3107
+ const connectionUri2 = await getNeonConnectionUri(token, existingProject.id);
3108
+ logger8.info("Neon project reused", {
3109
+ projectId: existingProject.id,
3110
+ projectName
3111
+ });
3112
+ return ok(buildNeonProvisionResult(existingProject.id, connectionUri2, "reused"));
3113
+ }
3114
+ const data = await neonFetch(token, "/projects", {
3115
+ method: "POST",
3116
+ body: JSON.stringify({
3117
+ project: {
3118
+ name: projectName,
3119
+ pg_version: 16
3120
+ }
3121
+ })
3122
+ });
3123
+ const projectId = data.project?.id ?? "";
3124
+ if (projectId)
3125
+ resource = { resourceInfo: `neon-project:${projectId}`, resourceStatus: "created" };
3126
+ const connectionUri = data.connection_uris?.[0]?.connection_uri ?? "";
3127
+ if (!projectId || !connectionUri) {
3128
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Neon" } }));
3129
+ }
3130
+ logger8.info("Neon database provisioned", { projectId });
3131
+ return ok(buildNeonProvisionResult(projectId, connectionUri, "created"));
3132
+ } catch (error) {
3133
+ logger8.error("Neon provisioning failed", { appName });
3134
+ if (resource) {
3135
+ return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
3136
+ projectName: appName,
3137
+ stage: "provision:db",
3138
+ resources: [{ serviceType: "db", provisionerName: "neon", ...resource }]
3139
+ });
3140
+ }
3141
+ return err(
3142
+ HaiDeployError.PROVISION_FAILED,
3143
+ deployM("deploy_provisionFailed", {
3144
+ params: {
3145
+ service: "neon",
3146
+ error: error instanceof Error ? error.message : String(error)
3147
+ }
3148
+ }),
3149
+ error
3150
+ );
3151
+ }
3152
+ }
3153
+ };
3154
+ }
3155
+ var logger9 = core.logger.child({ module: "deploy", scope: "provisioner-r2" });
3156
+ var CF_API = "https://api.cloudflare.com/client/v4";
3157
+ function createR2Provisioner() {
3158
+ let token = null;
3159
+ let accountId = null;
3160
+ let accessKey = "";
3161
+ let secretKey = "";
3162
+ return {
3163
+ name: "r2",
3164
+ serviceType: "storage",
3165
+ async authenticate(credentials) {
3166
+ logger9.debug("Authenticating with Cloudflare");
3167
+ try {
3168
+ const acctId = credentials.accountId ?? credentials.account_id ?? "";
3169
+ const apiTok = credentials.apiToken ?? credentials.api_token ?? credentials.token ?? "";
3170
+ if (!acctId || !apiTok || !credentials.accessKeyId || !credentials.secretAccessKey) {
3171
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "accountId, apiToken, accessKeyId, secretAccessKey" } }));
3172
+ }
3173
+ const res = await fetch(`${CF_API}/accounts/${acctId}/r2/buckets`, {
3174
+ headers: { Authorization: `Bearer ${apiTok}` }
3175
+ });
3176
+ if (!res.ok) {
3177
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(res.status) } }));
3178
+ }
3179
+ accountId = acctId;
3180
+ token = apiTok;
3181
+ accessKey = credentials.accessKeyId;
3182
+ secretKey = credentials.secretAccessKey;
3183
+ logger9.info("Cloudflare R2 authenticated", { accountId: acctId });
3184
+ return ok(acctId);
3185
+ } catch (error) {
3186
+ logger9.error("Cloudflare R2 authentication failed", { error });
3187
+ return err(
3188
+ HaiDeployError.AUTH_FAILED,
3189
+ deployM("deploy_authFailed", {
3190
+ params: { error: error instanceof Error ? error.message : String(error) }
3191
+ }),
3192
+ error
3193
+ );
3194
+ }
3195
+ },
3196
+ async provision(appName) {
3197
+ if (!token || !accountId) {
3198
+ return err(
3199
+ HaiDeployError.AUTH_REQUIRED,
3200
+ deployM("deploy_authRequired")
3201
+ );
3202
+ }
3203
+ const bucketName = `${appName}-storage`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
3204
+ logger9.debug("Provisioning R2 bucket", { appName, bucketName });
3205
+ try {
3206
+ const createRes = await fetch(
3207
+ `${CF_API}/accounts/${accountId}/r2/buckets`,
3208
+ {
3209
+ method: "POST",
3210
+ headers: {
3211
+ "Authorization": `Bearer ${token}`,
3212
+ "Content-Type": "application/json"
3213
+ },
3214
+ body: JSON.stringify({ name: bucketName })
3215
+ }
3216
+ );
3217
+ if (!createRes.ok && createRes.status !== 409) {
3218
+ throw new Error(deployM("deploy_apiError", { params: { service: "Cloudflare", status: String(createRes.status) } }));
3219
+ }
3220
+ logger9.info("R2 bucket provisioned", { bucketName });
3221
+ return ok({
3222
+ serviceType: "storage",
3223
+ provisionerName: "r2",
3224
+ resourceStatus: createRes.status === 409 ? "reused" : "created",
3225
+ envVars: {
3226
+ HAI_STORAGE: JSON.stringify({
3227
+ type: "s3",
3228
+ endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
3229
+ bucket: bucketName,
3230
+ region: "auto",
3231
+ accessKeyId: accessKey,
3232
+ secretAccessKey: secretKey
3233
+ })
3234
+ },
3235
+ resourceInfo: `r2-bucket:${bucketName}`
3236
+ });
3237
+ } catch (error) {
3238
+ logger9.error("R2 provisioning failed", { appName, error });
3239
+ return err(
3240
+ HaiDeployError.PROVISION_FAILED,
3241
+ deployM("deploy_provisionFailed", {
3242
+ params: {
3243
+ service: "r2",
3244
+ error: error instanceof Error ? error.message : String(error)
3245
+ }
3246
+ }),
3247
+ error
3248
+ );
3249
+ }
3250
+ }
3251
+ };
3252
+ }
3253
+ var logger10 = core.logger.child({ module: "deploy", scope: "provisioner-resend" });
3254
+ var RESEND_API = "https://api.resend.com";
3255
+ function createResendProvisioner() {
3256
+ let token = null;
3257
+ let from = "";
3258
+ return {
3259
+ name: "resend",
3260
+ serviceType: "email",
3261
+ async authenticate(credentials) {
3262
+ logger10.debug("Authenticating with Resend");
3263
+ try {
3264
+ const apiToken = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
3265
+ if (!apiToken || !credentials.from) {
3266
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "apiKey, from" } }));
3267
+ }
3268
+ const res = await fetch(`${RESEND_API}/domains`, {
3269
+ headers: { Authorization: `Bearer ${apiToken}` }
3270
+ });
3271
+ if (!res.ok) {
3272
+ throw new Error(deployM("deploy_apiError", { params: { service: "Resend", status: String(res.status) } }));
3273
+ }
3274
+ token = apiToken;
3275
+ from = credentials.from;
3276
+ logger10.info("Resend authenticated");
3277
+ return ok("resend-user");
3278
+ } catch (error) {
3279
+ logger10.error("Resend authentication failed", { error });
3280
+ return err(
3281
+ HaiDeployError.AUTH_FAILED,
3282
+ deployM("deploy_authFailed", {
3283
+ params: { error: error instanceof Error ? error.message : String(error) }
3284
+ }),
3285
+ error
3286
+ );
3287
+ }
3288
+ },
3289
+ async provision(_appName) {
3290
+ if (!token) {
3291
+ return err(
3292
+ HaiDeployError.AUTH_REQUIRED,
3293
+ deployM("deploy_authRequired")
3294
+ );
3295
+ }
3296
+ logger10.debug("Provisioning Resend email service");
3297
+ return ok({
3298
+ serviceType: "email",
3299
+ provisionerName: "resend",
3300
+ resourceStatus: "reused",
3301
+ envVars: {
3302
+ HAI_REACH_PROVIDERS: JSON.stringify([{ name: "email", type: "smtp", host: "smtp.resend.com", port: 465, secure: true, user: "resend", pass: token, from }])
3303
+ },
3304
+ resourceInfo: "resend:verify-only"
3305
+ });
3306
+ }
3307
+ };
3308
+ }
3309
+ var logger11 = core.logger.child({ module: "deploy", scope: "provisioner-upstash" });
3310
+ var UPSTASH_API = "https://api.upstash.com";
3311
+ function createUpstashAuthHeader(email, apiKey) {
3312
+ return `Basic ${Buffer.from(`${email}:${apiKey}`).toString("base64")}`;
3313
+ }
3314
+ async function upstashFetch(email, apiKey, path, options) {
3315
+ const response = await fetch(`${UPSTASH_API}${path}`, {
3316
+ ...options,
3317
+ headers: {
3318
+ "Authorization": createUpstashAuthHeader(email, apiKey),
3319
+ "Content-Type": "application/json",
3320
+ ...options?.headers
3321
+ }
3322
+ });
3323
+ if (!response.ok) {
3324
+ throw new Error(deployM("deploy_apiError", { params: { service: "Upstash", status: String(response.status) } }));
3325
+ }
3326
+ return response.json();
3327
+ }
3328
+ async function listUpstashDatabases(email, apiKey) {
3329
+ return upstashFetch(email, apiKey, "/v2/redis/databases");
3330
+ }
3331
+ async function findExistingUpstashDatabase(email, apiKey, databaseName) {
3332
+ const databases = await listUpstashDatabases(email, apiKey);
3333
+ return databases.find((database) => database.database_name === databaseName) ?? null;
3334
+ }
3335
+ async function getUpstashDatabase(email, apiKey, databaseId) {
3336
+ return upstashFetch(email, apiKey, `/v2/redis/database/${databaseId}`);
3337
+ }
3338
+ function buildUpstashProvisionResult(database, resourceStatus) {
3339
+ const databaseId = database.database_id ?? "";
3340
+ if (!databaseId || !database.endpoint || !database.password || !database.port) {
3341
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
3342
+ }
3343
+ return {
3344
+ serviceType: "cache",
3345
+ provisionerName: "upstash",
3346
+ resourceStatus,
3347
+ envVars: {
3348
+ HAI_CACHE: JSON.stringify({ type: "redis", url: `rediss://default:${encodeURIComponent(database.password)}@${database.endpoint}:${database.port}` })
3349
+ },
3350
+ resourceInfo: `upstash-db:${databaseId}`
3351
+ };
3352
+ }
3353
+ function createUpstashProvisioner() {
3354
+ let apiKey = null;
3355
+ let email = null;
3356
+ return {
3357
+ name: "upstash",
3358
+ serviceType: "cache",
3359
+ async authenticate(credentials) {
3360
+ logger11.debug("Authenticating with Upstash");
3361
+ try {
3362
+ const userEmail = credentials.email ?? "";
3363
+ const userKey = credentials.apiKey ?? credentials.api_key ?? credentials.token ?? "";
3364
+ if (!userEmail || !userKey) {
3365
+ throw new Error(deployM("deploy_credentialMissing", { params: { fields: "email, api_key" } }));
3366
+ }
3367
+ await listUpstashDatabases(userEmail, userKey);
3368
+ email = userEmail;
3369
+ apiKey = userKey;
3370
+ logger11.info("Upstash authenticated", { email: userEmail });
3371
+ return ok(userEmail);
3372
+ } catch (error) {
3373
+ logger11.error("Upstash authentication failed", { error });
3374
+ return err(
3375
+ HaiDeployError.AUTH_FAILED,
3376
+ deployM("deploy_authFailed", {
3377
+ params: { error: error instanceof Error ? error.message : String(error) }
3378
+ }),
3379
+ error
3380
+ );
3381
+ }
3382
+ },
3383
+ async provision(appName) {
3384
+ if (!apiKey || !email) {
3385
+ return err(
3386
+ HaiDeployError.AUTH_REQUIRED,
3387
+ deployM("deploy_authRequired")
3388
+ );
3389
+ }
3390
+ logger11.debug("Provisioning Upstash Redis", { appName });
3391
+ let resource;
3392
+ try {
3393
+ const databaseName = `${appName}-cache`;
3394
+ const existingDatabase = await findExistingUpstashDatabase(email, apiKey, databaseName);
3395
+ if (existingDatabase !== null) {
3396
+ const databaseId = existingDatabase.database_id ?? "";
3397
+ if (!databaseId) {
3398
+ throw new Error(deployM("deploy_provisionNoResult", { params: { service: "Upstash" } }));
3399
+ }
3400
+ resource = { resourceInfo: `upstash-db:${databaseId}`, resourceStatus: "reused" };
3401
+ const detail = await getUpstashDatabase(email, apiKey, databaseId);
3402
+ logger11.info("Upstash Redis reused", { databaseId, databaseName });
3403
+ return ok(buildUpstashProvisionResult({ ...existingDatabase, ...detail }, "reused"));
3404
+ }
3405
+ const data = await upstashFetch(email, apiKey, "/v2/redis/database", {
3406
+ method: "POST",
3407
+ body: JSON.stringify({
3408
+ name: databaseName,
3409
+ region: "global",
3410
+ tls: true
3411
+ })
3412
+ });
3413
+ if (data.database_id)
3414
+ resource = { resourceInfo: `upstash-db:${data.database_id}`, resourceStatus: "created" };
3415
+ logger11.info("Upstash Redis provisioned", { databaseId: data.database_id });
3416
+ return ok(buildUpstashProvisionResult(data, "created"));
3417
+ } catch (error) {
3418
+ logger11.error("Upstash provisioning failed", { appName });
3419
+ if (resource) {
3420
+ return recoveryFailure({ ...HaiDeployError.PROVISION_FAILED}, {
3421
+ projectName: appName,
3422
+ stage: "provision:cache",
3423
+ resources: [{ serviceType: "cache", provisionerName: "upstash", ...resource }]
3424
+ });
3425
+ }
3426
+ return err(
3427
+ HaiDeployError.PROVISION_FAILED,
3428
+ deployM("deploy_provisionFailed", {
3429
+ params: {
3430
+ service: "upstash",
3431
+ error: error instanceof Error ? error.message : String(error)
3432
+ }
3433
+ }),
3434
+ error
3435
+ );
3436
+ }
3437
+ }
3438
+ };
3439
+ }
3440
+
3441
+ // ../deploy/src/deploy-main.ts
3442
+ var logger12 = core.logger.child({ module: "deploy", scope: "main" });
3443
+ var currentConfig = null;
3444
+ var currentProvider = null;
3445
+ var currentContainerProvider = null;
3446
+ var currentProvisioners = /* @__PURE__ */ new Map();
3447
+ var initInProgress = false;
3448
+ var notInitialized = core.module.createNotInitializedKit(
3449
+ HaiDeployError.NOT_INITIALIZED,
3450
+ () => deployM("deploy_notInitialized")
3451
+ );
3452
+ function createProviderByType(type) {
3453
+ switch (type) {
3454
+ case "vercel":
3455
+ return createVercelProvider();
3456
+ default:
3457
+ throw new Error(deployM("deploy_unsupportedProviderType", { params: { type } }));
3458
+ }
3459
+ }
3460
+ function createProvisionerByName(provisioner) {
3461
+ switch (provisioner) {
3462
+ case "neon":
3463
+ return createNeonProvisioner();
3464
+ case "upstash":
3465
+ return createUpstashProvisioner();
3466
+ case "cloudflare-r2":
3467
+ return createR2Provisioner();
3468
+ case "resend":
3469
+ return createResendProvisioner();
3470
+ case "aliyun":
3471
+ return createAliyunProvisioner();
3472
+ default:
3473
+ throw new Error(deployM("deploy_unsupportedProvisionerType", { params: { type: provisioner } }));
3474
+ }
3475
+ }
3476
+ function extractCredentials(serviceConfig) {
3477
+ const result = {};
3478
+ for (const [key, value] of Object.entries(serviceConfig)) {
3479
+ if (key !== "provisioner" && typeof value === "string") {
3480
+ result[key] = value;
3481
+ }
3482
+ }
3483
+ return result;
3484
+ }
3485
+ var deployCredentials = {
3486
+ getPath: () => getCredentialsPath(),
3487
+ load: () => loadCredentials(),
3488
+ save: (key, value) => saveCredential(key, value),
3489
+ saveAll: (entries) => saveCredentials(entries)
3490
+ };
3491
+ function normalizeProjectName(name) {
3492
+ const normalized = name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3493
+ return normalized || "app";
3494
+ }
3495
+ function mergeProvisionEnv(results, scan, options) {
3496
+ let env = {};
3497
+ const reachRequirements = scan?.middlewares?.filter((item) => item.configName === "reach") ?? [];
3498
+ const reachProviders = reachRequirements.map((item) => item.config);
3499
+ let hasReachOutput = false;
3500
+ for (const prov of results) {
3501
+ if (prov.envVars.HAI_REACH_PROVIDERS) {
3502
+ hasReachOutput = true;
3503
+ const supplied = JSON.parse(prov.envVars.HAI_REACH_PROVIDERS);
3504
+ for (const provider of supplied) {
3505
+ const targets = reachRequirements.filter((item) => {
3506
+ const mode = options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.[item.service];
3507
+ return item.service === prov.serviceType && (!mode || mode === "default");
3508
+ });
3509
+ if (targets.length) {
3510
+ for (const target of targets)
3511
+ reachProviders[reachRequirements.indexOf(target)] = { ...provider, name: target.config.name ?? provider.name };
3512
+ } else {
3513
+ reachProviders.push(provider);
3514
+ }
3515
+ }
3516
+ }
3517
+ env = { ...env, ...prov.envVars };
3518
+ }
3519
+ if (hasReachOutput)
3520
+ env.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
3521
+ return env;
3522
+ }
3523
+ var deploy = {
3524
+ credentials: deployCredentials,
3525
+ async init(config) {
3526
+ if (initInProgress) {
3527
+ logger12.warn("Deploy init already in progress, skipping concurrent call");
3528
+ return err(
3529
+ HaiDeployError.CONFIG_ERROR,
3530
+ deployM("deploy_configError", { params: { error: "init already in progress" } })
3531
+ );
3532
+ }
3533
+ initInProgress = true;
3534
+ try {
3535
+ if (currentProvider || currentContainerProvider) {
3536
+ logger12.warn("Deploy module is already initialized, reinitializing");
3537
+ await deploy.close();
3538
+ }
3539
+ logger12.info("Initializing deploy module");
3540
+ const parseResult = DeployConfigSchema.safeParse(config);
3541
+ if (!parseResult.success) {
3542
+ logger12.error("Deploy config validation failed", { error: parseResult.error.message });
3543
+ return err(
3544
+ HaiDeployError.CONFIG_ERROR,
3545
+ deployM("deploy_configError", { params: { error: parseResult.error.message } }),
3546
+ parseResult.error
3547
+ );
3548
+ }
3549
+ const parsed = parseResult.data;
3550
+ try {
3551
+ let provider = null;
3552
+ let containerProvider = null;
3553
+ if (parsed.provider.type === "docker-ssh") {
3554
+ containerProvider = createDockerSshProvider(parsed.provider, parsed.container);
3555
+ logger12.info("Container deploy provider ready", { provider: "docker-ssh", host: parsed.provider.ssh?.host });
3556
+ } else {
3557
+ provider = createProviderByType(parsed.provider.type);
3558
+ const authResult = await provider.authenticate(parsed.provider.token);
3559
+ if (!authResult.success) {
3560
+ return err(authResult.error);
3561
+ }
3562
+ logger12.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
3563
+ }
3564
+ currentProvisioners.clear();
3565
+ if (parsed.services) {
3566
+ const serviceEntries = Object.entries(parsed.services);
3567
+ for (const [serviceType, serviceConfig] of serviceEntries) {
3568
+ if (!serviceConfig)
3569
+ continue;
3570
+ const provisioner = createProvisionerByName(serviceConfig.provisioner);
3571
+ const credentials = extractCredentials(serviceConfig);
3572
+ const provAuthResult = await provisioner.authenticate(credentials);
3573
+ if (!provAuthResult.success) {
3574
+ logger12.error("Provisioner authentication failed", {
3575
+ service: serviceType,
3576
+ provisioner: serviceConfig.provisioner
3577
+ });
3578
+ currentProvisioners.clear();
3579
+ return err(provAuthResult.error);
3580
+ }
3581
+ logger12.info("Provisioner authenticated", {
3582
+ service: serviceType,
3583
+ user: provAuthResult.data
3584
+ });
3585
+ currentProvisioners.set(serviceType, provisioner);
3586
+ }
3587
+ }
3588
+ currentProvider = provider;
3589
+ currentContainerProvider = containerProvider;
3590
+ currentConfig = parsed;
3591
+ logger12.info("Deploy module initialized", {
3592
+ provider: parsed.provider.type,
3593
+ provisioners: [...currentProvisioners.keys()]
3594
+ });
3595
+ return ok(void 0);
3596
+ } catch (error) {
3597
+ currentProvider = null;
3598
+ currentContainerProvider = null;
3599
+ currentConfig = null;
3600
+ currentProvisioners.clear();
3601
+ logger12.error("Deploy module initialization failed", { error });
3602
+ return err(
3603
+ HaiDeployError.CONFIG_ERROR,
3604
+ deployM("deploy_configError", {
3605
+ params: { error: error instanceof Error ? error.message : String(error) }
3606
+ }),
3607
+ error
3608
+ );
3609
+ }
3610
+ } finally {
3611
+ initInProgress = false;
3612
+ }
3613
+ },
3614
+ async close() {
3615
+ if (!currentProvider && !currentContainerProvider) {
3616
+ logger12.info("Deploy module already closed, skipping");
3617
+ return;
3618
+ }
3619
+ logger12.info("Closing deploy module");
3620
+ currentProvider = null;
3621
+ currentContainerProvider = null;
3622
+ currentConfig = null;
3623
+ currentProvisioners.clear();
3624
+ logger12.info("Deploy module closed");
3625
+ },
3626
+ get config() {
3627
+ return currentConfig;
3628
+ },
3629
+ get isInitialized() {
3630
+ return currentProvider !== null || currentContainerProvider !== null;
3631
+ },
3632
+ async scan(appDir, options) {
3633
+ logger12.debug("Scanning application", { appDir });
3634
+ return scanApp(appDir, options);
3635
+ },
3636
+ async provisionAll(projectName, services) {
3637
+ if (!currentProvider && !currentContainerProvider) {
3638
+ return notInitialized.result();
3639
+ }
3640
+ logger12.info("Provisioning all services", { projectName, count: currentProvisioners.size });
3641
+ const results = [];
3642
+ for (const [serviceType, provisioner] of currentProvisioners) {
3643
+ if (services && !services.includes(serviceType))
3644
+ continue;
3645
+ logger12.debug("Provisioning service", { serviceType, provisioner: provisioner.name });
3646
+ const provResult = await provisioner.provision(projectName);
3647
+ if (!provResult.success) {
3648
+ logger12.error("Service provisioning failed", {
3649
+ serviceType,
3650
+ provisioner: provisioner.name,
3651
+ code: provResult.error.code
3652
+ });
3653
+ return recoveryFailure(provResult.error, {
3654
+ projectName,
3655
+ stage: `provision:${serviceType}`,
3656
+ resources: [
3657
+ ...results.map(({ envVars: _envVars, ...resource }) => resource),
3658
+ ...getDeployRecovery(provResult.error)?.resources ?? [
3659
+ { serviceType, provisionerName: provisioner.name, resourceInfo: projectName, resourceStatus: "unknown" }
3660
+ ]
3661
+ ]
3662
+ });
3663
+ }
3664
+ results.push(provResult.data);
3665
+ logger12.info("Service provisioned", { serviceType, provisioner: provisioner.name });
3666
+ }
3667
+ return ok(results);
3668
+ },
3669
+ async deployApp(appDir, options) {
3670
+ if (!currentProvider && !currentContainerProvider) {
3671
+ return notInitialized.result();
3672
+ }
3673
+ logger12.info("Starting deployment", { appDir, environment: options?.environment, projectName: options?.projectName });
3674
+ const scanOptions = { ...options, environment: options?.environment ?? currentConfig?.environment };
3675
+ const scanResult = await scanApp(appDir, scanOptions);
3676
+ if (!scanResult.success) {
3677
+ return err(scanResult.error);
3678
+ }
3679
+ const scan = scanResult.data;
3680
+ appDir = scan.appDir ?? appDir;
3681
+ const environment = loadDeploymentEnvironment({ environment: scanOptions.environment ?? "prod", envDir: scan.envDir ?? appDir, requireFile: scanOptions.environment !== void 0 });
3682
+ if (!environment.success)
3683
+ return environment;
3684
+ const appEnv = Object.fromEntries(Object.entries(environment.data.values).filter(([key]) => !key.startsWith("HAI_CLI_") && (!key.startsWith("HAI_DEPLOY_") || key === "HAI_DEPLOY_ENVIRONMENT")));
3685
+ const projectName = `${options?.projectName ?? scan.appName}${scanOptions.environment ? `-${environment.data.environment}` : ""}`;
3686
+ options = {
3687
+ ...options,
3688
+ ...scanOptions.environment ? { environment: environment.data.environment } : {},
3689
+ ...options?.skipMiddleware ? { serviceModes: Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"])) } : {}
3690
+ };
3691
+ if (options.selectedServices) {
3692
+ options.serviceModes = {
3693
+ ...options.serviceModes,
3694
+ ...Object.fromEntries(scan.requiredServices.filter((service) => !options.selectedServices.includes(service)).map((service) => [service, "keep"]))
3695
+ };
3696
+ }
3697
+ if (currentContainerProvider) {
3698
+ return deployContainerApp(currentContainerProvider, appDir, scan, projectName, options, appEnv);
3699
+ }
3700
+ if (options?.bundleOnly)
3701
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_bundleOnlyContainer"));
3702
+ if (options.selectedServices)
3703
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectedContainerOnly"));
3704
+ if (!currentProvider) {
3705
+ return notInitialized.result();
3706
+ }
3707
+ if (scan.isSvelteKit && !scan.adapterInstalled) {
3708
+ return err(
3709
+ HaiDeployError.ADAPTER_MISSING,
3710
+ deployM("deploy_adapterMissing", {
3711
+ params: { adapter: "@sveltejs/adapter-vercel" }
3712
+ })
3713
+ );
3714
+ }
3715
+ if (!existsSync(resolveOutputDir(appDir, "vercel")))
3716
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
3717
+ let allEnvVars = appEnv;
3718
+ const recovery = { projectName, stage: "provision", resources: [] };
3719
+ if (!options?.skipMiddleware) {
3720
+ const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
3721
+ if (!provResults.success) {
3722
+ return err(provResults.error);
3723
+ }
3724
+ recovery.resources = provResults.data.map(({ envVars: _envVars, ...resource }) => resource);
3725
+ allEnvVars = { ...allEnvVars, ...mergeProvisionEnv(provResults.data, scan, options) };
3726
+ if (options?.environment) {
3727
+ const saved = saveDeploymentEnvironment(environment.data, mergeProvisionEnv(provResults.data, scan, options));
3728
+ if (!saved.success)
3729
+ return recoveryFailure(saved.error, { ...recovery, stage: "save-env" });
3730
+ }
3731
+ }
3732
+ const projectResult = await currentProvider.createProject(projectName);
3733
+ if (!projectResult.success) {
3734
+ return recoveryFailure(projectResult.error, { ...recovery, stage: "create-project" });
3735
+ }
3736
+ const projectId = projectResult.data;
3737
+ recovery.platformProjectId = projectId;
3738
+ if (Object.keys(allEnvVars).length > 0) {
3739
+ const envResult = await currentProvider.setEnvVars(projectId, allEnvVars);
3740
+ if (!envResult.success) {
3741
+ return recoveryFailure(envResult.error, { ...recovery, stage: "set-env" });
3742
+ }
3743
+ }
3744
+ const outputDir = resolveOutputDir(appDir, currentConfig?.provider.type ?? "vercel");
3745
+ const deployResult = await currentProvider.deploy(projectId, outputDir);
3746
+ if (!deployResult.success) {
3747
+ return recoveryFailure(deployResult.error, { ...recovery, stage: "deploy" });
3748
+ }
3749
+ const result = {
3750
+ ...deployResult.data,
3751
+ envVarsSet: Object.keys(allEnvVars)
3752
+ };
3753
+ logger12.info("Deployment completed", {
3754
+ url: result.url,
3755
+ deploymentId: result.deploymentId,
3756
+ status: result.status
3757
+ });
3758
+ return ok(result);
3759
+ }
3760
+ };
3761
+ async function deployContainerApp(provider, appDir, scan, rawProjectName, options, appEnv = {}) {
3762
+ const projectName = normalizeProjectName(rawProjectName);
3763
+ let artifact;
3764
+ let artifactDir = options?.artifactDir ?? resolveArtifactDirectory(appDir);
3765
+ try {
3766
+ if (existsSync(join(artifactDir, "latest"))) {
3767
+ const latest = readFileSync(join(artifactDir, "latest"), "utf8").trim();
3768
+ if (!/^[a-z0-9][\w.+-]*$/i.test(latest))
3769
+ throw new Error("Invalid artifact pointer");
3770
+ artifactDir = join(artifactDir, latest);
3771
+ }
3772
+ artifact = await readDeploymentArtifact(artifactDir);
3773
+ } catch {
3774
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
3775
+ }
3776
+ if (artifact.partial && !options?.selectedServices) {
3777
+ const selectedServices = artifact.images.map((image) => image.name);
3778
+ options = {
3779
+ ...options,
3780
+ selectedServices,
3781
+ serviceModes: {
3782
+ ...options?.serviceModes,
3783
+ ...Object.fromEntries(scan.requiredServices.map((service) => [service, "keep"]))
3784
+ }
3785
+ };
3786
+ }
3787
+ if (!options?.bundleOnly && currentConfig?.provider.type === "docker-ssh" && !currentConfig.provider.ssh)
3788
+ return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_sshRequired"));
3789
+ let provisionEnv = {};
3790
+ if (!options?.skipMiddleware) {
3791
+ const provResults = await deploy.provisionAll(projectName, options?.serviceModes ? scan.requiredServices.filter((service) => usesDefaultMiddleware(scan, service, options)) : void 0);
3792
+ if (!provResults.success) {
3793
+ return err(provResults.error);
3794
+ }
3795
+ provisionEnv = mergeProvisionEnv(provResults.data, scan, options);
3796
+ }
3797
+ return provider.deploy({ appDir, artifact, artifactDir, projectName, scan, provisionEnv, appEnv, options });
3798
+ }
3799
+
3800
+ // ../deploy/src/deploy-middleware.ts
3801
+ var field = (name, defaultValue, kind, secret = false) => ({ name, defaultValue, kind, secret });
3802
+ var required = (name, defaultValue, kind) => ({ ...field(name, defaultValue, kind), required: true });
3803
+ var DEPLOY_MIDDLEWARE = {
3804
+ db: [
3805
+ { backend: "sqlite", label: "SQLite", purpose: "deploy_purposeSqlite", fields: [required("database", "./data/app.db")] },
3806
+ ...["postgresql", "mysql"].map((backend) => ({
3807
+ backend,
3808
+ label: backend === "mysql" ? "MySQL" : "PostgreSQL",
3809
+ purpose: "deploy_purposeDatabase",
3810
+ provisioner: backend === "postgresql" ? "neon" : void 0,
3811
+ fields: [field("url", "", "url", true), required("host", "localhost"), required("port", backend === "mysql" ? "3306" : "5432", "port"), required("database", "hai"), field("user", "hai"), field("password", "", void 0, true), field("ssl", "false")]
3812
+ }))
3813
+ ],
3814
+ cache: [
3815
+ { backend: "memory", label: "Memory", purpose: "deploy_purposeMemory", fields: [] },
3816
+ { backend: "redis", label: "Redis", purpose: "deploy_purposeCache", provisioner: "upstash", fields: [field("url", "", "url", true), required("host", "localhost"), required("port", "6379", "port"), field("password", "", void 0, true), { ...field("db", "0", "number"), maximum: 15 }, field("tls", "false", "boolean"), field("keyPrefix"), field("connectTimeout", "10000", "number"), field("commandTimeout", "5000", "number")] }
3817
+ ],
3818
+ storage: [
3819
+ { backend: "local", label: "Local filesystem", purpose: "deploy_purposeLocal", fields: [required("root", "./data/storage")] },
3820
+ { backend: "s3", label: "S3 / RustFS / MinIO / Cloudflare R2", purpose: "deploy_purposeStorage", provisioner: "cloudflare-r2", fields: [field("endpoint", "", "url"), required("bucket", "hai"), required("region", "us-east-1"), required("accessKeyId"), { ...required("secretAccessKey"), secret: true }, field("forcePathStyle", "true", "boolean")] }
3821
+ ],
3822
+ vecdb: [
3823
+ { backend: "lancedb", label: "LanceDB", purpose: "deploy_purposeLocal", fields: [required("path", "./data/vectors")] },
3824
+ { backend: "qdrant", label: "Qdrant", purpose: "deploy_purposeVector", fields: [required("url", "http://localhost:6333", "url"), field("apiKey", "", void 0, true)] },
3825
+ { backend: "pgvector", label: "PostgreSQL / pgvector", purpose: "deploy_purposeVector", fields: [field("url", "", "url", true), required("host", "localhost"), required("port", "5432", "port"), required("database", "hai"), field("user", "hai"), field("password", "", void 0, true)] },
3826
+ { backend: "chroma", label: "Chroma", purpose: "deploy_purposeVector", fields: [required("url", "http://localhost:8000", "url")] }
3827
+ ],
3828
+ ldap: [
3829
+ { backend: "disabled", label: "Disabled", purpose: "deploy_purposeDisabled", fields: [] },
3830
+ { backend: "ldap", label: "LDAP / OpenLDAP", purpose: "deploy_purposeLdap", fields: [required("url", "ldap://localhost:389", "url"), required("bindDn", "cn=admin,dc=example,dc=org"), { ...required("bindPassword"), secret: true }, required("searchBase", "dc=example,dc=org"), field("searchFilter", "(uid={{username}})"), field("usernameAttribute", "uid"), field("emailAttribute", "mail"), field("displayNameAttribute", "cn"), field("useTls", "false", "boolean"), { ...field("connectTimeout", "5000", "number"), minimum: 1e3 }] }
3831
+ ],
3832
+ email: [
3833
+ { backend: "console", label: "Console", purpose: "deploy_purposeConsole", fields: [] },
3834
+ { backend: "smtp", label: "SMTP", purpose: "deploy_purposeEmail", provisioner: "resend", fields: [required("host", "localhost"), required("port", "465", "port"), field("secure", "true", "boolean"), field("user"), field("pass", "", void 0, true), required("from", "noreply@example.com")] },
3835
+ { backend: "api", label: "HTTP notification API", purpose: "deploy_purposeEmail", fields: [required("url", "", "url"), field("method", "POST"), field("headers"), field("timeout", "10000", "number")] }
3836
+ ],
3837
+ sms: [
3838
+ { backend: "console", label: "Console", purpose: "deploy_purposeConsole", fields: [] },
3839
+ { backend: "aliyun-sms", label: "Aliyun SMS", purpose: "deploy_purposeSms", provisioner: "aliyun", fields: [required("accessKeyId"), { ...required("accessKeySecret"), secret: true }, required("signName"), field("endpoint", "dysmsapi.aliyuncs.com")] },
3840
+ { backend: "api", label: "HTTP notification API", purpose: "deploy_purposeSms", fields: [required("url", "", "url"), field("method", "POST"), field("headers"), field("timeout", "10000", "number")] }
3841
+ ]
3842
+ };
3843
+ function describeMiddlewareBackend(backend) {
3844
+ return deployM(backend.purpose);
3845
+ }
3846
+
3847
+ export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, readDeploymentArtifact, saveDeploymentEnvironment };
3848
+ //# sourceMappingURL=src-DSJJXM2F.js.map
3849
+ //# sourceMappingURL=src-DSJJXM2F.js.map