@h-ai/cli 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.
Files changed (69) hide show
  1. package/README.en.md +13 -7
  2. package/README.ja.md +13 -7
  3. package/README.md +14 -8
  4. package/dist/{chunk-IPG3QNYK.js → chunk-FWN5B54L.js} +36 -4
  5. package/dist/chunk-FWN5B54L.js.map +1 -0
  6. package/dist/{chunk-47V5OT22.js → chunk-QYX6V7H2.js} +17 -43
  7. package/dist/chunk-QYX6V7H2.js.map +1 -0
  8. package/dist/cli-config-templates-3XSFIXBP.js +5 -0
  9. package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
  10. package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-TSYEQTCY.js} +3 -3
  11. package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
  12. package/dist/index.js +273 -208
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-6PPBKQ6Q.js → src-RDQ2EIBT.js} +506 -488
  15. package/dist/src-RDQ2EIBT.js.map +1 -0
  16. package/package.json +8 -8
  17. package/templates/apps/api/.dockerignore.hbs +4 -5
  18. package/templates/apps/api/.gitignore.hbs +2 -1
  19. package/templates/apps/api/Dockerfile.hbs +2 -15
  20. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
  21. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +0 -1
  22. package/templates/apps/api/hai.build.json.hbs +1 -0
  23. package/templates/apps/api/package.json.hbs +5 -8
  24. package/templates/apps/fullstack/.dockerignore.hbs +4 -5
  25. package/templates/apps/fullstack/.gitignore.hbs +2 -1
  26. package/templates/apps/fullstack/Dockerfile.hbs +3 -15
  27. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
  28. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  30. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
  31. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
  32. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  33. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
  35. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
  36. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  38. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +1 -0
  39. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  40. package/templates/apps/fullstack/package.json.hbs +5 -12
  41. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
  42. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
  43. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +1 -1
  44. package/templates/apps/mobile-app/index.html.hbs +1 -0
  45. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  46. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  47. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  48. package/templates/base/.dockerignore.hbs +4 -5
  49. package/templates/base/.gitignore.hbs +2 -1
  50. package/templates/base/Dockerfile.hbs +5 -22
  51. package/templates/base/docker/nginx.conf.hbs +6 -0
  52. package/templates/base/hai.build.json.hbs +1 -0
  53. package/templates/base/package.json.hbs +13 -17
  54. package/templates/base/svelte.config.js.hbs +2 -0
  55. package/templates/base/vite.config.ts.hbs +6 -2
  56. package/templates/shared/.gitattributes +5 -0
  57. package/templates/shared/container-runtime-config.sh +30 -0
  58. package/templates/shared/run-e2e.mjs +65 -0
  59. package/templates/shared/vite-runtime-config.ts +54 -0
  60. package/templates/skills/hai-app-create/development.md +3 -1
  61. package/templates/skills/hai-app-create/reference.md +2 -2
  62. package/templates/skills/hai-build/SKILL.md +16 -1
  63. package/templates/skills/hai-deploy/SKILL.md +52 -260
  64. package/templates/skills/hai-deploy/middleware.md +8 -14
  65. package/templates/skills/hai-ui/reference.md +2 -0
  66. package/dist/chunk-47V5OT22.js.map +0 -1
  67. package/dist/chunk-IPG3QNYK.js.map +0 -1
  68. package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
  69. package/dist/src-6PPBKQ6Q.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/cli",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.65",
4
+ "version": "0.1.0-alpha.66",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -30,7 +30,7 @@
30
30
  "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "@h-ai/core": "0.1.0-alpha.65",
33
+ "@h-ai/core": "0.1.0-alpha.66",
34
34
  "cac": "^7.0.1",
35
35
  "chalk": "^6.0.1",
36
36
  "fast-wrap-ansi": "^0.2.2",
@@ -41,12 +41,12 @@
41
41
  "yaml": "^2.9.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@h-ai/cache": "0.1.0-alpha.65",
