@h-ai/deploy 0.1.0-alpha.63 → 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.en.md CHANGED
@@ -2,242 +2,201 @@
2
2
 
3
3
  [简体中文](./README.md) | [English](./README.en.md) | [日本語](./README.ja.md)
4
4
 
5
- [Framework home](../../README.en.md) · Related modules: [@h-ai/core](../core/README.en.md)
5
+ [Framework home](../../README.en.md) · [CLI](../cli/README.en.md) · [Environment and middleware](../cli/templates/skills/hai-deploy/middleware.md)
6
6
 
7
- An automated deployment module with two targets: deploy SvelteKit apps to Vercel with cloud infrastructure provisioning, or containerize and deploy to any Linux host over SSH (docker-ssh).
7
+ Deployment SDK used by the `hai deploy` command in `@h-ai/cli`. It also runs independently in Node.js scripts.
8
8
 
9
- ## Deployment targets
9
+ ## Features and scope
10
10
 
11
- | Provider | Target | Description |
12
- | ------------ | --------------- | ------------------------------------------------------------- |
13
- | `vercel` | Vercel platform | SvelteKit + adapter-vercel with cloud provisioners |
14
- | `docker-ssh` | Any Linux host | SvelteKit + adapter-node, containerized and deployed over SSH |
11
+ | Target | Application | Operation |
12
+ | ------------ | --------------------------------------------------- | ----------------------------------------------------------------------------------- |
13
+ | `docker-ssh` | Native Node services or SvelteKit with adapter-node | Build locally, upload over SSH, run Compose on Linux and check HTTP health |
14
+ | `vercel` | SvelteKit with adapter-vercel | Build, upload, set environment variables and wait for deployment; requires internet |
15
15
 
16
- ## Cloud services (provisioners)
16
+ - Follow runtime dependencies and workspace links from the target importer in `pnpm-lock.yaml`, excluding development dependencies and peers. Application configuration selects middleware backends. Optional AI peers do not enable AI/CUDA.
17
+ - Select an environment and require `.env.<mode>` before choosing defaults, custom connections or keep unchanged per middleware. Complete environments can skip all middleware processing.
18
+ - Compose supports PostgreSQL, MySQL, Redis, RustFS/MinIO, Qdrant, pgvector, Chroma, OpenLDAP and Mailpit. SQLite, memory, local and LanceDB use their respective local storage. Mailpit captures test mail only.
19
+ - Cloud provisioning supports Neon, Upstash, Cloudflare R2, Resend and Alibaba Cloud SMS. R2 needs existing S3 credentials, Resend a verified sender domain, and SMS approved signatures/templates. Use custom connections for other backends.
17
20
 
18
- | Service type | Provisioner | Description |
19
- | ------------ | ------------- | ------------------------------- |
20
- | Database | Neon | PostgreSQL Serverless |
21
- | Cache | Upstash | Redis TCP + TLS |
22
- | Storage | Cloudflare R2 | S3-compatible object storage |
23
- | Email | Resend | SMTP (existing verified domain) |
24
- | SMS | Alibaba Cloud | Alibaba Cloud SMS |
21
+ **Fullstack API and Web are separate deployments.** `hai deploy packages/my-app-serv` deploys the API only. The root template Dockerfile has `runtime` and `web` targets for API and static Web images. Upload and run Web separately, setting `PUBLIC_API_BASE` and API CORS. There is no command to publish both together, nor automatic domain, TLS or reverse proxy configuration.
25
22
 
26
- ## Quick start
23
+ ## Command-line deployment
27
24
 
