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

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,168 @@
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
+ ## 简介
6
6
 
7
- 部署 SDK,供 `@h-ai/cli` 的 `hai deploy` 命令调用,也可在 Node.js 脚本中独立使用。
7
+ 部署已有镜像与 Vercel prebuilt 产物。支持完整离线交付、选择服务更新、SSH 自动发布和跨平台人工部署脚本。SDK 不构建源码,CLI 的 --build 明确编排先构建后发布。
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
+ - 一条命令完成构建、打包、SSH 上传、启动和访问检查。
12
+ - 完整包包含应用与自建中间件镜像;支持只更新选中的服务。
13
+ - 离线交付生成 tar 和 Windows/Linux/macOS 操作脚本,记录步骤结果并在失败时尝试恢复。
15
14
 
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 需要已验证发件域名,短信需要已审核签名与模板;其他后端使用自定义连接。
15
+ ## 使用入门
20
16
 
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 或反向代理。
17
+ ### 准备构建产物
22
18
 
23
- ## 命令行使用
19
+ 创建项目默认安装部署工具,可用 `hai create my-app --no-deploy` 关闭。部署 SDK 属于开发依赖,不进入应用运行镜像。
24
20
 
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(此步骤需要包源或完整离线依赖):
21
+ ```sh
22
+ pnpm build
23
+ pnpm build --target docker
24
+ pnpm build --target docker --images web
25
+ pnpm build --target desktop --platform windows --arch x64
26
+ ```
30
27
 
31
- ```powershell
32
- pnpm.cmd add -Dw @h-ai/cli @h-ai/deploy
33
- pnpm.cmd exec hai --help
28
+ 源码编译、镜像构建、桌面打包分别对应 compile/docker/desktop。构建不选择部署环境、不注入运行期密钥。原生安装包在对应操作系统准备 SDK 与 Rust 工具链后构建。
29
+
30
+ #### build 的可选参数
31
+
32
+ | 参数 | 用途 |
33
+ | -------------------------- | --------------------------------------- |
34
+ | `--target` | compile(默认)、docker、desktop |
35
+ | `--images` | app、web 或 app,web;只构建导出所选镜像 |
36
+ | `--runtime` | auto(优先 Podman)、podman、docker |
37
+ | `--network` | 容器构建网络,默认引擎设置 |
38
+ | `--no-http-proxy` | Podman 构建不继承本机代理 |
39
+ | `--sqlite-prebuild-mirror` | SQLite 预编译包的可信 HTTP(S) 根地址 |
40
+ | `--platform` / `--arch` | 原生安装包的系统与架构 |
41
+
42
+ #### 用 hai.build.json 描述要打什么包
43
+
44
+ ```json
45
+ {
46
+ "images": [
47
+ { "name": "app", "context": ".", "dockerfile": "images/app.Dockerfile", "containerPort": 3000 },
48
+ { "name": "web", "context": ".", "dockerfile": "images/web.Dockerfile", "containerPort": 8080 }
49
+ ],
50
+ "desktop": ["apps/demo-desktop"]
51
+ }
34
52
  ```
35
53
 
36
- 创建项目时选择 `--features deploy` 已包含项目本地 SDK,但不代表安装了全局 `hai`。已全局安装 CLI 时可直接运行 `hai deploy`。模板的 `pnpm run deploy` 是打包脚本,不等于执行 `hai deploy`。
54
+ 每个镜像独立 Dockerfile;web 的 Nginx 配置与启动资源放 `images/web/`。Dockerfile 内保留编译/运行阶段,配置不再选择阶段。没有 Web 或 desktop 时删除相应项。
37
55
 
38
- docker-ssh 还需要:
56
+ 构建器将锁文件、包配置、本地归档和补丁准备为 `images/dependencies/` 输入,结合 pnpm store 缓存与 Docker 分层复用下载。源码修改不应重新下载全部依赖;缓存为空仍能联网构建。Windows node_modules 不用于 Linux 镜像。完全离线构建还需预先准备基础镜像、工具链和匹配 ABI/架构/libc 的原生预编译包。
39
57
 
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`。缺文件时停止,要求先创建。
58
+ 版本来自应用根 package.json.version。制品位于 `.hai/build/docker/<buildId>/`,包括格式版本 3 的 manifest.json、所选镜像 tar 和 SHA-256;partial 标识部分构建,hasWeb 保留工程拓扑。清单还会记住配置中填写的 app/web 容器端口(appContainerPort、webContainerPort),只打包一个镜像也不会丢掉另一个的端口;没填写则沿用部署默认值。相同应用版本允许重建,构建编号和镜像引用区分实际内容;全部导出成功才更新本机构建入口。基础镜像、中间件及工具使用明确版本,不使用 latest。
44
59
 