45
- "@h-ai/crypto": "0.1.0-alpha.65",
46
- "@h-ai/deploy": "0.1.0-alpha.65",
47
- "@h-ai/reach": "0.1.0-alpha.65",
48
- "@h-ai/reldb": "0.1.0-alpha.65",
49
- "@h-ai/storage": "0.1.0-alpha.65",
44
+ "@h-ai/cache": "0.1.0-alpha.66",
45
+ "@h-ai/crypto": "0.1.0-alpha.66",
46
+ "@h-ai/deploy": "0.1.0-alpha.66",
47
+ "@h-ai/reach": "0.1.0-alpha.66",
48
+ "@h-ai/reldb": "0.1.0-alpha.66",
49
+ "@h-ai/storage": "0.1.0-alpha.66",
50
50
  "@types/fs-extra": "^11.0.4",
51
51
  "@types/node": "^26.6.5",
52
52
  "@types/prompts": "^2.4.9",
@@ -1,4 +1,5 @@
1
1
  .git
2
+ **/*.tar
2
3
  .github
3
4
  .agents
4
5
  .claude
@@ -12,8 +13,6 @@ playwright-report
12
13
  test-results
13
14
  **/data
14
15
  *.log
15
- # 仅排除本机私有配置,与 Git 处理一致;公共 .env 与 dev/test/prod 随上下文构建。
16
- **/.env.local
17
- **/.env.*.local
18
- **/.env.runtime
19
- **/.hai/deploy
16
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
17
+ **/.env.*
18
+ **/.hai
@@ -6,8 +6,9 @@ build
6
6
  playwright-report
7
7
  test-results
8
8
 
9
- # hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
9
+ # 构建与部署产物(镜像 / Compose tar / 私有运行配置)
10
10
  .hai/
11
+ *.tar
11
12
 
12
13
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
13
14
  .env.local
@@ -2,10 +2,6 @@
2
2
  FROM node:24-bookworm-slim AS build
3
3
  ARG SQLITE_PREBUILD_MIRROR
4
4
  ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
5
- ARG HAI_DEPLOY_ENVIRONMENT=prod
6
- ARG HAI_CORE_ENV=production
7
- ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
8
- ENV HAI_CORE_ENV=$HAI_CORE_ENV
9
5
 
10
6
  ENV PNPM_HOME=/pnpm
11
7
  ENV PATH=$PNPM_HOME:$PATH
@@ -13,21 +9,16 @@ ENV CI=true
13
9
  WORKDIR /workspace
14
10
 
15
11
  RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
16
- COPY pnpm-lock.yaml pnpm-workspace.yaml ./
17
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
18
12
  COPY . .
13
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
19
14
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
20
- RUN pnpm build
15
+ RUN pnpm compile
21
16
  RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
22
17
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{api.servicePackageName}} --prod /prod/app
23
18
 
24
19
  FROM node:24-bookworm-slim AS runtime-base
25
- ARG HAI_DEPLOY_ENVIRONMENT=prod
26
- ARG HAI_CORE_ENV=production
27
20
 
28
21
  ENV NODE_ENV=production
29
- ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
30
- ENV HAI_CORE_ENV=$HAI_CORE_ENV
31
22
  ENV HOST=0.0.0.0
32
23
  ENV PORT=3000
33
24
  WORKDIR /app
@@ -41,8 +32,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
41
32
  CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
42
33
  CMD ["node", "dist/index.js"]
43
34
 
44
- FROM runtime-base AS runtime-with-data
45
-
46
- COPY --from=embedded-data --chown=node:node / /app/data/
47
-
48
35
  FROM runtime-base AS runtime
@@ -11,9 +11,9 @@ pnpm --filter {{api.servicePackageName}} test
11
11
 
12
12
  ## 容器运行
13
13
 
14
- - 根目录运行 `pnpm docker:build` / `pnpm podman:build`;镜像监听 `0.0.0.0:3000`。
14
+ 从工程根运行 `pnpm build --target docker` 构建并导出镜像,再运行 `pnpm deploy --env test`。deploy 只消费已构建镜像;含 Web 时一并发布 API/Web。网络不通用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。
15
15
  - 配置默认打包自本目录 `config/`,可在运行时只读挂载到 `/app/config`,敏感值通过环境变量注入。