28
- ```typescript
29
- import { deploy } from '@h-ai/deploy'
25
+ ### 1. Install and prepare
30
26
 
31
- // 可选:从 ~/.hai/credentials.yml 注入 HAI_DEPLOY_* 环境变量
32
- deploy.credentials.load()
27
+ Node must satisfy `^22.22.2 || ^24.15.0 || >=26.0.0`; generated projects pin `pnpm@12.11.2`. Node 20 / pnpm 9.5.0 are unsupported. Use `pnpm.cmd` in Windows PowerShell.
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
+ In the generated workspace root, install CLI/SDK versions matching the application's framework and retain the lockfile. Installation needs a package source or complete offline dependencies:
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
+ Creation with `--features deploy` already adds the local SDK, but does not install a global `hai`. With a global CLI, run `hai deploy` directly. The template's `pnpm run deploy` packages artifacts; it does not invoke this deployment command.
48
37
 
49
- // 完整部署
50
- const result = await deploy.deployApp('./apps/my-app')
38
+ For docker-ssh, prepare:
51
39
 
52
- await deploy.close()
53
- ```
40
+ - A running local Docker/Podman engine capable of building Linux images for the server architecture. Windows needs the corresponding virtualization/WSL2/VM environment; a client alone is insufficient. `auto` prefers Podman; Docker can be selected explicitly.
41
+ - Local `ssh` and `scp`, noninteractive key/agent authentication and the server host fingerprint.
42
+ - Remote Docker/Podman with its `compose` command. The SSH user must run containers and write the deployment directory. Windows must reach SSH and the exposed application port.
43
+ - `config/_deploy.yml` in the target package and `.env.prod` or `.env.test` in the environment directory. Missing environment files stop deployment.
54
44
 
55
- ## Configuration
45
+ ### 2. Configure target and environment
56
46
 
57
- Keep Git-safe configuration in `config/_deploy.yml` and secrets in `~/.hai/credentials.yml`:
47
+ Minimal `config/_deploy.yml` for Docker; supply machine-specific target details through the whole-object `HAI_DEPLOY_PROVIDER` override in `.env.local`:
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
- These variables are explicit special mappings managed by `deploy.credentials`. If convention-based variables
76
- such as `HAI_DEPLOY_PROVIDER_TOKEN` are also set, the convention-based variables take precedence.
77
-
78
- Use `deploy.credentials.*` to manage credential files explicitly in scripts or the CLI:
58
+ Replace the host, user, ports and key path for your environment. Never commit `.env.local`:
79
59
 
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
- })
60
+ ```dotenv
61
+ 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}}'
86
62
  ```
87
63
 
88
- > `deploy.scan()` and `deploy.credentials.*()` use the local filesystem and do not require `deploy.init()`.
64
+ Alternatively, the wizard asks for missing target details, saves them in `.env.local` and reuses them. Store private key paths only. Edit `HAI_DEPLOY_PROVIDER` to switch a complete target.
89
65
 
90
- ## docker-ssh deployment
66
+ Precedence: `.env` → `.env.<mode>` → `.env.local` → process environment. Parent JSON such as `HAI_DB` overrides child variables. `.env.example` documents all fields and enums. Commit `.env` and `.env.dev/test/prod`; keep real credentials in `.env.local`. Fullstack's first start also needs a private `HAI_ADMIN_DEFAULT_PASSWORD` satisfying the IAM password policy.
91
67
 
92
- Containerize the application and deploy over SSH to any Linux host; no cloud account is required.
68
+ Fullstack production also requires an explicit Web Origin in `.env.prod`, such as `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`; the default `*` prevents production startup. Production refresh cookies use Secure, so Web/API need trusted HTTPS endpoints. Exposing HTTP ports alone does not validate complete login/session refresh.
93
69
 
94
- ### Prerequisites
70
+ ### 3. Deploy
95
71
 
96
- - **Local**: install docker or podman (`auto` prefers podman).
97
- - **Remote host**: SSH access, docker/podman and compose installed, and a login user permitted to run containers.
98
- - **Application**: SvelteKit must use `@sveltejs/adapter-node` (covered by the supplied `Dockerfile`); otherwise `ADAPTER_MISSING` is returned.
72
+ From the fullstack root, replace the service directory:
99
73
 
100
- ### Steps
74
+ ```powershell
75
+ pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir .
76
+ ```
101
77
 
102
- 1. Store the SSH private key path in the credential file, outside the repository. Edit `~/.hai/credentials.yml` or use the API:
78
+ The wizard shows the effective backend and purpose. Defaults generate Compose services/volumes; custom connections omit that middleware container. Keep unchanged is offered only when the target environment file explicitly declares complete configuration. Public values go to the selected environment file, credentials to `.env.local`, with redacted output.
103
79
 