45
- ### 2. 配置目标与环境
60
+ ### 部署到环境
46
61
 
47
- `config/_deploy.yml` 的最小 Docker 配置如下。实际主机参数从 `.env.local` 的整体 `HAI_DEPLOY_PROVIDER` 覆盖,避免每次输入,也避免提交机器信息:
62
+ #### 先准备环境与部署目标
63
+
64
+ 准备 `.env.<环境>`,例如 `.env.prod`,以及服务目录的 `config/_deploy.yml`。fullstack 的根 deploy 脚本指向 `packages/<项目>-serv`,API workspace 指向 `apps/<项目>-service`;这是配置扫描入口,镜像清单仍在工程根。
48
65
 
49
66
  ```yaml
50
67
  provider:
51
68
  type: docker-ssh
69
+ expose:
70
+ hostPort: 18080
52
71
  container:
53
- runtime: docker
54
72
  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 .
75
73
  ```
76
74
 
77
- 向导展示中间件当前后端与用途,再提供默认配置、自定义连接或保持不变。保持不变仅在目标环境文件明确声明完整配置时出现。默认配置在 Linux Compose 中生成服务/卷;自定义连接不生成该中间件。公共值写目标环境文件,凭据写 `.env.local`,输出脱敏。
78
-
79
- 已有数据库、缓存、存储连接全部配置完成时:
80
-
81
- ```powershell
82
- pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir . --yes --skip-middleware
83
- ```
75
+ 向导按“服务器地址、SSH 登录用户、私钥文件、访问端口”等说明提问,复用有效配置。SSH 参数仅写本机 `.env.local`;私钥只保存路径。仅打包不要求 SSH。
84
76
 
85
- `--skip-middleware`(别名 `--skip-provision`)跳过中间件提示、配置改写与资源创建,仍构建并部署应用。它不会启动尚未运行的外部中间件。`--yes` 单独使用会复用完整配置,并为其余项采用当前后端默认值。
77
+ #### 按应用真正需要的中间件处理
86
78
 
87
- `--project-name <name>` 指定名称,选定环境后名称追加环境后缀。**`--skip-build` 当前仅跳过 Vercel 的应用构建;docker-ssh 始终构建镜像,不支持直接指定已有应用镜像。** 命令失败返回非零退出码。
79
+ 根据锁文件运行依赖和模块配置识别数据库、缓存、文件存储、向量库、LDAP、邮件等。逐项选择默认、自定义或保持。配置完备可用 `--skip-middleware` 跳过向导和开通。SQLite、Memory cache、本地存储不产生外部镜像;Compose 自建服务的镜像自动加入完整交付包。外部服务仍需目标网络可达,云开通不属于完全离线部署。
88
80
 
89
- ## docker-ssh 的文件与执行流程
81
+ #### deploy 的可选参数
90
82
 
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 `/`,通过后才返回成功。
83
+ | 参数 | 用途 |
84
+ | --------------------- | -------------------------------------------------------- |
85
+ | `--env` / `--env-dir` | 环境和环境文件目录 |
86
+ | `--build` | 先构建选中应用镜像,再打包部署;可结合 bundle-only |
87
+ | `--services` | 只更新指定 Compose 服务,例如 web、app、app,web、storage |
88
+ | `--artifact-dir` | 精确选择已构建制品 |
89
+ | `--bundle-only` | 只生成 tar 和跨平台脚本,不连接服务器 |
90
+ | `--runtime` | 本地构建及中间件镜像导出引擎 |
91
+ | `--output` | 部署 tar 路径,自动补应用版本后缀 |
92
+ | `--skip-middleware` | 跳过中间件配置与开通 |
93
+ | `--yes` | 无交互使用有效配置;缺必要值明确报错 |
96
94
 
