@h-ai/deploy 0.1.0-alpha.64 → 0.1.0-alpha.65

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.md CHANGED
@@ -2,242 +2,200 @@
2
2
 
3
3
  [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
4
 
5
- [框架首页](../../README.md) · 相关模块:[@h-ai/core](../core/README.md)
5
+ [框架首页](../../README.md) · [CLI](../cli/README.md) · [环境与中间件配置](../cli/templates/skills/hai-deploy/middleware.md)
6
6
 
7
- 自动化部署模块,支持两种部署方式:一键将 SvelteKit 应用部署到 Vercel(并自动开通云基础设施),或将应用容器化后通过 SSH 部署到任意 Linux 主机(docker-ssh)。
7
+ 部署 SDK,供 `@h-ai/cli` 的 `hai deploy` 命令调用,也可在 Node.js 脚本中独立使用。
8
8
 
9
- ## 支持的部署方式
9
+ ## 功能与范围
10
10
 
11
- | Provider | 目标 | 说明 |
12
- | ------------ | --------------- | ---------------------------------------------- |
13
- | `vercel` | Vercel 平台 | SvelteKit + adapter-vercel,配合云 Provisioner |
14
- | `docker-ssh` | 任意 Linux 主机 | SvelteKit + adapter-node,容器化后经 SSH 部署 |
11
+ | 部署目标 | 应用 | 执行内容 |
12
+ | ------------ | --------------------------------------------------- | ----------------------------------------------------------------------- |
13
+ | `docker-ssh` | 原生 Node 服务、使用 adapter-node 的 SvelteKit 应用 | 本地构建镜像,经 SSH 上传,在 Linux 上运行 Compose 并检查 HTTP 健康状态 |
14
+ | `vercel` | 使用 adapter-vercel 的 SvelteKit 应用 | 构建、上传、设置环境变量、等待平台部署结果;需要互联网 |
15
15
 
16
- ## 支持的云服务(Provisioner)
16
+ - 从目标目录对应的 `pnpm-lock.yaml` importer 遍历实际运行依赖及 workspace link,排除开发依赖和 peer;结合应用配置识别中间件。不会因为可选 AI peer 部署 AI/CUDA。
17
+ - 先选择环境并检查 `.env.<mode>`,再逐项选择默认配置、自定义连接或保持不变。环境已配置完整时可跳过全部中间件处理。
18
+ - Compose 支持 PostgreSQL、MySQL、Redis、RustFS/MinIO、Qdrant、pgvector、Chroma、OpenLDAP、Mailpit;SQLite、memory、local、LanceDB 按各自本地存储方式处理。Mailpit 只捕获测试邮件。
19
+ - 云开通支持 Neon、Upstash、Cloudflare R2、Resend、阿里云短信。R2 需要已有 S3 密钥,Resend 需要已验证发件域名,短信需要已审核签名与模板;其他后端使用自定义连接。
17
20
 
18
- | 服务类型 | Provisioner | 说明 |
19
- | -------- | ------------- | ---------------------- |
20
- | 数据库 | Neon | PostgreSQL Serverless |
21
- | 缓存 | Upstash | Redis TCP + TLS |
22
- | 存储 | Cloudflare R2 | S3 兼容对象存储 |
23
- | 邮件 | Resend | SMTP(已有已验证域名) |
24
- | 短信 | 阿里云 | 阿里云短信服务 |
21
+ **fullstack 的 API 与 Web 分开部署。** `hai deploy packages/my-app-serv` 只部署 API,不会自动部署 `apps/my-app-web`。模板根 Dockerfile 的 `runtime` 和 `web` target 分别构建 API 与静态 Web 镜像;Web 需另行上传并运行,配置 `PUBLIC_API_BASE` 和 API 的 CORS。当前没有同时发布两者的命令,也不自动配置域名、TLS 或反向代理。
25
22
 
26
- ## 快速开始
23
+ ## 命令行使用
27
24
 
28
- ```typescript
29
- import { deploy } from '@h-ai/deploy'
25
+ ### 1. 安装与准备
30
26
 
31
- // 可选:从 ~/.hai/credentials.yml 注入 HAI_DEPLOY_* 环境变量
32
- deploy.credentials.load()
27
+ 当前 Node 要求为 `^22.22.2 || ^24.15.0 || >=26.0.0`,生成工程固定 `pnpm@12.11.2`。Node 20 / pnpm 9.5.0 不受支持。Windows 使用 `pnpm.cmd`。
33
28
 
34
- // 初始化
35
- await deploy.init({
36
- provider: { type: 'vercel', token: 'vel_xxx' },
37
- services: {
38
- db: { provisioner: 'neon', apiKey: 'neon_xxx' },
39
- cache: { provisioner: 'upstash', email: 'a@b.com', apiKey: 'up_xxx' },
40
- },
41
- })
29
+ 在生成工程根目录安装与应用框架版本匹配的 CLI 和 SDK,并保留 lockfile(此步骤需要包源或完整离线依赖):
42
30
 
43
- // 扫描应用依赖
44
- const scan = await deploy.scan('./apps/my-app')
31
+ ```powershell
32
+ pnpm.cmd add -Dw @h-ai/cli @h-ai/deploy
33
+ pnpm.cmd exec hai --help
34
+ ```
45
35
 
46
- // 开通基础设施
47
- const provisions = await deploy.provisionAll('my-app')
36
+ 创建项目时选择 `--features deploy` 已包含项目本地 SDK,但不代表安装了全局 `hai`。已全局安装 CLI 时可直接运行 `hai deploy`。模板的 `pnpm run deploy` 是打包脚本,不等于执行 `hai deploy`。
48
37
 
49
- // 完整部署
50
- const result = await deploy.deployApp('./apps/my-app')
38
+ docker-ssh 还需要:
51
39
 
52
- await deploy.close()
53
- ```
40
+ - 本地 Docker 或 Podman 引擎已启动,能够构建目标架构的 Linux 镜像;Windows 需准备对应虚拟化/WSL2/虚拟机环境。仅安装容器客户端不够。`auto` 优先 Podman,可显式选择 Docker。
41
+ - 本地有 `ssh`、`scp`,密钥或 ssh-agent 可非交互登录服务器;准备主机指纹。
42
+ - Linux 有 Docker/Podman 和对应的 `compose` 命令。SSH 用户能直接运行容器、写入部署目录;Windows 能访问 SSH 端口与应用映射端口。
43
+ - 目标包有 `config/_deploy.yml`;环境目录有 `.env.prod` 或 `.env.test`。缺文件时停止,要求先创建。
54
44
 
55
- ## 配置
45
+ ### 2. 配置目标与环境
56
46
 
57
- 通过 `config/_deploy.yml` 管理 git 安全的配置,密钥存放在 `~/.hai/credentials.yml`:
47
+ `config/_deploy.yml` 的最小 Docker 配置如下。实际主机参数从 `.env.local` 的整体 `HAI_DEPLOY_PROVIDER` 覆盖,避免每次输入,也避免提交机器信息:
58
48
 
59
49
  ```yaml
60
- # config/_deploy.yml
61
50
  provider:
62
- type: vercel
63
- token: ${HAI_DEPLOY_VERCEL_TOKEN}
64
-
65
- services:
66
- db:
67
- provisioner: neon
68
- apiKey: ${HAI_DEPLOY_NEON_API_KEY}
69
- cache:
70
- provisioner: upstash
71
- email: ${HAI_DEPLOY_UPSTASH_EMAIL}
72
- apiKey: ${HAI_DEPLOY_UPSTASH_API_KEY}
51
+ type: docker-ssh
52
+ container:
53
+ runtime: docker
54
+ s3Engine: rustfs
55
+ services: {}
73
56
  ```
74
57
 
75
- 这里的变量由 `deploy.credentials` 管理,属于显式特殊映射;若同时设置
76
- `HAI_DEPLOY_PROVIDER_TOKEN` 等按 YAML 路径生成的约定变量,约定变量优先。
58
+ ```dotenv
59
+ # .env.local:不提交 Git;地址、用户名、端口及路径按实际环境替换
60
+ HAI_DEPLOY_PROVIDER='{"type":"docker-ssh","ssh":{"host":"10.0.0.20","port":22,"username":"deploy","identityFile":"C:/Users/me/.ssh/hai_deploy"},"remote":{"baseDir":"/opt/hai/apps","runtime":"docker"},"expose":{"type":"port","containerPort":3000,"hostPort":18080}}'
61
+ ```
77
62
 
78
- 如需在脚本或 CLI 中显式管理凭证文件,请通过 `deploy.credentials.*` 访问:
63
+ 也可让交互向导填写缺失的目标参数,保存 `.env.local` 后复用。私钥只记录路径。已有完整目标会直接复用,切换目标时修改 `HAI_DEPLOY_PROVIDER`。
79
64
 
80
- ```typescript
81
- deploy.credentials.load()
82
- deploy.credentials.save('HAI_DEPLOY_VERCEL_TOKEN', 'vel_xxx')
83
- deploy.credentials.saveAll({
84
- HAI_DEPLOY_NEON_API_KEY: 'neon_xxx',
85
- })
65
+ 覆盖顺序为 `.env` → `.env.<mode>` → `.env.local` → 当前进程变量;父节点 JSON(如 `HAI_DB`)优先于叶子变量。`.env.example` 记录完整字段、说明与枚举;`.env`、`.env.dev/test/prod` 提交 Git,真实凭据只放 `.env.local`。首次启动 fullstack 需在私有配置中设置符合 IAM 策略的 `HAI_ADMIN_DEFAULT_PASSWORD`。
66
+
67
+ fullstack 生产环境还必须在 `.env.prod` 设置明确的 Web Origin,例如 `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`;默认 `*` 会使生产启动失败。生产 refresh Cookie 启用 Secure,Web/API 应通过已配置信任证书的 HTTPS 入口访问;只开放 HTTP 端口不足以验收完整登录与刷新会话。
68
+
69
+ ### 3. 执行部署
70
+
71
+ 从 fullstack 工程根目录运行,替换实际服务目录:
72
+
73
+ ```powershell
74
+ pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir .
86
75
  ```
87
76
 
88
- > `deploy.scan()` 与 `deploy.credentials.*()` 都是本地文件系统能力,不依赖 `deploy.init()`。
77
+ 向导展示中间件当前后端与用途,再提供默认配置、自定义连接或保持不变。保持不变仅在目标环境文件明确声明完整配置时出现。默认配置在 Linux Compose 中生成服务/卷;自定义连接不生成该中间件。公共值写目标环境文件,凭据写 `.env.local`,输出脱敏。
89
78
 
90
- ## docker-ssh 部署
79
+ 已有数据库、缓存、存储连接全部配置完成时:
91
80
 
92
- 将应用容器化后通过 SSH 部署到任意 Linux 主机,无需云平台账号。
81
+ ```powershell
82
+ pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir . --yes --skip-middleware
83
+ ```
93
84
 
94
- ### 前置条件
85
+ `--skip-middleware`(别名 `--skip-provision`)跳过中间件提示、配置改写与资源创建,仍构建并部署应用。它不会启动尚未运行的外部中间件。`--yes` 单独使用会复用完整配置,并为其余项采用当前后端默认值。
95
86
 
96
- - **本地**:安装 docker 或 podman(`auto` 优先 podman)。
97
- - **远程主机**:可 SSH 登录、已装 docker/podman 与 compose、登录用户可运行容器。
98
- - **应用**:SvelteKit 使用 `@sveltejs/adapter-node`(工程自带的 `Dockerfile` 已满足);缺少时返回 `ADAPTER_MISSING`。
87
+ `--project-name <name>` 指定名称,选定环境后名称追加环境后缀。**`--skip-build` 当前仅跳过 Vercel 的应用构建;docker-ssh 始终构建镜像,不支持直接指定已有应用镜像。** 命令失败返回非零退出码。
99
88
 
100
- ### 使用步骤
89
+ ## docker-ssh 的文件与执行流程
101
90
 
102
- 1)把 SSH 私钥路径存入凭证文件(不进仓库)。编辑 `~/.hai/credentials.yml`,或用 API:
91
+ 1. 本地选择目标包自己的 Dockerfile;不存在时生成兜底 Dockerfile,以 workspace 根为构建上下文。fullstack 服务包默认走兜底,不会使用根 Dockerfile 的 Web target。
92
+ 2. 本地构建应用镜像,使用 `docker/podman save` 导出 tar。
93
+ 3. SSH 检查远程容器引擎与 Compose;SCP 上传 tar,远程执行 `load`。
94
+ 4. 本地生成 `compose.yml`、`.env.runtime`、`start-<mode>.sh`,分别上传至 `<baseDir>/<projectName>-<mode>/`。它们与镜像归档分开传输,不打成一个 Compose 压缩包,也不把源码上传后在 Linux 构建。
95
+ 5. 远程脚本设置 `HAI_DEPLOY_ENVIRONMENT`、`HAI_CORE_ENV`,执行 `compose --env-file .env.runtime ... up -d`。SDK 从本地访问原生服务 `/health` 或 SvelteKit `/`,通过后才返回成功。
103
96
 
104
- ```typescript
105
- deploy.credentials.save('HAI_DEPLOY_SSH_KEY', '~/.ssh/hai_deploy')
106
- ```
97
+ 本地产物位于目标包 `.hai/deploy/<mode>/`,应忽略 Git;远程 `.env.runtime` 权限为 `600`。保留本地产物用于重复部署;“保持不变”保留本地上一份 Compose 中已有的服务/卷定义,不会自动读取服务器上任意现有 Compose。私有环境文件与部署归档不得复制进镜像。
107
98
 
108
- 2)在 `config/_deploy.yml` 声明目标主机:
99
+ ## 完全离线部署
109
100
 
110
- ```yaml
111
- provider:
112
- type: docker-ssh
113
- ssh:
114
- host: 10.0.0.20 # 主机 IP 或 ~/.ssh/config 别名
115
- port: 22
116
- username: deploy
117
- identityFile: ${HAI_DEPLOY_SSH_KEY} # 私钥文件路径,省略则用 ssh-agent
118
- remote:
119
- baseDir: /opt/hai/apps # 远程部署根目录
120
- runtime: auto # 远程运行时;auto 优先 podman,其次 docker
121
- expose:
122
- type: port
123
- containerPort: 3000 # 容器内端口(adapter-node 默认 3000)
124
- hostPort: 18080 # 宿主机端口 → 访问地址 http://10.0.0.20:18080
101
+ 离线指不能访问互联网,但 Windows 与 Linux 仍可通过内网 SSH/HTTP 通信。Vercel 和云资源开通不适用。
125
102
 
126
- container:
127
- runtime: auto # 本地构建运行时(可选)
103
+ ### 提前带入的材料
128
104
 
129
- services: {} # 启用的服务会自动生成 compose sidecar,见下
130
- ```
105
+ | 位置 | 必备材料或状态 |
106
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | Windows 工具 | 兼容的 Node(例如满足要求的 Node 24)、pnpm 12.11.2、已安装的 CLI/SDK 及传递依赖;OpenSSH;已初始化且可启动的 Linux 容器引擎 |
108
+ | 项目 | 完整工程、固定版本 lockfile、环境文件及私有凭据;能离线运行 `pnpm.cmd exec hai --help`。现场不要使用 `npx` / `pnpm dlx` 临时下载 |
109
+ | Linux | Docker CLI/Engine、Compose 插件(`docker compose version` 成功)、SSH 公钥授权、容器权限、可写目录、开放端口及足够镜像/卷空间;仅运行镜像不需要主机 Node/pnpm |
110
+ | 镜像 | 与 Linux CPU 架构一致的 API、Web 及中间件镜像;名称与 tag 和 Compose 完全一致;导入用 `docker load -i <archive.tar>` |
111
+ | 业务配置 | PostgreSQL 数据库/账号、Redis 连接及密码、RustFS 密钥/桶、首次管理员密码;已有服务须已启动且可从应用容器访问,`localhost` 指应用容器自身 |
131
112
 
132
- 3)部署并验证(CLI 或 API,二者等价):
113
+ 默认 Compose 使用 `postgres:16-alpine`、`redis:7-alpine`、`ghcr.io/rustfs/rustfs:1.0.1`。已有其他 tag 不等于命中这些镜像;先核对并导入/正确标记已验证兼容的镜像,或使用自定义连接。中间件镜像不会由 SDK 自动打包上传。其他固定镜像见 [Compose 实现](src/container/deploy-container-compose.ts)。
133
114
 
134
- ```bash
135
- hai deploy # 读取 config/_deploy.yml,输出访问地址
136
- ```
115
+ **默认源码构建并非开箱即用的离线构建。** Dockerfile 会准备 pnpm、下载包和 SQLite 预编译驱动;fullstack 根 Dockerfile 还使用 `pnpm fetch` 与外部 Dockerfile frontend。Windows 的 `node_modules` 或 pnpm store 不能代替 Linux 构建依赖。若现场从源码构建,应带齐本地引擎中的 Node 基础镜像、构建 frontend、Corepack/pnpm、Linux 平台依赖和原生二进制缓存,并调整 Dockerfile 使用这些材料。在准备环境断开外网完整构建一次;`pnpm install --offline --frozen-lockfile` 仅解决包存储读取,不覆盖安装脚本的额外下载。
137
116
 
138
- ```typescript
139
- import { deploy } from '@h-ai/deploy'
117
+ SQLite 预编译包须匹配 better-sqlite3 版本、Node ABI、CPU 架构和 libc。即使运行期选 PostgreSQL,当前依赖安装仍可能需要该包。可预置 Linux 构建环境的 prebuild-install 缓存,或通过 `container.sqlitePrebuildMirror` 指向内网镜像;两者都没有时需提前带入二进制或成品镜像。默认 fullstack 不需要 CUDA。
140
118
 
141
- deploy.credentials.load() // 注入 HAI_DEPLOY_* 环境变量
142
- await deploy.init(dockerSshConfig)
143
- const result = await deploy.deployApp('./apps/my-app')
144
- if (result.success) {
145
- result.data.url // http://<host>:<hostPort>
146
- result.data.healthCheck // 'passed'(HTTP 探测成功)
147
- }
148
- await deploy.close()
149
- ```
119
+ ### 使用预构建镜像离线执行 SDK
150
120
 
151
- ### 实现逻辑
121
+ 较易交付的方式是提前构建镜像,现场让 SDK 复用它。以下以工程名 `my-app`、Web 前端和与服务器一致的构建架构为前提;在可提供依赖的材料准备环境执行,不是在离线现场下载:
152
122
 
153
- `deployApp()` 对 docker-ssh 依次执行:
123
+ ```powershell
124
+ docker build --target runtime -t my-app-api:offline .
125
+ docker build --target web -t my-app-web:offline .
126
+ docker save -o my-app-images.tar my-app-api:offline my-app-web:offline
127
+ ```
154
128
 
155
- 1. **扫描**:读取 `package.json` 与 `config/*.yml`,识别 `@sveltejs/adapter-node` 与所需服务及其后端类型。
156
- 2. **本地构建**:用工程自带的 `Dockerfile` 构建镜像 `hai-<app>:<部署ID>`(podman 追加 `--format docker --jobs=1`)。
157
- 3. **导出**:`docker/podman save` 导出为 tar。
158
- 4. **预检**:SSH 连通性 + 远程 `docker/podman version` 与 `compose version`。
159
- 5. **传输加载**:`scp` 上传 tar → 远程 `load` → 重打为 `localhost/` 本地标签(避免误从 registry 拉取)。
160
- 6. **生成运行描述**:按扫描结果生成 `compose.yml`(app + sidecar)与 `.env.runtime`(含 `HAI_*` 覆盖变量与凭据),上传到 `<baseDir>/<app>/`,`.env.runtime` 设为 `600`。
161
- 7. **启动**:`compose up -d`(Compose 项目名固定 `hai-<app>`,重复部署幂等)。
162
- 8. **健康检查**:轮询 `http://<host>:<hostPort>/`,返回 200 才判定 `ready`。
129
+ 把归档带入离线 Windows 的本地 Linux 引擎,执行 `docker load -i my-app-images.tar`。在 `packages/my-app-serv/Dockerfile` 写入下列内容,使 SDK 的必经构建只复用成品镜像,无包安装步骤;业务代码变化后须重制成品镜像:
163
130
 
164
- > 镜像、`compose.yml`、`.env.runtime` 落在应用 `.hai/deploy/`(已在模板 `.gitignore` 忽略)。
131
+ ```dockerfile
132
+ FROM my-app-api:offline
133
+ ```
165
134
 
166
- ### compose 按功能生成 sidecar
135
+ 为服务包添加 `.dockerignore`(内容 `*`),限制此专用构建上下文。配置部署目标后执行上面的 `hai deploy`:需 SDK 创建中间件容器时,先在 `.env.prod` 选择 `HAI_DB_TYPE=postgresql`、`HAI_CACHE_TYPE=redis`、`HAI_STORAGE_TYPE=s3`,再走向导默认配置;已有服务则填写自定义连接,配置完整后可用 `--skip-middleware`。API 镜像由 SDK 上传;提前在断外网条件下演练,确认构建器不会尝试联网。
167
136
 
168
- `deploy.scan()` 会识别所需服务及其**实际后端类型**(读取 `_db.yml` / `_vecdb.yml` 等的 `type`),生成匹配的 sidecar 并注入对应 `HAI_*` 覆盖变量:
137
+ Web 镜像需另外传输到 Linux 并运行。以下假设已另行准备 `https://app.hai.internal` 与 `https://api.hai.internal` 的内网 TLS 入口,分别代理 Web 的 18081 端口与 API 的 18080 端口;需带入代理配置/镜像、证书,并配置内网 DNS/hosts 和客户端信任。SSH 登录后执行:
138
+
139
+ ```sh
140
+ docker load -i /path/to/my-app-images.tar
141
+ docker run -d --name my-app-web-prod --restart unless-stopped -p 18081:8080 -e PUBLIC_API_BASE=https://api.hai.internal my-app-web:offline
142
+ ```
169
143
 
170
- | 扫描到的服务 | 后端 (`type`) | 追加的 sidecar | 注入变量 |
171
- | ------------ | ------------- | ----------------------------- | ------------- |
172
- | `db` | `postgresql` | `postgres:16-alpine` | `HAI_DB` |
173
- | `db` | `mysql` | `mysql:8.4` | `HAI_DB` |
174
- | `db` | `sqlite` | 无(挂载 `/app/data` 数据卷) | — |
175
- | `cache` | `redis` | `redis:7-alpine` | `HAI_CACHE` |
176
- | `storage` | `s3` | `minio` + 初始化 bucket | `HAI_STORAGE` |
177
- | `vecdb` | `qdrant` | `qdrant/qdrant` | `HAI_VECDB` |
178
- | `vecdb` | `pgvector` | `pgvector/pgvector` | `HAI_VECDB` |
179
- | `vecdb` | `chroma` | `chromadb/chroma` | `HAI_VECDB` |
180
- | `vecdb` | `lancedb` | 无(挂载 `/app/data` 数据卷) | — |
144
+ API 地址须能被用户浏览器访问,API 设置 `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`。验收 API `/health`、Web 登录/业务请求、hash 页面刷新、会话刷新、服务重启及持久化。这里是离线材料与适配方式说明,联网部署测试不能替代实际断网验收。
181
145
 
182
- - `cache` 为 `memory`、`reach`(邮件/短信)为外部 API,均不生成 sidecar。
183
- - 若同一服务已配置云 Provisioner(如 Neon 提供 `HAI_DB`),优先用云资源,不再自建 sidecar。
184
- - sidecar 的密码等敏感值仅写入远程 `.env.runtime`(`600`),不会进入 `compose.yml`。
146
+ 镜像归档见 Docker 官方 [save](https://docs.docker.com/reference/cli/docker/image/save/) / [load](https://docs.docker.com/reference/cli/docker/image/load/),离线包安装见 [pnpm install](https://pnpm.io/cli/install),Compose 插件见 [安装说明](https://docs.docker.com/compose/install/linux/)。
185
147
 
186
- > 安全:`identityFile` 为私钥**路径**(非私钥内容);`strictHostKeyChecking: true`(默认)
187
- > 采用 accept-new(首次信任、拒绝变更)。首次连接未知主机可先手动 `ssh` 一次。
148
+ ## SDK 与错误处理
188
149
 
189
- ## 错误处理
150
+ 脚本调用无需 CLI。`config` 是已解析的部署配置;需要相同环境解析时使用公开的 `loadDeploymentEnvironment` / `loadDeploymentConfig`。最小生命周期:
190
151
 
191
152
  ```typescript
192
- import { HaiDeployError } from '@h-ai/deploy'
193
-
194
- const result = await deploy.deployApp('./apps/my-app')
195
- if (!result.success) {
196
- switch (result.error.code) {
197
- case HaiDeployError.AUTH_FAILED.code:
198
- // Vercel Token 无效
199
- break
200
- case HaiDeployError.BUILD_FAILED.code:
201
- // 构建失败
202
- break
153
+ import type { DeployConfigInput } from '@h-ai/deploy'
154
+ import { deploy } from '@h-ai/deploy'
155
+
156
+ /**
157
+ * 使用现有中间件发布生产 API,并释放部署模块。
158
+ * @param config - 从部署环境解析得到的配置
159
+ * @returns 初始化或部署的 HaiResult,由调用者检查 success
160
+ * @example `const result = await publish(config)`
161
+ */
162
+ export async function publish(config: DeployConfigInput) {
163
+ const initialized = await deploy.init(config)
164
+ if (!initialized.success)
165
+ return initialized
166
+ try {
167
+ return await deploy.deployApp('./packages/my-app-serv', {
168
+ environment: 'prod',
169
+ envDir: '.',
170
+ skipProvision: true,
171
+ })
172
+ }
173
+ finally {
174
+ await deploy.close()
203
175
  }
204
176
  }
205
177
  ```
206
178
 
207
- 常用错误码:
179
+ 调用者先判断 `result.success`,失败读取 `result.error.code/message`,成功读取 `result.data.url/healthCheck`。`deploy.scan()` 与 `deploy.credentials.*()` 不依赖初始化;`deployApp()` 已包含开通流程,无须预先 `provisionAll()`。手动开通同样检查结果。SDK 的 `skipProvision` 与 CLI 跳过中间件语义一致。
208
180
 
209
- | 错误码 | code | 说明 |
210
- | --------------------------------- | ---------------- | ------------ |
211
- | `HaiDeployError.DEPLOY_FAILED` | `hai:deploy:001` | 部署失败 |
212
- | `HaiDeployError.BUILD_FAILED` | `hai:deploy:003` | 构建失败 |
213
- | `HaiDeployError.AUTH_FAILED` | `hai:deploy:006` | 认证失败 |
214
- | `HaiDeployError.PROVISION_FAILED` | `hai:deploy:007` | 资源开通失败 |
215
- | `HaiDeployError.NOT_INITIALIZED` | `hai:deploy:010` | 未初始化 |
216
- | `HaiDeployError.ENV_VAR_FAILED` | `hai:deploy:011` | 环境变量失败 |
181
+ 云资源返回 `HAI_DB`、`HAI_CACHE`、`HAI_STORAGE` 或 `HAI_REACH_PROVIDERS` 整体配置;应用须加载对应 YAML 并初始化模块。失败时 `getDeployRecovery(result.error)` 返回阶段及资源 ID;`unknown` 表示结果不确定,应先在平台核对,同名重试复用资源,不自动删除。完整类型、配置及错误码见 [公开入口](src/index.ts)。
217
182
 
218
- docker-ssh 专属错误码:
183
+ ## 测试与二次开发
219
184
 
220
- | 错误码 | code | 说明 |
221
- | -------------------------------------------- | ---------------- | ---------------- |
222
- | `HaiDeployError.CONTAINER_RUNTIME_NOT_FOUND` | `hai:deploy:015` | 未找到容器运行时 |
223
- | `HaiDeployError.CONTAINER_BUILD_FAILED` | `hai:deploy:016` | 镜像构建失败 |
224
- | `HaiDeployError.REMOTE_CONNECT_FAILED` | `hai:deploy:017` | 远程连接失败 |
225
- | `HaiDeployError.REMOTE_COMMAND_FAILED` | `hai:deploy:018` | 远程命令失败 |
226
- | `HaiDeployError.IMAGE_TRANSFER_FAILED` | `hai:deploy:019` | 镜像传输失败 |
227
- | `HaiDeployError.HEALTH_CHECK_FAILED` | `hai:deploy:020` | 健康检查未通过 |
185
+ 框架仓库根目录安装依赖后执行(Windows 使用 `pnpm.cmd`):
228
186
 
229
- ## 测试
230
-
231
- ```bash
232
- pnpm --filter @h-ai/deploy test
187
+ ```powershell
188
+ pnpm.cmd --filter @h-ai/deploy typecheck
189
+ pnpm.cmd --filter @h-ai/deploy lint
190
+ pnpm.cmd --filter @h-ai/deploy build
191
+ pnpm.cmd --filter @h-ai/deploy test
192
+ pnpm.cmd check:skills
233
193
  ```
234
194
 
235
- ## License
195
+ `src/deploy-scanner.ts` 处理 lockfile 依赖图,`deploy-middleware.ts` 定义连接字段,`container/` 负责引擎、SSH 与 Compose,`providers/` 实现部署目标,`provisioners/` 实现云开通;CLI 向导位于 `../cli/src/commands/cli-deploy*.ts`。新增后端/配置时同步这些消费者、环境示例、测试和 [部署 skill](../cli/templates/skills/hai-deploy/SKILL.md)。
236
196
 
237
- Apache-2.0
238
-
239
- 云资源配置消费:Neon 输出 HAI_DB(PostgreSQL JSON),Upstash 输出 HAI_CACHE(Redis rediss URL JSON),R2 输出 HAI_STORAGE(扁平 S3 JSON)。邮件/短信输出 HAI_REACH_PROVIDERS 数组,deployApp 合并两个渠道并保留 YAML 中的消息模板;此覆盖替换原 providers 数组。Core 支持整体对象/数组覆盖,父节点优先于叶子变量,仍执行 Schema 校验;构建子进程与目标运行环境使用相同开通结果。R2 必须提供已有 S3 accessKeyId/secretAccessKey;Resend 必须提供已验证域名的 from;阿里云必须提供已审核 signName/模板。应用须已有相应\_db/\_cache/\_storage/\_reach.yml 并在启动时加载,网络客户端需要 Node 运行时。
197
+ 修改链路后运行 CLI 的 typecheck/lint/build/test,再执行 `pnpm.cmd --filter @h-ai/cli test:deploy-gates`:使用当前包归档生成 fullstack,经过 Podman/SSH 验证健康状态、环境、登录和重复部署。门禁需要本地引擎、OpenSSH;首次准备夹具会下载镜像和系统/Node 依赖,并非离线验收。模板变更另运行 `pnpm.cmd --filter @h-ai/cli test:scaffold-gates`。单元测试不证明云资源已开通,付费云服务另行验收。日志、截图和验收报告保留在 Git 忽略的 `tmp/` / `test-results/`。
240
198
 
241
- 配置依据:[Upstash TLS](https://upstash.com/docs/redis/features/security)、[Resend SMTP](https://resend.com/docs/send-with-smtp)。确定性验收真实生成应用并通过 Core 和各模块 Schema;未创建云资源、未发送邮件/短信,不能据此声称云端写入读取已验收。
199
+ ## License
242
200
 
243
- 部署失败恢复:`ProvisionResult.resourceStatus` 标记 created/reused;`getDeployRecovery(result.error)` 读取 projectName、stage、platformProjectId 和资源清单,unknown 表示请求结果不确定,必须先在服务商控制台核对。失败资源已返回 ID 时保留该 ID。恢复结果不含 envVars、原始异常或连接凭证,CLI 会展示清单和建议。同名重试复用现有资源;不自动删除复用资源,新建资源仅在人工确认未被使用后清理。
201
+ Apache-2.0
package/dist/index.d.ts CHANGED
@@ -88,6 +88,7 @@ type PortExposeConfig = z.infer<typeof PortExposeSchema>;
88
88
  type DockerSshProviderConfig = z.infer<typeof DockerSshProviderConfigSchema>;
89
89
  /** 部署模块完整配置 Schema */
90
90
  declare const DeployConfigSchema: z.ZodObject<{
91
+ environment: z.ZodOptional<z.ZodString>;
91
92
  provider: z.ZodDiscriminatedUnion<[z.ZodObject<{
92
93
  type: z.ZodLiteral<"vercel">;
93
94
  token: z.ZodString;
@@ -151,6 +152,12 @@ declare const DeployConfigSchema: z.ZodObject<{
151
152
  docker: "docker";
152
153
  podman: "podman";
153
154
  }>>;
155
+ httpProxy: z.ZodDefault<z.ZodBoolean>;
156
+ s3Engine: z.ZodDefault<z.ZodEnum<{
157
+ rustfs: "rustfs";
158
+ minio: "minio";
159
+ }>>;
160
+ sqlitePrebuildMirror: z.ZodOptional<z.ZodURL>;
154
161
  }, z.core.$strip>>;
155
162
  }, z.core.$strip>;
156
163
  /** 部署配置类型(parse 后) */
@@ -158,6 +165,58 @@ type DeployConfig = z.infer<typeof DeployConfigSchema>;
158
165
  /** 部署配置输入类型(用户输入) */
159
166
  type DeployConfigInput = z.input<typeof DeployConfigSchema>;
160
167
 
168
+ /** 部署环境的加载和持久化;公开环境文件不保存真实凭据。 */
169
+
170
+ /** 部署环境快照;例如 prod 的 targetValues 只来自 `.env.prod`,不包含基础/私有覆盖。 */
171
+ interface DeploymentEnvironment {
172
+ /** 文件模式(dev/test/prod 或其他合法模式名)。 */
173
+ environment: string;
174
+ /** 环境文件的绝对目录。 */
175
+ envDir: string;
176
+ /** 合并后的部署变量,可能含私有值,不得直接记录日志。 */
177
+ values: Record<string, string>;
178
+ /** 仅目标环境文件显式声明的变量,用于判断能否“保持不变”。 */
179
+ targetValues: Record<string, string>;
180
+ }
181
+ /**
182
+ * 按部署环境解析配置;未选择的可选 Provisioner 缺少凭据时保留空值,选择完成后由 Schema 校验。
183
+ * 不修改 core 缓存或进程变量,不在 YAML 文本上拼接凭据。
184
+ * @param name - 模块配置名,例如 deploy/db/iam
185
+ * @param path - YAML 文件绝对路径
186
+ * @param values - 已加载的目标环境变量
187
+ * @returns 配置对象;解析错误已脱敏
188
+ * @example `loadDeploymentConfig('deploy', '/app/config/_deploy.yml', environment.values)`
189
+ */
190
+ declare function loadDeploymentConfig(name: string, path: string, values: Record<string, string | undefined>): HaiResult<Record<string, unknown>>;
191
+ /**
192
+ * 加载部署环境;指定环境文件缺失时返回错误并要求用户创建。
193
+ * @param options - 模式、环境目录与是否强制存在目标文件
194
+ * @param options.environment - dev/test/prod 或自定义模式
195
+ * @param options.envDir - 环境文件所在目录
196
+ * @param options.requireFile - 默认 true;扫描无显式环境时可只读取已有基础文件
197
+ * @returns 环境快照,可能包含私有值,不得直接输出
198
+ * @example `loadDeploymentEnvironment({ environment: 'prod', envDir: '.' })`
199
+ */
200
+ declare function loadDeploymentEnvironment(options: {
201
+ environment: string;
202
+ envDir: string;
203
+ requireFile?: boolean;
204
+ }): HaiResult<DeploymentEnvironment>;
205
+ /**
206
+ * 保存部署产生的变量:公共值写目标环境文件,凭据写 `.env.local`。
207
+ * 返回实际写入的变量名,调用者只展示名称或脱敏值。
208
+ * @param environment - 已加载的目标环境快照
209
+ * @param values - 需要更新的变量;未提供的项保持原状
210
+ * @param options - private 为 true 时所有值只保存在本机;适用于 SSH 地址、端口及密钥路径
211
+ * @param options.private - 是否将全部值仅保存到 .env.local
212
+ * @returns 已保存的变量名或脱敏错误
213
+ * @example `saveDeploymentEnvironment(snapshot, { HAI_DB_TYPE: 'postgresql' })`
214
+ * @example `saveDeploymentEnvironment(snapshot, { HAI_DEPLOY_PROVIDER: JSON.stringify(provider) }, { private: true })`
215
+ */
216
+ declare function saveDeploymentEnvironment(environment: DeploymentEnvironment, values: Record<string, string>, options?: {
217
+ private?: boolean;
218
+ }): HaiResult<string[]>;
219
+
161
220
  declare const HaiDeployError: {
162
221
  readonly DEPLOY_FAILED: _h_ai_core.HaiErrorDef;
163
222
  readonly PROJECT_CREATE_FAILED: _h_ai_core.HaiErrorDef;
@@ -203,14 +262,48 @@ interface DeployError {
203
262
  cause?: unknown;
204
263
  }
205
264
  /** 基础设施服务类型 */
206
- type ServiceType = 'db' | 'cache' | 'storage' | 'email' | 'sms' | 'vecdb';
265
+ type ServiceType = 'db' | 'cache' | 'storage' | 'email' | 'sms' | 'vecdb' | 'ldap';
266
+ /** 每项中间件的处理方式;keep 不修改配置,也不开通服务。 */
267
+ type MiddlewareMode = 'default' | 'custom' | 'keep';
268
+ /** 扫描环境;例如 `{ environment: 'test', envDir: '/workspace/app' }`。 */
269
+ interface DeployScanOptions {
270
+ /** 环境文件模式。 */
271
+ environment?: string;
272
+ /** 环境文件目录;默认自动定位到应用或 workspace 根。 */
273
+ envDir?: string;
274
+ }
275
+ /** 依赖对应的中间件;例如 IAM 的 LDAP 位于 `iam.ldap`,可处于 disabled 状态。 */
276
+ interface MiddlewareRequirement {
277
+ /** 中间件类别。 */
278
+ service: ServiceType;
279
+ /** 生效后端标识。 */
280
+ backend: string;
281
+ /** 检测来源(依赖包或配置名),用于解释为何需要此项。 */
282
+ source: string;
283
+ /** 模块配置名。 */
284
+ configName: string;
285
+ /** 模块配置内的路径;例如 LDAP 为 ['ldap']。 */
286
+ configPath: string[];
287
+ /** 约定变量前缀。 */
288
+ envPrefix: string;
289
+ /** 生效连接配置,可能含凭据,仅用于输入默认值,不得原样输出日志。 */
290
+ config: Record<string, unknown>;
291
+ }
207
292
  /**
208
293
  * 应用扫描结果
209
294
  *
210
295
  * 由 scanner 分析应用目录后返回,描述应用的框架类型、依赖和部署需求。
211
296
  */
212
297
  interface ScanResult {
213
- /** 应用名称(来自 package.json name) */
298
+ /** 调用者指定的部署目录,对应 lockfile 中的一个 importer。 */
299
+ appDir?: string;
300
+ /** 公共环境文件目录。 */
301
+ envDir?: string;
302
+ /** 依赖与配置共同确定的中间件详情。 */
303
+ middlewares?: MiddlewareRequirement[];
304
+ /** 从运行依赖图识别的 Hai 模块;peer 和开发依赖不启用模块。 */
305
+ haiModules?: string[];
306
+ /** 应用名称(目录名称;可用 projectName 显式覆盖部署资源名)。 */
214
307
  appName: string;
215
308
  /** 是否为 SvelteKit 项目 */
216
309
  isSvelteKit: boolean;
@@ -275,7 +368,13 @@ interface DeployRecoveryInfo {
275
368
  }
276
369
  /** 部署选项 */
277
370
  interface DeployAppOptions {
278
- /** 项目名称(默认从 package.json name 提取) */
371
+ /** 部署环境标识;显式指定时必须存在 .env.<environment>。 */
372
+ environment?: string;
373
+ /** 环境文件所在目录,默认自动定位。 */
374
+ envDir?: string;
375
+ /** 按服务或变量前缀选择处理方式;如 HAI_REACH_PROVIDERS_1 可独立于另一邮件连接。 */
376
+ serviceModes?: Partial<Record<string, MiddlewareMode>>;
377
+ /** 项目名称(默认使用明确指定的部署目录名称)。 */
279
378
  projectName?: string;
280
379
  /** 是否跳过基础设施开通 */
281
380
  skipProvision?: boolean;
@@ -338,14 +437,14 @@ interface DeployFunctions {
338
437
  *
339
438
  * @param appDir - 应用根目录路径
340
439
  */
341
- scan: (appDir: string) => Promise<HaiResult<ScanResult>>;
440
+ scan: (appDir: string, options?: DeployScanOptions) => Promise<HaiResult<ScanResult>>;
342
441
  /**
343
442
  * 对所有已配置的 Provisioner 执行基础设施开通
344
443
  *
345
444
  * @param projectName - 用于资源命名的项目名
346
445
  * @returns 所有 Provisioner 的结果列表
347
446
  */
348
- provisionAll: (projectName: string) => Promise<HaiResult<ProvisionResult[]>>;
447
+ provisionAll: (projectName: string, services?: ServiceType[]) => Promise<HaiResult<ProvisionResult[]>>;
349
448
  /**
350
449
  * 执行完整部署流程(provision → build → deploy)
351
450
  *
@@ -376,7 +475,75 @@ interface DeployFunctions {
376
475
  */
377
476
  declare const deploy: DeployFunctions;
378
477
 
478
+ /**
479
+ * @h-ai/deploy — i18n
480
+ *
481
+ * 本文件提供部署模块的 i18n 文案访问入口。
482
+ * @module deploy-i18n
483
+ */
484
+ /**
485
+ * 获取部署模块文案
486
+ *
487
+ * @param key - 文案 Key
488
+ * @param options - 插值参数与格式化选项
489
+ * @returns 本地化后的文案字符串
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * const text = deployM('deploy_notInitialized')
494
+ * ```
495
+ */
496
+ declare const deployM: (key: "deploy_notInitialized" | "deploy_initFailed" | "deploy_unsupportedProviderType" | "deploy_unsupportedProvisionerType" | "deploy_configTokenRequired" | "deploy_authRequired" | "deploy_authFailed" | "deploy_projectCreateFailed" | "deploy_buildFailed" | "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_containerRuntimeNotFound" | "deploy_containerBuildFailed" | "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", options?: _h_ai_core.MessageOptions) => string;
497
+
498
+ /**
499
+ * 部署中间件的后端与连接字段目录,供扫描、CLI 和容器规划共同使用。
500
+ * 字段名与对应模块公开 Schema 保持一致,不把模块依赖等同于必须创建外部服务。
501
+ * @module deploy-middleware
502
+ */
503
+
504
+ /** 连接字段;例如 `{ name: 'port', defaultValue: '5432', kind: 'port' }`。 */
505
+ interface MiddlewareField {
506
+ /** 模块配置字段名,camelCase 不拆词。 */
507
+ name: string;
508
+ /** 无现有配置时使用的提示默认值。 */
509
+ defaultValue?: string;
510
+ /** 是否为连接所必需;可选项允许留空。 */
511
+ required?: boolean;
512
+ /** 密码输入与私有环境文件写入标记。 */
513
+ secret?: boolean;
514
+ /** 交互校验类型;端口范围为 1–65535。 */
515
+ kind?: 'port' | 'boolean' | 'number' | 'url';
516
+ /** 数值下限;未声明时为 0。 */
517
+ minimum?: number;
518
+ /** 数值上限;例如 Redis db 为 15。 */
519
+ maximum?: number;
520
+ }
521
+ /** 后端目录项;例如 PostgreSQL 对应 `backend: 'postgresql'`。 */
522
+ interface MiddlewareBackend {
523
+ /** 模块 Schema 接受的后端标识。 */
524
+ backend: string;
525
+ /** 可直接识别的产品名称。 */
526
+ label: string;
527
+ /** 用途说明的 i18n 消息键。 */
528
+ purpose: Parameters<typeof deployM>[0];
529
+ /** 云自动开通器;缺失表示不能自动开通该云服务。 */
530
+ provisioner?: 'neon' | 'upstash' | 'cloudflare-r2' | 'resend' | 'aliyun';
531
+ /** 自定义连接需要逐项输入的字段。 */
532
+ fields: MiddlewareField[];
533
+ }
534
+ /**
535
+ * 所有可部署中间件的连接目录。
536
+ * SQLite、memory、local 与 disabled 保留本地/禁用语义,不创建无关 sidecar。
537
+ * @example `DEPLOY_MIDDLEWARE.db.map(item => item.label)` 返回数据库选择项。
538
+ */
539
+ declare const DEPLOY_MIDDLEWARE: Record<ServiceType, MiddlewareBackend[]>;
540
+ /**
541
+ * 返回当前语言的后端用途说明。
542
+ * @example `describeMiddlewareBackend(DEPLOY_MIDDLEWARE.db[0])`
543
+ */
544
+ declare function describeMiddlewareBackend(backend: MiddlewareBackend): string;
545
+
379
546
  /** 读取错误中的恢复清单;仅返回明确允许展示的字段。 */
380
547
  declare function getDeployRecovery(error: HaiError): DeployRecoveryInfo | undefined;
381
548
 
382
- export { type DeployAppOptions, type DeployConfig, type DeployConfigInput, DeployConfigSchema, type DeployCredentialOperations, type DeployError, type DeployFunctions, type DeployRecoveryInfo, type DeployResult, type DockerSshProviderConfig, HaiDeployError, type PortExposeConfig, ProviderConfigSchema, type ProvisionResult, type ScanResult, type ServiceType, type SshConfig, deploy, getDeployRecovery };
549
+ export { DEPLOY_MIDDLEWARE, type DeployAppOptions, type DeployConfig, type DeployConfigInput, DeployConfigSchema, type DeployCredentialOperations, type DeployError, type DeployFunctions, type DeployRecoveryInfo, type DeployResult, type DeployScanOptions, type DeploymentEnvironment, type DockerSshProviderConfig, HaiDeployError, type MiddlewareBackend, type MiddlewareField, type MiddlewareMode, type MiddlewareRequirement, type PortExposeConfig, ProviderConfigSchema, type ProvisionResult, type ScanResult, type ServiceType, type SshConfig, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, saveDeploymentEnvironment };