104
- ```typescript
105
- deploy.credentials.save('HAI_DEPLOY_SSH_KEY', '~/.ssh/hai_deploy')
80
+ When all database, cache and storage connections are already configured:
81
+
82
+ ```powershell
83
+ pnpm.cmd exec hai deploy packages/my-app-serv --env prod --env-dir . --yes --skip-middleware
106
84
  ```
107
85
 
108
- 2. Declare the target host in `config/_deploy.yml`:
86
+ `--skip-middleware` (alias `--skip-provision`) skips middleware prompts, configuration updates and resource creation; application build/deployment still run. It does not start external middleware. `--yes` alone keeps complete settings and otherwise uses the current backend defaults.
109
87
 
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
88
+ `--project-name <name>` sets the name; an explicit environment adds a suffix. **`--skip-build` currently skips only Vercel application builds. docker-ssh always builds an image and has no option to deploy a specified existing image directly.** Failures return a nonzero exit code.
125
89
 
126
- container:
127
- runtime: auto # 本地构建运行时(可选)
90
+ ## docker-ssh execution and files
128
91
 
129
- services: {} # 启用的服务会自动生成 compose sidecar,见下
130
- ```
92
+ 1. Use the target package's Dockerfile, or generate a fallback with the workspace root as context. Fullstack service packages use the fallback by default, not the root Web target.
93
+ 2. Build the image locally and export a tar with `docker/podman save`.
94
+ 3. Check the remote engine and Compose over SSH, upload the tar with SCP, then run `load` remotely.
95
+ 4. Generate `compose.yml`, `.env.runtime` and `start-<mode>.sh` locally and upload each to `<baseDir>/<projectName>-<mode>/`. These are separate from the image archive; no single Compose bundle is uploaded and no source build runs on Linux.
96
+ 5. The remote script sets `HAI_DEPLOY_ENVIRONMENT`/`HAI_CORE_ENV` and runs `compose --env-file .env.runtime ... up -d`. The SDK checks native `/health` or SvelteKit `/` from the local machine before succeeding.
131
97
 
132
- 3. Deploy and verify with the CLI or API (equivalent behavior):
98
+ Local artifacts live in the target package's Git-ignored `.hai/deploy/<mode>/`. Remote `.env.runtime` uses mode `600`. Retain local artifacts for repeat deployment: keep unchanged preserves services/volumes from the previous local Compose, not arbitrary server-side Compose definitions. Exclude private environment files and deployment archives from image contexts.
133
99
 
134
- ```bash
135
- hai deploy # 读取 config/_deploy.yml,输出访问地址
136
- ```
100
+ ## Fully offline deployment
137
101
 
138
- ```typescript
139
- import { deploy } from '@h-ai/deploy'
102
+ Offline means no internet; Windows and Linux must still communicate over an internal SSH/HTTP network. Vercel and cloud provisioning are unavailable.
140
103
 
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
- ```
104
+ ### Materials to bring in advance
105
+
106
+ | Location | Requirements |
107
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | Windows tools | Compatible Node (for example a supported Node 24), pnpm 12.11.2, installed CLI/SDK and transitive dependencies, OpenSSH, initialized and runnable Linux container engine |
109
+ | Project | Complete source, pinned lockfile, environment files and private credentials; `pnpm.cmd exec hai --help` must work offline. Avoid on-demand `npx` / `pnpm dlx` downloads |
110
+ | Linux | Docker CLI/Engine, Compose plugin (`docker compose version` works), authorized SSH key, container permissions, writable deployment directory, ports and disk space. Host Node/pnpm are unnecessary for running images |
111
+ | Images | API, Web and middleware images matching Linux CPU architecture and exact Compose names/tags; import with `docker load -i <archive.tar>` |
112
+ | Application | PostgreSQL database/account, Redis connection/password, RustFS keys/bucket and initial administrator password. Existing services must already run and be reachable from the app container; `localhost` means that container |
113
+
114
+ Defaults use `postgres:16-alpine`, `redis:7-alpine` and `ghcr.io/rustfs/rustfs:1.0.1`. Other tags do not match automatically: import the exact images, correctly retag verified compatible images, or use custom connections. The SDK does not transfer middleware images. Other pinned images are in the [Compose implementation](src/container/deploy-container-compose.ts).
115
+
116
+ **The default source build is not ready for offline use without preparation.** Dockerfiles prepare pnpm and download packages and SQLite prebuilds; the fullstack root also runs `pnpm fetch` and uses an external Dockerfile frontend. Windows `node_modules`/pnpm store cannot replace Linux build dependencies. For offline source builds, bring Node base images, the build frontend, Corepack/pnpm, Linux dependencies and native binary caches into the local engine/build environment, adapt the Dockerfile, and rehearse with internet disconnected. `pnpm install --offline --frozen-lockfile` only addresses package-store access, not additional downloads by install scripts.
117
+
118
+ SQLite prebuilds must match better-sqlite3 version, Node ABI, CPU architecture and libc. Installation may still need them when runtime uses PostgreSQL. Preload prebuild-install's Linux cache or use an internal `container.sqlitePrebuildMirror`; otherwise bring the binary or a finished image. Default fullstack needs no CUDA.
150
119
 
