@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68

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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -1,19 +1,25 @@
1
- .git
1
+ **/.git
2
+ **/node_modules
3
+ **/.pnpm-store
4
+ **/.cache
5
+ **/.svelte-kit
6
+ **/.turbo
7
+ **/build
8
+ **/dist
9
+ **/coverage
10
+ **/playwright-report
11
+ **/test-results
12
+ **/src-tauri/target
13
+ **/hash-core/target
14
+ tmp
2
15
  **/*.tar
3
16
  .github
4
17
  .agents
5
18
  .claude
6
19
  .codex
7
20
  .opencode
8
- .svelte-kit
9
- .turbo
10
21
  android
11
- build
12
- dist
13
22
  ios
14
- node_modules
15
- playwright-report
16
- test-results
17
23
  **/data
18
24
  *.log
19
25
  # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
@@ -32,3 +32,10 @@ playwright-report/
32
32
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
33
33
  .env.local
34
34
  .env.*.local
35
+
36
+ # 构建器生成的安装输入快照
37
+ **/images/dependencies/
38
+
39
+ # 项目本地缓存
40
+ **/.pnpm-store/
41
+ **/.cache/
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'node:crypto'
1
2
  import { mkdir } from 'node:fs/promises'
2
3
  import path from 'node:path'
3
4
  import process from 'node:process'