97
- 本地产物位于目标包 `.hai/deploy/<mode>/`,应忽略 Git;远程 `.env.runtime` 权限为 `600`。保留本地产物用于重复部署;“保持不变”保留本地上一份 Compose 中已有的服务/卷定义,不会自动读取服务器上任意现有 Compose。私有环境文件与部署归档不得复制进镜像。
95
+ #### 两机连通:直接部署
98
96
 
99
- ## 完全离线部署
100
-
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
97
+ ```sh
98
+ pnpm run deploy --env prod --build
99
+ pnpm run deploy --env prod --artifact-dir .hai/build/docker/<buildId>
100
+ pnpm run deploy --env prod --build --services web
127
101
  ```
128
102
 
129
- 把归档带入离线 Windows 的本地 Linux 引擎,执行 `docker load -i my-app-images.tar`。在 `packages/my-app-serv/Dockerfile` 写入下列内容,使 SDK 的必经构建只复用成品镜像,无包安装步骤;业务代码变化后须重制成品镜像:
103
+ 部分更新需要服务器已有成功的完整安装,并且端口、应用布局相同。以服务器成功状态为基线,只替换选中的镜像和配置,保留其他服务、连接与卷。部署脚本先按项目和服务名移除选中的旧容器(保留数据卷),再执行 `compose up -d --no-deps --no-build --pull never --no-recreate web` 创建新容器;其他容器保持运行,不执行整个项目 down。首次安装使用完整包。storage-init 是创建 S3 桶的一次性任务,只在涉及存储初始化时执行,成功后再启动应用。
130
104
 
131
- ```dockerfile
132
- FROM my-app-api:offline
133
- ```
105
+ 命令中的 `-d` 表示后台启动,`--no-deps` 不连带启动或重启依赖,`--no-build` 不在服务器编译,`--pull never` 禁止拉取镜像,`--no-recreate` 保留仍存在的容器;选中的旧容器已由脚本移除,因此会创建新容器,末尾 `web` 是 Compose 服务名(不是镜像标签)。多个服务用空格分隔。
134
106
 
135
- 为服务包添加 `.dockerignore`(内容 `*`),限制此专用构建上下文。配置部署目标后执行上面的 `hai deploy`:需 SDK 创建中间件容器时,先在 `.env.prod` 选择 `HAI_DB_TYPE=postgresql`、`HAI_CACHE_TYPE=redis`、`HAI_STORAGE_TYPE=s3`,再走向导默认配置;已有服务则填写自定义连接,配置完整后可用 `--skip-middleware`。API 镜像由 SDK 上传;提前在断外网条件下演练,确认构建器不会尝试联网。
107
+ `storage-init` 复用应用镜像内的 S3 SDK,等待存储服务就绪并检查桶;桶存在就跳过,不删除数据。脚本先启动 storage,再执行 `compose up --no-deps --no-build --pull never --force-recreate --exit-code-from storage-init storage-init`;初始化退出码为 0 后才启动选中应用。 部分更新时,脚本先移除旧的 storage-init 容器,并将该命令的 `--force-recreate` 改为 `--no-recreate`,避免连带重建应用。只更新 `storage` 时,初始化仍需包含 `app` 镜像的制品;若最近只构建了 Web,用 `--artifact-dir` 选择原完整制品,或先执行 `pnpm build --target docker --images app`。该镜像用于初始化,不会更新正在运行的 `app` 服务。
136
108
 
137
- Web 镜像需另外传输到 Linux 并运行。以下假设已另行准备 `https://app.hai.internal` 与 `https://api.hai.internal` 的内网 TLS 入口,分别代理 Web 的 18081 端口与 API 的 18080 端口;需带入代理配置/镜像、证书,并配置内网 DNS/hosts 和客户端信任。SSH 登录后执行:
109
+ #### 两机不通:生成包,手工复制
138
110
 
139
111
  ```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
112
+ pnpm run deploy --env prod --build --bundle-only --output release-prod.tar
142
113
  ```
143
114
 