151
- ### How it works
120
+ ### Reuse prebuilt images through the SDK offline
152
121
 
153
- For docker-ssh, `deployApp()` runs these steps:
122
+ Prepare finished images before delivery. For a project named `my-app` with a Web frontend, build on the server's architecture in an environment with dependencies available:
154
123
 
155
- 1. **Scan**: read `package.json` and `config/*.yml` to identify `@sveltejs/adapter-node`, required services, and backend types.
156
- 2. **Local build**: use the project's `Dockerfile` to build `hai-<app>:<deploymentId>` (podman adds `--format docker --jobs=1`).
157
- 3. **Export**: save the image as tar with `docker/podman save`.
158
- 4. **Preflight**: verify SSH connectivity and remote `docker/podman version` / `compose version`.
159
- 5. **Transfer/load**: upload tar with `scp`, load remotely, and retag under `localhost/` to avoid accidental registry pulls.
160
- 6. **Runtime files**: generate `compose.yml` (app + sidecars) and `.env.runtime` (including `HAI_*` overrides and credentials), upload to `<baseDir>/<app>/`, and set `.env.runtime` permissions to `600`.
161
- 7. **Start**: `compose up -d` with fixed project name `hai-<app>` for idempotent redeployment.
162
- 8. **Health check**: poll `http://<host>:<hostPort>/`; only HTTP 200 yields `ready`.
124
+ ```powershell
125
+ docker build --target runtime -t my-app-api:offline .
126
+ docker build --target web -t my-app-web:offline .
127
+ docker save -o my-app-images.tar my-app-api:offline my-app-web:offline
128
+ ```
129
+
130
+ Import the archive into the offline Windows Linux engine with `docker load -i my-app-images.tar`. Create `packages/my-app-serv/Dockerfile` containing only:
163
131
 
164
- > Images, `compose.yml`, and `.env.runtime` are stored in the application's `.hai/deploy/`, excluded by the template `.gitignore`.
132
+ ```dockerfile
133
+ FROM my-app-api:offline
134
+ ```
165
135
 
166
- ### Feature-based compose sidecars
136
+ Add a service-package `.dockerignore` containing `*`. This makes the SDK's mandatory build reuse the finished API without package installation. Rebuild the finished image after source changes. Configure the target, then run the deployment command above. To create middleware containers, select `HAI_DB_TYPE=postgresql`, `HAI_CACHE_TYPE=redis`, `HAI_STORAGE_TYPE=s3` in `.env.prod` and choose defaults in the wizard. For existing services, enter custom connections; once complete, `--skip-middleware` is available. The SDK uploads the API image. Rehearse with internet disconnected to confirm the builder needs no network.
167
137
 
168
- `deploy.scan()` identifies services and their **actual backend types** (`type` in `_db.yml` / `_vecdb.yml`, etc.), generates matching sidecars, and injects `HAI_*` overrides:
138
+ Transfer the Web image separately. This example assumes separately prepared internal TLS endpoints `https://app.hai.internal` and `https://api.hai.internal`, proxying Web port 18081 and API port 18080. Bring proxy configuration/images and certificates, configure internal DNS/hosts and client certificate trust, then run over SSH:
139
+
140
+ ```sh
141
+ docker load -i /path/to/my-app-images.tar
142
+ 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
143
+ ```
169
144
 