@@ -23,6 +24,16 @@ test('home page renders from browser', async ({ page }) => {
23
24
  await expect(page.locator('main, section').first()).toBeVisible()
24
25
  await expect(page.locator('header').first()).toBeVisible()
25
26
  await expect(page.locator('footer').first()).toBeAttached()
27
+ // 大屏使用可用空间,窄屏由组件内部滚动,不能撑宽整个页面。
28
+ for (const width of [390, 1440, 1920, 2560, 3840]) {
29
+ await page.setViewportSize({ width, height: 960 })
30
+ const availableWidth = await page.evaluate(() => document.documentElement.clientWidth)
31
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
32
+ const shell = page.locator('.hai-mobile-shell')
33
+ if (await shell.count())
34
+ expect((await shell.boundingBox())!.width).toBeGreaterThan(availableWidth - 2)
35
+ }
36
+ await page.setViewportSize({ width: 390, height: 844 })
26
37
  {{#if_eq appType 'h5'}}
27
38
  // 在手机宽度验证导航真正切换路由,避免样式失效但首页渲染测试仍通过。
28
39
  const nav = page.locator('nav')
@@ -57,7 +68,7 @@ test('configured IAM registration and password login', async ({ page, context })
57
68
  {{/if_eq}}
58
69
  const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
59
70
  await mkdir(evidence, { recursive: true })
60
- const username = `scaf${Date.now()}`
71
+ const username = `scaf${randomUUID().replaceAll('-', '').slice(0, 12)}`
61
72
  const password = 'ScaffoldPassword123'
62
73
  const authUrl = '{{#if isCapacitorApp}}/{{else}}/auth/login{{/if}}'
63
74
  await page.goto(authUrl)
@@ -66,11 +77,15 @@ test('configured IAM registration and password login', async ({ page, context })
66
77
  await expect(page.locator('footer')).toBeVisible()
67
78
  await page.screenshot({ path: path.join(evidence, '{{projectName}}-login.png'), fullPage: true })
68
79
  await page.getByRole('{{#if isCapacitorApp}}button{{else}}link{{/if}}', { name: /立即注册|Register now/i }).click()
69
- await expect(page.locator('input[name="username"]')).toBeVisible()
80
+ {{#unless isCapacitorApp}}
81
+ await expect(page).toHaveURL(/\/auth\/register(?:\?|$)/)
82
+ {{/unless}}
83
+ const registerForm = page.locator('.register-form')
84
+ await expect(registerForm.locator('#register-username')).toBeVisible()
70
85
  await page.screenshot({ path: path.join(evidence, '{{projectName}}-register.png'), fullPage: true })
71
86
  if (process.env.IAM_NOTIFICATION_URL) {
72
87
  // 协议弹窗留在认证页,关闭后继续填写注册表单。
73
- await page.getByRole('button', { name: /用户协议|User Agreement/i, exact: true }).click()
88
+ await registerForm.getByRole('button', { name: /用户协议|Terms of Service/i, exact: true }).click()
74
89
  await expect(page.getByRole('dialog')).toBeVisible()
75
90
  await expect(page.getByRole('dialog').getByRole('heading', { name: 'Configured registration agreement' })).toBeVisible()
76
91
  await expect(page.locator('iframe')).toHaveCount(0)
@@ -78,11 +93,11 @@ test('configured IAM registration and password login', async ({ page, context })
78
93
  await page.keyboard.press('Escape')
79
94
  await expect(page.getByRole('dialog')).not.toBeVisible()
80
95
  }
81
- await page.locator('input[name="username"]').fill(username)
82
- await page.locator('input[name="email"]').fill(`${username}@example.com`)
83
- await page.locator('input[type="password"]').nth(0).fill(password)
84
- await page.locator('input[type="password"]').nth(1).fill(password)
85
- await page.locator('button[type="submit"]').click()
96
+ await registerForm.locator('#register-username').fill(username)
97
+ await registerForm.locator('#register-email').fill(`${username}@example.com`)
98
+ await registerForm.locator('#register-password').fill(password)
99
+ await registerForm.locator('#register-confirm-password').fill(password)
100
+ await registerForm.locator('button[type="submit"]').click()
86
101
  await expect(page.locator('.register-form')).not.toBeVisible()
87
102
  {{#unless isCapacitorApp}}
88
103
  const me = await page.request.get('/api/auth/me')
@@ -1 +1 @@
1
- { "images": [{ "name": "app", "target": "runtime", "containerPort": {{#if isSvelteOnlyApp}}8080{{else}}3000{{/if}} }] }
1
+ { "images": [{ "name": "app", "dockerfile": "images/app.Dockerfile", "containerPort": {{#if isSvelteOnlyApp}}8080{{else}}3000{{/if}} }] }
@@ -1,48 +1,51 @@
1
- # syntax=docker/dockerfile:1.7
2
1
  {{#if isSvelteOnlyApp}}
3
- FROM node:24-bookworm-slim AS build
4
- ARG SQLITE_PREBUILD_MIRROR
5
- ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
2
+ FROM node:24.22.1-bookworm-slim AS build
6
3
 
7
4
  ENV PNPM_HOME=/pnpm
8
5
  ENV PATH=$PNPM_HOME:$PATH
9
6
  ENV CI=true
7
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
10
8
  WORKDIR /workspace
11
9
 
12
- RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
10
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
11
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
12
+ COPY images/dependencies/ ./
13
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
13
14
  COPY . .
14
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
15
15
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
16
16
  RUN pnpm compile
17
17
 
18
- FROM nginxinc/nginx-unprivileged:1.29-alpine AS runtime
18
+ FROM nginxinc/nginx-unprivileged:1.29.8-alpine AS runtime
19
19
 
20
- COPY --chmod=755 docker/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
21
- COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
20
+ COPY --chmod=755 images/web/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
21
+ COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
22
22
  COPY --from=build /workspace/dist /usr/share/nginx/html
23
23
 
24
24
  EXPOSE 8080
25
25
  HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
26
26
  CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
27
27
  {{else}}
28
- FROM node:24-bookworm-slim AS build
29
- ARG SQLITE_PREBUILD_MIRROR
28
+ FROM node:24.22.1-bookworm-slim AS build
29
+ ARG SQLITE_PREBUILD_MIRROR=
30
30
  ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
31
31
 
32
32
  ENV PNPM_HOME=/pnpm
33
33
  ENV PATH=$PNPM_HOME:$PATH
34
34
  ENV CI=true
35
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
35
36
  WORKDIR /workspace
36
37
 
37
- RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
38
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
39
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
40
+ COPY images/dependencies/ ./
41
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
38
42
  COPY . .
39
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
40
43
  RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
41
44
  RUN pnpm compile
42
- 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
43
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{projectName}} --prod /prod/app
45
+ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds 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
46
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{projectName}} --prod /prod/app
44
47
 
45
- FROM node:24-bookworm-slim AS runtime-base
48
+ FROM node:24.22.1-bookworm-slim AS runtime-base
46
49
 
47
50
  ENV NODE_ENV=production
48
51
  ENV HOST=0.0.0.0
@@ -58,5 +61,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
58
61
  CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
59
62
  CMD ["node", "build"]
60
63
 
61
- FROM runtime-base AS runtime
62
64
  {{/if}}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
3
3
  "name": "{{projectName}}",
4
- "version": "0.1.0-alpha.66",
4
+ "version": "0.1.0-alpha.68",
5
5
  "private": true,
6
6
  "packageManager": "pnpm@12.11.2",
7
7
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
@@ -25,7 +25,9 @@
25
25
  "dev:prod": "vite dev --mode prod",
26
26
  "compile": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
27
27
  "build": "hai build",
28
+ {{#if features.deploy}}
28
29
  "deploy": "hai deploy",
30
+ {{/if}}
29
31
  {{#unless isSvelteOnlyApp}}
30
32
  "start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js",
31
33
  "start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local build/index.js",
@@ -57,6 +59,9 @@
57
59
  {{#if isSvelteOnlyApp}}
58
60
  "@h-ai/api-client": "{{haiPackageSpecifier}}",
59
61
  "@h-ai/capacitor": "{{haiPackageSpecifier}}",
62
+ {{#if features.crypto}}
63
+ "@h-ai/crypto": "{{haiPackageSpecifier}}",
64
+ {{/if}}
60
65
  "@h-ai/ui": "{{haiPackageSpecifier}}"
61
66
  {{else}}
62
67
  "@h-ai/core": "{{haiPackageSpecifier}}",
@@ -73,16 +78,19 @@
73
78
  "@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
74
79
  "@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
75
80
  "@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
76
- "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
77
- "@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
81
+ "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
78
82
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/if}}{{#if features.capacitor}},
79
83
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/if}}{{#if isCapacitorApp}}{{#unless features.api-client}},
80
84
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/unless}}{{#unless features.capacitor}},
81
85
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/unless}}{{/if}},
86
+ "sm-crypto": "0.5.7",
82
87
  "zod": "{{versions.zod}}"
83
88
  {{/if}}
84
89
  },
85
90
  "devDependencies": {
91
+ {{#if features.deploy}}
92
+ "@h-ai/deploy": "{{haiPackageSpecifier}}",
93
+ {{/if}}
86
94
  "@h-ai/cli": "{{haiPackageSpecifier}}",
87
95
  {{#if isSvelteOnlyApp}}
88
96
  "@h-ai/core": "{{haiPackageSpecifier}}",
@@ -1,5 +1,6 @@
1
1
  import process from 'node:process'
2
2
  import { defineConfig } from '@playwright/test'
3
+ import { createE2eNotificationEnv, createE2eServerEnv, createE2eTransportRing } from './scripts/e2e-env.js'
3
4
 
4
5
  const DEFAULT_BASE_URL = 'http://localhost:4173'
5
6
  const baseURL = process.env.BASE_URL || DEFAULT_BASE_URL
@@ -35,9 +36,9 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
35
36
  */
36
37
  export default defineConfig({
37
38
  testDir: './e2e',
38
- fullyParallel: false,
39
+ fullyParallel: true,
39
40
  forbidOnly: !!process.env.CI,
40
- retries: process.env.CI ? 2 : 0,
41
+ retries: 0,
41
42
  workers: 2,
42
43
  reporter: 'list',
43
44
  timeout: 30_000,
@@ -45,13 +46,23 @@ export default defineConfig({
45
46
  use: {
46
47
  baseURL,
47
48
  channel: 'chrome',
48
- trace: 'on-first-retry',
49
+ trace: 'retain-on-failure',
49
50
  screenshot: 'only-on-failure',
50
51
  },
51
52
 
52
53
  webServer: {
53
54
  command: `pnpm build && pnpm preview --host ${previewHost} --port ${previewPort} --strictPort`,
54
- env: { HAI_E2E: '1', BASE_URL: baseURL },
55
+ env: createE2eServerEnv({
56
+ ...createE2eNotificationEnv(),
57
+ HAI_CORE_ENV: 'test',
58
+ BASE_URL: baseURL,
59
+ PUBLIC_API_BASE: process.env.PUBLIC_API_BASE || '',
60
+ HAI_DB: JSON.stringify({ type: 'sqlite', database: `${process.env.HAI_E2E_DATA_ROOT}/app.db` }),
61
+ HAI_CACHE: JSON.stringify({ type: 'memory' }),
62
+ HAI_STORAGE: JSON.stringify({ type: 'local', root: `${process.env.HAI_E2E_DATA_ROOT}/uploads` }),
63
+ HAI_VECDB: JSON.stringify({ type: 'lancedb', path: `${process.env.HAI_E2E_DATA_ROOT}/vecdb` }),
64
+ HAI_CRYPTO_TRANSPORT_SERVERKEYRING: createE2eTransportRing(),
65
+ }),
55
66
  url: baseURL,
56
67
  reuseExistingServer: false,
57
68
  timeout: 180_000,
@@ -5,10 +5,16 @@
5
5
  */
6
6
 
7
7
  import type { Handle } from '@sveltejs/kit'
8
+ {{#if features.cache}}
9
+ import process from 'node:process'
10
+ {{/if}}
8
11
  {{#if hasI18n}}
9
12
  import { paraglideMiddleware } from '$lib/paraglide/server.js'
10
13
  {{/if}}
11
14
  import { initApp } from '$lib/server/init.js'
15
+ {{#if features.cache}}
16
+ import { cache } from '@h-ai/cache'
17
+ {{/if}}
12
18
  import { core } from '@h-ai/core'
13
19
  {{#if features.iam}}
14
20
  import { iam } from '@h-ai/iam'
@@ -54,16 +60,14 @@ const i18nHandle: Handle = async ({ event, resolve }) => {
54
60
  if (event.url.pathname.startsWith('/api/')) {
55
61
  const locale = event.cookies.get('PARAGLIDE_LOCALE') ?? '{{defaultLocale}}'
56
62
  event.locals.locale = locale
57
- kit.i18n.setLocale(locale)
58
- return resolve(event)
63
+ return kit.i18n.runWithLocale(locale, () => resolve(event))
59
64
  }
60
65
 
61
66
  return paraglideMiddleware(event.request, async ({ locale }) => {
62
67
  event.locals.locale = locale
63
- kit.i18n.setLocale(locale)
64
- return resolve(event, {
68
+ return kit.i18n.runWithLocale(locale, () => resolve(event, {
65
69
  transformPageChunk: ({ html }) => html.replace('%lang%', locale),
66
- })
70
+ }))
67
71
  })
68
72
  }
69
73
 
@@ -95,7 +99,12 @@ const haiHandle = kit.createHandle({
95
99
  operations: () => iam.auth,
96
100
  },
97
101
  {{/if}}
98
- rateLimit: { windowMs: 60000, maxRequests: 200 },
102
+ {{#if features.cache}}
103
+ rateLimit: { windowMs: 60000, maxRequests: 200, store: kit.middleware.createCacheRateLimitStore(cache, { namespace: '{{projectName}}', allowMemory: process.env.NODE_ENV !== 'production' || process.env.HAI_E2E === '1' }) },
104
+ {{else}}
105
+ // 未选择cache能力时不启用限流;启用前须接入共享原子store。
106
+ rateLimit: false,
107
+ {{/if}}
99
108
  logging: { logBody: false },
100
109
  onError: (error: unknown, _event) => {
101
110
  core.logger.error('Request error:', { error })
@@ -33,7 +33,7 @@ import type { AIConfigInput } from '@h-ai/ai'
33
33
  {{#if features.audit}}
34
34
  import type { AuditInitConfigInput } from '@h-ai/audit'
35
35
  {{/if}}
36
- import type { CoreConfig } from '@h-ai/core'
36
+ import type { CoreConfig, HaiResult } from '@h-ai/core'
37
37
  {{#if features.iam}}
38
38
  import type { IamConfigSettingsInput } from '@h-ai/iam'
39
39
  {{/if}}
@@ -44,7 +44,7 @@ import type { ReachConfigInput } from '@h-ai/reach'
44
44
  import type { StorageConfigInput } from '@h-ai/storage'
45
45
  {{/if}}
46
46
  {{#if features.ai}}
47
- import { ai } from '@h-ai/ai'
47
+ import { ai, AIConfigSchema } from '@h-ai/ai'
48
48
  {{/if}}
49
49
  {{#if features.audit}}
50
50
  import { audit, AuditInitConfigSchema } from '@h-ai/audit'
@@ -68,6 +68,9 @@ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
68
68
  {{#if features.storage}}
69
69
  import { storage, StorageConfigSchema } from '@h-ai/storage'
70
70
  {{/if}}
71
+ {{#if features.vecdb}}
72
+ import { vecdb, VecdbConfigSchema } from '@h-ai/vecdb'
73
+ {{/if}}
71
74
 
72
75
  {{#if features.db}}
73
76
  type DbConfigInput = Parameters<typeof reldb.init>[0]
@@ -77,14 +80,46 @@ type CacheConfigInput = Parameters<typeof cache.init>[0]
77
80
  {{/if}}
78
81
 
79
82
  let initialized = false
83
+ let initializing: Promise<void> | undefined
84
+ let closing: Promise<void> | undefined
85
+ let cleanupPending = false
80
86
 
81
87
  /**
82
88
  * 初始化应用
83
89
  */
84
90
  export async function initApp(): Promise<void> {
91
+ if (closing)
92
+ await closing
85
93
  if (initialized)
86
94
  return
95
+ if (!initializing) {
96
+ initializing = (async () => {
97
+ if (cleanupPending)
98
+ await cleanupModules()
99
+ cleanupPending = true
100
+ try {
101
+ await doInit()
102
+ initialized = true
103
+ cleanupPending = false
104
+ }
105
+ catch (error) {
106
+ try {
107
+ await cleanupModules()
108
+ }
109
+ catch (cleanupError) {
110
+ throw new AggregateError([error, cleanupError], 'Application startup and cleanup failed')
111
+ }
112
+ throw error
113
+ }
114
+ })().finally(() => {
115
+ initializing = undefined
116
+ })
117
+ }
118
+ return initializing
119
+ }
87
120
 
121
+ /** 实际启动只有一个调用者执行,失败由外层检查清理结果后再抛出。 */
122
+ async function doInit(): Promise<void> {
88
123
  // 1. 加载配置
89
124
  core.init({
90
125
  configDir: './config',
@@ -115,6 +150,16 @@ export async function initApp(): Promise<void> {
115
150
  if (!auditValidation.success)
116
151
  throw new Error(`Audit config invalid: ${auditValidation.error.message}`)
117
152
  {{/if}}
153
+ {{#if features.vecdb}}
154
+ const vectorValidation = core.config.validate('vecdb', VecdbConfigSchema)
155
+ if (!vectorValidation.success)
156
+ throw new Error(`VecDB config invalid: ${vectorValidation.error.message}`)
157
+ {{/if}}
158
+ {{#if features.ai}}
159
+ const aiValidation = core.config.validate('ai', AIConfigSchema)
160
+ if (!aiValidation.success)
161
+ throw new Error(`AI config invalid: ${aiValidation.error.message}`)
162
+ {{/if}}
118
163
 
119
164
  {{#if features.db}}
120
165
  // ─── 获取配置 ───
@@ -130,13 +175,16 @@ export async function initApp(): Promise<void> {
130
175
  const auditConfig = core.config.getOrThrow<AuditInitConfigInput>('audit')
131
176
  {{/if}}
132
177
  {{#if features.storage}}
133
- const storageConfig = core.config.get<StorageConfigInput>('storage')
178
+ const storageConfig = core.config.getOrThrow<StorageConfigInput>('storage')
134
179
  {{/if}}
135
180
  {{#if features.ai}}
136
- const aiConfig = core.config.get<AIConfigInput>('ai')
181
+ const aiConfig = core.config.getOrThrow<AIConfigInput>('ai')
182
+ {{/if}}
183
+ {{#if features.vecdb}}
184
+ const vectorConfig = core.config.getOrThrow<Parameters<typeof vecdb.init>[0]>('vecdb')
137
185
  {{/if}}
138
186
  {{#if features.reach}}
139
- const reachConfig = core.config.get<ReachConfigInput>('reach')
187
+ const reachConfig = core.config.getOrThrow<ReachConfigInput>('reach')
140
188
  {{/if}}
141
189
 
142
190
  {{#if features.db}}
@@ -166,26 +214,25 @@ export async function initApp(): Promise<void> {
166
214
  {{/if}}
167
215
 
168
216
  {{#if features.storage}}
169
- // ─── 初始化存储(可选) ───
170
- if (storageConfig) {
171
- const storageValidation = core.config.validate('storage', StorageConfigSchema)
172
- if (storageValidation.success) {
173
- const storageResult = await storage.init(storageConfig)
174
- if (!storageResult.success) {
175
- core.logger.warn('Storage initialization failed', { error: storageResult.error.message })
176
- }
177
- }
178
- }
217
+ const storageValidation = core.config.validate('storage', StorageConfigSchema)
218
+ if (!storageValidation.success)
219
+ throw new Error(`Storage config invalid: ${storageValidation.error.message}`)
220
+ const storageResult = await storage.init(storageConfig)
221
+ if (!storageResult.success)
222
+ throw new Error(`Storage initialization failed: ${storageResult.error.message}`)
223
+ {{/if}}
224
+
225
+ {{#if features.vecdb}}
226
+ // 共享向量存储必须在 AI 选择默认 Store 之前就绪。
227
+ const vectorResult = await vecdb.init(vectorConfig)
228
+ if (!vectorResult.success)
229
+ throw new Error(`VecDB initialization failed: ${vectorResult.error.message}`)
179
230
  {{/if}}
180
231
 
181
232
  {{#if features.reach}}
182
- // ─── 初始化触达服务(可选) ───
183
- if (reachConfig) {
184
- const reachResult = await reach.init(reachConfig)
185
- if (!reachResult.success) {
186
- core.logger.warn('Reach initialization failed', { error: reachResult.error.message })
187
- }
188
- }
233
+ const reachResult = await reach.init(reachConfig)
234
+ if (!reachResult.success)
235
+ throw new Error(`Reach initialization failed: ${reachResult.error.message}`)
189
236
  {{/if}}
190
237
 
191
238
  {{#if features.iam}}
@@ -205,15 +252,77 @@ export async function initApp(): Promise<void> {
205
252
  {{/if}}
206
253
 
207
254
  {{#if features.ai}}
208
- // ─── 初始化 AI(可选) ───
209
- if (aiConfig?.llm?.apiKey) {
210
- const aiResult = await ai.init(aiConfig)
211
- if (!aiResult.success) {
212
- core.logger.warn('AI initialization failed', { error: aiResult.error.message })
213
- }
214
- }
255
+ // 显式启用的 AI 不得在持久 Store 不可用时降级为启动成功。
256
+ const aiResult = await ai.init(aiConfig)
257
+ if (!aiResult.success)
258
+ throw new Error(`AI initialization failed: ${aiResult.error.message}`)
215
259
  {{/if}}
216
260
 
217
- initialized = true
218
261
  core.logger.info('Application initialized', { app: core.config.getOrThrow<CoreConfig>('core').name })
219
262
  }
263
+
264
+ /** 逆序关闭所有应用拥有的模块;保留失败义务以便下一次重试。 */
265
+ async function cleanupModules(): Promise<void> {
266
+ const failures: unknown[] = []
267
+ const closers: Array<() => Promise<HaiResult<void> | void>> = [
268
+ {{#if features.ai}}
269
+ () => ai.close(),
270
+ {{/if}}
271
+ {{#if features.audit}}
272
+ () => audit.close(),
273
+ {{/if}}
274
+ {{#if features.iam}}
275
+ () => iam.close(),
276
+ {{/if}}
277
+ {{#if features.reach}}
278
+ () => reach.close(),
279
+ {{/if}}
280
+ {{#if features.vecdb}}
281
+ () => vecdb.close(),
282
+ {{/if}}
283
+ {{#if features.storage}}
284
+ () => storage.close(),
285
+ {{/if}}
286
+ {{#if features.cache}}
287
+ () => cache.close(),
288
+ {{/if}}
289
+ {{#if features.db}}
290
+ () => reldb.close(),
291
+ {{/if}}
292
+ {{#if features.crypto}}
293
+ () => crypto.close(),
294
+ {{/if}}
295
+ ]
296
+ for (const close of closers) {
297
+ try {
298
+ const result = await close()
299
+ if (result && !result.success)
300
+ failures.push(result.error)
301
+ }
302
+ catch (error) {
303
+ failures.push(error)
304
+ }
305
+ }
306
+ initialized = false
307
+ cleanupPending = failures.length > 0
308
+ if (cleanupPending)
309
+ throw new AggregateError(failures, 'Application cleanup failed; retry closeApp()')
310
+ }
311
+
312
+ /**
313
+ * 关闭应用模块并与并发启动/关闭合并;清理失败抛错并允许重试。
314
+ * @example `await closeApp()`
315
+ */
316
+ export async function closeApp(): Promise<void> {
317
+ if (!closing) {
318
+ const pending = initializing
319
+ closing = (async () => {
320
+ if (pending)
321
+ await pending.catch(() => undefined)
322
+ await cleanupModules()
323
+ })().finally(() => {
324
+ closing = undefined
325
+ })
326
+ }
327
+ return closing
328
+ }
@@ -27,7 +27,7 @@ import { publicRuntimeConfig } from './scripts/vite-runtime-config.js'
27
27
  {{/if}}
28
28
 
29
29
  export default defineConfig(({ mode, command }) => {
30
- const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir: process.cwd() })
30
+ const envResult = command === 'build' || process.env.HAI_E2E === '1' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir: process.cwd() })
31
31
  if (!envResult.success)
32
32
  throw new Error(envResult.error.message)
33
33
  const env = envResult.data
@@ -53,7 +53,7 @@ export default defineConfig(({ mode, command }) => {
53
53
  {{/if}}
54
54
  plugins: [
55
55
  {{#if isSvelteOnlyApp}}
56
- publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, core: appConfig }),
56
+ publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, {{#if features.crypto}}apiTransport: process.env.PUBLIC_API_TRANSPORT, apiKeyExchangePath: process.env.PUBLIC_API_KEY_EXCHANGE_PATH, {{/if}}core: appConfig }),
57
57
  svelte(),
58
58
  {{else}}
59
59
  sveltekit(),
@@ -80,7 +80,9 @@ export default defineConfig(({ mode, command }) => {
80
80
  {{/if}}
81
81
  {{#unless isSvelteOnlyApp}}
82
82
  ssr: {
83
- noExternal: [/@h-ai\//],
83
+ noExternal: ['@h-ai/ui', '@h-ai/kit'],
84
+ // sm-crypto 同时列入生产依赖,避免 adapter-node 再次打包其动态 require。
85
+ external: ['@h-ai/core', 'sm-crypto'],
84
86
  },
85
87
  {{/unless}}
86
88
  server: {
@@ -7,11 +7,16 @@ import { kit } from '@h-ai/kit'
7
7
 
8
8
  export const POST: RequestHandler = kit.handler(async ({ cookies, locals }) => {
9
9
  const token = cookies.get('session_token')
10
- if (token) {
11
- const result = await iam.auth.logout(token)
12
- if (!result.success)
13
- return kit.response.fromError(result.error, locals.requestId)
10
+ try {
11
+ if (token) {
12
+ const result = await iam.auth.logout(token)
13
+ if (!result.success)
14
+ return kit.response.fromError(result.error, locals.requestId)
15
+ }
16
+ return kit.response.ok({ authenticated: false }, locals.requestId)
17
+ }
18
+ finally {
19
+ // 本地Cookie清理不代表服务端撤销成功,失败响应必须保留。
20
+ cookies.delete('session_token', { path: '/' })
14
21
  }
15
- cookies.delete('session_token', { path: '/' })
16
- return kit.response.ok({ authenticated: false }, locals.requestId)
17
22
  })