144
- API 地址须能被用户浏览器访问,API 设置 `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`。验收 API `/health`、Web 登录/业务请求、hash 页面刷新、会话刷新、服务重启及持久化。这里是离线材料与适配方式说明,联网部署测试不能替代实际断网验收。
115
+ 以应用版本 1.2.3 为例,得到 `release-prod-1.2.3.tar` 和同名 `-deploy/` 操作目录。包包含所选应用/中间件镜像、服务配置、私有环境、校验和、README 和 apply.sh。Windows 运行操作目录的 deploy.ps1,Linux/macOS 运行 deploy.sh;脚本复用目标配置,通过 SSH 上传后执行同一服务器流程。Windows 可在 deploy-target.json 修改目标信息,或给 deploy.ps1 传入 -IdentityFile;Linux/macOS 可用 sh deploy.sh 主机 [端口] [用户] 覆盖连接地址,用 HAI_DEPLOY_IDENTITY_FILE 指定私钥、HAI_DEPLOY_KNOWN_HOSTS_FILE 指定服务器指纹文件。换电脑或操作系统后,这两个文件路径都要指向当前电脑上的实际文件;Windows 在 deploy-target.json 中修改。
145
116
 
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/)。
117
+ 完全隔离的 Linux 服务器上,将 tar 解到独立目录,运行 `sh apply.sh`,也可传入部署根目录覆盖默认值。每步检查结果,任何失败返回非零退出码;镜像导入、服务启动和 HTTP 检查分别报告。结果写到服务器部署目录 `result-<部署ID>.json`,只有检查通过才更新 current 成功状态。重复应用同一部署包会明确停止;相同版本重新生成的新包允许发布。已有版本失败时尝试恢复选中的旧容器和配置,保留数据卷及诊断材料;不会回退已写入的业务数据。
147
118
 
148
- ## SDK 与错误处理
119
+ #### 完全离线前再核对一次
149
120
 
150
- 脚本调用无需 CLI。`config` 是已解析的部署配置;需要相同环境解析时使用公开的 `loadDeploymentEnvironment` / `loadDeploymentConfig`。最小生命周期:
121
+ - 完整 tar 已含自建中间件归档,镜像架构与服务器一致;服务器启动不访问 registry。
122
+ - Linux 已安装 Docker/Podman、可执行的 Compose、sh、tar、sha256sum、awk、curl;用户有目录与容器权限。
123
+ - 包含私有配置,安全搬运,不提交 Git;持久化卷身份跨版本稳定。
124
+ - 实际断外网验证启动、登录、页面刷新、重启与数据持久化。生产 HTTPS、证书、DNS 和外部服务需另行准备。
151
125
 
152
- ```typescript
153
- import type { DeployConfigInput } from '@h-ai/deploy'
126
+ ### SDK
127
+
128
+ ```ts
154
129
  import { deploy } from '@h-ai/deploy'
155
130
 
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
- }
131
+ const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 18080 } } })
132
+ if (!initialized.success)
133
+ throw new Error(initialized.error.message)
134
+ try {
135
+ const result = await deploy.deployApp('./packages/demo-serv', {
136
+ environment: 'prod',
137
+ envDir: '.',
138
+ bundleOnly: true,
139
+ artifactDir: './.hai/build/docker/<buildId>',
140
+ runtime: 'auto',
141
+ })
142
+ if (!result.success)
143
+ throw new Error(result.error.message)
144
+ // result.data.bundlePath / instructions; packaged does not mean server ready.
145
+ }
146
+ finally {
147
+ await deploy.close()
176
148
  }
177
149
  ```
178
150
 
179
- 调用者先判断 `result.success`,失败读取 `result.error.code/message`,成功读取 `result.data.url/healthCheck`。`deploy.scan()` 与 `deploy.credentials.*()` 不依赖初始化;`deployApp()` 已包含开通流程,无须预先 `provisionAll()`。手动开通同样检查结果。SDK 的 `skipProvision` 与 CLI 跳过中间件语义一致。
151
+ `selectedServices: ['web']` 选择局部更新,`onProgress` 返回打包与服务器执行阶段。`deploy.scan` 和 `deploy.credentials.*` 可在初始化前使用。先判断 HaiResult.success 再读取数据。Vercel 使用 prebuilt API 上传已有 .vercel/output,需要网络,不提供离线交付或 Compose 局部发布。
180
152
 
181
- 云资源返回 `HAI_DB`、`HAI_CACHE`、`HAI_STORAGE` 或 `HAI_REACH_PROVIDERS` 整体配置;应用须加载对应 YAML 并初始化模块。失败时 `getDeployRecovery(result.error)` 返回阶段及资源 ID;`unknown` 表示结果不确定,应先在平台核对,同名重试复用资源,不自动删除。完整类型、配置及错误码见 [公开入口](src/index.ts)。
153
+ ## 开发测试
182
154
 
183
- ## 测试与二次开发
184
-
185
- 框架仓库根目录安装依赖后执行(Windows 使用 `pnpm.cmd`):
186
-
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
155
+ ```sh
156
+ pnpm --filter @h-ai/deploy typecheck
157
+ pnpm --filter @h-ai/deploy lint
158
+ pnpm --filter @h-ai/deploy build
159
+ pnpm --filter @h-ai/deploy test
160
+ pnpm --filter @h-ai/cli test:scaffold-gates
161
+ pnpm --filter @h-ai/cli test:deploy-gates
193
162
  ```
194
163
 
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)。
196
-
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/`。
164
+ 验证真实生成项目与 SSH/人工部署,覆盖禁止访问镜像仓库、局部更新、归档损坏、同版本发布和健康失败。finally/teardown 只清理本次测试创建的项目、镜像、容器、日志和进程。
198
165
 