16
- - `data/` 可作为 SQLite/本地存储目录;`pnpm docker:build:data` / `pnpm podman:build:data` 会将其固化为镜像初始数据,生产写入应挂载 `/app/data` volume。
16
+ 从工程根运行 `pnpm build --target docker` 构建并导出镜像,再运行 `pnpm deploy --env test`。deploy 只消费已构建镜像;含 Web 时一并发布 API/Web。网络不通用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。
17
17
 
18
18
  ## 关键目录
19
19
 
@@ -21,7 +21,6 @@
21
21
  ],
22
22
  "scripts": {
23
23
  "build": "tsup",
24
- "build:prod": "pnpm build",
25
24
  "check": "pnpm typecheck",
26
25
  "typecheck": "tsc --noEmit",
27
26
  "lint": "eslint .",
@@ -0,0 +1 @@
1
+ { "images": [{ "name": "app", "target": "runtime", "containerPort": 3000 }] }
@@ -13,20 +13,16 @@
13
13
  "start:prod": "pnpm --filter {{api.servicePackageName}} start:prod",
14
14
  "dev:test": "pnpm --filter {{api.servicePackageName}} dev:test",
15
15
  "dev:prod": "pnpm --filter {{api.servicePackageName}} dev:prod",
16
- "compile": "pnpm typecheck && pnpm build",
17
- "build": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
18
- "docker:build": "docker build --target runtime -t {{projectName}}:latest .",
19
- "docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
20
- "podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
21
- "podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
16
+ "compile": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
17
+ "build": "hai build",
18
+ "deploy": "hai deploy apps/{{projectName}}-service",
22
19
  "check": "pnpm typecheck",
23
20
  "typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
24
21
  "lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
25
22
  "lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
26
23
  "test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
27
24
  "test:watch": "pnpm -r --parallel --if-present test:watch",
28
- "build:prod": "pnpm build",
29
- "test:e2e": "pnpm build && playwright test",
25
+ "test:e2e": "pnpm compile && node scripts/run-e2e.mjs",
30
26
  "clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
31
27
  },
32
28
  "dependencies": {
@@ -34,6 +30,7 @@
34
30
  "{{api.contractPackageName}}": "workspace:*"
35
31
  },