170
- | Detected service | Backend (`type`) | Added sidecar | Injected variables |
171
- | ---------------- | ---------------- | ------------------------------- | ------------------ |
172
- | `db` | `postgresql` | `postgres:16-alpine` | `HAI_DB` |
173
- | `db` | `mysql` | `mysql:8.4` | `HAI_DB` |
174
- | `db` | `sqlite` | None (mount `/app/data` volume) | — |
175
- | `cache` | `redis` | `redis:7-alpine` | `HAI_CACHE` |
176
- | `storage` | `s3` | `minio` + bucket initialization | `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` | None (mount `/app/data` volume) | — |
145
+ Use a browser-reachable API URL and set API `HAI_SERV_CORS_ORIGIN=https://app.hai.internal`. Verify API `/health`, Web login/business requests, hash-route reload, session refresh, restarts and persistence. These are preparation/adaptation instructions; online deployment tests do not prove offline acceptance.
181
146
 
182
- - Memory `cache` and external email/SMS `reach` APIs do not generate sidecars.
183
- - Existing cloud provisioners take precedence (for example Neon providing `HAI_DB`); no duplicate sidecar is created.
184
- - Sidecar secrets such as passwords go only in remote `.env.runtime` (`600`), never `compose.yml`.
147
+ Official references: 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 plugin installation](https://docs.docker.com/compose/install/linux/).
185
148
 
186
- > Security: `identityFile` is a private key **path**, not key content. `strictHostKeyChecking: true` (default)
187
- > uses accept-new: trust first use and reject changed keys. You may connect once manually with `ssh` for an unknown host.
149
+ ## SDK and failures
188
150
 
189
- ## Error handling
151
+ Scripts do not need the CLI. Supply parsed configuration; use public `loadDeploymentEnvironment` / `loadDeploymentConfig` for the same environment resolution:
190
152
 
191
153
  ```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
154
+ import type { DeployConfigInput } from '@h-ai/deploy'
155
+ import { deploy } from '@h-ai/deploy'
156
+
157
+ /**
158
+ * 使用现有中间件发布生产 API,并释放部署模块。
159
+ * @param config - 从部署环境解析得到的配置
160
+ * @returns 初始化或部署的 HaiResult,由调用者检查 success
161
+ * @example `const result = await publish(config)`
162
+ */
163
+ export async function publish(config: DeployConfigInput) {
164
+ const initialized = await deploy.init(config)
165
+ if (!initialized.success)
166
+ return initialized
167
+ try {
168
+ return await deploy.deployApp('./packages/my-app-serv', {
169
+ environment: 'prod',
170
+ envDir: '.',
171
+ skipProvision: true,
172
+ })
173
+ }
174
+ finally {
175
+ await deploy.close()
203
176
  }
204
177
  }
205
178
  ```
206
179
 
207
- Common error codes:
180
+ Check `result.success` before reading `error.code/message` or `data.url/healthCheck`. `deploy.scan()` and `deploy.credentials.*()` require no initialization. `deployApp()` includes provisioning, so do not also call `provisionAll()` beforehand. Check results for manual provisioning too. SDK `skipProvision` has the CLI's skip-middleware semantics.
208
181
 
209
- | Error code | code | Description |
210
- | --------------------------------- | ---------------- | ------------------------------------- |
211
- | `HaiDeployError.DEPLOY_FAILED` | `hai:deploy:001` | Deployment failed |
212
- | `HaiDeployError.BUILD_FAILED` | `hai:deploy:003` | Build failed |
213
- | `HaiDeployError.AUTH_FAILED` | `hai:deploy:006` | Authentication failed |
214
- | `HaiDeployError.PROVISION_FAILED` | `hai:deploy:007` | Provisioning failed |
215
- | `HaiDeployError.NOT_INITIALIZED` | `hai:deploy:010` | Not initialized |
216
- | `HaiDeployError.ENV_VAR_FAILED` | `hai:deploy:011` | Environment variable operation failed |
182
+ Cloud provisioners return whole-object `HAI_DB`, `HAI_CACHE`, `HAI_STORAGE` or `HAI_REACH_PROVIDERS`; applications must load their YAML and initialize the modules. `getDeployRecovery(result.error)` returns stage/resource IDs; verify `unknown` outcomes in the platform before retrying. Same-name retries reuse resources without automatic deletion. Types, configuration and error codes are available through the [public entry](src/index.ts).
217
183
 
218
- docker-ssh-specific error codes:
184
+ ## Testing and development
219
185
 
220
- | Error code | code | Description |
221
- | -------------------------------------------- | ---------------- | --------------------------- |
222
- | `HaiDeployError.CONTAINER_RUNTIME_NOT_FOUND` | `hai:deploy:015` | Container runtime not found |
223
- | `HaiDeployError.CONTAINER_BUILD_FAILED` | `hai:deploy:016` | Image build failed |
224
- | `HaiDeployError.REMOTE_CONNECT_FAILED` | `hai:deploy:017` | Remote connection failed |
225
- | `HaiDeployError.REMOTE_COMMAND_FAILED` | `hai:deploy:018` | Remote command failed |
226
- | `HaiDeployError.IMAGE_TRANSFER_FAILED` | `hai:deploy:019` | Image transfer failed |
227
- | `HaiDeployError.HEALTH_CHECK_FAILED` | `hai:deploy:020` | Health check failed |
186
+ After installing dependencies, run from the framework root (Windows: `pnpm.cmd`):
228
187
 
229
- ## Tests
230
-
231
- ```bash
232
- pnpm --filter @h-ai/deploy test
188
+ ```powershell
189
+ pnpm.cmd --filter @h-ai/deploy typecheck
190
+ pnpm.cmd --filter @h-ai/deploy lint
191
+ pnpm.cmd --filter @h-ai/deploy build
192
+ pnpm.cmd --filter @h-ai/deploy test
193
+ pnpm.cmd check:skills
233
194
  ```
234
195
 
235
- ## License
196
+ `src/deploy-scanner.ts` handles lockfile traversal, `deploy-middleware.ts` connection fields, `container/` engines/SSH/Compose, `providers/` deployment targets and `provisioners/` cloud resources. CLI prompts live in `../cli/src/commands/cli-deploy*.ts`. Changes to backends/configuration must update consumers, environment examples, tests and the [deployment skill](../cli/templates/skills/hai-deploy/SKILL.md).
236
197
 
237
- Apache-2.0
238
-
239
- Provisioned configuration: Neon emits HAI_DB (PostgreSQL JSON), Upstash HAI_CACHE (Redis rediss URL JSON), and R2 HAI_STORAGE (flat S3 JSON). Email/SMS emit HAI_REACH_PROVIDERS; deployApp merges both channels while preserving YAML message templates, replacing the original providers array. Core supports whole-object/array overrides, with parents preceding leaves and schema validation retained. Build subprocesses and target runtimes share provisioned values. R2 needs existing S3 accessKeyId/secretAccessKey, Resend a verified-domain from address, and Alibaba Cloud approved signName/templates. Applications need matching `_db.yml` / `_cache.yml` / `_storage.yml` / `_reach.yml` loaded at startup; network clients require Node.
198
+ For deployment changes, also run CLI typecheck/lint/build/test and `pnpm.cmd --filter @h-ai/cli test:deploy-gates`: current package archives → generated fullstack → Podman/SSH → health, environment, login and repeat deployment. This needs a local engine and OpenSSH; first fixture setup downloads images and OS/Node dependencies, so it is not an offline acceptance test. Template changes also require `pnpm.cmd --filter @h-ai/cli test:scaffold-gates`. Unit tests do not prove cloud resource creation; validate paid cloud operations separately. Keep logs/screenshots/reports in Git-ignored `tmp/` or `test-results/`.
240
199
 
241
- Configuration references: [Upstash TLS](https://upstash.com/docs/redis/features/security), [Resend SMTP](https://resend.com/docs/send-with-smtp). Deterministic acceptance generates real applications and validates Core and module schemas. It does not create cloud resources or send email/SMS, so it does not establish cloud read/write acceptance.
200
+ ## License
242
201
 
243
- Deployment recovery: `ProvisionResult.resourceStatus` marks created/reused. `getDeployRecovery(result.error)` reads projectName, stage, platformProjectId, and resource lists; unknown means an uncertain request result that must first be checked in the provider console. Preserve returned IDs for failed resources. Recovery excludes envVars, raw exceptions, and credentials; the CLI shows resources and advice. Same-name retries reuse resources. Reused resources are never deleted automatically; clean up newly created resources only after manually confirming they are unused.
202
+ Apache-2.0