199
- ## License
166
+ ## 许可证
200
167
 
201
168
  Apache-2.0
package/dist/index.d.ts CHANGED
@@ -2,6 +2,39 @@ 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: 3, appVersion: '1.2.3', buildId: '1.2.3-build1', partial: false, hasWeb: false, images: [...] }`。 */
6
+ declare const DeploymentArtifactSchema: z.ZodObject<{
7
+ version: z.ZodLiteral<3>;
8
+ appVersion: z.ZodString;
9
+ buildId: z.ZodString;
10
+ partial: z.ZodDefault<z.ZodBoolean>;
11
+ hasWeb: z.ZodDefault<z.ZodBoolean>;
12
+ appContainerPort: z.ZodOptional<z.ZodNumber>;
13
+ webContainerPort: z.ZodOptional<z.ZodNumber>;
14
+ images: z.ZodArray<z.ZodObject<{
15
+ name: z.ZodEnum<{
16
+ app: "app";
17
+ web: "web";
18
+ }>;
19
+ file: z.ZodString;
20
+ imageRef: z.ZodString;
21
+ sha256: z.ZodString;
22
+ containerPort: z.ZodOptional<z.ZodNumber>;
23
+ }, z.core.$strip>>;
24
+ }, z.core.$strip>;
25
+ /** 构建器与部署器共享的镜像交付结构;参见 DeploymentArtifactSchema 示例。 */
26
+ type DeploymentArtifact = z.infer<typeof DeploymentArtifactSchema>;
27
+ /**
28
+ * 流式计算制品校验和,避免将大镜像一次性读入内存。
29
+ * @example `const checksum = await artifactChecksum("/release/app-1.2.3.tar")`
30
+ */
31
+ declare function artifactChecksum(file: string): Promise<string>;
32
+ /**
33
+ * 校验完整构建产物;缺失、损坏或非法清单均抛错,由部署边界转换为 HaiResult。
34
+ * @example `const artifact = await readDeploymentArtifact("/release/build-1")`
35
+ */
36
+ declare function readDeploymentArtifact(directory: string): Promise<DeploymentArtifact>;
37
+
5
38
  /**
6
39
  * @h-ai/deploy — 部署配置 Schema
7
40
  *
@@ -28,7 +61,7 @@ declare const PortExposeSchema: z.ZodObject<{
28
61
  /** docker-ssh 部署平台配置 */
29
62
  declare const DockerSshProviderConfigSchema: z.ZodObject<{
30
63
  type: z.ZodLiteral<"docker-ssh">;
31
- ssh: z.ZodObject<{
64
+ ssh: z.ZodOptional<z.ZodObject<{
32
65
  host: z.ZodString;
33
66
  port: z.ZodDefault<z.ZodNumber>;
34
67
  username: z.ZodString;
@@ -36,7 +69,7 @@ declare const DockerSshProviderConfigSchema: z.ZodObject<{
36
69
  knownHostsFile: z.ZodOptional<z.ZodString>;
37
70
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
38
71
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
39
- }, z.core.$strip>;
72
+ }, z.core.$strip>>;
40
73
  remote: z.ZodDefault<z.ZodObject<{
41
74
  baseDir: z.ZodDefault<z.ZodString>;
42
75
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -57,7 +90,7 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
57
90
  token: z.ZodString;
58
91
  }, z.core.$strip>, z.ZodObject<{
59
92
  type: z.ZodLiteral<"docker-ssh">;
60
- ssh: z.ZodObject<{
93
+ ssh: z.ZodOptional<z.ZodObject<{
61
94
  host: z.ZodString;
62
95
  port: z.ZodDefault<z.ZodNumber>;
63
96
  username: z.ZodString;
@@ -65,7 +98,7 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
65
98
  knownHostsFile: z.ZodOptional<z.ZodString>;
66
99
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
67
100
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
68
- }, z.core.$strip>;
101
+ }, z.core.$strip>>;
69
102
  remote: z.ZodDefault<z.ZodObject<{
70
103
  baseDir: z.ZodDefault<z.ZodString>;
71
104
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -94,7 +127,7 @@ declare const DeployConfigSchema: z.ZodObject<{
94
127
  token: z.ZodString;
95
128
  }, z.core.$strip>, z.ZodObject<{
96
129
  type: z.ZodLiteral<"docker-ssh">;
97
- ssh: z.ZodObject<{
130
+ ssh: z.ZodOptional<z.ZodObject<{
98
131
  host: z.ZodString;
99
132
  port: z.ZodDefault<z.ZodNumber>;
100
133
  username: z.ZodString;
@@ -102,7 +135,7 @@ declare const DeployConfigSchema: z.ZodObject<{
102
135
  knownHostsFile: z.ZodOptional<z.ZodString>;
103
136
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
104
137
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
105
- }, z.core.$strip>;
138
+ }, z.core.$strip>>;
106
139
  remote: z.ZodDefault<z.ZodObject<{
107
140
  baseDir: z.ZodDefault<z.ZodString>;
108
141
  runtime: z.ZodDefault<z.ZodEnum<{
@@ -147,17 +180,10 @@ declare const DeployConfigSchema: z.ZodObject<{
147
180
  }, z.core.$strip>], "provisioner">>;
148
181
  }, z.core.$strip>>;
149
182
  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
183
  s3Engine: z.ZodDefault<z.ZodEnum<{
157
184
  rustfs: "rustfs";
158
185
  minio: "minio";
159
186
  }>>;
160
- sqlitePrebuildMirror: z.ZodOptional<z.ZodURL>;
161
187
  }, z.core.$strip>>;
162
188
  }, z.core.$strip>;
163
189
  /** 部署配置类型(parse 后) */
@@ -220,7 +246,6 @@ declare function saveDeploymentEnvironment(environment: DeploymentEnvironment, v
220
246
  declare const HaiDeployError: {
221
247
  readonly DEPLOY_FAILED: _h_ai_core.HaiErrorDef;
222
248
  readonly PROJECT_CREATE_FAILED: _h_ai_core.HaiErrorDef;
223
- readonly BUILD_FAILED: _h_ai_core.HaiErrorDef;
224
249
  readonly UPLOAD_FAILED: _h_ai_core.HaiErrorDef;
225
250
  readonly AUTH_REQUIRED: _h_ai_core.HaiErrorDef;
226
251
  readonly AUTH_FAILED: _h_ai_core.HaiErrorDef;
@@ -232,8 +257,6 @@ declare const HaiDeployError: {
232
257
  readonly UNSUPPORTED_TYPE: _h_ai_core.HaiErrorDef;
233
258
  readonly CONFIG_ERROR: _h_ai_core.HaiErrorDef;
234
259
  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
260
  readonly REMOTE_CONNECT_FAILED: _h_ai_core.HaiErrorDef;
238
261
  readonly REMOTE_COMMAND_FAILED: _h_ai_core.HaiErrorDef;
239
262
  readonly IMAGE_TRANSFER_FAILED: _h_ai_core.HaiErrorDef;
@@ -315,8 +338,6 @@ interface ScanResult {
315
338
  requiredServices: ServiceType[];
316
339
  /** 各服务检测到的后端类型(如 db→postgresql/mysql,vecdb→qdrant),用于生成匹配的 sidecar */
317
340
  serviceBackends: Partial<Record<ServiceType, string>>;
318
- /** 构建命令 */
319
- buildCommand: string;
320
341
  }
321
342
  /**
322
343
  * 部署结果
@@ -329,7 +350,11 @@ interface DeployResult {
329
350
  /** 部署 ID */
330
351
  deploymentId: string;
331
352
  /** 部署状态 */
332
- status: 'ready' | 'building' | 'error';
353
+ status: 'ready' | 'packaged' | 'building' | 'error';
354
+ /** Compose 部署 tar 的绝对路径;包含私有配置,应安全传输。 */
355
+ bundlePath?: string;
356
+ /** 手工搬运和启动说明;仅打包不代表服务器已运行。 */
357
+ instructions?: string;
333
358
  /** 已设置的环境变量名列表 */
334
359
  envVarsSet: string[];
335
360
  /** 部署 Provider 名称(如 vercel / docker-ssh),容器部署时返回 */
@@ -368,6 +393,12 @@ interface DeployRecoveryInfo {
368
393
  }
369
394
  /** 部署选项 */
370
395
  interface DeployAppOptions {
396
+ /** 接收实际执行阶段,用于终端进度显示;不包含凭据或配置原文。 */
397
+ onProgress?: (stage: 'packaging' | 'preflight' | 'images' | 'dependencies' | 'start' | 'health' | 'complete') => void;
398
+ /** 只更新指定的 Compose 服务;省略时完整制品部署全部,部分制品只部署其镜像。 */
399
+ selectedServices?: string[];
400
+ /** 打包中间件镜像所用的本地引擎,auto 优先 Podman。 */
401
+ runtime?: 'auto' | 'podman' | 'docker';
371
402
  /** 部署环境标识;显式指定时必须存在 .env.<environment>。 */
372
403
  environment?: string;
373
404
  /** 环境文件所在目录,默认自动定位。 */
@@ -376,10 +407,14 @@ interface DeployAppOptions {
376
407
  serviceModes?: Partial<Record<string, MiddlewareMode>>;
377
408
  /** 项目名称(默认使用明确指定的部署目录名称)。 */
378
409
  projectName?: string;
379
- /** 是否跳过基础设施开通 */
380
- skipProvision?: boolean;
381
- /** 是否跳过构建 */
382
- skipBuild?: boolean;
410
+ /** 是否跳过全部中间件配置与开通,默认 false;例如配置完备时设为 true。 */
411
+ skipMiddleware?: boolean;
412
+ /** 已构建镜像目录;默认最近构建入口/锁文件目录下的 .hai/build/docker。 */
413
+ artifactDir?: string;
414
+ /** 只生成部署 tar,不连接 SSH;仅 docker-ssh 支持。 */
415
+ bundleOnly?: boolean;
416
+ /** 部署 tar 输出路径;默认 .hai/deploy/<环境>/bundle-<项目>-<部署ID>-<版本>.tar,自定义文件名自动补 -<版本>。 */
417
+ output?: string;
383
418
  }
384
419
  /**
385
420
  * Deploy 凭证文件操作。
@@ -446,7 +481,7 @@ interface DeployFunctions {
446
481
  */
447
482
  provisionAll: (projectName: string, services?: ServiceType[]) => Promise<HaiResult<ProvisionResult[]>>;
448
483
  /**
449
- * 执行完整部署流程(provision → build → deploy)
484
+ * 部署已有构建产物(环境和中间件 → 打包 → 上传/启动);不会调用构建命令。
450
485
  *
451
486
  * @param appDir - 应用根目录路径
452
487
  * @param options - 部署选项
@@ -493,7 +528,7 @@ declare const deploy: DeployFunctions;
493
528
  * const text = deployM('deploy_notInitialized')
494
529
  * ```
495
530
  */
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;
531
+ 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" | "deploy_selectedContainerOnly" | "deploy_selectionInvalid" | "deploy_storageArtifactRequired" | "deploy_imageVersionRequired" | "deploy_bundleRuntimeRequired" | "deploy_bundleFailed" | "deploy_configFileInvalid" | "deploy_configFileUnreadable" | "deploy_bundleNameInvalid" | "deploy_secretsInvalid", options?: _h_ai_core.MessageOptions) => string;
497
532
 
498
533
  /**
499
534
  * 部署中间件的后端与连接字段目录,供扫描、CLI 和容器规划共同使用。
@@ -546,4 +581,4 @@ declare function describeMiddlewareBackend(backend: MiddlewareBackend): string;
546
581
  /** 读取错误中的恢复清单;仅返回明确允许展示的字段。 */
547
582
  declare function getDeployRecovery(error: HaiError): DeployRecoveryInfo | undefined;
548
583
 
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 };
584
+ 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 };