36
32
  "devDependencies": {
33
+ "@h-ai/cli": "{{haiPackageSpecifier}}",
37
34
  "@h-ai/api-client": "{{haiPackageSpecifier}}",
38
35
  {{#if features.crypto}}
39
36
  "@h-ai/crypto": "{{haiPackageSpecifier}}",
@@ -1,4 +1,5 @@
1
1
  .git
2
+ **/*.tar
2
3
  Dockerfile
3
4
  .github
4
5
  .agents
@@ -14,8 +15,6 @@ playwright-report
14
15
  test-results
15
16
  **/data
16
17
  *.log
17
- # 仅排除本机私有配置,与 Git 处理一致;公共 .env 与 dev/test/prod 随上下文构建。
18
- **/.env.local
19
- **/.env.*.local
20
- **/.env.runtime
21
- **/.hai/deploy
18
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
19
+ **/.env.*
20
+ **/.hai
@@ -22,8 +22,9 @@ playwright-report/
22
22
  # 本地数据目录
23
23
  **/data/
24
24
 
25
- # hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
25
+ # 构建与部署产物(镜像 / Compose tar / 私有运行配置)
26
26
  .hai/
27
+ *.tar
27
28
 
28
29
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
29
30
  .env.local
@@ -2,10 +2,6 @@
2
2
  FROM node:24-bookworm-slim AS build
3
3
  ARG SQLITE_PREBUILD_MIRROR
4
4
  ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
5
- ARG HAI_DEPLOY_ENVIRONMENT=prod
6
- ARG HAI_CORE_ENV=production
7
- ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
8
- ENV HAI_CORE_ENV=$HAI_CORE_ENV
9
5
 
10
6
  ENV PNPM_HOME=/pnpm
11
7
  ENV PATH=$PNPM_HOME:$PATH
@@ -13,11 +9,11 @@ ENV CI=true
13
9
  WORKDIR /workspace
14
10
 
15
11
  RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
16
- COPY pnpm-lock.yaml pnpm-workspace.yaml ./
17
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
12
+ # 本地 file: 归档和 workspace 文件必须先进入上下文,离线材料不能只复制 lockfile。
18
13
  COPY . .
14
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
19
15
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
20
- RUN pnpm build
16
+ RUN pnpm compile
21
17
 
22
18
  FROM build AS service-build
23
19
 
@@ -25,12 +21,8 @@ RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3;
25
21
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{fullstack.servPackageName}} --prod /prod/service
26
22
 
27
23
  FROM node:24-bookworm-slim AS service
28
- ARG HAI_DEPLOY_ENVIRONMENT=prod
29
- ARG HAI_CORE_ENV=production
30
24
 
31
25
  ENV NODE_ENV=production
32
- ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
33
- ENV HAI_CORE_ENV=$HAI_CORE_ENV
34
26
  ENV HOST=0.0.0.0
35
27
  ENV PORT=3000
36
28
  WORKDIR /app
@@ -44,10 +36,6 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
44
36
  CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
45
37
  CMD ["node", "dist/index.js"]
46
38
 
47
- FROM service AS runtime-with-data
48
-
49
- COPY --from=embedded-data --chown=node:node / /app/data/
50
-
51
39
  FROM service AS runtime
52
40
 
53
41
  {{#if fullstack.frontends.web}}
@@ -29,9 +29,6 @@ pnpm --filter {{projectName}}-app package
29
29
  pnpm --filter {{projectName}}-app cap:build:android:debug
30
30
  pnpm --filter {{projectName}}-app cap:build:android:release
31
31
  pnpm --filter {{projectName}}-app cap:build:ios
32
-
33
- # 当前模板的 deploy 入口默认执行 package,可按目标商店或 CI 平台替换
34
- pnpm --filter {{projectName}}-app deploy
35
32
  ```
36
33
 
37
34
  部署前确认 `PUBLIC_API_BASE` 在 `.env` 中指向可访问的后端服务。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000`。
@@ -13,9 +13,7 @@
13
13
  "dev:prod": "vite --mode prod",
14
14
  "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.app}}",
15
15
  "build": "pnpm paraglide:compile && vite build",
16
- "build:prod": "pnpm paraglide:compile && vite build --mode prod",
17
16
  "package": "pnpm cap:sync",
18
- "deploy": "pnpm package",
19
17
  "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.app}} --strictPort",
20
18
  "check": "pnpm typecheck",
21
19
  "typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
@@ -11,8 +11,8 @@ import { defineConfig } from 'vitest/config'
11
11
 
12
12
  const envDir = fileURLToPath(new URL('../../', import.meta.url))
13
13
 
14
- export default defineConfig(({ mode }) => {
15
- const envResult = core.config.loadEnvFiles({ mode, envDir })
14
+ export default defineConfig(({ mode, command }) => {
15
+ const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
16
16
  if (!envResult.success)
17
17
  throw new Error(envResult.error.message)
18
18
  const env = envResult.data
@@ -26,14 +26,11 @@ pnpm --filter {{projectName}}-desktop tauri:dev
26
26
  # 编译 WebView 前端产物到 dist/
27
27
  pnpm --filter {{projectName}}-desktop build
28
28
 
29
- # 打包桌面安装包
30
- pnpm --filter {{projectName}}-desktop package
31
-
32
- # 当前模板的 deploy 入口默认执行 package,可按发布平台替换
33
- pnpm --filter {{projectName}}-desktop deploy
29
+ # 在工程根打包桌面安装包;在对应系统构建 windows / linux / macos
30
+ pnpm build --target desktop --platform windows --arch x64
34
31
  ```
35
32
 
36
- 打包前需要安装 Rust 工具链和 Tauri 平台依赖。生产环境部署时同步调整 `.env` 中的 `PUBLIC_API_BASE` 与 `src-tauri/tauri.conf.json` 的 `connect-src` 白名单。
33
+ 打包前需要安装 Rust 工具链和 Tauri 平台依赖。构建不读取 .env.prod/.env.local;启动已安装程序前设置进程 PUBLIC_API_BASE(HTTP(S),不含凭据、查询和片段)。Rust 会在页面加载前注入公开地址,仅将该 Origin 加入 CSP;不需要为 test/prod 重打安装包。缺省连接 http://localhost:3000/api/v1。
37
34
 
38
35
  ## IAM 认证配置
39
36
 
@@ -12,10 +12,9 @@
12
12
  "dev:test": "vite --mode test",
13
13
  "dev:prod": "vite --mode prod",
14
14
  "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.desktop}}",
15
- "build": "pnpm paraglide:compile && vite build",
16
- "build:prod": "pnpm paraglide:compile && vite build --mode prod",
15
+ "compile": "pnpm paraglide:compile && vite build",
16
+ "build": "pnpm compile",
17
17
  "package": "pnpm tauri:build",
18
- "deploy": "pnpm package",
19
18
  "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.desktop}} --strictPort",
20
19
  "check": "pnpm typecheck",
21
20
  "typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
@@ -1,16 +1,87 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.desktop}}
3
+ use std::collections::HashMap;
4
+ use tauri::utils::config::{Csp, CspDirectiveSources};
5
+
6
+ /// 校验运行期 API 地址;例如 `https://api.example.test/api/v1`,不接收认证信息或查询参数。
7
+ fn parse_runtime_api(value: &str) -> Result<tauri::Url, &'static str> {
8
+ let url = tauri::Url::parse(value).map_err(|_| "Invalid PUBLIC_API_BASE")?;
9
+ if !["http", "https"].contains(&url.scheme())
10
+ || url.host_str().is_none()
11
+ || !url.username().is_empty()
12
+ || url.password().is_some()
13
+ || url.query().is_some()
14
+ || url.fragment().is_some()
15
+ {
16
+ return Err(
17
+ "PUBLIC_API_BASE must be an HTTP(S) URL without credentials, query or fragment",
18
+ );
19
+ }
20
+ Ok(url)
21
+ }
22
+
23
+ /// 仅将当前运行期 API 的 origin 加入 CSP;不放宽到所有 HTTP/HTTPS 站点。
24
+ fn runtime_csp(policy: Csp, origin: &str) -> Csp {
25
+ let mut directives: HashMap<String, CspDirectiveSources> = policy.into();
26
+ for name in ["connect-src", "img-src"] {
27
+ directives.entry(name.to_string()).or_default().push(origin);
28
+ }
29
+ directives.into()
30
+ }
31
+
3
32
  #[tauri::command]
4
33
  fn greet(name: &str) -> String {
5
- format!("Hello, {}! Welcome to {{projectName}} Desktop.", name)
34
+ format!("Hello, {}! Welcome to Desktop.", name)
6
35
  }
7
36
 
8
37
  #[cfg_attr(mobile, tauri::mobile_entry_point)]
9
38
  pub fn run() {
39
+ // 构建不读取部署环境;启动时从进程 PUBLIC_API_BASE 获取公开地址。
40
+ let api = parse_runtime_api(
41
+ &std::env::var("PUBLIC_API_BASE")
42
+ .unwrap_or_else(|_| "http://localhost:3000/api/v1".to_string()),
43
+ )
44
+ .expect("Invalid runtime API configuration");
45
+ let mut context = tauri::generate_context!();
46
+ if let Some(policy) = context.config_mut().app.security.csp.take() {
47
+ context.config_mut().app.security.csp =
48
+ Some(runtime_csp(policy, &api.origin().ascii_serialization()));
49
+ }
50
+ let script = format!(
51
+ "globalThis.__HAI_PUBLIC_CONFIG__={};",
52
+ serde_json::json!({ "apiBase": api.as_str() })
53
+ );
10
54
  tauri::Builder::default()
11
55
  .plugin(tauri_plugin_shell::init())
56
+ .append_invoke_initialization_script(script)
12
57
  .invoke_handler(tauri::generate_handler![greet])
13
- .run(tauri::generate_context!())
58
+ .run(context)
14
59
  .expect("error while running tauri application");
15
60
  }
61
+
62
+ #[cfg(test)]
63
+ mod tests {
64
+ use super::*;
65
+
66
+ #[test]
67
+ fn validates_runtime_address_and_scopes_csp() {
68
+ let api = parse_runtime_api("https://api.example.test:8443/custom/v2").unwrap();
69
+ let policy = runtime_csp(
70
+ Csp::Policy("default-src 'self'; connect-src 'self'".into()),
71
+ &api.origin().ascii_serialization(),
72
+ );
73
+ let directives: HashMap<String, CspDirectiveSources> = policy.into();
74
+ let sources: Vec<String> = directives["connect-src"].clone().into();
75
+ assert!(sources.contains(&"https://api.example.test:8443".to_string()));
76
+ assert!(!sources.contains(&"https:".to_string()));
77
+ for value in [
78
+ "file:///tmp/config",
79
+ "https://user:secret@host",
80
+ "https://host/?token=secret",
81
+ "relative",
82
+ ] {
83
+ assert!(parse_runtime_api(value).is_err());
84
+ }
85
+ }
86
+ }
16
87
  {{/if}}
@@ -3,11 +3,11 @@
3
3
  {
4
4
  "$schema": "https://schema.tauri.app/config/2",
5
5
  "productName": {{json fullstack.appName}},
6
- "version": "0.1.0",
6
+ "version": "../../../package.json",
7
7
  "identifier": "com.hai.{{fullstack.nativeAppIdSegment}}.desktop",
8
8
  "build": {
9
9
  "beforeDevCommand": "pnpm dev",
10
- "beforeBuildCommand": "pnpm build",
10
+ "beforeBuildCommand": "pnpm compile",
11
11
  "devUrl": "http://localhost:{{fullstack.ports.desktop}}",
12
12
  "frontendDist": "../dist"
13
13
  },
@@ -12,8 +12,8 @@ import { defineConfig } from 'vitest/config'
12
12
  const host = process.env.TAURI_DEV_HOST
13
13
  const envDir = fileURLToPath(new URL('../../', import.meta.url))
14
14
 
15
- export default defineConfig(({ mode }) => {
16
- const envResult = core.config.loadEnvFiles({ mode, envDir })
15
+ export default defineConfig(({ mode, command }) => {
16
+ const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
17
17
  if (!envResult.success)
18
18
  throw new Error(envResult.error.message)
19
19
  const env = envResult.data
@@ -20,14 +20,10 @@ pnpm --filter {{projectName}}-web dev
20
20
 
21
21
  # 编译 Web 前端
22
22
  pnpm --filter {{projectName}}-web build
23
-
24
- # 打包部署产物;默认等同 build,可按目标平台替换
25
- pnpm --filter {{projectName}}-web package
26
-
27
- # 当前模板的 deploy 入口默认执行 package,可替换为 CDN / 容器发布脚本
28
- pnpm --filter {{projectName}}-web deploy
29
23
  ```
30
24
 
25
+ 从工程根运行 `pnpm build --target docker` 构建并导出镜像。启用 deploy 功能后,运行根命令 `pnpm deploy --env test`,一次发布 API/Web;网络不通时用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。部署不重新构建。
26
+
31
27
  ## IAM 认证配置
32
28
 
33
29
  服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
@@ -13,9 +13,7 @@
13
13
  "dev:prod": "vite --mode prod",
14
14
  "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.web}}",
15
15
  "build": "pnpm paraglide:compile && vite build",
16
- "build:prod": "pnpm paraglide:compile && vite build --mode prod",
17
16
  "package": "pnpm build",
18
- "deploy": "pnpm package",
19
17
  "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.web}} --strictPort",
20
18
  "check": "pnpm typecheck",
21
19
  "typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
@@ -11,8 +11,8 @@ import { defineConfig } from 'vitest/config'
11
11
 
12
12
  const envDir = fileURLToPath(new URL('../../', import.meta.url))
13
13
 
14
- export default defineConfig(({ mode }) => {
15
- const envResult = core.config.loadEnvFiles({ mode, envDir })
14
+ export default defineConfig(({ mode, command }) => {
15
+ const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
16
16
  if (!envResult.success)
17
17
  throw new Error(envResult.error.message)
18
18
  const env = envResult.data
@@ -96,6 +96,7 @@ test('branded authentication, profile layout and blur validation work across scr
96
96
  await capture(page, '{{projectName}}-register-mobile.png', testInfo.outputPath('register-mobile.png'))
97
97
  expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
98
98
  await page.reload()
99
+ await expect(page.getByTestId('login-form')).toBeVisible()
99
100
  await capture(page, '{{projectName}}-login-mobile.png', testInfo.outputPath('login-mobile.png'))
100
101
  await page.setViewportSize({ width: 1440, height: 960 })
101
102
  await login(page, 'admin', adminPassword)
@@ -0,0 +1,7 @@
1
+ {
2
+ "images": [
3
+ { "name": "app", "target": "runtime", "containerPort": 3000 }{{#if fullstack.frontends.web}},
4
+ { "name": "web", "target": "web", "containerPort": 8080 }{{/if}}
5
+ ]{{#if fullstack.frontends.desktop}},
6
+ "desktop": ["apps/{{projectName}}-desktop"]{{/if}}
7
+ }
@@ -15,24 +15,16 @@
15
15
  "start:prod": "pnpm --filter {{fullstack.servPackageName}} start:prod",
16
16
  "dev:test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev:test",
17
17
  "dev:prod": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev:prod",
18
- "build:prod": "pnpm build && pnpm -r --if-present build:prod",
19
- "compile": "pnpm typecheck && pnpm build",
20
- "build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
21
- "docker:build": "docker build --target runtime -t {{projectName}}:latest .",
22
- "docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
23
- "docker:build:web": "docker build --target web -t {{projectName}}-web:latest ."{{/if}},
24
- "podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
25
- "podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
26
- "podman:build:web": "podman build --format docker --jobs=1 --target web -t {{projectName}}-web:latest ."{{/if}},
27
- "package": "pnpm build{{#if fullstack.frontends.web}} && pnpm --filter {{projectName}}-web package{{/if}}{{#if fullstack.frontends.app}} && pnpm --filter {{projectName}}-app package{{/if}}{{#if fullstack.frontends.desktop}} && pnpm --filter {{projectName}}-desktop package{{/if}}",
28
- "deploy": "pnpm package",
18
+ "compile": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
19
+ "build": "hai build",
20
+ "deploy": "hai deploy packages/{{projectName}}-serv",
29
21
  "check": "pnpm typecheck",
30
22
  "typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
31
23
  "lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
32
24
  "lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
33
25
  "test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present test",
34
26
  "test:watch": "pnpm -r --parallel --if-present test:watch",
35
- "test:e2e": "pnpm build && playwright test",
27
+ "test:e2e": "pnpm compile && node scripts/run-e2e.mjs",
36
28
  "clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
37
29
  },
38
30
  "dependencies": {
@@ -41,6 +33,7 @@
41
33
  "{{fullstack.sharedPackageName}}": "workspace:*"
42
34
  },
43
35
  "devDependencies": {
36
+ "@h-ai/cli": "{{haiPackageSpecifier}}",
44
37
  {{#if features.deploy}}
45
38
  "@h-ai/deploy": "{{haiPackageSpecifier}}",
46
39
  {{/if}}
@@ -35,11 +35,11 @@ CLI 已生成根目录 `.env.local`,`HAI_ADMIN_DEFAULT_PASSWORD` 为每个项
35
35
 
36
36
  ## 容器运行
37
37
 
38
- - 根目录 `pnpm docker:build` / `pnpm podman:build` 直接生成本服务镜像;Web 使用独立的 `docker:build:web` / `podman:build:web` 镜像。
38
+ 从工程根运行 `pnpm build --target docker` 构建并导出镜像。启用 deploy 功能后,运行根命令 `pnpm deploy --env test`;含 Web 镜像时一并发布 API/Web,部署不重新构建。网络不通时用 `pnpm deploy --env prod --bundle-only`,按说明手工搬运 tar。
39
+
39
40
  - 服务镜像直接由 `@h-ai/serv` 对外监听,配置挂载点为 `/app/config`,数据目录为 `/app/data`,不包含额外转发进程。
40
41
  - Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
41
42
  - YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
42
- - 将 SQLite/本地存储放入本目录 `data/` 后运行 `pnpm docker:build:data` / `pnpm podman:build:data`,可生成带初始数据的镜像,持续写入仍应使用 volume。
43
43
 
44
44
  ## IAM 认证配置
45
45
 
@@ -20,7 +20,6 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build": "tsup",
23
- "build:prod": "pnpm build",
24
23
  "check": "pnpm typecheck",
25
24
  "typecheck": "tsc --noEmit",
26
25
  "lint": "eslint .",
@@ -39,7 +39,7 @@ The scaffold theme configuration determines the themes compiled into frontend CS
39
39
 
40
40
  ## Validation
41
41
 
42
- Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation. Set `HAI_AUTH_EVIDENCE_DIR` to keep screenshots outside the test output directory. These browser checks do not replace native-shell or miniapp device acceptance.
42
+ Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation. Clean screenshots, reports, temporary projects, containers and helper processes in finally/teardown, including failure paths. These browser checks do not replace native-shell or miniapp device acceptance.
43
43
 
44
44
  ## IAM configuration
45
45
 
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
6
  <meta name="theme-color" content="#2563eb" />
7
7
  <title></title>
8
+ <script src="/runtime-config.js"></script>
8
9
  </head>
9
10
  <body>
10
11
  <div id="app"></div>
@@ -0,0 +1 @@
1
+ globalThis.__HAI_PUBLIC_CONFIG__ ??= {}
@@ -10,7 +10,8 @@ const DEFAULT_API_BASE = 'http://localhost:3000/api/v1'
10
10
  let initialized = false
11
11
 
12
12
  export function resolveApiBase(): string {
13
- return import.meta.env.PUBLIC_API_BASE ?? DEFAULT_API_BASE
13
+ const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { apiBase?: string } }).__HAI_PUBLIC_CONFIG__
14
+ return runtime?.apiBase || import.meta.env.PUBLIC_API_BASE || DEFAULT_API_BASE
14
15
  }
15
16
 
16
17
  export async function usesNativeTokenStorage(): Promise<boolean> {
@@ -5,8 +5,9 @@ declare const __APP_CORE_CONFIG__: Pick<CoreConfig, 'name' | 'version' | 'env' |
5
5
  logging?: Pick<NonNullable<CoreConfig['logging']>, 'level' | 'format'>
6
6
  }
7
7
 
8
- /** Vite 经 core 加载后注入的公开配置,修改 YAML 后重新构建客户端。 */
9
- export const appConfig = __APP_CORE_CONFIG__
8
+ /** 构建包含公开默认值;预览或容器启动时的公开配置覆盖默认值,不重建静态资源。 */
9
+ const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { core?: Partial<typeof __APP_CORE_CONFIG__> } }).__HAI_PUBLIC_CONFIG__?.core
10
+ export const appConfig = { ...__APP_CORE_CONFIG__, ...runtime, logging: { ...__APP_CORE_CONFIG__.logging, ...runtime?.logging } }
10
11
 
11
12
  export function initClientCore(): void {
12
13
  core.init({ logging: appConfig.logging })
@@ -1,4 +1,5 @@
1
1
  .git
2
+ **/*.tar
2
3
  .github
3
4
  .agents
4
5
  .claude
@@ -15,8 +16,6 @@ playwright-report
15
16
  test-results
16
17
  **/data
17
18
  *.log
18
- # 仅排除本机私有配置,与 Git 处理一致;公共 .env 与 dev/test/prod 随上下文构建。
19
- **/.env.local
20
- **/.env.*.local
21
- **/.env.runtime
22
- **/.hai/deploy
19
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
20
+ **/.env.*
21
+ **/.hai