@h-ai/cli 0.1.0-alpha.64 → 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 (122) hide show
  1. package/README.en.md +31 -9
  2. package/README.ja.md +30 -8
  3. package/README.md +38 -16
  4. package/dist/{chunk-X3RSYNBG.js → chunk-FWN5B54L.js} +94 -4
  5. package/dist/chunk-FWN5B54L.js.map +1 -0
  6. package/dist/chunk-QYX6V7H2.js +5613 -0
  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-QRF6B5T4.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
  10. package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-TSYEQTCY.js} +3 -3
  11. package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
  12. package/dist/index.js +733 -210
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-DUGLX4YB.js → src-RDQ2EIBT.js} +1054 -660
  15. package/dist/{src-DUGLX4YB.js.map → src-RDQ2EIBT.js.map} +1 -1
  16. package/package.json +9 -7
  17. package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
  18. package/templates/apps/api/.dockerignore.hbs +4 -0
  19. package/templates/apps/api/.gitignore.hbs +6 -5
  20. package/templates/apps/api/Dockerfile.hbs +5 -8
  21. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
  23. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
  24. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
  25. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +8 -2
  26. package/templates/apps/api/hai.build.json.hbs +1 -0
  27. package/templates/apps/api/package.json.hbs +13 -7
  28. package/templates/apps/fullstack/.dockerignore.hbs +4 -3
  29. package/templates/apps/fullstack/.gitignore.hbs +24 -10
  30. package/templates/apps/fullstack/Dockerfile.hbs +6 -8
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +5 -1
  33. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +8 -4
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +6 -2
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +8 -4
  39. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
  40. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +5 -1
  41. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +8 -4
  42. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +24 -1
  43. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  44. package/templates/apps/fullstack/package.json.hbs +16 -11
  45. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
  46. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
  47. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +8 -6
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +8 -2
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +4 -2
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +14 -12
  57. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
  58. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
  59. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
  60. package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
  61. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
  62. package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
  63. package/templates/apps/mobile-app/index.html.hbs +1 -0
  64. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  65. package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
  66. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  67. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  68. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
  69. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
  70. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
  71. package/templates/base/.dockerignore.hbs +4 -0
  72. package/templates/base/.gitignore.hbs +20 -5
  73. package/templates/base/Dockerfile.hbs +10 -11
  74. package/templates/base/docker/nginx.conf.hbs +6 -0
  75. package/templates/base/e2e/app.spec.ts.hbs +14 -0
  76. package/templates/base/hai.build.json.hbs +1 -0
  77. package/templates/base/package.json.hbs +25 -17
  78. package/templates/base/svelte.config.js.hbs +2 -0
  79. package/templates/base/tests/scaffold.test.ts.hbs +9 -3
  80. package/templates/base/vite.config.ts.hbs +11 -5
  81. package/templates/shared/.gitattributes +5 -0
  82. package/templates/shared/container-runtime-config.sh +30 -0
  83. package/templates/shared/hash-router.svelte.ts +83 -0
  84. package/templates/shared/run-e2e.mjs +65 -0
  85. package/templates/shared/vite-runtime-config.ts +54 -0
  86. package/templates/skills/AGENTS.md +15 -1
  87. package/templates/skills/bridges/admin/AGENTS.md +17 -1
  88. package/templates/skills/bridges/api/AGENTS.md +17 -3
  89. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
  90. package/templates/skills/bridges/h5/AGENTS.md +17 -1
  91. package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
  92. package/templates/skills/bridges/website/AGENTS.md +17 -1
  93. package/templates/skills/hai-ai/reference.md +2 -2
  94. package/templates/skills/hai-app-create/SKILL.md +5 -3
  95. package/templates/skills/hai-app-create/development.md +128 -2
  96. package/templates/skills/hai-app-create/reference.md +4 -4
  97. package/templates/skills/hai-app-review/SKILL.md +2 -2
  98. package/templates/skills/hai-app-tests/SKILL.md +2 -1
  99. package/templates/skills/hai-app-tests/reference.md +1 -1
  100. package/templates/skills/hai-build/SKILL.md +16 -1
  101. package/templates/skills/hai-core/SKILL.md +2 -1
  102. package/templates/skills/hai-deploy/SKILL.md +52 -252
  103. package/templates/skills/hai-deploy/middleware.md +88 -0
  104. package/templates/skills/hai-framework-sync/SKILL.md +1 -1
  105. package/templates/skills/hai-framework-sync/sync.md +1 -1
  106. package/templates/skills/hai-reach/reference.md +8 -8
  107. package/templates/skills/hai-spec-implement/SKILL.md +12 -9
  108. package/templates/skills/hai-spec-implement/changes.md +67 -0
  109. package/templates/skills/hai-spec-implement/templates.md +25 -3
  110. package/templates/skills/hai-spec-implement/workflow.md +7 -6
  111. package/templates/skills/hai-spec-review/SKILL.md +14 -5
  112. package/templates/skills/hai-spec-review/checklist.md +4 -3
  113. package/templates/skills/hai-storage/SKILL.md +2 -0
  114. package/templates/skills/hai-ui/reference.md +2 -0
  115. package/dist/chunk-WW2MIO6T.js +0 -992
  116. package/dist/chunk-WW2MIO6T.js.map +0 -1
  117. package/dist/chunk-X3RSYNBG.js.map +0 -1
  118. package/dist/cli-config-templates-QRF6B5T4.js +0 -5
  119. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
  120. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
  121. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/ProfileSettings.svelte.hbs → pages/settings.svelte.hbs} +0 -0
  122. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.hbs} +0 -0
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.64",
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.64",
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,11 +41,12 @@
41
41
  "yaml": "^2.9.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@h-ai/cache": "0.1.0-alpha.64",
