@h-ai/deploy 0.1.0-alpha9 → 1.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +292 -0
- package/README.ja.md +290 -0
- package/README.md +286 -66
- package/dist/index.d.ts +944 -155
- package/dist/index.js +5012 -414
- package/dist/index.js.map +1 -1
- package/docs/credential-import.en.md +40 -0
- package/docs/credential-import.ja.md +40 -0
- package/docs/credential-import.md +70 -0
- package/package.json +20 -12
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,264 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import * as _h_ai_core from '@h-ai/core';
|
|
3
|
-
import { HaiResult } from '@h-ai/core';
|
|
3
|
+
import { HaiResult, HaiError } from '@h-ai/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 镜像制品清单,例如 `{ version: 4, primaryService: 'serv', appVersion: '1.2.3', buildId: '1.2.3-build1', partial: false, hasWeb: false, images: [...] }`。
|
|
7
|
+
* 根目录制品保留格式4;非根目录使用格式5,强制旧部署器拒绝而非静默忽略挂载路径。
|
|
8
|
+
* 完整产物包含primaryService,hasWeb记录浏览器端拓扑;结构验证不能代替真实归档校验。
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { readFile } from 'node:fs/promises'
|
|
12
|
+
* import { DeploymentArtifactSchema } from '@h-ai/deploy'
|
|
13
|
+
* const content = await readFile('.hai/build/docker/build-id/manifest.json', 'utf8')
|
|
14
|
+
* const parsed = DeploymentArtifactSchema.safeParse(JSON.parse(content))
|
|
15
|
+
* if (!parsed.success) throw new Error('Invalid artifact manifest')
|
|
16
|
+
* // Schema 只验证结构;文件路径与 SHA-256 核验使用 readDeploymentArtifact。
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare const DeploymentArtifactSchema: z.ZodObject<{
|
|
20
|
+
version: z.ZodUnion<readonly [z.ZodLiteral<4>, z.ZodLiteral<5>]>;
|
|
21
|
+
primaryService: z.ZodEnum<{
|
|
22
|
+
serv: "serv";
|
|
23
|
+
web: "web";
|
|
24
|
+
}>;
|
|
25
|
+
appVersion: z.ZodString;
|
|
26
|
+
buildId: z.ZodString;
|
|
27
|
+
partial: z.ZodDefault<z.ZodBoolean>;
|
|
28
|
+
hasWeb: z.ZodDefault<z.ZodBoolean>;
|
|
29
|
+
webBasePath: z.ZodOptional<z.ZodString>;
|
|
30
|
+
servContainerPort: z.ZodOptional<z.ZodNumber>;
|
|
31
|
+
webContainerPort: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
images: z.ZodArray<z.ZodObject<{
|
|
33
|
+
name: z.ZodEnum<{
|
|
34
|
+
serv: "serv";
|
|
35
|
+
web: "web";
|
|
36
|
+
}>;
|
|
37
|
+
file: z.ZodString;
|
|
38
|
+
imageRef: z.ZodString;
|
|
39
|
+
sha256: z.ZodString;
|
|
40
|
+
containerPort: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
/**
|
|
44
|
+
* 构建器与部署器共享的镜像交付结构;参见 DeploymentArtifactSchema 示例。
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import type { DeploymentArtifact } from '@h-ai/deploy'
|
|
48
|
+
* import { readDeploymentArtifact } from '@h-ai/deploy'
|
|
49
|
+
* const directory = '.hai/build/docker/build-id'
|
|
50
|
+
* const artifact = await readDeploymentArtifact(directory)
|
|
51
|
+
* const verified: DeploymentArtifact = artifact
|
|
52
|
+
* const references = verified.images.map(image => image.imageRef)
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
type DeploymentArtifact = z.infer<typeof DeploymentArtifactSchema>;
|
|
56
|
+
/**
|
|
57
|
+
* 流式计算制品校验和,避免将大镜像一次性读入内存。
|
|
58
|
+
* @throws Error - 文件读取失败;返回真实 tar 内容的 SHA-256,不校验清单其它字段。
|
|
59
|
+
* @param file - 真实镜像归档文件路径。
|
|
60
|
+
* @returns 文件内容的 SHA-256 十六进制摘要。
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { artifactChecksum } from '@h-ai/deploy'
|
|
64
|
+
* const digest = await artifactChecksum('.hai/build/docker/build-id/serv-1.0.0.tar')
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare function artifactChecksum(file: string): Promise<string>;
|
|
68
|
+
/**
|
|
69
|
+
* 校验完整构建产物;缺失、损坏或非法清单均抛错,由部署边界转换为 HaiResult。
|
|
70
|
+
* @throws Error - 清单、路径、镜像 tar 或摘要校验失败;不能用未验证的清单绕过本函数。
|
|
71
|
+
* @param directory - 包含已准备制品或部署文件的目录;具体见函数用途。
|
|
72
|
+
* @returns 已经验证的构建制品清单。
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { readDeploymentArtifact } from '@h-ai/deploy'
|
|
76
|
+
* const directory = '.hai/build/docker/build-id'
|
|
77
|
+
* const artifact = await readDeploymentArtifact(directory)
|
|
78
|
+
* const images = artifact.images.map(image => ({ name: image.name, imageRef: image.imageRef }))
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
declare function readDeploymentArtifact(directory: string): Promise<DeploymentArtifact>;
|
|
4
82
|
|
|
5
83
|
/**
|
|
6
84
|
* @h-ai/deploy — 部署配置 Schema
|
|
7
85
|
*
|
|
8
|
-
*
|
|
86
|
+
* 本文件定义部署模块的 Zod Schema 和配置类型;错误定义位于 deploy-types.ts。
|
|
9
87
|
* @module deploy-config
|
|
10
88
|
*/
|
|
11
89
|
|
|
12
|
-
/**
|
|
90
|
+
/** SSH 连接配置(docker-ssh Provider 使用 OpenSSH 客户端连接远程主机) */
|
|
91
|
+
declare const SshConfigSchema: z.ZodObject<{
|
|
92
|
+
host: z.ZodString;
|
|
93
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
94
|
+
username: z.ZodString;
|
|
95
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
96
|
+
key: "key";
|
|
97
|
+
password: "password";
|
|
98
|
+
}>>;
|
|
99
|
+
password: z.ZodOptional<z.ZodString>;
|
|
100
|
+
identityFile: z.ZodOptional<z.ZodString>;
|
|
101
|
+
knownHostsFile: z.ZodOptional<z.ZodString>;
|
|
102
|
+
strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
|
|
103
|
+
connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
/** 端口直出暴露配置 */
|
|
106
|
+
declare const PortExposeSchema: z.ZodObject<{
|
|
107
|
+
type: z.ZodDefault<z.ZodLiteral<"port">>;
|
|
108
|
+
containerPort: z.ZodDefault<z.ZodNumber>;
|
|
109
|
+
hostPort: z.ZodNumber;
|
|
110
|
+
}, z.core.$strip>;
|
|
111
|
+
/** docker-ssh 部署平台配置 */
|
|
112
|
+
declare const DockerSshProviderConfigSchema: z.ZodObject<{
|
|
113
|
+
type: z.ZodLiteral<"docker-ssh">;
|
|
114
|
+
ssh: z.ZodOptional<z.ZodObject<{
|
|
115
|
+
host: z.ZodString;
|
|
116
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
117
|
+
username: z.ZodString;
|
|
118
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
119
|
+
key: "key";
|
|
120
|
+
password: "password";
|
|
121
|
+
}>>;
|
|
122
|
+
password: z.ZodOptional<z.ZodString>;
|
|
123
|
+
identityFile: z.ZodOptional<z.ZodString>;
|
|
124
|
+
knownHostsFile: z.ZodOptional<z.ZodString>;
|
|
125
|
+
strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
|
|
126
|
+
connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
127
|
+
}, z.core.$strip>>;
|
|
128
|
+
remote: z.ZodDefault<z.ZodObject<{
|
|
129
|
+
baseDir: z.ZodDefault<z.ZodString>;
|
|
130
|
+
runtime: z.ZodDefault<z.ZodEnum<{
|
|
131
|
+
auto: "auto";
|
|
132
|
+
docker: "docker";
|
|
133
|
+
podman: "podman";
|
|
134
|
+
}>>;
|
|
135
|
+
}, z.core.$strip>>;
|
|
136
|
+
expose: z.ZodObject<{
|
|
137
|
+
type: z.ZodDefault<z.ZodLiteral<"port">>;
|
|
138
|
+
containerPort: z.ZodDefault<z.ZodNumber>;
|
|
139
|
+
hostPort: z.ZodNumber;
|
|
140
|
+
}, z.core.$strip>;
|
|
141
|
+
trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
/**
|
|
144
|
+
* 部署平台配置联合类型(后续可扩展更多平台)
|
|
145
|
+
* 按 type 区分 Vercel 与 docker-ssh;仅校验配置形状,不验证远端连通性或云凭据。
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { ProviderConfigSchema } from '@h-ai/deploy'
|
|
149
|
+
* const result = ProviderConfigSchema.safeParse({ type: 'docker-ssh', expose: { hostPort: 8080 } })
|
|
150
|
+
* if (!result.success) throw new Error('Invalid deployment provider')
|
|
151
|
+
* const provider = result.data
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
13
154
|
declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
14
155
|
type: z.ZodLiteral<"vercel">;
|
|
15
156
|
token: z.ZodString;
|
|
157
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
158
|
+
type: z.ZodLiteral<"docker-ssh">;
|
|
159
|
+
ssh: z.ZodOptional<z.ZodObject<{
|
|
160
|
+
host: z.ZodString;
|
|
161
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
162
|
+
username: z.ZodString;
|
|
163
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
164
|
+
key: "key";
|
|
165
|
+
password: "password";
|
|
166
|
+
}>>;
|
|
167
|
+
password: z.ZodOptional<z.ZodString>;
|
|
168
|
+
identityFile: z.ZodOptional<z.ZodString>;
|
|
169
|
+
knownHostsFile: z.ZodOptional<z.ZodString>;
|
|
170
|
+
strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
|
|
171
|
+
connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
172
|
+
}, z.core.$strip>>;
|
|
173
|
+
remote: z.ZodDefault<z.ZodObject<{
|
|
174
|
+
baseDir: z.ZodDefault<z.ZodString>;
|
|
175
|
+
runtime: z.ZodDefault<z.ZodEnum<{
|
|
176
|
+
auto: "auto";
|
|
177
|
+
docker: "docker";
|
|
178
|
+
podman: "podman";
|
|
179
|
+
}>>;
|
|
180
|
+
}, z.core.$strip>>;
|
|
181
|
+
expose: z.ZodObject<{
|
|
182
|
+
type: z.ZodDefault<z.ZodLiteral<"port">>;
|
|
183
|
+
containerPort: z.ZodDefault<z.ZodNumber>;
|
|
184
|
+
hostPort: z.ZodNumber;
|
|
185
|
+
}, z.core.$strip>;
|
|
186
|
+
trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
|
|
16
187
|
}, z.core.$strip>], "type">;
|
|
17
|
-
/**
|
|
188
|
+
/**
|
|
189
|
+
* SSH 连接配置(parse 后)
|
|
190
|
+
* 这是 Schema 解析后的类型,默认字段已必填;auth 省略时非空密码选择 password,否则使用 ssh-agent/默认密钥;显式 key 忽略残留 password。
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* import type { SshConfig } from '@h-ai/deploy'
|
|
194
|
+
* const config: SshConfig = { host: 'deploy.example.net', username: 'deploy', port: 22, strictHostKeyChecking: true, connectTimeoutSeconds: 10 }
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
type SshConfig = z.infer<typeof SshConfigSchema>;
|
|
198
|
+
/**
|
|
199
|
+
* 端口暴露配置(parse 后)
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* import type { PortExposeConfig } from '@h-ai/deploy'
|
|
203
|
+
* const ports: PortExposeConfig = { type: 'port', hostPort: 8080, containerPort: 3000 }
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
type PortExposeConfig = z.infer<typeof PortExposeSchema>;
|
|
207
|
+
/**
|
|
208
|
+
* docker-ssh 部署平台配置(parse 后)
|
|
209
|
+
* SSH 可省略用于离线打包;真正在线发布必须提供 SSH 配置。
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import type { DockerSshProviderConfig } from '@h-ai/deploy'
|
|
213
|
+
* const provider: DockerSshProviderConfig = { type: 'docker-ssh', remote: { baseDir: '/opt/hai/apps', runtime: 'auto' }, expose: { type: 'port', hostPort: 8080, containerPort: 3000 } }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
type DockerSshProviderConfig = z.infer<typeof DockerSshProviderConfigSchema>;
|
|
217
|
+
/**
|
|
218
|
+
* 部署模块完整配置 Schema
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* import { DeployConfigSchema } from '@h-ai/deploy'
|
|
222
|
+
* const parsed = DeployConfigSchema.safeParse({ environment: 'prod', provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } })
|
|
223
|
+
* if (!parsed.success) throw new Error('Invalid deployment configuration')
|
|
224
|
+
* const baseDir = parsed.data.provider.type === 'docker-ssh' ? parsed.data.provider.remote.baseDir : undefined
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
18
227
|
declare const DeployConfigSchema: z.ZodObject<{
|
|
228
|
+
environment: z.ZodOptional<z.ZodString>;
|
|
19
229
|
provider: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
20
230
|
type: z.ZodLiteral<"vercel">;
|
|
21
231
|
token: z.ZodString;
|
|
232
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
233
|
+
type: z.ZodLiteral<"docker-ssh">;
|
|
234
|
+
ssh: z.ZodOptional<z.ZodObject<{
|
|
235
|
+
host: z.ZodString;
|
|
236
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
237
|
+
username: z.ZodString;
|
|
238
|
+
auth: z.ZodOptional<z.ZodEnum<{
|
|
239
|
+
key: "key";
|
|
240
|
+
password: "password";
|
|
241
|
+
}>>;
|
|
242
|
+
password: z.ZodOptional<z.ZodString>;
|
|
243
|
+
identityFile: z.ZodOptional<z.ZodString>;
|
|
244
|
+
knownHostsFile: z.ZodOptional<z.ZodString>;
|
|
245
|
+
strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
|
|
246
|
+
connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
247
|
+
}, z.core.$strip>>;
|
|
248
|
+
remote: z.ZodDefault<z.ZodObject<{
|
|
249
|
+
baseDir: z.ZodDefault<z.ZodString>;
|
|
250
|
+
runtime: z.ZodDefault<z.ZodEnum<{
|
|
251
|
+
auto: "auto";
|
|
252
|
+
docker: "docker";
|
|
253
|
+
podman: "podman";
|
|
254
|
+
}>>;
|
|
255
|
+
}, z.core.$strip>>;
|
|
256
|
+
expose: z.ZodObject<{
|
|
257
|
+
type: z.ZodDefault<z.ZodLiteral<"port">>;
|
|
258
|
+
containerPort: z.ZodDefault<z.ZodNumber>;
|
|
259
|
+
hostPort: z.ZodNumber;
|
|
260
|
+
}, z.core.$strip>;
|
|
261
|
+
trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
|
|
22
262
|
}, z.core.$strip>], "type">;
|
|
23
263
|
services: z.ZodOptional<z.ZodObject<{
|
|
24
264
|
db: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -32,113 +272,183 @@ declare const DeployConfigSchema: z.ZodObject<{
|
|
|
32
272
|
}, z.core.$strip>], "provisioner">>;
|
|
33
273
|
storage: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
34
274
|
provisioner: z.ZodLiteral<"cloudflare-r2">;
|
|
275
|
+
accessKeyId: z.ZodString;
|
|
276
|
+
secretAccessKey: z.ZodString;
|
|
35
277
|
accountId: z.ZodString;
|
|
36
278
|
apiToken: z.ZodString;
|
|
37
279
|
}, z.core.$strip>], "provisioner">>;
|
|
38
280
|
email: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
39
281
|
provisioner: z.ZodLiteral<"resend">;
|
|
282
|
+
from: z.ZodString;
|
|
40
283
|
apiKey: z.ZodString;
|
|
41
284
|
}, z.core.$strip>], "provisioner">>;
|
|
42
285
|
sms: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
43
286
|
provisioner: z.ZodLiteral<"aliyun">;
|
|
287
|
+
signName: z.ZodString;
|
|
44
288
|
accessKeyId: z.ZodString;
|
|
45
289
|
accessKeySecret: z.ZodString;
|
|
46
290
|
}, z.core.$strip>], "provisioner">>;
|
|
47
291
|
}, z.core.$strip>>;
|
|
292
|
+
container: z.ZodOptional<z.ZodObject<{
|
|
293
|
+
s3Engine: z.ZodDefault<z.ZodEnum<{
|
|
294
|
+
rustfs: "rustfs";
|
|
295
|
+
minio: "minio";
|
|
296
|
+
}>>;
|
|
297
|
+
}, z.core.$strip>>;
|
|
48
298
|
}, z.core.$strip>;
|
|
49
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* 部署配置类型(parse 后)
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* import type { DeployConfig } from '@h-ai/deploy'
|
|
304
|
+
* const config: DeployConfig = { environment: 'prod', provider: { type: 'docker-ssh', remote: { baseDir: '/opt/hai/apps', runtime: 'auto' }, expose: { type: 'port', hostPort: 8080, containerPort: 3000 } } }
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
50
307
|
type DeployConfig = z.infer<typeof DeployConfigSchema>;
|
|
51
|
-
/**
|
|
308
|
+
/**
|
|
309
|
+
* 部署配置输入类型(用户输入)
|
|
310
|
+
* @example
|
|
311
|
+
* ```ts
|
|
312
|
+
* import type { DeployConfigInput } from '@h-ai/deploy'
|
|
313
|
+
* const input: DeployConfigInput = { provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } } // 由 init 应用 remote 等默认值。
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
52
316
|
type DeployConfigInput = z.input<typeof DeployConfigSchema>;
|
|
53
317
|
|
|
318
|
+
/** 部署环境的加载和持久化;公开环境文件不保存真实凭据。 */
|
|
319
|
+
|
|
54
320
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
321
|
+
* 部署环境快照;例如 prod 的 targetValues 只来自 `.env.prod`,不包含基础/私有覆盖。
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* import type { DeploymentEnvironment } from '@h-ai/deploy'
|
|
325
|
+
* const environment: DeploymentEnvironment = { environment: 'prod', envDir: '/workspace/shop', values: { HAI_DB_TYPE: 'postgresql' }, targetValues: { HAI_DB_TYPE: 'postgresql' } }
|
|
326
|
+
* // values 可能还含私有凭据,不能整体写入日志。
|
|
327
|
+
* ```
|
|
59
328
|
*/
|
|
60
|
-
|
|
329
|
+
interface DeploymentEnvironment {
|
|
330
|
+
/** 文件模式(dev/test/prod 或其他合法模式名)。 */
|
|
331
|
+
environment: string;
|
|
332
|
+
/** 环境文件的绝对目录。 */
|
|
333
|
+
envDir: string;
|
|
334
|
+
/** 合并后的部署变量,可能含私有值,不得直接记录日志。 */
|
|
335
|
+
values: Record<string, string>;
|
|
336
|
+
/** 仅目标环境文件显式声明的变量,用于判断能否“保持不变”。 */
|
|
337
|
+
targetValues: Record<string, string>;
|
|
338
|
+
}
|
|
61
339
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @
|
|
340
|
+
* 按来源归并环境变量;HAI 高层父配置清除旧叶,同层按父→叶处理。
|
|
341
|
+
* @param layers - 从低到高优先级的环境来源。
|
|
342
|
+
* @param prefix - 可选变量前缀,仅归并该模块。
|
|
343
|
+
* @returns 归并后的字面量环境变量,可能包含秘密。
|
|
344
|
+
* @example 模块内部上下文
|
|
345
|
+
* ```ts
|
|
346
|
+
* const env = mergeEnvironmentLayers([{ HAI_CACHE_TYPE: 'memory' }, { HAI_CACHE: '{"type":"redis"}' }])
|
|
347
|
+
* ```
|
|
65
348
|
*/
|
|
66
|
-
declare function
|
|
349
|
+
declare function mergeEnvironmentLayers(layers: Array<Record<string, string | undefined>>, prefix?: string): Record<string, string>;
|
|
67
350
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* @returns 成功返回加载的凭证键名列表
|
|
75
|
-
*
|
|
351
|
+
* 按部署环境解析配置;未选择的可选 Provisioner 缺少凭据时保留空值,选择完成后由 Schema 校验。
|
|
352
|
+
* hai.json 只读取对应的根分区;运行时模块继续读取 YAML。不修改 core 缓存或进程变量。
|
|
353
|
+
* @param name - 模块配置名,例如 deploy/db/iam
|
|
354
|
+
* @param path - hai.json 或运行时 YAML 文件的绝对路径
|
|
355
|
+
* @param values - 已加载的目标环境变量
|
|
356
|
+
* @returns 配置对象;解析错误已脱敏
|
|
76
357
|
* @example
|
|
77
358
|
* ```ts
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* }
|
|
359
|
+
* import { loadDeploymentConfig, loadDeploymentEnvironment } from '@h-ai/deploy'
|
|
360
|
+
* const environment = loadDeploymentEnvironment({ environment: 'prod', envDir: process.cwd() })
|
|
361
|
+
* if (!environment.success) throw new Error(environment.error.message)
|
|
362
|
+
* const config = loadDeploymentConfig('deploy', `${process.cwd()}/hai.json`, environment.data.values)
|
|
363
|
+
* if (!config.success) throw new Error(config.error.message)
|
|
364
|
+
* // 返回对象仍需经 DeployConfigSchema 或 deploy.init 验证。
|
|
82
365
|
* ```
|
|
83
366
|
*/
|
|
84
|
-
declare function
|
|
367
|
+
declare function loadDeploymentConfig(name: string, path: string, values: Record<string, string | undefined>): HaiResult<Record<string, unknown>>;
|
|
85
368
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @param
|
|
91
|
-
* @param
|
|
92
|
-
*
|
|
369
|
+
* 加载部署环境;指定环境文件缺失时返回错误并要求用户创建。
|
|
370
|
+
* 部署目标按进程 > 模式私有 > 模式公共 > 旧全局私有 > 基础归并。
|
|
371
|
+
* 其它模块保留进程 > 模式私有 > 旧全局私有 > 模式公共 > 基础;targetValues 仅记录模式公共项。
|
|
372
|
+
* @param options - 模式、环境目录与是否强制存在目标文件
|
|
373
|
+
* @param options.environment - dev/test/prod 或自定义模式
|
|
374
|
+
* @param options.envDir - 环境文件所在目录
|
|
375
|
+
* @param options.requireFile - 默认 true;扫描无显式环境时可只读取已有基础文件
|
|
376
|
+
* @returns 环境快照,可能包含私有值,不得直接输出
|
|
93
377
|
* @example
|
|
94
378
|
* ```ts
|
|
95
|
-
*
|
|
379
|
+
* import { loadDeploymentEnvironment } from '@h-ai/deploy'
|
|
380
|
+
* const loaded = loadDeploymentEnvironment({ environment: 'prod', envDir: process.cwd() })
|
|
381
|
+
* if (!loaded.success) throw new Error(loaded.error.message)
|
|
382
|
+
* const explicitlyConfigured = loaded.data.targetValues.HAI_DB_TYPE
|
|
96
383
|
* ```
|
|
97
384
|
*/
|
|
98
|
-
declare function
|
|
385
|
+
declare function loadDeploymentEnvironment(options: {
|
|
386
|
+
environment: string;
|
|
387
|
+
envDir: string;
|
|
388
|
+
requireFile?: boolean;
|
|
389
|
+
}): HaiResult<DeploymentEnvironment>;
|
|
99
390
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* @param
|
|
103
|
-
*
|
|
391
|
+
* 保存部署变量:公共值写目标环境文件,凭据写 `.env.<environment>.local`;旧共享私有文件不删除。
|
|
392
|
+
* 返回实际写入的变量名,调用者只展示名称或脱敏值。
|
|
393
|
+
* @param environment - 已加载的目标环境快照
|
|
394
|
+
* @param values - 需要更新的变量;未提供的项保持原状
|
|
395
|
+
* @param options - private 为 true 时所有值只保存在本机;适用于 SSH 地址、端口及密钥路径
|
|
396
|
+
* @param options.private - 是否将全部值保存到选中模式的私有文件
|
|
397
|
+
* @returns 已保存的变量名或脱敏错误
|
|
104
398
|
* @example
|
|
105
399
|
* ```ts
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* })
|
|
400
|
+
* import { loadDeploymentEnvironment, saveDeploymentEnvironment } from '@h-ai/deploy'
|
|
401
|
+
* const loaded = loadDeploymentEnvironment({ environment: 'prod', envDir: process.cwd() })
|
|
402
|
+
* if (!loaded.success) throw new Error(loaded.error.message)
|
|
403
|
+
* const saved = saveDeploymentEnvironment(loaded.data, { HAI_DB_HOST: 'db' })
|
|
404
|
+
* if (!saved.success) throw new Error(saved.error.message)
|
|
405
|
+
* // 密钥应以 { private: true } 写入被忽略的 .env.prod.local,不能放公共环境文件。
|
|
110
406
|
* ```
|
|
111
407
|
*/
|
|
112
|
-
declare function
|
|
408
|
+
declare function saveDeploymentEnvironment(environment: DeploymentEnvironment, values: Record<string, string>, options?: {
|
|
409
|
+
private?: boolean;
|
|
410
|
+
}): HaiResult<string[]>;
|
|
113
411
|
|
|
412
|
+
/**
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```ts
|
|
416
|
+
* import { err } from '@h-ai/core'
|
|
417
|
+
* import { HaiDeployError } from '@h-ai/deploy'
|
|
418
|
+
* const result = err(HaiDeployError.CONFIG_ERROR, 'Invalid deployment configuration')
|
|
419
|
+
* if (!result.success && result.error.code === HaiDeployError.CONFIG_ERROR.code) {
|
|
420
|
+
* process.stderr.write(result.error.message + '\n')
|
|
421
|
+
* }
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
114
424
|
declare const HaiDeployError: {
|
|
115
425
|
readonly DEPLOY_FAILED: _h_ai_core.HaiErrorDef;
|
|
116
426
|
readonly PROJECT_CREATE_FAILED: _h_ai_core.HaiErrorDef;
|
|
117
|
-
readonly BUILD_FAILED: _h_ai_core.HaiErrorDef;
|
|
118
427
|
readonly UPLOAD_FAILED: _h_ai_core.HaiErrorDef;
|
|
119
428
|
readonly AUTH_REQUIRED: _h_ai_core.HaiErrorDef;
|
|
120
429
|
readonly AUTH_FAILED: _h_ai_core.HaiErrorDef;
|
|
121
430
|
readonly PROVISION_FAILED: _h_ai_core.HaiErrorDef;
|
|
122
431
|
readonly ADAPTER_MISSING: _h_ai_core.HaiErrorDef;
|
|
123
432
|
readonly SCAN_FAILED: _h_ai_core.HaiErrorDef;
|
|
124
|
-
readonly ENV_VAR_FAILED: _h_ai_core.HaiErrorDef;
|
|
125
433
|
readonly NOT_INITIALIZED: _h_ai_core.HaiErrorDef;
|
|
434
|
+
readonly ENV_VAR_FAILED: _h_ai_core.HaiErrorDef;
|
|
126
435
|
readonly UNSUPPORTED_TYPE: _h_ai_core.HaiErrorDef;
|
|
127
436
|
readonly CONFIG_ERROR: _h_ai_core.HaiErrorDef;
|
|
128
437
|
readonly CREDENTIAL_ERROR: _h_ai_core.HaiErrorDef;
|
|
438
|
+
readonly REMOTE_CONNECT_FAILED: _h_ai_core.HaiErrorDef;
|
|
439
|
+
readonly REMOTE_COMMAND_FAILED: _h_ai_core.HaiErrorDef;
|
|
440
|
+
readonly IMAGE_TRANSFER_FAILED: _h_ai_core.HaiErrorDef;
|
|
441
|
+
readonly HEALTH_CHECK_FAILED: _h_ai_core.HaiErrorDef;
|
|
129
442
|
};
|
|
130
443
|
/**
|
|
131
444
|
* 部署错误接口
|
|
132
445
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
446
|
+
* 用于组合错误定义、消息与可选 cause 的描述对象;业务操作返回的 error 遵循 core.HaiError,请按 success 分支读取 code/message。
|
|
135
447
|
* @example
|
|
136
448
|
* ```ts
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* // 根据 error.code 处理
|
|
141
|
-
* }
|
|
449
|
+
* import type { DeployError } from '@h-ai/deploy'
|
|
450
|
+
* import { HaiDeployError } from '@h-ai/deploy'
|
|
451
|
+
* const detail: DeployError = { def: HaiDeployError.DEPLOY_FAILED, message: 'Deployment failed' }
|
|
142
452
|
* ```
|
|
143
453
|
*/
|
|
144
454
|
interface DeployError {
|
|
@@ -149,29 +459,110 @@ interface DeployError {
|
|
|
149
459
|
/** 原始错误(可选) */
|
|
150
460
|
cause?: unknown;
|
|
151
461
|
}
|
|
152
|
-
/**
|
|
153
|
-
|
|
462
|
+
/**
|
|
463
|
+
* 基础设施服务类型
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* import type { ServiceType } from '@h-ai/deploy'
|
|
467
|
+
* const service: ServiceType = 'db'
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
type ServiceType = 'db' | 'cache' | 'storage' | 'email' | 'sms' | 'vecdb' | 'ldap';
|
|
471
|
+
/**
|
|
472
|
+
* 每项中间件的处理方式;keep 不修改配置,也不开通服务。
|
|
473
|
+
* @example
|
|
474
|
+
* ```ts
|
|
475
|
+
* import type { MiddlewareMode } from '@h-ai/deploy'
|
|
476
|
+
* const mode: MiddlewareMode = 'keep' // 复用已有服务,不修改连接配置或自动开通。
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
type MiddlewareMode = 'default' | 'custom' | 'keep';
|
|
480
|
+
/**
|
|
481
|
+
* 扫描环境;例如 `{ environment: 'test', envDir: '/workspace/app' }`。
|
|
482
|
+
* @example
|
|
483
|
+
* ```ts
|
|
484
|
+
* import type { DeployScanOptions } from '@h-ai/deploy'
|
|
485
|
+
* const options: DeployScanOptions = { environment: 'prod', envDir: '/workspace/shop' }
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
488
|
+
interface DeployScanOptions {
|
|
489
|
+
/** 环境文件模式。 */
|
|
490
|
+
environment?: string;
|
|
491
|
+
/** 环境文件目录;默认自动定位到应用或 workspace 根。 */
|
|
492
|
+
envDir?: string;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* 依赖对应的中间件;例如 IAM 的 LDAP 位于 `iam.ldap`,可处于 disabled 状态。
|
|
496
|
+
* @example
|
|
497
|
+
* ```ts
|
|
498
|
+
* import type { MiddlewareRequirement } from '@h-ai/deploy'
|
|
499
|
+
* const requirement: MiddlewareRequirement = { service: 'db', backend: 'postgresql', source: '@h-ai/reldb', configName: 'db', configPath: [], envPrefix: 'HAI_DB', config: { host: 'db', database: 'shop' } }
|
|
500
|
+
* // config 可能含私有值;展示时只选择安全字段。
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
interface MiddlewareRequirement {
|
|
504
|
+
/** 中间件类别。 */
|
|
505
|
+
service: ServiceType;
|
|
506
|
+
/** 生效后端标识。 */
|
|
507
|
+
backend: string;
|
|
508
|
+
/** 检测来源(依赖包或配置名),用于解释为何需要此项。 */
|
|
509
|
+
source: string;
|
|
510
|
+
/** 模块配置名。 */
|
|
511
|
+
configName: string;
|
|
512
|
+
/** 模块配置内的路径;例如 LDAP 为 ['ldap']。 */
|
|
513
|
+
configPath: string[];
|
|
514
|
+
/** 约定变量前缀。 */
|
|
515
|
+
envPrefix: string;
|
|
516
|
+
/** 生效连接配置,可能含凭据,仅用于输入默认值,不得原样输出日志。 */
|
|
517
|
+
config: Record<string, unknown>;
|
|
518
|
+
}
|
|
154
519
|
/**
|
|
155
520
|
* 应用扫描结果
|
|
156
521
|
*
|
|
157
522
|
* 由 scanner 分析应用目录后返回,描述应用的框架类型、依赖和部署需求。
|
|
523
|
+
* 扫描是文件系统能力,不依赖 init,也不代表远程服务已经连通。
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* import type { ScanResult } from '@h-ai/deploy'
|
|
527
|
+
* import { deploy } from '@h-ai/deploy'
|
|
528
|
+
* const result = await deploy.scan(process.cwd(), { environment: 'prod' })
|
|
529
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
530
|
+
* const scan: ScanResult = result.data
|
|
531
|
+
* const services = scan.requiredServices
|
|
532
|
+
* ```
|
|
158
533
|
*/
|
|
159
534
|
interface ScanResult {
|
|
160
|
-
/**
|
|
535
|
+
/** 调用者指定的部署目录,对应 lockfile 中的一个 importer。 */
|
|
536
|
+
appDir?: string;
|
|
537
|
+
/** 公共环境文件目录。 */
|
|
538
|
+
envDir?: string;
|
|
539
|
+
/** 依赖与配置共同确定的中间件详情。 */
|
|
540
|
+
middlewares?: MiddlewareRequirement[];
|
|
541
|
+
/** 从运行依赖图识别的 Hai 模块;peer 和开发依赖不启用模块。 */
|
|
542
|
+
haiModules?: string[];
|
|
543
|
+
/** 工程名称(工程根 package.json.name;缺失时报错)。 */
|
|
161
544
|
appName: string;
|
|
162
545
|
/** 是否为 SvelteKit 项目 */
|
|
163
546
|
isSvelteKit: boolean;
|
|
164
547
|
/** 目标平台 adapter 是否已安装(如 @sveltejs/adapter-vercel) */
|
|
165
548
|
adapterInstalled: boolean;
|
|
549
|
+
/** 容器部署所需的 @sveltejs/adapter-node 是否已安装 */
|
|
550
|
+
nodeAdapterInstalled: boolean;
|
|
166
551
|
/** 检测到的模块依赖需要的服务列表 */
|
|
167
552
|
requiredServices: ServiceType[];
|
|
168
|
-
/**
|
|
169
|
-
|
|
553
|
+
/** 各服务检测到的后端类型(如 db→postgresql/mysql,vecdb→qdrant),用于生成匹配的 sidecar */
|
|
554
|
+
serviceBackends: Partial<Record<ServiceType, string>>;
|
|
170
555
|
}
|
|
171
556
|
/**
|
|
172
557
|
* 部署结果
|
|
173
558
|
*
|
|
174
559
|
* 包含部署后的访问 URL、状态和已设置的环境变量。
|
|
560
|
+
* @example
|
|
561
|
+
* ```ts
|
|
562
|
+
* import type { DeployResult } from '@h-ai/deploy'
|
|
563
|
+
* const packaged: DeployResult = { url: '', deploymentId: 'deploy-shop-001', status: 'packaged', bundlePath: '/workspace/shop/.hai/deploy/shop-1.0.0.tar', envVarsSet: [], provider: 'docker-ssh', healthCheck: 'skipped' }
|
|
564
|
+
* // packaged 只代表交付包生成;不将其展示为服务已上线。
|
|
565
|
+
* ```
|
|
175
566
|
*/
|
|
176
567
|
interface DeployResult {
|
|
177
568
|
/** 部署后的访问 URL */
|
|
@@ -179,16 +570,32 @@ interface DeployResult {
|
|
|
179
570
|
/** 部署 ID */
|
|
180
571
|
deploymentId: string;
|
|
181
572
|
/** 部署状态 */
|
|
182
|
-
status: 'ready' | 'building' | 'error';
|
|
573
|
+
status: 'ready' | 'packaged' | 'building' | 'error';
|
|
574
|
+
/** Compose 部署 tar 的绝对路径;包含私有配置,应安全传输。 */
|
|
575
|
+
bundlePath?: string;
|
|
576
|
+
/** 手工搬运和启动说明;仅打包不代表服务器已运行。 */
|
|
577
|
+
instructions?: string;
|
|
183
578
|
/** 已设置的环境变量名列表 */
|
|
184
579
|
envVarsSet: string[];
|
|
580
|
+
/** 部署 Provider 名称(如 vercel / docker-ssh),容器部署时返回 */
|
|
581
|
+
provider?: string;
|
|
582
|
+
/** 健康检查结果:passed 表示 HTTP 探测成功,container-only 表示仅确认容器运行 */
|
|
583
|
+
healthCheck?: 'passed' | 'container-only' | 'skipped';
|
|
185
584
|
}
|
|
186
585
|
/**
|
|
187
586
|
* 基础设施开通结果
|
|
188
587
|
*
|
|
189
588
|
* 由 Provisioner 开通服务后返回,包含要注入到部署平台的环境变量。
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* import type { ProvisionResult } from '@h-ai/deploy'
|
|
592
|
+
* const reused: ProvisionResult = { resourceStatus: 'reused', serviceType: 'db', provisionerName: 'neon', envVars: { HAI_DB_TYPE: 'postgresql' }, resourceInfo: 'Existing shop database' }
|
|
593
|
+
* // envVars 可能含密码;不能直接输出。
|
|
594
|
+
* ```
|
|
190
595
|
*/
|
|
191
596
|
interface ProvisionResult {
|
|
597
|
+
/** 本次新建或复用;凭证型服务始终为 reused */
|
|
598
|
+
resourceStatus: 'created' | 'reused';
|
|
192
599
|
/** 服务类型 */
|
|
193
600
|
serviceType: ServiceType;
|
|
194
601
|
/** Provisioner 名称 */
|
|
@@ -199,130 +606,289 @@ interface ProvisionResult {
|
|
|
199
606
|
resourceInfo: string;
|
|
200
607
|
}
|
|
201
608
|
/**
|
|
202
|
-
*
|
|
609
|
+
* 部署失败后的安全恢复信息,不含环境变量值或原始异常。
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* import type { DeployRecoveryInfo } from '@h-ai/deploy'
|
|
613
|
+
* const recovery: DeployRecoveryInfo = { projectName: 'shop', stage: 'provision', resources: [{ serviceType: 'db', provisionerName: 'neon', resourceInfo: 'shop', resourceStatus: 'unknown' }] }
|
|
614
|
+
* // 先在供应商控制台核对 unknown 资源;避免重复创建计费资源。
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
interface DeployRecoveryInfo {
|
|
618
|
+
projectName: string;
|
|
619
|
+
stage: string;
|
|
620
|
+
platformProjectId?: string;
|
|
621
|
+
resources: Array<{
|
|
622
|
+
serviceType: ServiceType;
|
|
623
|
+
provisionerName: string;
|
|
624
|
+
resourceInfo: string;
|
|
625
|
+
resourceStatus: 'created' | 'reused' | 'unknown';
|
|
626
|
+
}>;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 命令失败后的已核验执行状态;远端 finished 仅指部署脚本及恢复已完成,不代表容器停止。
|
|
630
|
+
* @example
|
|
631
|
+
* ```ts
|
|
632
|
+
* import type { DeployCommandState } from '@h-ai/deploy'
|
|
633
|
+
* const state: DeployCommandState = { reason: 'timeout', localTermination: 'confirmed', remoteExecution: 'unknown', deploymentId: 'deploy-shop-001', resultFile: '/opt/hai/apps/shop/results/deploy-shop-001' }
|
|
634
|
+
* if (state.remoteExecution === 'unknown') process.stderr.write('Verify the matching remote deployment result before retrying\n')
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
interface DeployCommandState {
|
|
638
|
+
/** 失败触发原因。 */
|
|
639
|
+
reason: 'timeout' | 'output' | 'input' | 'spawn' | 'exit' | 'configuration';
|
|
640
|
+
/** 本地受管理树已全部退出、无法证明,或尚未启动。 */
|
|
641
|
+
localTermination: 'confirmed' | 'unknown' | 'not-started';
|
|
642
|
+
/** SSH 失败默认 unknown;读取匹配部署 ID 的完整终态结果后才为 finished。 */
|
|
643
|
+
remoteExecution?: 'unknown' | 'finished';
|
|
644
|
+
/** 本次不可变部署 ID,可与服务器结果核对。 */
|
|
645
|
+
deploymentId?: string;
|
|
646
|
+
/** 服务器端结果文件;无凭据。 */
|
|
647
|
+
resultFile?: string;
|
|
648
|
+
/** 匹配终态结果中的真实脚本退出码。 */
|
|
649
|
+
remoteExitCode?: number;
|
|
650
|
+
/** 匹配终态结果中的最后阶段。 */
|
|
651
|
+
remoteStage?: string;
|
|
652
|
+
/** 匹配终态中的恢复结果;passed 表示旧服务和 HTTP 验证通过,不包含业务数据回退。 */
|
|
653
|
+
remoteRecovery?: 'not-needed' | 'passed' | 'failed' | 'stopped';
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* 部署选项
|
|
657
|
+
* 仅更新指定服务时仍受完整/部分制品与首次部署约束;bundleOnly 不触发 SSH。
|
|
658
|
+
* @example
|
|
659
|
+
* ```ts
|
|
660
|
+
* import type { DeployAppOptions } from '@h-ai/deploy'
|
|
661
|
+
* const options: DeployAppOptions = { environment: 'prod', bundleOnly: true, artifactDir: '.hai/build/docker/build-id', selectedServices: ['serv'], output: '.hai/deploy/shop.tar' }
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
interface DeployAppOptions {
|
|
665
|
+
/** 接收实际执行阶段,用于终端进度显示;不包含凭据或配置原文。 */
|
|
666
|
+
onProgress?: (stage: 'packaging' | 'preflight' | 'images' | 'dependencies' | 'start' | 'health' | 'complete') => void;
|
|
667
|
+
/** 只更新指定的 Compose 服务;省略时完整制品部署全部,部分制品只部署其镜像。 */
|
|
668
|
+
selectedServices?: string[];
|
|
669
|
+
/** 打包中间件镜像所用的本地引擎,auto 优先 Podman。 */
|
|
670
|
+
runtime?: 'auto' | 'podman' | 'docker';
|
|
671
|
+
/** 部署环境标识;显式指定时必须存在 .env.<environment>。 */
|
|
672
|
+
environment?: string;
|
|
673
|
+
/** 环境文件所在目录,默认自动定位。 */
|
|
674
|
+
envDir?: string;
|
|
675
|
+
/** 按服务或变量前缀选择处理方式;如 HAI_REACH_PROVIDERS_1 可独立于另一邮件连接。 */
|
|
676
|
+
serviceModes?: Partial<Record<string, MiddlewareMode>>;
|
|
677
|
+
/** 是否跳过全部中间件配置与开通,默认 false;例如配置完备时设为 true。 */
|
|
678
|
+
skipMiddleware?: boolean;
|
|
679
|
+
/** 已构建镜像目录;默认最近构建入口/锁文件目录下的 .hai/build/docker。 */
|
|
680
|
+
artifactDir?: string;
|
|
681
|
+
/** 只生成独立交付目录(含 tar 和部署脚本),不连接 SSH;仅 docker-ssh 支持。 */
|
|
682
|
+
bundleOnly?: boolean;
|
|
683
|
+
/** 交付包命名路径;默认在 .hai/deploy/<环境>/bundle-<项目>-<部署ID>-<版本>/ 中输出同名 tar 和脚本,自定义 release.tar 输出 release-<版本>/release-<版本>.tar;已有目录拒绝覆盖。 */
|
|
684
|
+
output?: string;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Deploy 凭证文件操作。
|
|
203
688
|
*
|
|
204
|
-
*
|
|
689
|
+
* 这些方法仅操作 `~/.hai/credentials.yml`,不依赖 `deploy.init()`。
|
|
690
|
+
* @example
|
|
691
|
+
* ```ts
|
|
692
|
+
* import type { DeployCredentialOperations } from '@h-ai/deploy'
|
|
693
|
+
* import { deploy } from '@h-ai/deploy'
|
|
694
|
+
* const credentials: DeployCredentialOperations = deploy.credentials
|
|
695
|
+
* const path = credentials.getPath() // 无需先 init。
|
|
696
|
+
* ```
|
|
205
697
|
*/
|
|
206
|
-
interface
|
|
207
|
-
/** Provider 名称 */
|
|
208
|
-
readonly name: string;
|
|
698
|
+
interface DeployCredentialOperations {
|
|
209
699
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* @
|
|
700
|
+
* 获取凭证文件绝对路径。
|
|
701
|
+
* @example
|
|
702
|
+
* ```ts
|
|
703
|
+
* import { deploy } from '@h-ai/deploy'
|
|
704
|
+
* const path = deploy.credentials.getPath() // 当前用户的全局凭证文件。
|
|
705
|
+
* ```
|
|
214
706
|
*/
|
|
215
|
-
|
|
707
|
+
getPath: () => string;
|
|
216
708
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
709
|
+
* 加载凭证文件并补齐 `process.env` 缺项;已有同名或父配置优先,返回文件中可识别的键名。
|
|
710
|
+
* @example
|
|
711
|
+
* ```ts
|
|
712
|
+
* import { deploy } from '@h-ai/deploy'
|
|
713
|
+
* const loaded = deploy.credentials.load()
|
|
714
|
+
* if (!loaded.success) throw new Error(loaded.error.message)
|
|
715
|
+
* const names = loaded.data // 不输出被注入的值。
|
|
716
|
+
* ```
|
|
221
717
|
*/
|
|
222
|
-
|
|
718
|
+
load: () => HaiResult<string[]>;
|
|
223
719
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
720
|
+
* 保存单个凭证。
|
|
721
|
+
* @example
|
|
722
|
+
* ```ts
|
|
723
|
+
* import { deploy } from '@h-ai/deploy'
|
|
724
|
+
* const token = process.env.HAI_DEPLOY_PROVIDER_TOKEN
|
|
725
|
+
* if (!token) throw new Error('Provide HAI_DEPLOY_PROVIDER_TOKEN privately')
|
|
726
|
+
* const saved = deploy.credentials.save('HAI_DEPLOY_PROVIDER_TOKEN', token)
|
|
727
|
+
* if (!saved.success) throw new Error(saved.error.message)
|
|
728
|
+
* ```
|
|
228
729
|
*/
|
|
229
|
-
|
|
730
|
+
save: (key: string, value: string) => HaiResult<void>;
|
|
230
731
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
732
|
+
* 批量保存凭证。
|
|
733
|
+
* @example
|
|
734
|
+
* ```ts
|
|
735
|
+
* import { deploy } from '@h-ai/deploy'
|
|
736
|
+
* const token = process.env.HAI_DEPLOY_PROVIDER_TOKEN
|
|
737
|
+
* if (!token) throw new Error('Provide HAI_DEPLOY_PROVIDER_TOKEN privately')
|
|
738
|
+
* const saved = deploy.credentials.saveAll({ HAI_DEPLOY_PROVIDER_TOKEN: token })
|
|
739
|
+
* if (!saved.success) throw new Error(saved.error.message)
|
|
740
|
+
* ```
|
|
236
741
|
*/
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* 基础设施服务 Provisioner 接口(Neon / Upstash / R2 / Resend / Aliyun)
|
|
241
|
-
*
|
|
242
|
-
* 通过工厂函数创建(如 `createNeonProvisioner(config)`),负责开通/验证云服务。
|
|
243
|
-
*/
|
|
244
|
-
interface ServiceProvisioner {
|
|
245
|
-
/** Provisioner 名称 */
|
|
246
|
-
readonly name: string;
|
|
247
|
-
/** 对应的服务类型 */
|
|
248
|
-
readonly serviceType: ServiceType;
|
|
249
|
-
/**
|
|
250
|
-
* 验证凭证有效性
|
|
251
|
-
*
|
|
252
|
-
* @param credentials - 凭证键值对
|
|
253
|
-
* @returns 账户标识信息
|
|
254
|
-
*/
|
|
255
|
-
authenticate: (credentials: Record<string, string>) => Promise<HaiResult<string>>;
|
|
256
|
-
/**
|
|
257
|
-
* 开通/查找服务资源(幂等:按项目名查找已有资源)
|
|
258
|
-
*
|
|
259
|
-
* @param projectName - 项目名称,用作资源命名
|
|
260
|
-
* @returns 开通结果(含环境变量映射)
|
|
261
|
-
*/
|
|
262
|
-
provision: (projectName: string) => Promise<HaiResult<ProvisionResult>>;
|
|
263
|
-
}
|
|
264
|
-
/** 部署选项 */
|
|
265
|
-
interface DeployAppOptions {
|
|
266
|
-
/** 项目名称(默认从 package.json name 提取) */
|
|
267
|
-
projectName?: string;
|
|
268
|
-
/** 是否跳过基础设施开通 */
|
|
269
|
-
skipProvision?: boolean;
|
|
270
|
-
/** 是否跳过构建 */
|
|
271
|
-
skipBuild?: boolean;
|
|
742
|
+
saveAll: (entries: Record<string, string>) => HaiResult<void>;
|
|
272
743
|
}
|
|
273
744
|
/**
|
|
274
745
|
* Deploy 模块功能接口
|
|
275
746
|
*
|
|
276
747
|
* 提供自动化部署的完整 API:
|
|
748
|
+
* - `deploy.credentials.*()` — 凭证文件读写(不依赖 init)
|
|
277
749
|
* - `deploy.init()` — 初始化(加载配置、创建 Provider 和 Provisioner)
|
|
278
|
-
* - `deploy.scan()` —
|
|
750
|
+
* - `deploy.scan()` — 扫描应用依赖(纯文件系统能力,不依赖 init)
|
|
279
751
|
* - `deploy.provisionAll()` — 开通所有已配置的基础设施
|
|
280
752
|
* - `deploy.deployApp()` — 完整部署流程
|
|
281
753
|
* - `deploy.close()` — 关闭模块
|
|
282
|
-
*
|
|
754
|
+
* scan 与 credentials 不需要 init;部署与开通遵循 init → 检查结果 → use → finally close。close 返回 Promise<void>,不伪造 HaiResult。
|
|
283
755
|
* @example
|
|
284
756
|
* ```ts
|
|
757
|
+
* import type { DeployFunctions } from '@h-ai/deploy'
|
|
285
758
|
* import { deploy } from '@h-ai/deploy'
|
|
286
|
-
*
|
|
287
|
-
* await
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* console.log(`Deployed: ${result.data.url}`)
|
|
291
|
-
* }
|
|
292
|
-
* await deploy.close()
|
|
759
|
+
* const operations: DeployFunctions = deploy
|
|
760
|
+
* const scan = await operations.scan(process.cwd())
|
|
761
|
+
* if (!scan.success) throw new Error(scan.error.message)
|
|
762
|
+
* const required = scan.data.requiredServices
|
|
293
763
|
* ```
|
|
294
764
|
*/
|
|
295
765
|
interface DeployFunctions {
|
|
296
|
-
/**
|
|
766
|
+
/** 全局部署凭证文件操作。 */
|
|
767
|
+
readonly credentials: DeployCredentialOperations;
|
|
768
|
+
/**
|
|
769
|
+
* 初始化模块(加载配置、创建 Provider 和 Provisioner 实例)
|
|
770
|
+
* @example
|
|
771
|
+
* ```ts
|
|
772
|
+
* import { deploy } from '@h-ai/deploy'
|
|
773
|
+
* const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } })
|
|
774
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
775
|
+
* try {
|
|
776
|
+
* const ready = deploy.isInitialized
|
|
777
|
+
* }
|
|
778
|
+
* finally {
|
|
779
|
+
* await deploy.close()
|
|
780
|
+
* }
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
297
783
|
init: (config: DeployConfigInput) => Promise<HaiResult<void>>;
|
|
298
|
-
/**
|
|
784
|
+
/**
|
|
785
|
+
* 关闭模块
|
|
786
|
+
* @example
|
|
787
|
+
* ```ts
|
|
788
|
+
* import { deploy } from '@h-ai/deploy'
|
|
789
|
+
* const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } })
|
|
790
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
791
|
+
* try {
|
|
792
|
+
* const provider = deploy.config?.provider.type
|
|
793
|
+
* }
|
|
794
|
+
* finally {
|
|
795
|
+
* await deploy.close() // Promise<void>;不检查不存在的 success 字段。
|
|
796
|
+
* }
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
299
799
|
close: () => Promise<void>;
|
|
300
800
|
/** 当前配置 */
|
|
301
801
|
readonly config: DeployConfig | null;
|
|
302
802
|
/** 是否已初始化 */
|
|
303
803
|
readonly isInitialized: boolean;
|
|
304
804
|
/**
|
|
305
|
-
*
|
|
805
|
+
* 扫描应用目录,检测依赖和所需服务。
|
|
806
|
+
*
|
|
807
|
+
* 这是纯文件系统分析能力,不依赖云端认证状态,可在 `deploy.init()` 之前调用。
|
|
306
808
|
*
|
|
307
809
|
* @param appDir - 应用根目录路径
|
|
810
|
+
* @example
|
|
811
|
+
* ```ts
|
|
812
|
+
* import { deploy } from '@h-ai/deploy'
|
|
813
|
+
* const scanned = await deploy.scan(process.cwd(), { environment: 'prod' })
|
|
814
|
+
* if (!scanned.success) throw new Error(scanned.error.message)
|
|
815
|
+
* const services = scanned.data.requiredServices // 无需 init。
|
|
816
|
+
* ```
|
|
308
817
|
*/
|
|
309
|
-
scan: (appDir: string) => Promise<HaiResult<ScanResult>>;
|
|
818
|
+
scan: (appDir: string, options?: DeployScanOptions) => Promise<HaiResult<ScanResult>>;
|
|
310
819
|
/**
|
|
311
820
|
* 对所有已配置的 Provisioner 执行基础设施开通
|
|
312
821
|
*
|
|
313
822
|
* @param projectName - 用于资源命名的项目名
|
|
314
823
|
* @returns 所有 Provisioner 的结果列表
|
|
824
|
+
* @example
|
|
825
|
+
* ```ts
|
|
826
|
+
* import { deploy, getDeployRecovery } from '@h-ai/deploy'
|
|
827
|
+
* const apiKey = process.env.HAI_DEPLOY_SERVICES_DB_APIKEY
|
|
828
|
+
* if (!apiKey) throw new Error('Provide HAI_DEPLOY_SERVICES_DB_APIKEY privately')
|
|
829
|
+
* const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 8080 } }, services: { db: { provisioner: 'neon', apiKey } } })
|
|
830
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
831
|
+
* try {
|
|
832
|
+
* // 会访问真实云服务并可能新建资源。
|
|
833
|
+
* const result = await deploy.provisionAll('shop', ['db'])
|
|
834
|
+
* if (!result.success) {
|
|
835
|
+
* const recovery = getDeployRecovery(result.error)
|
|
836
|
+
* if (recovery?.resources.some(resource => resource.resourceStatus === 'unknown')) {
|
|
837
|
+
* process.stderr.write('Verify unknown resources at the provider before retrying\n')
|
|
838
|
+
* }
|
|
839
|
+
* throw new Error(result.error.message)
|
|
840
|
+
* }
|
|
841
|
+
* }
|
|
842
|
+
* finally {
|
|
843
|
+
* await deploy.close()
|
|
844
|
+
* }
|
|
845
|
+
* ```
|
|
315
846
|
*/
|
|
316
|
-
provisionAll: (projectName: string) => Promise<HaiResult<ProvisionResult[]>>;
|
|
847
|
+
provisionAll: (projectName: string, services?: ServiceType[]) => Promise<HaiResult<ProvisionResult[]>>;
|
|
317
848
|
/**
|
|
318
|
-
*
|
|
849
|
+
* 部署已有构建产物(环境和中间件 → 打包 → 上传/启动);不会调用构建命令。
|
|
319
850
|
*
|
|
320
851
|
* @param appDir - 应用根目录路径
|
|
321
852
|
* @param options - 部署选项
|
|
853
|
+
* @example
|
|
854
|
+
* ```ts
|
|
855
|
+
* import { deploy } from '@h-ai/deploy'
|
|
856
|
+
* const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } })
|
|
857
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
858
|
+
* try {
|
|
859
|
+
* // 当前项目必须已有构建镜像清单与生产环境配置。
|
|
860
|
+
* const result = await deploy.deployApp(process.cwd(), { environment: 'prod', bundleOnly: true })
|
|
861
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
862
|
+
* const status = result.data.status // packaged 不代表线上运行。
|
|
863
|
+
* }
|
|
864
|
+
* finally {
|
|
865
|
+
* await deploy.close()
|
|
866
|
+
* }
|
|
867
|
+
* ```
|
|
322
868
|
*/
|
|
323
869
|
deployApp: (appDir: string, options?: DeployAppOptions) => Promise<HaiResult<DeployResult>>;
|
|
324
870
|
}
|
|
325
871
|
|
|
872
|
+
/**
|
|
873
|
+
* @h-ai/deploy — 部署辅助函数
|
|
874
|
+
*
|
|
875
|
+
* 包含已有产物目录解析等与部署流程相关的辅助操作。 从 deploy-main.ts 中提取,遵循 main 仅做生命周期管理的规范。
|
|
876
|
+
* @module deploy-functions
|
|
877
|
+
*/
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* 将工程根 package.json.name 归一化为镜像与部署资源共用的身份;移除 scope、转小写并合并非法字符。
|
|
881
|
+
* @throws Error - 名称为空或归一化后不含字母数字。
|
|
882
|
+
* @param name - 工程根 package.json.name。
|
|
883
|
+
* @returns 可用于 OCI 镜像和 Compose 资源的工程名。
|
|
884
|
+
* @example
|
|
885
|
+
* ```ts
|
|
886
|
+
* import { normalizeProjectName } from '@h-ai/deploy'
|
|
887
|
+
* const name = normalizeProjectName('@shop/orders')
|
|
888
|
+
* ```
|
|
889
|
+
*/
|
|
890
|
+
declare function normalizeProjectName(name: string): string;
|
|
891
|
+
|
|
326
892
|
/**
|
|
327
893
|
* @h-ai/deploy — 模块主入口
|
|
328
894
|
*
|
|
@@ -332,39 +898,262 @@ interface DeployFunctions {
|
|
|
332
898
|
|
|
333
899
|
/**
|
|
334
900
|
* Deploy 模块单例
|
|
335
|
-
*
|
|
901
|
+
* 每次 init 检查 HaiResult;只打包不会连接 SSH,成功打包也不代表服务器已部署。远端 unknown 必须先核对匹配部署 ID 的结果,不能盲目重试。
|
|
336
902
|
* @example
|
|
337
903
|
* ```ts
|
|
338
|
-
* import { deploy } from '@h-ai/deploy'
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
904
|
+
* import { deploy, getDeployCommandState } from '@h-ai/deploy'
|
|
905
|
+
* const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } })
|
|
906
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
907
|
+
* try {
|
|
908
|
+
* // 当前项目已有镜像产物与部署环境;只打包,不连接服务器。
|
|
909
|
+
* const result = await deploy.deployApp(process.cwd(), { environment: 'prod', bundleOnly: true })
|
|
910
|
+
* if (!result.success) {
|
|
911
|
+
* const state = getDeployCommandState(result.error)
|
|
912
|
+
* if (state?.remoteExecution === 'unknown') {
|
|
913
|
+
* process.stderr.write('Inspect the matching server result before retrying\n')
|
|
914
|
+
* }
|
|
915
|
+
* throw new Error(result.error.message)
|
|
916
|
+
* }
|
|
917
|
+
* if (result.data.status === 'packaged') {
|
|
918
|
+
* const bundle = result.data.bundlePath
|
|
919
|
+
* }
|
|
920
|
+
* }
|
|
921
|
+
* finally {
|
|
922
|
+
* await deploy.close()
|
|
923
|
+
* }
|
|
343
924
|
* ```
|
|
344
925
|
*/
|
|
345
926
|
declare const deploy: DeployFunctions;
|
|
346
927
|
|
|
347
928
|
/**
|
|
348
|
-
* @h-ai/deploy —
|
|
929
|
+
* @h-ai/deploy — i18n
|
|
349
930
|
*
|
|
350
|
-
*
|
|
351
|
-
* @module deploy-
|
|
931
|
+
* 本文件提供部署模块的 i18n 文案访问入口。
|
|
932
|
+
* @module deploy-i18n
|
|
352
933
|
*/
|
|
934
|
+
/**
|
|
935
|
+
* 获取部署模块文案
|
|
936
|
+
* 使用 core 请求/全局 locale 的现有中英字典;不自行缓存调用时的语言。
|
|
937
|
+
* @param key - 文案 Key
|
|
938
|
+
* @param options - 插值参数与格式化选项
|
|
939
|
+
* @returns 本地化后的文案字符串
|
|
940
|
+
* @example 模块内部上下文
|
|
941
|
+
* ```ts
|
|
942
|
+
* const message = deployM('deploy_sshRequired')
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
declare const deployM: (key: "deploy_notInitialized" | "deploy_initFailed" | "deploy_unsupportedProviderType" | "deploy_unsupportedProvisionerType" | "deploy_configTokenRequired" | "deploy_authRequired" | "deploy_authFailed" | "deploy_projectCreateFailed" | "deploy_uploadFailed" | "deploy_deployFailed" | "deploy_provisionFailed" | "deploy_adapterMissing" | "deploy_scanFailed" | "deploy_envVarFailed" | "deploy_credentialError" | "deploy_credentialInvalidKey" | "deploy_credentialSaved" | "deploy_credentialLoaded" | "deploy_configError" | "deploy_credentialMissing" | "deploy_apiError" | "deploy_fileUploadFailed" | "deploy_provisionNoResult" | "deploy_recoveryFailure" | "deploy_recoverySuggestion" | "deploy_commandTimeout" | "deploy_commandFailed" | "deploy_remoteConnectFailed" | "deploy_remoteRuntimeMissing" | "deploy_imageTransferFailed" | "deploy_healthCheckFailed" | "deploy_invalidEnvironment" | "deploy_environmentMissing" | "deploy_environmentReadFailed" | "deploy_environmentWriteFailed" | "deploy_multipleApplications" | "deploy_purposeSqlite" | "deploy_purposeDatabase" | "deploy_purposeMemory" | "deploy_purposeLocal" | "deploy_purposeCache" | "deploy_purposeStorage" | "deploy_purposeVector" | "deploy_purposeDisabled" | "deploy_purposeLdap" | "deploy_purposeConsole" | "deploy_purposeEmail" | "deploy_purposeSms" | "deploy_webProxyInvalid" | "deploy_webBaseConflict" | "deploy_storageProxyInvalid" | "deploy_storagePublicEndpointInvalid" | "deploy_storageBackendRequired" | "deploy_artifactInvalid" | "deploy_sshRequired" | "deploy_sshPasswordInvalid" | "deploy_sshAskpassFailed" | "deploy_sshAskpassCleanupFailed" | "deploy_targetAuth" | "deploy_targetPasswordEnv" | "deploy_bundleInstructions" | "deploy_bundleMetadata" | "deploy_targetHost" | "deploy_targetPort" | "deploy_targetUsername" | "deploy_targetIdentityFile" | "deploy_targetKnownHostsFile" | "deploy_targetStrictHostKeyChecking" | "deploy_targetConnectTimeoutSeconds" | "deploy_targetDirectory" | "deploy_targetBundle" | "deploy_bundleOnlyContainer" | "deploy_selectedContainerOnly" | "deploy_selectionInvalid" | "deploy_imageVersionRequired" | "deploy_bundleRuntimeRequired" | "deploy_bundleFailed" | "deploy_configFileInvalid" | "deploy_configFileUnreadable" | "deploy_bundleNameInvalid" | "deploy_localStopped" | "deploy_localUnknown" | "deploy_localNotStarted" | "deploy_commandOutputFailed" | "deploy_commandInputFailed" | "deploy_commandTreeActive" | "deploy_commandExecutionFailed" | "deploy_remoteUnknown" | "deploy_remoteFinished" | "deploy_bundleDirectoryExists" | "deploy_bundleFull" | "deploy_bundlePartial" | "deploy_bundleSelection" | "deploy_transportBodyLimitInvalid" | "deploy_transportKeyRingInvalid" | "deploy_uploadPasswordPrompt" | "deploy_uploadPasswordUnavailable" | "deploy_uploadPasswordCancelled" | "deploy_uploadPasswordInvalid" | "deploy_managedSecretsInvalid" | "deploy_secretImportFailed" | "deploy_projectNameRequired" | "deploy_projectNameOverrideRemoved" | "deploy_requestAccessInvalid", options?: _h_ai_core.MessageOptions) => string;
|
|
353
946
|
|
|
354
947
|
/**
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
* @
|
|
358
|
-
|
|
359
|
-
|
|
948
|
+
* 部署中间件的后端与连接字段目录,供扫描、CLI 和容器规划共同使用。
|
|
949
|
+
* 字段名与对应模块公开 Schema 保持一致,不把模块依赖等同于必须创建外部服务。
|
|
950
|
+
* @module deploy-middleware
|
|
951
|
+
*/
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* 连接字段;例如 `{ name: 'port', defaultValue: '5432', kind: 'port' }`。
|
|
955
|
+
* @example
|
|
956
|
+
* ```ts
|
|
957
|
+
* import type { MiddlewareField } from '@h-ai/deploy'
|
|
958
|
+
* const field: MiddlewareField = { name: 'port', defaultValue: '5432', required: true, kind: 'port' }
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
interface MiddlewareField {
|
|
962
|
+
/** 模块配置字段名,camelCase 不拆词。 */
|
|
963
|
+
name: string;
|
|
964
|
+
/** 无现有配置时使用的提示默认值。 */
|
|
965
|
+
defaultValue?: string;
|
|
966
|
+
/** 是否为连接所必需;可选项允许留空。 */
|
|
967
|
+
required?: boolean;
|
|
968
|
+
/** 密码输入与私有环境文件写入标记。 */
|
|
969
|
+
secret?: boolean;
|
|
970
|
+
/** 交互校验类型;端口范围为 1–65535。 */
|
|
971
|
+
kind?: 'port' | 'boolean' | 'number' | 'url';
|
|
972
|
+
/** 数值下限;未声明时为 0。 */
|
|
973
|
+
minimum?: number;
|
|
974
|
+
/** 数值上限;例如 Redis db 为 15。 */
|
|
975
|
+
maximum?: number;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 后端目录项;例如 PostgreSQL 对应 `backend: 'postgresql'`。
|
|
979
|
+
* @example
|
|
980
|
+
* ```ts
|
|
981
|
+
* import type { MiddlewareBackend } from '@h-ai/deploy'
|
|
982
|
+
* const backend: MiddlewareBackend = { backend: 'postgresql', label: 'PostgreSQL', purpose: 'deploy_purposeDatabase', fields: [{ name: 'database', required: true }] }
|
|
983
|
+
* ```
|
|
984
|
+
*/
|
|
985
|
+
interface MiddlewareBackend {
|
|
986
|
+
/** 模块 Schema 接受的后端标识。 */
|
|
987
|
+
backend: string;
|
|
988
|
+
/** 可直接识别的产品名称。 */
|
|
989
|
+
label: string;
|
|
990
|
+
/** 用途说明的 i18n 消息键。 */
|
|
991
|
+
purpose: Parameters<typeof deployM>[0];
|
|
992
|
+
/** 云自动开通器;缺失表示不能自动开通该云服务。 */
|
|
993
|
+
provisioner?: 'neon' | 'upstash' | 'cloudflare-r2' | 'resend' | 'aliyun';
|
|
994
|
+
/** 自定义连接需要逐项输入的字段。 */
|
|
995
|
+
fields: MiddlewareField[];
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* 所有可部署中间件的连接目录。
|
|
999
|
+
* SQLite、memory、local 与 disabled 保留本地/禁用语义,不创建无关 sidecar。
|
|
1000
|
+
* 目录同时描述本机、禁用、自建与云后端;存在依赖不等于必须开通新资源。
|
|
360
1001
|
* @example
|
|
361
1002
|
* ```ts
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
1003
|
+
* import { DEPLOY_MIDDLEWARE, describeMiddlewareBackend } from '@h-ai/deploy'
|
|
1004
|
+
* const choices = DEPLOY_MIDDLEWARE.db.map(backend => ({ value: backend.backend, description: describeMiddlewareBackend(backend) }))
|
|
1005
|
+
* ```
|
|
1006
|
+
*/
|
|
1007
|
+
declare const DEPLOY_MIDDLEWARE: Record<ServiceType, MiddlewareBackend[]>;
|
|
1008
|
+
/**
|
|
1009
|
+
* 返回当前语言的后端用途说明。
|
|
1010
|
+
* @param backend - 部署目录中的后端条目。
|
|
1011
|
+
* @returns 当前 locale 的用途说明。
|
|
1012
|
+
* @example
|
|
1013
|
+
* ```ts
|
|
1014
|
+
* import { DEPLOY_MIDDLEWARE, describeMiddlewareBackend } from '@h-ai/deploy'
|
|
1015
|
+
* const backend = DEPLOY_MIDDLEWARE.db.find(item => item.backend === 'postgresql')
|
|
1016
|
+
* if (backend) {
|
|
1017
|
+
* const description = describeMiddlewareBackend(backend)
|
|
1018
|
+
* }
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
declare function describeMiddlewareBackend(backend: MiddlewareBackend): string;
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* 查看与执行入口相同的扫描、环境、项目身份和制品校验结果。远端 current 只报告为待执行时核验。
|
|
1025
|
+
* @example
|
|
1026
|
+
* ```ts
|
|
1027
|
+
* import { createDeploymentPlan } from '@h-ai/deploy'
|
|
1028
|
+
* const plan = await createDeploymentPlan('/workspace/shop', { provider: { type: 'docker-ssh', expose: { hostPort: 8080 } } }, { environment: 'prod' })
|
|
1029
|
+
* ```
|
|
1030
|
+
*/
|
|
1031
|
+
declare function createDeploymentPlan(appDir: string, input: DeployConfigInput, options?: DeployAppOptions & {
|
|
1032
|
+
inspectRemote?: boolean;
|
|
1033
|
+
}): Promise<{
|
|
1034
|
+
success: false;
|
|
1035
|
+
error: _h_ai_core.HaiError;
|
|
1036
|
+
} | {
|
|
1037
|
+
success: true;
|
|
1038
|
+
data: {
|
|
1039
|
+
projectName: string;
|
|
1040
|
+
root: string;
|
|
1041
|
+
appDir: string;
|
|
1042
|
+
environment: string;
|
|
1043
|
+
target: {
|
|
1044
|
+
provider: "docker-ssh";
|
|
1045
|
+
host: string | undefined;
|
|
1046
|
+
port: number | undefined;
|
|
1047
|
+
directory: string | undefined;
|
|
1048
|
+
runtime: "auto" | "docker" | "podman";
|
|
1049
|
+
hostPort: number;
|
|
1050
|
+
} | {
|
|
1051
|
+
provider: "vercel";
|
|
1052
|
+
host?: undefined;
|
|
1053
|
+
port?: undefined;
|
|
1054
|
+
directory?: undefined;
|
|
1055
|
+
runtime?: undefined;
|
|
1056
|
+
hostPort?: undefined;
|
|
1057
|
+
};
|
|
1058
|
+
artifact: {
|
|
1059
|
+
directory: string;
|
|
1060
|
+
version: string;
|
|
1061
|
+
buildId: string;
|
|
1062
|
+
verified: boolean;
|
|
1063
|
+
images: {
|
|
1064
|
+
name: "serv" | "web";
|
|
1065
|
+
reference: string;
|
|
1066
|
+
sha256: string;
|
|
1067
|
+
}[];
|
|
1068
|
+
} | undefined;
|
|
1069
|
+
selected: string[];
|
|
1070
|
+
retained: string[];
|
|
1071
|
+
containers: string[];
|
|
1072
|
+
composeManagedServices: string[];
|
|
1073
|
+
logicalVolumes: string[];
|
|
1074
|
+
middleware: {
|
|
1075
|
+
service: ServiceType;
|
|
1076
|
+
backend: string;
|
|
1077
|
+
configuration: string;
|
|
1078
|
+
action: MiddlewareMode;
|
|
1079
|
+
provisioner: string | undefined;
|
|
1080
|
+
}[];
|
|
1081
|
+
configuration: {
|
|
1082
|
+
files: string[];
|
|
1083
|
+
environmentKeys: string[];
|
|
1084
|
+
};
|
|
1085
|
+
baseline: {
|
|
1086
|
+
required: boolean;
|
|
1087
|
+
state: string;
|
|
1088
|
+
current: string | undefined;
|
|
1089
|
+
release: string | undefined;
|
|
1090
|
+
};
|
|
1091
|
+
recovery: {
|
|
1092
|
+
releases: string | undefined;
|
|
1093
|
+
point: string;
|
|
1094
|
+
preservesVolumes: boolean;
|
|
1095
|
+
databaseRollback: boolean;
|
|
1096
|
+
};
|
|
1097
|
+
};
|
|
1098
|
+
}>;
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* 读取命令失败的安全状态;不把本地 SSH 退出解释为远端停止。
|
|
1102
|
+
* @param error - 部署错误
|
|
1103
|
+
* @returns 通过结构校验的命令状态,或 undefined
|
|
1104
|
+
* @example
|
|
1105
|
+
* ```ts
|
|
1106
|
+
* import { getDeployCommandState, HaiDeployError } from '@h-ai/deploy'
|
|
1107
|
+
* const state = getDeployCommandState({ ...HaiDeployError.REMOTE_COMMAND_FAILED, message: 'SSH connection interrupted', ext: { commandExecution: { reason: 'exit', localTermination: 'confirmed', remoteExecution: 'unknown' } } })
|
|
1108
|
+
* if (state?.remoteExecution === 'unknown') process.stderr.write('Remote execution is unknown\n')
|
|
1109
|
+
* ```
|
|
1110
|
+
*/
|
|
1111
|
+
declare function getDeployCommandState(error: HaiError): DeployCommandState | undefined;
|
|
1112
|
+
/**
|
|
1113
|
+
* 读取错误中的恢复清单;仅返回明确允许展示的字段。
|
|
1114
|
+
* @param error - 上层捕获的部署 HaiError;不直接展示其 cause。
|
|
1115
|
+
* @returns 安全恢复清单;结构不满足约定时为 undefined。
|
|
1116
|
+
* @example
|
|
1117
|
+
* ```ts
|
|
1118
|
+
* import { getDeployRecovery, HaiDeployError } from '@h-ai/deploy'
|
|
1119
|
+
* const recovery = getDeployRecovery({ ...HaiDeployError.PROVISION_FAILED, message: 'Provisioning failed', ext: { recovery: { projectName: 'shop', stage: 'provision', resources: [] } } })
|
|
1120
|
+
* if (recovery) {
|
|
1121
|
+
* const stage = recovery.stage
|
|
365
1122
|
* }
|
|
366
1123
|
* ```
|
|
367
1124
|
*/
|
|
368
|
-
declare function
|
|
1125
|
+
declare function getDeployRecovery(error: HaiError): DeployRecoveryInfo | undefined;
|
|
1126
|
+
|
|
1127
|
+
/** 旧部署凭据的显式、只读预检与原子导入;不轮换密码、不变更容器和数据卷。 */
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* 在目标服务器导入前核验旧凭据;dryRun 默认 true,apply 需要 Linux flock 与运行中的原服务。
|
|
1131
|
+
* 输入文件必须由操作者明确指定,权限 0600;API 不读取旧部署包猜测密码。
|
|
1132
|
+
* @param options - 服务器项目目录、受保护的导入 JSON、容器引擎和只读开关。
|
|
1133
|
+
* @param options.directory - 目标 Linux 服务器上已存在的项目部署目录。
|
|
1134
|
+
* @param options.inputFile - 操作者提供的版本化私有 JSON 文件路径。
|
|
1135
|
+
* @param options.runtime - 实际使用的 Docker 或 Podman 引擎。
|
|
1136
|
+
* @param options.dryRun - 默认 true;只有显式 false 才在部署锁内保存状态。
|
|
1137
|
+
* @returns 仅包含项目、凭据组和卷名的回执,不包含密码;失败不推进 current 或改变服务。
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```ts
|
|
1140
|
+
* import { importDeploymentSecrets } from '@h-ai/deploy'
|
|
1141
|
+
* const plan = await importDeploymentSecrets({ directory: '/opt/hai/apps/shop-test', inputFile: '/private/shop-import.json', runtime: 'docker' })
|
|
1142
|
+
* if (!plan.success) throw new Error(plan.error.message)
|
|
1143
|
+
* // 停止业务写入、备份数据后,用相同输入显式传 dryRun: false 导入。
|
|
1144
|
+
* ```
|
|
1145
|
+
*/
|
|
1146
|
+
declare function importDeploymentSecrets(options: {
|
|
1147
|
+
directory: string;
|
|
1148
|
+
inputFile: string;
|
|
1149
|
+
runtime: 'docker' | 'podman';
|
|
1150
|
+
dryRun?: boolean;
|
|
1151
|
+
}): Promise<HaiResult<{
|
|
1152
|
+
projectName: string;
|
|
1153
|
+
stateVersion: 2;
|
|
1154
|
+
groups: string[];
|
|
1155
|
+
volumes: string[];
|
|
1156
|
+
changed: boolean;
|
|
1157
|
+
}>>;
|
|
369
1158
|
|
|
370
|
-
export { type DeployAppOptions, type DeployConfig, type DeployConfigInput, DeployConfigSchema, type DeployError, type DeployFunctions, type
|
|
1159
|
+
export { DEPLOY_MIDDLEWARE, type DeployAppOptions, type DeployCommandState, type DeployConfig, type DeployConfigInput, DeployConfigSchema, type DeployCredentialOperations, type DeployError, type DeployFunctions, type DeployRecoveryInfo, type DeployResult, type DeployScanOptions, type DeploymentArtifact, DeploymentArtifactSchema, type DeploymentEnvironment, type DockerSshProviderConfig, HaiDeployError, type MiddlewareBackend, type MiddlewareField, type MiddlewareMode, type MiddlewareRequirement, type PortExposeConfig, ProviderConfigSchema, type ProvisionResult, type ScanResult, type ServiceType, type SshConfig, artifactChecksum, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, readDeploymentArtifact, saveDeploymentEnvironment };
|