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

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
@@ -1,201 +1,145 @@
1
1
  # @h-ai/deploy
2
2
 
3
- [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
3
+ [简体中文](README.md) | [English](README.en.md) | [日本語](README.ja.md)
4
4
 
5
- [框架首页](../../README.md) · [CLI](../cli/README.md) · [环境与中间件配置](../cli/templates/skills/hai-deploy/middleware.md)
5
+ 部署 SDK,也是 `@h-ai/cli` 的 `hai deploy` 后端。**只部署已有产物,不编译源码、不构建镜像。**
6
6
 
7
- 部署 SDK,供 `@h-ai/cli` 的 `hai deploy` 命令调用,也可在 Node.js 脚本中独立使用。
7
+ 完整的创建、开发、构建与部署流程见[使用指南](../../docs/usage/README.md);下文侧重部署准备、SDK 与二次开发。
8
8
 
9
- ## 功能与范围
9
+ ## 功能
10
10
 
11
- | 部署目标 | 应用 | 执行内容 |
12
- | ------------ | --------------------------------------------------- | ----------------------------------------------------------------------- |
13
- | `docker-ssh` | 原生 Node 服务、使用 adapter-node 的 SvelteKit 应用 | 本地构建镜像,经 SSH 上传,在 Linux 上运行 Compose 并检查 HTTP 健康状态 |
14
- | `vercel` | 使用 adapter-vercel 的 SvelteKit 应用 | 构建、上传、设置环境变量、等待平台部署结果;需要互联网 |
11
+ - `docker-ssh`:读取镜像清单,选择环境和中间件,生成一个 Compose tar;上传到 Linux 后解包、导入镜像、启动并检查健康状态。
12
+ - `--bundle-only`:只生成同样的 tar 和操作说明,不连接 SSH;支持 U 盘或其他手工搬运方式。
13
+ - fullstack 的构建清单含 app/web 两个镜像时一起部署。公开端口提供 Web 和同源 API;Web 不接收 API 的私有变量,支持 hash 页面刷新。
14
+ - `vercel`:上传已有 `.vercel/output` 并设置运行变量;需要网络,按 [Build Output API](https://vercel.com/docs/build-output-api/primitives) 的 prebuilt 协议部署,不执行本地或平台源码构建。
15
+ - 只从目标 importer 的 `pnpm-lock.yaml` 遍历运行依赖(含 workspace link),排除 dev/peer,识别数据库、cache、storage、vecdb、LDAP、邮件和短信等中间件。
16
+ - 中间件逐项选择默认、自定义或保持不变;配置完整时用 `--skip-middleware` 跳过全部处理。Compose 默认支持 PostgreSQL、MySQL、Redis、RustFS/MinIO、Qdrant、pgvector、Chroma、OpenLDAP、Mailpit;云开通支持 Neon、Upstash、R2、Resend、阿里云短信。详细字段见 [环境与中间件](../cli/templates/skills/hai-deploy/middleware.md)。
15
17
 
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 需要已验证发件域名,短信需要已审核签名与模板;其他后端使用自定义连接。
18
+ ## 构建与部署
20
19
 
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 或反向代理。
20
+ 当前工程要求 Node `^22.22.2 || ^24.15.0 || >=26.0.0`、pnpm 12.11.2。Windows 用 `pnpm.cmd`;Node 20 / pnpm 9.5.0 不受支持。
22
21
 
23
- ## 命令行使用
24
-
25
- ### 1. 安装与准备
26
-
27
- 当前 Node 要求为 `^22.22.2 || ^24.15.0 || >=26.0.0`,生成工程固定 `pnpm@12.11.2`。Node 20 / pnpm 9.5.0 不受支持。Windows 使用 `pnpm.cmd`。
28
-
29
- 在生成工程根目录安装与应用框架版本匹配的 CLI 和 SDK,并保留 lockfile(此步骤需要包源或完整离线依赖):
22
+ 生成工程的 `pnpm build` 调用 `hai build`,默认只编译源码。镜像和原生安装包按目标显式构建,**不选择 dev/test/prod,不读部署文件,也不注入运行期凭据**:
30
23
 
31
24
  ```powershell
32
- pnpm.cmd add -Dw @h-ai/cli @h-ai/deploy
33
- pnpm.cmd exec hai --help
25
+ pnpm.cmd build
26
+ pnpm.cmd build --target docker
27
+ # 自动选择可用的 Podman,失败后尝试 Docker;也可显式指定
28
+ pnpm.cmd build --target docker --runtime docker
29
+ # 启用 desktop 的 fullstack;需在对应系统上安装 Rust target/系统 SDK/打包工具
30
+ pnpm.cmd build --target desktop --platform windows --arch x64
34
31
  ```
35
32
 
36
- 创建项目时选择 `--features deploy` 已包含项目本地 SDK,但不代表安装了全局 `hai`。已全局安装 CLI 时可直接运行 `hai deploy`。模板的 `pnpm run deploy` 是打包脚本,不等于执行 `hai deploy`。
33
+ `hai.build.json` 指定 Dockerfile 阶段、context 和 Tauri 目录。compile 只编译源码,docker 导出镜像,desktop 调用 Tauri,具体默认值和示例见[构建参数](../../docs/usage/README.md#build-的可选参数)。版本取应用根 package.json.version;以 1.2.3 为例,输出 `.hai/build/docker/1.2.3-<构建编号>/`,含格式版本 2 的 manifest.json(appVersion 为应用版本)、app-1.2.3.tar、可选 web-1.2.3.tar 和 SHA-256。全部成功才更新 latest。Tauri 使用相同根版本,安装包在对应工程 src-tauri/target/<Rust target>/release/bundle/。各 OS 分别准备主机/runner 与 SDK,不承诺跨系统编译。
37
34
 
38
- docker-ssh 还需要:
35
+ fullstack 在工程根执行:
39
36
 
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`。缺文件时停止,要求先创建。
37
+ ```powershell
38
+ pnpm.cmd run deploy --env test
39
+ pnpm.cmd run deploy --env prod --skip-middleware
40
+ # 精确复用一次构建,绝不重新构建
41
+ pnpm.cmd run deploy --env prod --artifact-dir .hai/build/docker/<buildId>
42
+ # 服务器网络不通,只交付包
43
+ pnpm.cmd run deploy --env prod --bundle-only --output release-prod.tar
44
+ ```
44
45
 
45
- ### 2. 配置目标与环境
46
+ 等价命令为 `pnpm exec hai deploy packages/my-app-serv --env prod --env-dir .`。API workspace 的服务目录为 `apps/my-app-service`。模板已安装项目 CLI;选择 deploy 功能时安装对应 SDK。旧工程需自行安装与框架版本匹配的 `@h-ai/cli`、`@h-ai/deploy`,并提供构建配置和 `_deploy.yml`。
46
47
 
47
- `config/_deploy.yml` 的最小 Docker 配置如下。实际主机参数从 `.env.local` 的整体 `HAI_DEPLOY_PROVIDER` 覆盖,避免每次输入,也避免提交机器信息:
48
+ 先检查 `.env.<mode>`;缺少则停止并提示创建。`.env` 公共基础、`.env.dev/test/prod` 环境覆盖和完整说明 `.env.example` 提交 Git;`.env.local` 保存私有值并忽略。优先级:进程 > `.env.local` > 环境文件 > `.env`,整体 JSON 节点优先于子字段。
48
49
 
49
50
  ```yaml
51
+ # packages/my-app-serv/config/_deploy.yml
50
52
  provider:
51
53
  type: docker-ssh
54
+ expose:
55
+ hostPort: 18080
52
56
  container:
53
- runtime: docker
54
- s3Engine: rustfs
55
- services: {}
56
- ```
57
-
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
- ```
62
-
63
- 也可让交互向导填写缺失的目标参数,保存 `.env.local` 后复用。私钥只记录路径。已有完整目标会直接复用,切换目标时修改 `HAI_DEPLOY_PROVIDER`。
64
-
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 .
57
+ s3Engine: ${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}
75
58
  ```
76
59
 
77
- 向导展示中间件当前后端与用途,再提供默认配置、自定义连接或保持不变。保持不变仅在目标环境文件明确声明完整配置时出现。默认配置在 Linux Compose 中生成服务/卷;自定义连接不生成该中间件。公共值写目标环境文件,凭据写 `.env.local`,输出脱敏。
60
+ container.s3Engine 可省略,默认 rustfs;仅自动创建 Compose S3 服务时区分 RustFS/MinIO 的镜像和桶初始化,既有自定义 S3 不受它影响。向导会保存选择,完整说明见[使用指南](../../docs/usage/README.md#先准备环境与部署目标)。跳过全部中间件处理使用 --skip-middleware,SDK 对应 skipMiddleware。
78
61
 
79
- 已有数据库、缓存、存储连接全部配置完成时:
62
+ 自动 SSH 部署首次向导填写目标,写入 `.env.local` 后复用;也可直接配置:
80
63
 
81
- ```powershell
82
- pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir . --yes --skip-middleware
64
+ ```dotenv
65
+ HAI_DEPLOY_PROVIDER='{"type":"docker-ssh","ssh":{"host":"10.0.0.20","username":"deploy","port":22,"identityFile":"C:/Users/me/.ssh/hai_deploy"},"remote":{"baseDir":"/opt/hai/apps","runtime":"docker"},"expose":{"hostPort":18080,"containerPort":3000}}'
83
66
  ```
84
67
 
85
- `--skip-middleware`(别名 `--skip-provision`)跳过中间件提示、配置改写与资源创建,仍构建并部署应用。它不会启动尚未运行的外部中间件。`--yes` 单独使用会复用完整配置,并为其余项采用当前后端默认值。
86
-
87
- `--project-name <name>` 指定名称,选定环境后名称追加环境后缀。**`--skip-build` 当前仅跳过 Vercel 的应用构建;docker-ssh 始终构建镜像,不支持直接指定已有应用镜像。** 命令失败返回非零退出码。
88
-
89
- ## docker-ssh 的文件与执行流程
68
+ 仅打包不要求 SSH 参数。本地部署工具只需要已安装依赖、`tar` 和构建产物;**不需要本地 Docker/Podman**。自动部署另外需要 `ssh/scp`、服务器指纹与非交互登录权限、可写部署目录、SSH 及应用端口可达。
90
69
 
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 `/`,通过后才返回成功。
70
+ ## tar 内容与手工启动
96
71
 
97
- 本地产物位于目标包 `.hai/deploy/<mode>/`,应忽略 Git;远程 `.env.runtime` 权限为 `600`。保留本地产物用于重复部署;“保持不变”保留本地上一份 Compose 中已有的服务/卷定义,不会自动读取服务器上任意现有 Compose。私有环境文件与部署归档不得复制进镜像。
72
+ 部署 tar 默认名为 bundle-<项目>-<部署ID>-<应用版本>.tar,自定义 --output release-prod.tar 也会自动得到 release-prod-1.2.3.tar(示例版本 1.2.3)。包含 docker-compose-1.2.3.yml、manifest.json、.env.runtime、可选 web.conf、镜像归档、SHA256SUMS、start-<mode>.sh 和 README.txt。它含私有凭据,权限 600,不提交 Git。packaged 不代表服务器已运行;自动部署上传一个 tar,在远程解包启动,不传源码或构建镜像。
98
73
 
99
- ## 完全离线部署
74
+ 将包搬到 Linux 后,按包内 README 操作:
100
75
 
101
- 离线指不能访问互联网,但 Windows 与 Linux 仍可通过内网 SSH/HTTP 通信。Vercel 和云资源开通不适用。
102
-
103
- ### 提前带入的材料
104
-
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` 指应用容器自身 |
112
-
113
- 默认 Compose 使用 `postgres:16-alpine`、`redis:7-alpine`、`ghcr.io/rustfs/rustfs:1.0.1`。已有其他 tag 不等于命中这些镜像;先核对并导入/正确标记已验证兼容的镜像,或使用自定义连接。中间件镜像不会由 SDK 自动打包上传。其他固定镜像见 [Compose 实现](src/container/deploy-container-compose.ts)。
114
-
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` 仅解决包存储读取,不覆盖安装脚本的额外下载。
116
-
117
- SQLite 预编译包须匹配 better-sqlite3 版本、Node ABI、CPU 架构和 libc。即使运行期选 PostgreSQL,当前依赖安装仍可能需要该包。可预置 Linux 构建环境的 prebuild-install 缓存,或通过 `container.sqlitePrebuildMirror` 指向内网镜像;两者都没有时需提前带入二进制或成品镜像。默认 fullstack 不需要 CUDA。
118
-
119
- ### 使用预构建镜像离线执行 SDK
120
-
121
- 较易交付的方式是提前构建镜像,现场让 SDK 复用它。以下以工程名 `my-app`、Web 前端和与服务器一致的构建架构为前提;在可提供依赖的材料准备环境执行,不是在离线现场下载:
122
-
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
- ```
128
-
129
- 把归档带入离线 Windows 的本地 Linux 引擎,执行 `docker load -i my-app-images.tar`。在 `packages/my-app-serv/Dockerfile` 写入下列内容,使 SDK 的必经构建只复用成品镜像,无包安装步骤;业务代码变化后须重制成品镜像:
130
-
131
- ```dockerfile
132
- FROM my-app-api:offline
76
+ ```sh
77
+ mkdir -p release && chmod 700 release
78
+ tar -xf release-prod-1.2.3.tar -C release
79
+ cd release
80
+ HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME=docker sh start-prod.sh
133
81
  ```
134
82
 
135
- 为服务包添加 `.dockerignore`(内容 `*`),限制此专用构建上下文。配置部署目标后执行上面的 `hai deploy`:需 SDK 创建中间件容器时,先在 `.env.prod` 选择 `HAI_DB_TYPE=postgresql`、`HAI_CACHE_TYPE=redis`、`HAI_STORAGE_TYPE=s3`,再走向导默认配置;已有服务则填写自定义连接,配置完整后可用 `--skip-middleware`。API 镜像由 SDK 上传;提前在断外网条件下演练,确认构建器不会尝试联网。
83
+ 启动脚本自动 -f 选择带版本的 Compose,检查归档和镜像、导入 app/web、导出环境标识后启动。缺中间件镜像时停止,不联网下载。卷按项目和环境隔离,跨版本重复部署保留数据。手工检查使用 `docker compose -f docker-compose-1.2.3.yml --env-file .env.runtime ps`(或 logs),并验证 Web、/health。生产 HTTPS、CORS、DNS 与证书须另行配置。
136
84
 
137
- Web 镜像需另外传输到 Linux 并运行。以下假设已另行准备 `https://app.hai.internal` 与 `https://api.hai.internal` 的内网 TLS 入口,分别代理 Web 的 18081 端口与 API 的 18080 端口;需带入代理配置/镜像、证书,并配置内网 DNS/hosts 和客户端信任。SSH 登录后执行:
85
+ ## 完全离线准备(Windows → Linux)
138
86
 
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
- ```
87
+ 已有 PostgreSQL、Redis、RustFS 镜像还不够,准备以下内容:
143
88
 
144
- API 地址须能被用户浏览器访问,API 设置 `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`。验收 API `/health`、Web 登录/业务请求、hash 页面刷新、会话刷新、服务重启及持久化。这里是离线材料与适配方式说明,联网部署测试不能替代实际断网验收。
89
+ 1. Windows 上受支持的 Node/pnpm、完整项目、锁文件和已安装 CLI/SDK 依赖;验证断网后命令可启动。本机无容器引擎时,从构建机复制完整 `.hai/build/docker/<buildId>`。
90
+ 2. 在构建机预先执行 `pnpm build --target docker`,确认镜像架构与服务器一致。离线部署只消费这些 tar,不需要 Node 基础镜像、pnpm store、CUDA、编译器或 SQLite 下载。无 AI 运行依赖的 fullstack 不安装 AI/CUDA。
91
+ 3. 若连构建也要离线,则构建机另需 Linux 容器引擎、所有基础镜像、pnpm/Corepack、完整 Linux 依赖缓存、SQLite 对应驱动版本/Node ABI/架构/libc 的预编译包。Windows node_modules 不能替代 Linux 原生依赖。镜像构建可用 `--sqlite-prebuild-mirror http://<可信地址>/better-sqlite3`;下载失败应修复缓存/网络,不在 deploy 安装编译环境。
92
+ 4. Linux 上安装对应架构 Docker/Podman、可执行的 Compose 命令、`sh`、`tar`、`sha256sum`;运行用户有容器/目录/卷权限。中间件镜像**名称和标签**须与生成 README 一致,先用 `docker load` 导入;SDK 不自动捆绑中间件镜像。自定义连接则要准备已运行的服务、账号、数据库/桶、容器可达地址。
93
+ 5. `.env.prod`、私有管理员密码/连接密钥、部署公开端口、持久化磁盘和必要证书。切勿把容器内 localhost 当服务器宿主机。
94
+ 6. 无任何两机网络时,用 `--bundle-only`,手工复制包;有内网但无互联网时也可 SSH 自动部署。云 Provider/Provisioner 不适用于完全离线,应选 Compose 或自定义已有服务。
145
95
 
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/)。
96
+ 离线验收须实际断开外网后打包/搬运/启动、登录、页面刷新、重启和数据持久化;联网通过不等于断网通过。
147
97
 
148
- ## SDK 与错误处理
98
+ ## Node API
149
99
 
150
- 脚本调用无需 CLI。`config` 是已解析的部署配置;需要相同环境解析时使用公开的 `loadDeploymentEnvironment` / `loadDeploymentConfig`。最小生命周期:
151
-
152
- ```typescript
153
- import type { DeployConfigInput } from '@h-ai/deploy'
100
+ ```ts
154
101
  import { deploy } from '@h-ai/deploy'
155
102
 
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()
175
- }
103
+ const initialized = await deploy.init({
104
+ provider: { type: 'docker-ssh', expose: { hostPort: 18080 } },
105
+ })
106
+ if (!initialized.success)
107
+ throw new Error(initialized.error.message)
108
+ try {
109
+ const result = await deploy.deployApp('./packages/my-app-serv', {
110
+ environment: 'prod',
111
+ envDir: '.',
112
+ bundleOnly: true,
113
+ artifactDir: './.hai/build/docker/<buildId>',
114
+ skipMiddleware: true,
115
+ })
116
+ if (!result.success)
117
+ throw new Error(result.error.message)
118
+ // result.data.bundlePath / instructions;packaged 不代表服务器 ready。
119
+ }
120
+ finally {
121
+ await deploy.close()
176
122
  }
177
123
  ```
178
124
 
179
- 调用者先判断 `result.success`,失败读取 `result.error.code/message`,成功读取 `result.data.url/healthCheck`。`deploy.scan()` 与 `deploy.credentials.*()` 不依赖初始化;`deployApp()` 已包含开通流程,无须预先 `provisionAll()`。手动开通同样检查结果。SDK 的 `skipProvision` 与 CLI 跳过中间件语义一致。
180
-
181
- 云资源返回 `HAI_DB`、`HAI_CACHE`、`HAI_STORAGE` 或 `HAI_REACH_PROVIDERS` 整体配置;应用须加载对应 YAML 并初始化模块。失败时 `getDeployRecovery(result.error)` 返回阶段及资源 ID;`unknown` 表示结果不确定,应先在平台核对,同名重试复用资源,不自动删除。完整类型、配置及错误码见 [公开入口](src/index.ts)。
182
-
183
- ## 测试与二次开发
125
+ `deploy.scan` 与 `deploy.credentials` 不依赖 init;业务 API 返回 HaiResult,先检查 success。资源开通失败保留恢复信息,不自动删除已创建资源。
184
126
 
185
- 框架仓库根目录安装依赖后执行(Windows 使用 `pnpm.cmd`):
127
+ ## 二次开发与验证
186
128
 
187
129
  ```powershell
188
130
  pnpm.cmd --filter @h-ai/deploy typecheck
189
131
  pnpm.cmd --filter @h-ai/deploy lint
190
132
  pnpm.cmd --filter @h-ai/deploy build
191
133
  pnpm.cmd --filter @h-ai/deploy test
192
- pnpm.cmd check:skills
134
+ pnpm.cmd --filter @h-ai/cli test
135
+ pnpm.cmd --filter @h-ai/cli test:scaffold-gates
136
+ pnpm.cmd --filter @h-ai/cli test:deploy-gates
193
137
  ```
194
138
 
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)。
139
+ 最后一项使用当前源码打包生成 fullstack,显式构建一次 API/Web,再验证 SSH 上传和手工 tar 启动、真实 Web 登录/刷新。需要运行的 Podman/Docker 和 OpenSSH。测试用 finally/teardown 清理临时工程、镜像、容器、报告、截图和辅助进程,失败同样清理;验收材料不提交 Git。原生安装包需在相应系统另做真实构建及安装验收。
196
140
 
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/`。
141
+ 部署门禁默认不传递本机代理。需要代理下载构建依赖时,设置测试专用 `HAI_DEPLOY_E2E_CONTAINER_PROXY`(默认不设置),例如 Windows Podman 在 PowerShell 中设置 `$env:HAI_DEPLOY_E2E_CONTAINER_PROXY='http://host.containers.internal:10808'`;地址必须能从构建容器访问,不写入应用配置或交付镜像。
198
142
 
199
- ## License
143
+ 测试专用 `HAI_DEPLOY_E2E_CONTAINER_NETWORK` 默认不设置,指定后传给 build 的 `--network`;例如 rootless Podman 可设置 `$env:HAI_DEPLOY_E2E_CONTAINER_NETWORK='pasta'`。构建与运行的网络可能不同,应同时校对代理在构建网络中的地址。
200
144
 
201
- Apache-2.0
145
+ Windows runner 可设置测试开关 HAI_CLI_NATIVE_BUILD=1(默认不设置),再执行 `pnpm --filter @h-ai/cli test:scaffold-gates -t "fullstack scaffold"`,额外构建生成项目的 Tauri 安装包,检查根版本 2.4.6 进入 exe 文件名。需已安装 Rust/MSVC/Windows SDK;测试的原生输出也随临时工程自动清理。
package/dist/index.d.ts CHANGED
@@ -2,6 +2,35 @@ import { z } from 'zod';
2
2
  import * as _h_ai_core from '@h-ai/core';
3
3
  import { HaiResult, HaiError } from '@h-ai/core';
4
4
 
5
+ /** 镜像制品清单,例如 `{ version: 2, appVersion: '1.2.3', buildId: '1.2.3-build1', images: [{ name: 'app', file: 'app-1.2.3.tar', imageRef: 'localhost/demo:1.2.3-build1', sha256: '...' }] }`。 */
6
+ declare const DeploymentArtifactSchema: z.ZodObject<{
7
+ version: z.ZodLiteral<2>;
8
+ appVersion: z.ZodString;
9
+ buildId: z.ZodString;
10
+ images: z.ZodArray<z.ZodObject<{
11
+ name: z.ZodEnum<{
12
+ app: "app";
13
+ web: "web";
14
+ }>;
15
+ file: z.ZodString;
16
+ imageRef: z.ZodString;
17
+ sha256: z.ZodString;
18
+ containerPort: z.ZodOptional<z.ZodNumber>;
19
+ }, z.core.$strip>>;
20
+ }, z.core.$strip>;
21
+ /** 构建器与部署器共享的镜像交付结构;参见 DeploymentArtifactSchema 示例。 */
22
+ type DeploymentArtifact = z.infer<typeof DeploymentArtifactSchema>;
23
+ /**
24
+ * 流式计算制品校验和,避免将大镜像一次性读入内存。
25
+ * @example `const checksum = await artifactChecksum("/release/app-1.2.3.tar")`
26
+ */
27
+ declare function artifactChecksum(file: string): Promise<string>;
28
+ /**
29
+ * 校验完整构建产物;缺失、损坏或非法清单均抛错,由部署边界转换为 HaiResult。
30
+ * @example `const artifact = await readDeploymentArtifact("/release/build-1")`
31
+ */
32
+ declare function readDeploymentArtifact(directory: string): Promise<DeploymentArtifact>;
33
+
5
34
  /**
6
35
  * @h-ai/deploy — 部署配置 Schema
7
36
  *
@@ -28,7 +57,7 @@ declare const PortExposeSchema: z.ZodObject<{
28
57
  /** docker-ssh 部署平台配置 */
29
58
  declare const DockerSshProviderConfigSchema: z.ZodObject<{
30
59
  type: z.ZodLiteral<"docker-ssh">;
31
- ssh: z.ZodObject<{
60
+ ssh: z.ZodOptional<z.ZodObject<{
32
61
  host: z.ZodString;
33
62
  port: z.ZodDefault<z.ZodNumber>;
34
63
  username: z.ZodString;
@@ -36,7 +65,7 @@ declare const DockerSshProviderConfigSchema: z.ZodObject<{
36
65
  knownHostsFile: z.ZodOptional<z.ZodString>;
37
66
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
38
67
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
39
- }, z.core.$strip>;
68
+ }, z.core.$strip>>;
40
69
  remote: z.ZodDefault<z.ZodObject<{
41
70
  baseDir: z.ZodDefault<z.ZodString>;
42
71
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -57,7 +86,7 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
57
86
  token: z.ZodString;
58
87
  }, z.core.$strip>, z.ZodObject<{
59
88
  type: z.ZodLiteral<"docker-ssh">;
60
- ssh: z.ZodObject<{
89
+ ssh: z.ZodOptional<z.ZodObject<{
61
90
  host: z.ZodString;
62
91
  port: z.ZodDefault<z.ZodNumber>;
63
92
  username: z.ZodString;
@@ -65,7 +94,7 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
65
94
  knownHostsFile: z.ZodOptional<z.ZodString>;
66
95
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
67
96
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
68
- }, z.core.$strip>;
97
+ }, z.core.$strip>>;
69
98
  remote: z.ZodDefault<z.ZodObject<{
70
99
  baseDir: z.ZodDefault<z.ZodString>;
71
100
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -94,7 +123,7 @@ declare const DeployConfigSchema: z.ZodObject<{
94
123
  token: z.ZodString;
95
124
  }, z.core.$strip>, z.ZodObject<{
96
125
  type: z.ZodLiteral<"docker-ssh">;
97
- ssh: z.ZodObject<{
126
+ ssh: z.ZodOptional<z.ZodObject<{
98
127
  host: z.ZodString;
99
128
  port: z.ZodDefault<z.ZodNumber>;
100
129
  username: z.ZodString;
@@ -102,7 +131,7 @@ declare const DeployConfigSchema: z.ZodObject<{
102
131
  knownHostsFile: z.ZodOptional<z.ZodString>;
103
132
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
104
133
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
105
- }, z.core.$strip>;
134
+ }, z.core.$strip>>;
106
135
  remote: z.ZodDefault<z.ZodObject<{
107
136
  baseDir: z.ZodDefault<z.ZodString>;
108
137
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -147,17 +176,10 @@ declare const DeployConfigSchema: z.ZodObject<{
147
176
  }, z.core.$strip>], "provisioner">>;
148
177
  }, z.core.$strip>>;
149
178
  container: z.ZodOptional<z.ZodObject<{
150
- runtime: z.ZodDefault<z.ZodEnum<{
151
- auto: "auto";
152
- docker: "docker";
153
- podman: "podman";
154
- }>>;
155
- httpProxy: z.ZodDefault<z.ZodBoolean>;
156
179
  s3Engine: z.ZodDefault<z.ZodEnum<{
157
180
  rustfs: "rustfs";
158
181
  minio: "minio";
159
182
  }>>;
160
- sqlitePrebuildMirror: z.ZodOptional<z.ZodURL>;
161
183
  }, z.core.$strip>>;
162
184
  }, z.core.$strip>;
163
185
  /** 部署配置类型(parse 后) */
@@ -220,7 +242,6 @@ declare function saveDeploymentEnvironment(environment: DeploymentEnvironment, v
220
242
  declare const HaiDeployError: {
221
243
  readonly DEPLOY_FAILED: _h_ai_core.HaiErrorDef;
222
244
  readonly PROJECT_CREATE_FAILED: _h_ai_core.HaiErrorDef;
223
- readonly BUILD_FAILED: _h_ai_core.HaiErrorDef;
224
245
  readonly UPLOAD_FAILED: _h_ai_core.HaiErrorDef;
225
246
  readonly AUTH_REQUIRED: _h_ai_core.HaiErrorDef;
226
247
  readonly AUTH_FAILED: _h_ai_core.HaiErrorDef;
@@ -232,8 +253,6 @@ declare const HaiDeployError: {
232
253
  readonly UNSUPPORTED_TYPE: _h_ai_core.HaiErrorDef;
233
254
  readonly CONFIG_ERROR: _h_ai_core.HaiErrorDef;
234
255
  readonly CREDENTIAL_ERROR: _h_ai_core.HaiErrorDef;
235
- readonly CONTAINER_RUNTIME_NOT_FOUND: _h_ai_core.HaiErrorDef;
236
- readonly CONTAINER_BUILD_FAILED: _h_ai_core.HaiErrorDef;
237
256
  readonly REMOTE_CONNECT_FAILED: _h_ai_core.HaiErrorDef;
238
257
  readonly REMOTE_COMMAND_FAILED: _h_ai_core.HaiErrorDef;
239
258
  readonly IMAGE_TRANSFER_FAILED: _h_ai_core.HaiErrorDef;
@@ -315,8 +334,6 @@ interface ScanResult {
315
334
  requiredServices: ServiceType[];
316
335
  /** 各服务检测到的后端类型(如 db→postgresql/mysql,vecdb→qdrant),用于生成匹配的 sidecar */
317
336
  serviceBackends: Partial<Record<ServiceType, string>>;
318
- /** 构建命令 */
319
- buildCommand: string;
320
337
  }
321
338
  /**
322
339
  * 部署结果
@@ -329,7 +346,11 @@ interface DeployResult {
329
346
  /** 部署 ID */
330
347
  deploymentId: string;
331
348
  /** 部署状态 */
332
- status: 'ready' | 'building' | 'error';
349
+ status: 'ready' | 'packaged' | 'building' | 'error';
350
+ /** Compose 部署 tar 的绝对路径;包含私有配置,应安全传输。 */
351
+ bundlePath?: string;
352
+ /** 手工搬运和启动说明;仅打包不代表服务器已运行。 */
353
+ instructions?: string;
333
354
  /** 已设置的环境变量名列表 */
334
355
  envVarsSet: string[];
335
356
  /** 部署 Provider 名称(如 vercel / docker-ssh),容器部署时返回 */
@@ -376,10 +397,14 @@ interface DeployAppOptions {
376
397
  serviceModes?: Partial<Record<string, MiddlewareMode>>;
377
398
  /** 项目名称(默认使用明确指定的部署目录名称)。 */
378
399
  projectName?: string;
379
- /** 是否跳过基础设施开通 */
380
- skipProvision?: boolean;
381
- /** 是否跳过构建 */
382
- skipBuild?: boolean;
400
+ /** 是否跳过全部中间件配置与开通,默认 false;例如配置完备时设为 true。 */
401
+ skipMiddleware?: boolean;
402
+ /** 已构建镜像目录;默认最近构建入口/锁文件目录下的 .hai/build/docker。 */
403
+ artifactDir?: string;
404
+ /** 只生成部署 tar,不连接 SSH;仅 docker-ssh 支持。 */
405
+ bundleOnly?: boolean;
406
+ /** 部署 tar 输出路径;默认 .hai/deploy/<环境>/bundle-<项目>-<部署ID>-<版本>.tar,自定义文件名自动补 -<版本>。 */
407
+ output?: string;
383
408
  }
384
409
  /**
385
410
  * Deploy 凭证文件操作。
@@ -446,7 +471,7 @@ interface DeployFunctions {
446
471
  */
447
472
  provisionAll: (projectName: string, services?: ServiceType[]) => Promise<HaiResult<ProvisionResult[]>>;
448
473
  /**
449
- * 执行完整部署流程(provision → build → deploy)
474
+ * 部署已有构建产物(环境和中间件 → 打包 → 上传/启动);不会调用构建命令。
450
475
  *
451
476
  * @param appDir - 应用根目录路径
452
477
  * @param options - 部署选项
@@ -493,7 +518,7 @@ declare const deploy: DeployFunctions;
493
518
  * const text = deployM('deploy_notInitialized')
494
519
  * ```
495
520
  */
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;
521
+ 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_artifactInvalid" | "deploy_sshRequired" | "deploy_bundleInstructions" | "deploy_bundleMetadata" | "deploy_bundleOnlyContainer", options?: _h_ai_core.MessageOptions) => string;
497
522
 
498
523
  /**
499
524
  * 部署中间件的后端与连接字段目录,供扫描、CLI 和容器规划共同使用。
@@ -546,4 +571,4 @@ declare function describeMiddlewareBackend(backend: MiddlewareBackend): string;
546
571
  /** 读取错误中的恢复清单;仅返回明确允许展示的字段。 */
547
572
  declare function getDeployRecovery(error: HaiError): DeployRecoveryInfo | undefined;
548
573
 
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 };
574
+ export { DEPLOY_MIDDLEWARE, type DeployAppOptions, 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, deploy, describeMiddlewareBackend, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, readDeploymentArtifact, saveDeploymentEnvironment };