45
- "@h-ai/deploy": "0.1.0-alpha.64",
46
- "@h-ai/reach": "0.1.0-alpha.64",
47
- "@h-ai/reldb": "0.1.0-alpha.64",
48
- "@h-ai/storage": "0.1.0-alpha.64",
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",
49
50
  "@types/fs-extra": "^11.0.4",
50
51
  "@types/node": "^26.6.5",
51
52
  "@types/prompts": "^2.4.9",
@@ -62,6 +63,7 @@
62
63
  "lint": "eslint .",
63
64
  "lint:fix": "eslint . --fix",
64
65
  "test": "vitest run",
66
+ "test:deploy-gates": "node tests/run-scaffold-gates.mjs --deploy",
65
67
  "test:e2e": "pnpm test:scaffold-gates",
66
68
  "test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
67
69
  "test:watch": "vitest",
@@ -32,6 +32,8 @@
32
32
  <title>{page.data.appConfig.name}</title>
33
33
  </svelte:head>
34
34
 
35
- <div class='min-h-screen bg-base-200'>
36
- {@render children()}
35
+ <div class='min-h-screen flex flex-col bg-base-200'>
36
+ <header class='navbar border-b border-base-300 bg-base-100 px-6'><span class='text-lg font-semibold'>{page.data.appConfig.name}</span></header>
37
+ <div class='flex-1'>{@render children()}</div>
38
+ <footer class='border-t border-base-300 bg-base-100 p-3 text-center text-xs text-base-content/60'>{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}</footer>
37
39
  </div>
@@ -1,4 +1,5 @@
1
1
  .git
2
+ **/*.tar
2
3
  .github
3
4
  .agents
4
5
  .claude
@@ -12,3 +13,6 @@ playwright-report
12
13
  test-results
13
14
  **/data
14
15
  *.log
16
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
17
+ **/.env.*
18
+ **/.hai
@@ -5,10 +5,11 @@ dist
5
5
  build
6
6
  playwright-report
7
7
  test-results
8
- .env
9
- .env.*
10
- !.env.example
11
- !.env.example.hbs
12
8
 
13
- # hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
9
+ # 构建与部署产物(镜像 / Compose tar / 私有运行配置)
14
10
  .hai/
11
+ *.tar
12
+
13
+ # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
14
+ .env.local
15
+ .env.*.local
@@ -1,5 +1,7 @@
1
1
  # syntax=docker/dockerfile:1.7
2
2
  FROM node:24-bookworm-slim AS build
3
+ ARG SQLITE_PREBUILD_MIRROR
4
+ ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
3
5
 
4
6
  ENV PNPM_HOME=/pnpm
5
7
  ENV PATH=$PNPM_HOME:$PATH
@@ -7,17 +9,16 @@ ENV CI=true
7
9
  WORKDIR /workspace
8
10
 
9
11
  RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
10
- COPY pnpm-lock.yaml pnpm-workspace.yaml ./
11
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
12
12
  COPY . .
13
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
13
14
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
14
- RUN pnpm build
15
+ RUN pnpm compile
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
15
17
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{api.servicePackageName}} --prod /prod/app
16
18
 
17
19
  FROM node:24-bookworm-slim AS runtime-base
18
20
 
19
21
  ENV NODE_ENV=production
20
- ENV HAI_CORE_ENV=production
21
22
  ENV HOST=0.0.0.0
22
23
  ENV PORT=3000
23
24
  WORKDIR /app
@@ -31,8 +32,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
31
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))"
32
33
  CMD ["node", "dist/index.js"]
33
34
 
34
- FROM runtime-base AS runtime-with-data
35
-
36
- COPY --from=embedded-data --chown=node:node / /app/data/
37
-
38
35
  FROM runtime-base AS runtime
@@ -19,8 +19,10 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "build": "tsup",
22
+ "check": "pnpm typecheck",
22
23
  "typecheck": "tsc --noEmit",
23
24
  "lint": "eslint .",
25
+ "lint:fix": "eslint . --fix",
24
26
  "test": "vitest run",
25
27
  "test:watch": "vitest",
26
28
  "clean": "rimraf dist .turbo"
@@ -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
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  name: {{projectName}} API Service
6
6
  version: 0.1.0
7
- env: ${HAI_ENV:development}
8
- debug: ${HAI_DEBUG:false}
7
+ env: ${HAI_CORE_ENV:development}
8
+ debug: ${HAI_CORE_DEBUG:false}
9
9
 
10
10
  logging:
11
11
  level: info
@@ -18,10 +18,10 @@ http:
18
18
  rpc: false
19
19
 
20
20
  cors:
21
- origin: ${HAI_CORS_ORIGIN:*}
22
- nativeOrigins: ${HAI_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
23
- {{#if features.iam}} credentials: ${HAI_CORS_CREDENTIALS:true}
24
- {{else}} credentials: ${HAI_CORS_CREDENTIALS:false}
21
+ origin: ${HAI_SERV_CORS_ORIGIN:*}
22
+ nativeOrigins: ${HAI_SERV_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
23
+ {{#if features.iam}} credentials: ${HAI_SERV_CORS_CREDENTIALS:true}
24
+ {{else}} credentials: ${HAI_SERV_CORS_CREDENTIALS:false}
25
25
  {{/if}} allowedHeaders:
26
26
  - Authorization
27
27
  - Content-Type
@@ -21,13 +21,19 @@
21
21
  ],
22
22
  "scripts": {
23
23
  "build": "tsup",
24
+ "check": "pnpm typecheck",
24
25
  "typecheck": "tsc --noEmit",
25
26
  "lint": "eslint .",
27
+ "lint:fix": "eslint . --fix",
26
28
  "test": "vitest run",
27
29
  "test:watch": "vitest",
28
30
  "clean": "rimraf dist .turbo",
29
- "dev": "pnpm build && node dist/index.js",
30
- "start": "node dist/index.js"
31
+ "dev": "pnpm build && node --env-file=.env --env-file=.env.dev --env-file-if-exists=.env.local dist/index.js",
32
+ "start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local dist/index.js",
33
+ "dev:test": "pnpm build && node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local dist/index.js",
34
+ "dev:prod": "pnpm build && pnpm start",
35
+ "start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local dist/index.js",
36
+ "start:prod": "pnpm start"
31
37
  },
32
38
  "dependencies": {
33
39
  "@h-ai/core": "{{haiPackageSpecifier}}",
@@ -0,0 +1 @@
1
+ { "images": [{ "name": "app", "target": "runtime", "containerPort": 3000 }] }
@@ -8,16 +8,21 @@
8
8
  "description": "Typed hai API workspace with separated contract and service packages.",
9
9
  "scripts": {
10
10
  "dev": "pnpm --filter {{api.servicePackageName}} dev",
11
- "compile": "pnpm typecheck && pnpm build",
12
- "build": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
13
- "docker:build": "docker build --target runtime -t {{projectName}}:latest .",
14
- "docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
15
- "podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
16
- "podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
11
+ "start": "pnpm --filter {{api.servicePackageName}} start",
12
+ "start:test": "pnpm --filter {{api.servicePackageName}} start:test",
13
+ "start:prod": "pnpm --filter {{api.servicePackageName}} start:prod",
14
+ "dev:test": "pnpm --filter {{api.servicePackageName}} dev:test",
15
+ "dev:prod": "pnpm --filter {{api.servicePackageName}} dev:prod",
16
+ "compile": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
17
+ "build": "hai build",
18
+ "deploy": "hai deploy apps/{{projectName}}-service",
19
+ "check": "pnpm typecheck",
17
20
  "typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
18
21
  "lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
22
+ "lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
19
23
  "test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
20
- "test:e2e": "pnpm build && playwright test",
24
+ "test:watch": "pnpm -r --parallel --if-present test:watch",
25
+ "test:e2e": "pnpm compile && node scripts/run-e2e.mjs",
21
26
  "clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
22
27
  },
23
28
  "dependencies": {
@@ -25,6 +30,7 @@
25
30
  "{{api.contractPackageName}}": "workspace:*"
26
31
  },
27
32
  "devDependencies": {
33
+ "@h-ai/cli": "{{haiPackageSpecifier}}",
28
34
  "@h-ai/api-client": "{{haiPackageSpecifier}}",
29
35
  {{#if features.crypto}}
30
36
  "@h-ai/crypto": "{{haiPackageSpecifier}}",
@@ -1,4 +1,5 @@
1
1
  .git
2
+ **/*.tar
2
3
  Dockerfile
3
4
  .github
4
5
  .agents
@@ -6,9 +7,6 @@ Dockerfile
6
7
  .codex
7
8
  .opencode
8
9
  .turbo
9
- .env
10
- .env.*
11
- !.env.example
12
10
  **/build
13
11
  **/dist
14
12
  **/node_modules
@@ -17,3 +15,6 @@ playwright-report
17
15
  test-results
18
16
  **/data
19
17
  *.log
18
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
19
+ **/.env.*
20
+ **/.hai
@@ -1,17 +1,31 @@
1
+ # 构建产物目录
1
2
  node_modules
2
- **/data/
3
- .turbo
4
- dist
5
3
  build
4
+ dist
5
+ .turbo
6
+ android
7
+ ios
6
8
  **/src/lib/paraglide
7
- playwright-report
8
- test-results
9
- .env
10
- .env.*
11
- !.env.example
12
- !.env.example.hbs
13
9
  **/src-tauri/gen/
14
10
  **/src-tauri/target/
11
+ **/hash-core/target/
12
+
13
+ # 日志测试目录
14
+ *.log
15
+ test-results/
16
+ playwright-report/
15
17
 
16
- # hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
18
+
19
+ # 系统文件
20
+ .DS_Store
21
+
22
+ # 本地数据目录
23
+ **/data/
24
+
25
+ # 构建与部署产物(镜像 / Compose tar / 私有运行配置)
17
26
  .hai/
27
+ *.tar
28
+
29
+ # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
30
+ .env.local
31
+ .env.*.local
@@ -1,5 +1,7 @@
1
1
  # syntax=docker/dockerfile:1.7
2
2
  FROM node:24-bookworm-slim AS build
3
+ ARG SQLITE_PREBUILD_MIRROR
4
+ ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
3
5
 
4
6
  ENV PNPM_HOME=/pnpm
5
7
  ENV PATH=$PNPM_HOME:$PATH
@@ -7,20 +9,20 @@ ENV CI=true
7
9
  WORKDIR /workspace
8
10
 
9
11
  RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
10
- COPY pnpm-lock.yaml pnpm-workspace.yaml ./
11
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
12
+ # 本地 file: 归档和 workspace 文件必须先进入上下文,离线材料不能只复制 lockfile。
12
13
  COPY . .
14
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
13
15
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
14
- RUN pnpm build
16
+ RUN pnpm compile
15
17
 
16
18
  FROM build AS service-build
17
19
 
20
+ 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
18
21
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{fullstack.servPackageName}} --prod /prod/service
19
22
 
20
23
  FROM node:24-bookworm-slim AS service
21
24
 
22
25
  ENV NODE_ENV=production
23
- ENV HAI_CORE_ENV=production
24
26
  ENV HOST=0.0.0.0
25
27
  ENV PORT=3000
26
28
  WORKDIR /app
@@ -34,10 +36,6 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
34
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))"
35
37
  CMD ["node", "dist/index.js"]
36
38
 
37
- FROM service AS runtime-with-data
38
-
39
- COPY --from=embedded-data --chown=node:node / /app/data/
40
-
41
39
  FROM service AS runtime
42
40
 
43
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`。
@@ -9,13 +9,16 @@
9
9
  "scripts": {
10
10
  "paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
11
11
  "paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
12
+ "dev:test": "vite --mode test",
13
+ "dev:prod": "vite --mode prod",
12
14
  "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.app}}",
13
15
  "build": "pnpm paraglide:compile && vite build",
14
16
  "package": "pnpm cap:sync",
15
- "deploy": "pnpm package",
16
17
  "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.app}} --strictPort",
18
+ "check": "pnpm typecheck",
17
19
  "typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
18
20
  "lint": "eslint .",
21
+ "lint:fix": "eslint . --fix",
19
22
  "test": "pnpm paraglide:compile && vitest run --passWithNoTests",
20
23
  "test:watch": "pnpm paraglide:compile && vitest",
21
24
  "clean": "rimraf dist build android ios .turbo src/lib/paraglide",
@@ -50,6 +53,7 @@
50
53
  "@capacitor/preferences": "{{fullstack.packageSpecifiers.capacitorPreferences}}",
51
54
  "@capacitor/push-notifications": "{{fullstack.packageSpecifiers.capacitorPushNotifications}}",
52
55
  "@capacitor/status-bar": "{{fullstack.packageSpecifiers.capacitorStatusBar}}",
56
+ "@h-ai/core": "{{haiPackageSpecifier}}",
53
57
  "@iconify-json/tabler": "{{fullstack.packageSpecifiers.iconifyJsonTabler}}",
54
58
  "@iconify/tailwind4": "{{fullstack.packageSpecifiers.iconifyTailwind}}",
55
59
  "@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
@@ -2,21 +2,25 @@
2
2
  {{#if fullstack.frontends.app}}
3
3
  import process from 'node:process'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { core } from '@h-ai/core'
5
6
  import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
6
7
  import { paraglideVitePlugin } from '@inlang/paraglide-js'
7
8
  import { svelte } from '@sveltejs/vite-plugin-svelte'
8
9
  import tailwindcss from '@tailwindcss/vite'
9
- import { loadEnv } from 'vite'
10
10
  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 env = loadEnv(mode, envDir, 'PUBLIC_')
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
+ if (!envResult.success)
17
+ throw new Error(envResult.error.message)
18
+ const env = envResult.data
19
+ Object.assign(process.env, env)
16
20
  const target = (env.PUBLIC_API_BASE || process.env.SERVICE_BASE_URL || 'http://127.0.0.1:3000').replace(/\/api\/v1\/?$/, '')
17
21
  const proxy = { '/api': { target, changeOrigin: true } }
18
22
  return {
19
- envDir,
23
+ envDir: false,
20
24
  server: { proxy },
21
25
  preview: { proxy },
22
26
  build: {
@@ -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
 
@@ -9,11 +9,14 @@
9
9
  "scripts": {
10
10
  "paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
11
11
  "paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
12
+ "dev:test": "vite --mode test",
13
+ "dev:prod": "vite --mode prod",
12
14
  "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.desktop}}",
13
- "build": "pnpm paraglide:compile && vite build",
15
+ "compile": "pnpm paraglide:compile && vite build",
16
+ "build": "pnpm compile",
14
17
  "package": "pnpm tauri:build",
15
- "deploy": "pnpm package",
16
18
  "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.desktop}} --strictPort",
19
+ "check": "pnpm typecheck",
17
20
  "typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
18
21
  "lint": "eslint src vite.config.ts svelte.config.js",
19
22
  "lint:fix": "eslint src vite.config.ts svelte.config.js --fix",
@@ -31,6 +34,7 @@
31
34
  },
32
35
  "devDependencies": {
33
36
  "@antfu/eslint-config": "{{fullstack.packageSpecifiers.antfuEslintConfig}}",
37
+ "@h-ai/core": "{{haiPackageSpecifier}}",
34
38
  "@iconify-json/tabler": "{{fullstack.packageSpecifiers.iconifyJsonTabler}}",
35
39
  "@iconify/tailwind4": "{{fullstack.packageSpecifiers.iconifyTailwind}}",
36
40
  "@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
@@ -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
  },
@@ -2,23 +2,27 @@
2
2
  {{#if fullstack.frontends.desktop}}
3
3
  import process from 'node:process'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { core } from '@h-ai/core'
5
6
  import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
6
7
  import { paraglideVitePlugin } from '@inlang/paraglide-js'
7
8
  import { svelte } from '@sveltejs/vite-plugin-svelte'
8
9
  import tailwindcss from '@tailwindcss/vite'
9
- import { loadEnv } from 'vite'
10
10
  import { defineConfig } from 'vitest/config'
11
11
 
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 env = loadEnv(mode, envDir, 'PUBLIC_')
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
+ if (!envResult.success)
18
+ throw new Error(envResult.error.message)
19
+ const env = envResult.data
20
+ Object.assign(process.env, env)
17
21
  const target = (env.PUBLIC_API_BASE || process.env.SERVICE_BASE_URL || 'http://127.0.0.1:3000').replace(/\/api\/v1\/?$/, '')
18
22
  const proxy = { '/api': { target, changeOrigin: true } }
19
23
 
20
24
  return {
21
- envDir,
25
+ envDir: false,
22
26
  preview: { proxy },
23
27
  build: {
24
28
  rolldownOptions: {
@@ -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 仅用于单进程。