@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.21

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 (254) hide show
  1. package/README.en.md +74 -154
  2. package/README.ja.md +74 -154
  3. package/README.md +74 -154
  4. package/dist/{chunk-AKV2CQZV.js → chunk-2ZL5ORQ2.js} +316 -97
  5. package/dist/chunk-2ZL5ORQ2.js.map +1 -0
  6. package/dist/{chunk-EF7WOFWZ.js → chunk-L53P43B2.js} +1312 -163
  7. package/dist/chunk-L53P43B2.js.map +1 -0
  8. package/dist/cli-config-templates-LWMD2I6V.js +5 -0
  9. package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
  10. package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
  11. package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +30118 -3253
  14. package/dist/index.js.map +1 -1
  15. package/dist/metafile-esm.json +1 -1
  16. package/package.json +22 -18
  17. package/templates/README.md +45 -30
  18. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
  19. package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
  20. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
  21. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
  22. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
  23. package/templates/apps/api/.dockerignore.hbs +10 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +8 -0
  25. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
  26. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
  27. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
  28. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  29. package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +6 -2
  30. package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +24 -2
  31. package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
  32. package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +74 -24
  33. package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  34. package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +153 -37
  35. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
  36. package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +106 -33
  37. package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
  38. package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  39. package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
  40. package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
  41. package/templates/apps/api/eslint.config.js.hbs +3 -1
  42. package/templates/apps/api/images/serv.Dockerfile.hbs +15 -10
  43. package/templates/apps/api/playwright.config.ts.hbs +23 -19
  44. package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
  45. package/templates/apps/fullstack/.dockerignore.hbs +10 -0
  46. package/templates/apps/fullstack/.gitignore.hbs +7 -2
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +14 -12
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +5 -3
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -1
  51. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
  52. package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
  55. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
  56. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -8
  57. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +3 -3
  58. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
  59. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
  60. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +0 -2
  61. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
  62. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
  63. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  64. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +9 -9
  65. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
  66. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +1 -0
  67. package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
  68. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
  69. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
  70. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
  71. package/templates/apps/fullstack/docs/security-configuration.md.hbs +4 -1
  72. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +140 -28
  73. package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
  74. package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
  75. package/templates/apps/fullstack/images/serv.Dockerfile.hbs +19 -11
  76. package/templates/apps/fullstack/images/web.Dockerfile.hbs +17 -8
  77. package/templates/apps/fullstack/package.json.hbs +2 -1
  78. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +7 -5
  79. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
  80. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
  81. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  82. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +17 -7
  83. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +13 -7
  84. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +17 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +10 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
  87. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  88. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
  89. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
  90. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
  91. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
  92. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
  93. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
  94. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +147 -33
  95. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
  96. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +146 -30
  97. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
  98. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +142 -26
  99. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
  100. package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +48 -66
  102. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +1 -0
  103. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +1 -0
  104. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +1 -0
  105. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
  106. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
  107. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
  108. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
  109. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
  110. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
  111. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
  112. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
  113. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
  114. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
  115. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
  116. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
  117. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
  118. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
  119. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
  120. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
  121. package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
  122. package/templates/apps/fullstack/playwright.config.ts.hbs +25 -3
  123. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -14
  124. package/templates/apps/h5/src/routes/+layout.svelte.hbs +74 -30
  125. package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
  126. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
  127. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
  128. package/templates/apps/mobile-app/capacitor.config.ts.hbs +5 -2
  129. package/templates/apps/mobile-app/messages/en-US.json +2 -2
  130. package/templates/apps/mobile-app/messages/ja-JP.json +2 -2
  131. package/templates/apps/mobile-app/messages/zh-CN.json +2 -2
  132. package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
  133. package/templates/apps/mobile-app/src/lib/api.ts.hbs +20 -6
  134. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
  135. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
  136. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -1
  137. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
  138. package/templates/apps/website/scripts/static-website.mjs.hbs +19 -6
  139. package/templates/apps/website/src/routes/+layout.svelte.hbs +56 -29
  140. package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
  141. package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
  142. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
  143. package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
  144. package/templates/base/.dockerignore.hbs +10 -0
  145. package/templates/base/.gitignore.hbs +7 -2
  146. package/templates/base/e2e/app.spec.ts.hbs +130 -36
  147. package/templates/base/eslint-rules.js +44 -25
  148. package/templates/base/eslint.config.js.hbs +3 -1
  149. package/templates/base/images/web.Dockerfile.hbs +25 -14
  150. package/templates/base/package.json.hbs +13 -7
  151. package/templates/base/patches/daisyui-5.7.50.patch +8 -0
  152. package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
  153. package/templates/base/playwright.config.ts +3 -1
  154. package/templates/base/pnpm-workspace.yaml.hbs +33 -16
  155. package/templates/base/src/app.d.ts.hbs +1 -1
  156. package/templates/base/src/app.html.hbs +1 -0
  157. package/templates/base/src/hooks.server.ts.hbs +44 -33
  158. package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
  159. package/templates/base/src/lib/server/init.ts.hbs +146 -35
  160. package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
  161. package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
  162. package/templates/base/svelte.config.js.hbs +10 -100
  163. package/templates/base/tests/scaffold.test.ts.hbs +12 -3
  164. package/templates/base/tsconfig.json.hbs +7 -4
  165. package/templates/base/vite.config.ts.hbs +108 -14
  166. package/templates/base/vitest.config.ts.hbs +6 -2
  167. package/templates/features/ai/routes/api/chat/+server.ts +3 -1
  168. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
  169. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
  170. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
  171. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
  172. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
  173. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
  174. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
  175. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
  176. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
  177. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
  178. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
  179. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
  180. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +14 -3
  181. package/templates/shared/browser-check/startup.js +115 -0
  182. package/templates/shared/browser-check/update.css +37 -0
  183. package/templates/shared/browser-check/update.html +19 -0
  184. package/templates/shared/browser-startup.spec.ts +23 -0
  185. package/templates/shared/build-metadata.ts +18 -4
  186. package/templates/shared/e2e-env.ts +22 -6
  187. package/templates/shared/native-name.d.mts +5 -0
  188. package/templates/shared/native-name.mjs +94 -0
  189. package/templates/shared/public-base-path.mjs +17 -4
  190. package/templates/shared/run-e2e.mjs +46 -12
  191. package/templates/shared/run-node.mjs +56 -0
  192. package/templates/shared/run-tauri.mjs +74 -0
  193. package/templates/shared/vite-runtime-config.ts +9 -3
  194. package/templates/skills/AGENTS.md +10 -73
  195. package/templates/skills/bridges/admin/AGENTS.md +9 -65
  196. package/templates/skills/bridges/api/AGENTS.md +8 -59
  197. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
  198. package/templates/skills/bridges/h5/AGENTS.md +8 -63
  199. package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
  200. package/templates/skills/bridges/website/AGENTS.md +8 -63
  201. package/templates/skills/hai-ai/SKILL.md +6 -2
  202. package/templates/skills/hai-ai/acp.md +153 -0
  203. package/templates/skills/hai-ai/guide.md +27 -12
  204. package/templates/skills/hai-ai/reference.md +24 -15
  205. package/templates/skills/hai-ai/resources.md +69 -0
  206. package/templates/skills/hai-api-client/SKILL.md +1 -1
  207. package/templates/skills/hai-api-client/reference.md +3 -1
  208. package/templates/skills/hai-app-create/collaboration.md +7 -0
  209. package/templates/skills/hai-app-create/development.md +19 -10
  210. package/templates/skills/hai-app-create/project.md +33 -0
  211. package/templates/skills/hai-app-create/reference.md +10 -10
  212. package/templates/skills/hai-app-tests/reference.md +1 -1
  213. package/templates/skills/hai-app-tests/validation.md +1 -0
  214. package/templates/skills/hai-build/SKILL.md +19 -9
  215. package/templates/skills/hai-build/paths.md +4 -2
  216. package/templates/skills/hai-build/reference.md +26 -8
  217. package/templates/skills/hai-cache/reference.md +6 -2
  218. package/templates/skills/hai-capacitor/SKILL.md +1 -0
  219. package/templates/skills/hai-capacitor/reference.md +24 -0
  220. package/templates/skills/hai-ci/SKILL.md +3 -0
  221. package/templates/skills/hai-core/SKILL.md +9 -4
  222. package/templates/skills/hai-core/reference.md +40 -10
  223. package/templates/skills/hai-core/request-access.md +2 -2
  224. package/templates/skills/hai-crypto/SKILL.md +2 -2
  225. package/templates/skills/hai-crypto/reference.md +7 -3
  226. package/templates/skills/hai-deploy/SKILL.md +41 -5
  227. package/templates/skills/hai-deploy/middleware.md +26 -14
  228. package/templates/skills/hai-framework-sync/SKILL.md +13 -9
  229. package/templates/skills/hai-framework-sync/sync.md +2 -0
  230. package/templates/skills/hai-iam/reference.md +2 -2
  231. package/templates/skills/hai-kit/SKILL.md +1 -1
  232. package/templates/skills/hai-kit/reference.md +19 -5
  233. package/templates/skills/hai-payment/reference.md +41 -17
  234. package/templates/skills/hai-reach/SKILL.md +3 -1
  235. package/templates/skills/hai-reach/reference.md +29 -9
  236. package/templates/skills/hai-reldb/SKILL.md +1 -1
  237. package/templates/skills/hai-reldb/reference.md +9 -2
  238. package/templates/skills/hai-scheduler/reference.md +1 -1
  239. package/templates/skills/hai-serv/SKILL.md +2 -1
  240. package/templates/skills/hai-serv/reference.md +26 -15
  241. package/templates/skills/hai-storage/reference.md +23 -6
  242. package/templates/skills/hai-ui/SKILL.md +11 -6
  243. package/templates/skills/hai-ui/charts.md +51 -0
  244. package/templates/skills/hai-ui/guide.md +1 -1
  245. package/templates/skills/hai-ui/reference.md +58 -38
  246. package/templates/skills/hai-vecdb/reference.md +8 -3
  247. package/dist/chunk-AKV2CQZV.js.map +0 -1
  248. package/dist/chunk-EF7WOFWZ.js.map +0 -1
  249. package/dist/chunk-MUHRBJSW.js +0 -29272
  250. package/dist/chunk-MUHRBJSW.js.map +0 -1
  251. package/dist/cli-config-templates-HHFHNXBN.js +0 -5
  252. package/dist/src-TEBHR5I5.js +0 -5
  253. package/dist/src-TEBHR5I5.js.map +0 -1
  254. package/templates/apps/fullstack/playwright.session-close.config.ts.hbs +0 -18
@@ -4,19 +4,9 @@
4
4
  * 官网根布局
5
5
  */
6
6
  import type { Snippet } from 'svelte'
7
- {{#if hasI18n}}
8
- import { browser } from '$app/environment'
9
- {{else}}
10
- {{#if features.crypto}}
11
- import { browser } from '$app/environment'
12
- {{/if}}
13
- {{/if}}
14
- import { {{#if features.iam}}base, {{else}}{{#if features.crypto}}base, {{/if}}{{/if}}resolve } from '$app/paths'
7
+ import { browser } from '$app/env'
8
+ import { resolve } from '$app/paths'
15
9
  import { page } from '$app/state'
16
- {{#if hasI18n}}
17
- import * as m from '$lib/paraglide/messages'
18
- import { getLocale, setLocale } from '$lib/paraglide/runtime.js'
19
- {{/if}}
20
10
  {{#if features.crypto}}
21
11
  import { crypto } from '@h-ai/crypto'
22
12
  import { kit } from '@h-ai/kit'
@@ -26,12 +16,17 @@
26
16
  {{/if}}
27
17
  {{/if}}
28
18
  {{#if hasI18n}}
29
- import { LanguageSwitch, setGlobalLocale, ToastContainer } from '@h-ai/ui'
19
+ import { LanguageSwitch, setGlobalLocale, ThemeSelector, ToastContainer } from '@h-ai/ui'
30
20
  {{else}}
31
- import { ToastContainer } from '@h-ai/ui'
21
+ import { ThemeSelector, ToastContainer } from '@h-ai/ui'
32
22
  {{/if}}
23
+ import { applyTheme, getSavedTheme } from '@h-ai/ui/theme'
33
24
  {{#if features.crypto}}
34
25
  import { untrack } from 'svelte'
26
+ {{/if}}
27
+ {{#if hasI18n}}
28
+ import * as m from '#lib/paraglide/messages.js'
29
+ import { getLocale, setLocale } from '#lib/paraglide/runtime.js'
35
30
  {{/if}}
36
31
  import '../app.css'
37
32
 
@@ -40,26 +35,53 @@
40
35
  }
41
36
 
42
37
  const { children }: Props = $props()
43
- {{#if features.crypto}}
38
+ const initialTheme = browser
39
+ ? getSavedTheme()
40
+ : 'light'
41
+ let currentTheme = $state(initialTheme)
44
42
  if (browser)
45
- kit.client.installBrowserTransport(untrack(() => page.data.kitConfig), { crypto, basePath: base })
43
+ applyTheme(initialTheme, false)
44
+
45
+ function handleThemeChange(theme: string) {
46
+ currentTheme = theme
47
+ applyTheme(theme)
48
+ }
49
+ {{#if features.crypto}}
50
+ if (browser) {
51
+ kit.client.installBrowserTransport(untrack(() => page.data.kitConfig), {
52
+ crypto,
53
+ basePath: resolve('/').replace(/\/$/, ''),
54
+ })
55
+ }
46
56
  {{/if}}
47
57
  {{#if features.iam}}
48
- const authActions = kit.client.createAuthActions({ basePath: `${base}/api/auth` })
58
+ const authActions = kit.client.createAuthActions({ basePath: `${resolve('/').replace(/\/$/, '')}/api/auth` })
49
59
  $effect(() => authActions.bindPageExit({ enabled: page.data.iamPublicConfig?.session.endOnPageHide }))
50
60
  {{/if}}
51
61
 
52
62
  const navItems = [
53
63
  {{#if hasI18n}}
54
- { path: '/', label: m.nav_home() },
55
- { path: '/about', label: m.nav_about() },
56
- { path: '/services', label: m.nav_services() },
57
- { path: '/contact', label: m.nav_contact() },
64
+ {
65
+ path: resolve('/'),
66
+ label: m.nav_home(),
67
+ },
68
+ {
69
+ path: resolve('about'),
70
+ label: m.nav_about(),
71
+ },
72
+ {
73
+ path: resolve('services'),
74
+ label: m.nav_services(),
75
+ },
76
+ {
77
+ path: resolve('contact'),
78
+ label: m.nav_contact(),
79
+ },
58
80
  {{else}}
59
- { path: '/', label: '首页' },
60
- { path: '/about', label: '关于' },
61
- { path: '/services', label: '服务' },
62
- { path: '/contact', label: '联系我们' },
81
+ { path: resolve('/'), label: '首页' },
82
+ { path: resolve('about'), label: '关于' },
83
+ { path: resolve('services'), label: '服务' },
84
+ { path: resolve('contact'), label: '联系我们' },
63
85
  {{/if}}
64
86
  ] as const
65
87
  {{#if hasI18n}}
@@ -84,21 +106,24 @@
84
106
  <header class='navbar flex-wrap bg-base-100 shadow-sm'>
85
107
  <div class='flex-1'>
86
108
  {{#if hasI18n}}
87
- <a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
109
+ <a href={resolve('/')} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
88
110
  {{else}}
89
- <a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
111
+ <a href={resolve('/')} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
90
112
  {{/if}}
91
113
  </div>
92
114
  <nav class='w-full flex-none sm:w-auto'>
93
115
  <ul class='menu menu-horizontal flex-wrap px-1'>
94
116
  {#each navItems as item (item.path)}
95
- <li><a href={resolve(item.path, {})}>{item.label}</a></li>
117
+ <li><!-- eslint-disable svelte/no-navigation-without-resolve -- 菜单字段已由 resolve 生成完整挂载地址;规则无法追踪派生/筛选后的集合,不能二次添加前缀。 -->
118
+ <a href={item.path}>{item.label}</a>
119
+ <!-- eslint-enable svelte/no-navigation-without-resolve --></li>
96
120
  {/each}
97
121
  </ul>
98
122
  </nav>
99
123
  {{#if hasI18n}}
100
124
  <LanguageSwitch currentLanguage={getLocale()} onchange={handleLanguageChange} />
101
125
  {{/if}}
126
+ <ThemeSelector {currentTheme} onchange={handleThemeChange} compact />
102
127
  </header>
103
128
 
104
129
  <main class='flex-1'>
@@ -108,7 +133,9 @@
108
133
  <footer class='footer footer-center p-4 bg-base-300 text-base-content'>
109
134
  <span>{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}</span>
110
135
  {{#if hasI18n}}
111
- <p>{m.footer_copyright({ year: new Date().getFullYear() })}</p>
136
+ <p>{m.footer_copyright({
137
+ year: new Date().getFullYear(),
138
+ })}</p>
112
139
  {{else}}
113
140
  <p>Copyright © 2025 - All rights reserved</p>
114
141
  {{/if}}
@@ -5,10 +5,10 @@
5
5
  */
6
6
  import { resolve } from '$app/paths'
7
7
  import { page } from '$app/state'
8
+ import { Badge } from '@h-ai/ui'
8
9
  {{#if hasI18n}}
9
- import * as m from '$lib/paraglide/messages'
10
+ import * as m from '#lib/paraglide/messages.js'
10
11
  {{/if}}
11
- import { Badge } from '@h-ai/ui'
12
12
  </script>
13
13
 
14
14
  <svelte:head>
@@ -35,14 +35,14 @@
35
35
  {{#if hasI18n}}
36
36
  <h1 class='mt-5 text-5xl font-black tracking-tight'>{m.page_home_title()}</h1>
37
37
  <p class='py-6 text-base-content/60'>{m.page_home_subtitle()}</p>
38
- <a href={resolve('/about', {})} class='btn btn-primary'>
38
+ <a href={resolve('about')} class='btn btn-primary'>
39
39
  <span class='icon-[tabler--arrow-right] size-5'></span>
40
40
  {m.page_home_cta()}
41
41
  </a>
42
42
  {{else}}
43
43
  <h1 class='mt-5 text-5xl font-black tracking-tight'>欢迎</h1>
44
44
  <p class='py-6 text-base-content/60'>基于 hai Agent Framework 构建</p>
45
- <a href={resolve('/about', {})} class='btn btn-primary'>
45
+ <a href={resolve('about')} class='btn btn-primary'>
46
46
  <span class='icon-[tabler--arrow-right] size-5'></span>
47
47
  了解更多
48
48
  </a>
@@ -3,10 +3,10 @@
3
3
  /**
4
4
  * 关于页面
5
5
  */
6
+ import { Card, PageHeader } from '@h-ai/ui'
6
7
  {{#if hasI18n}}
7
- import * as m from '$lib/paraglide/messages'
8
+ import * as m from '#lib/paraglide/messages.js'
8
9
  {{/if}}
9
- import { Card, PageHeader } from '@h-ai/ui'
10
10
  </script>
11
11
 
12
12
  <svelte:head>
@@ -3,10 +3,10 @@
3
3
  /**
4
4
  * 联系我们页面
5
5
  */
6
+ import { Button, Card, FormField, FormLayout, Input, notifyWarning, PageHeader, Textarea } from '@h-ai/ui'
6
7
  {{#if hasI18n}}
7
- import * as m from '$lib/paraglide/messages'
8
+ import * as m from '#lib/paraglide/messages.js'
8
9
  {{/if}}
9
- import { Button, Card, FormField, FormLayout, Input, notifyWarning, PageHeader, Textarea } from '@h-ai/ui'
10
10
 
11
11
  let name = $state('')
12
12
  let email = $state('')
@@ -3,16 +3,31 @@
3
3
  /**
4
4
  * 服务页面
5
5
  */
6
+ import { Card, PageHeader } from '@h-ai/ui'
6
7
  {{#if hasI18n}}
7
- import * as m from '$lib/paraglide/messages'
8
+ import * as m from '#lib/paraglide/messages.js'
8
9
  {{/if}}
9
- import { Card, PageHeader } from '@h-ai/ui'
10
10
 
11
11
  {{#if hasI18n}}
12
12
  const services = $derived([
13
- { id: 'consulting', title: m.page_services_item1(), desc: m.page_services_desc(), icon: 'icon-[tabler--message-circle]' },
14
- { id: 'development', title: m.page_services_item2(), desc: m.page_services_desc(), icon: 'icon-[tabler--code]' },
15
- { id: 'operations', title: m.page_services_item3(), desc: m.page_services_desc(), icon: 'icon-[tabler--server-cog]' },
13
+ {
14
+ id: 'consulting',
15
+ title: m.page_services_item1(),
16
+ desc: m.page_services_desc(),
17
+ icon: 'icon-[tabler--message-circle]',
18
+ },
19
+ {
20
+ id: 'development',
21
+ title: m.page_services_item2(),
22
+ desc: m.page_services_desc(),
23
+ icon: 'icon-[tabler--code]',
24
+ },
25
+ {
26
+ id: 'operations',
27
+ title: m.page_services_item3(),
28
+ desc: m.page_services_desc(),
29
+ icon: 'icon-[tabler--server-cog]',
30
+ },
16
31
  ])
17
32
  {{else}}
18
33
  const services = [
@@ -25,3 +25,13 @@ ios
25
25
  # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
26
26
  **/.env.*
27
27
  **/.hai
28
+ # 部署证书和测试材料不进入镜像构建上下文。
29
+ **/*.pem
30
+ **/*.key
31
+ **/*.p12
32
+ **/*.pfx
33
+ **/tests
34
+ **/e2e
35
+ **/*.test.*
36
+ **/*.spec.*
37
+ docs
@@ -6,8 +6,13 @@ node_modules
6
6
  build
7
7
  dist
8
8
  .turbo
9
- android
10
- ios
9
+ android/.gradle/
10
+ android/**/build/
11
+ ios/Pods/
12
+ ios/App/Pods/
13
+ *.keystore
14
+ *.jks
15
+ *.p12
11
16
  **/src/lib/paraglide
12
17
  **/src-tauri/gen/
13
18
  **/src-tauri/target/
@@ -1,3 +1,6 @@
1
+ {{#if_eq appType 'h5'}}
2
+ import type { Page } from '@playwright/test'
3
+ {{/if_eq}}
1
4
  {{#if features.iam}}
2
5
  import { randomUUID } from 'node:crypto'
3
6
  {{/if}}
@@ -6,17 +9,40 @@ import path from 'node:path'
6
9
  import process from 'node:process'
7
10
  import { expect, test } from '@playwright/test'
8
11
 
12
+ {{#if_eq appType 'h5'}}
13
+ /** 延迟客户端脚本,覆盖 SSR 已显示而事件尚未绑定的阶段。 */
14
+ async function whileClientBootPaused(page: Page, inspect: () => Promise<void>) {
15
+ let resumeClient = () => { }
16
+ const clientBoot = new Promise<void>((resolve) => {
17
+ resumeClient = resolve
18
+ })
19
+ await page.route('**/*.js', async (route) => {
20
+ if (!new URL(route.request().url()).pathname.endsWith('/_hai/browser-check/startup.js'))
21
+ await clientBoot
22
+ await route.continue()
23
+ })
24
+ try {
25
+ await inspect()
26
+ }
27
+ finally {
28
+ resumeClient()
29
+ await page.unrouteAll({ behavior: 'wait' })
30
+ }
31
+ }
32
+ {{/if_eq}}
9
33
  {{#if_eq appType 'admin'}}
10
34
  test('high contrast appearance is selectable and survives reload', async ({ page }) => {
11
35
  await page.goto('./')
12
36
  const header = page.locator('header').first()
13
- await header.getByRole('button', { name: /选择主题|Select theme|テーマを選択/, exact: true }).click()
37
+ const themeToggle = header.getByRole('button', { name: /选择主题|Select theme|テーマを選択/, exact: true })
38
+ await themeToggle.click()
14
39
  await header.getByRole('button', { name: /高对比|High contrast|ハイコントラスト/, exact: true }).click()
15
40
  await expect(page.locator('html')).toHaveAttribute('data-theme', 'high-contrast')
16
41
  await page.reload()
17
42
  await expect(page.locator('html')).toHaveAttribute('data-theme', 'high-contrast')
18
43
  await expect.poll(() => page.evaluate(() => getComputedStyle(document.documentElement).colorScheme)).toBe('dark')
19
- await header.getByRole('button', { name: /选择主题|Select theme|テーマを選択/, exact: true }).press('Enter')
44
+ await expect(themeToggle).toBeEnabled()
45
+ await themeToggle.press('Enter')
20
46
  await header.getByRole('button', { name: /简洁商务|Formal|ビジネス(角丸なし)/, exact: true }).press('Enter')
21
47
  await expect(page.locator('html')).toHaveAttribute('data-theme', 'formal')
22
48
  await page.reload()
@@ -24,12 +50,49 @@ test('high contrast appearance is selectable and survives reload', async ({ page
24
50
  expect(await header.locator('button').first().evaluate(element => getComputedStyle(element).borderTopLeftRadius)).toBe('0px')
25
51
  })
26
52
  {{/if_eq}}
27
-
53
+ {{#if hasUi}}
54
+ {{#unless isCapacitorApp}}
55
+ test('eye care appearance is selectable and survives reload', async ({ page }, testInfo) => {
56
+ const header = page.locator('header').first()
57
+ const themeToggle = header.getByRole('button', { name: /选择主题|Select theme|テーマを選択/, exact: true })
58
+ {{#if_eq appType 'h5'}}
59
+ await whileClientBootPaused(page, async () => {
60
+ await page.goto('./', { waitUntil: 'commit' })
61
+ await expect(themeToggle).toBeDisabled()
62
+ })
63
+ {{else}}
64
+ await page.goto('./')
65
+ {{/if_eq}}
66
+ await page.setViewportSize({ width: 1440, height: 960 })
67
+ await themeToggle.click()
68
+ await header.getByRole('button', { name: /护眼|Eye care|目にやさしい/, exact: true }).click()
69
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'eye-care')
70
+ await page.reload()
71
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'eye-care')
72
+ await expect.poll(() => page.evaluate(() => getComputedStyle(document.documentElement).colorScheme)).toBe('light')
73
+ await expect.poll(() => page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--color-base-100').trim())).toBe('#e8f0df')
74
+ await page.screenshot({
75
+ path: testInfo.outputPath('eye-care-desktop.png'),
76
+ animations: 'disabled',
77
+ })
78
+ await page.setViewportSize({ width: 390, height: 844 })
79
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
80
+ await page.screenshot({
81
+ path: testInfo.outputPath('eye-care-mobile.png'),
82
+ animations: 'disabled',
83
+ })
84
+ })
85
+ {{/unless}}
86
+ {{/if}}
28
87
  {{#if_eq appType 'h5'}}
29
- test.use({ viewport: { width: 390, height: 844 } })
88
+ test.use({
89
+ viewport: { width: 390, height: 844 },
90
+ })
30
91
  {{/if_eq}}
31
92
  {{#if isCapacitorApp}}
32
- test.use({ viewport: { width: 390, height: 844 } })
93
+ test.use({
94
+ viewport: { width: 390, height: 844 },
95
+ })
33
96
  {{/if}}
34
97
 
35
98
  /** 首页从真实浏览器页面发起,覆盖基础渲染链路。 */
@@ -60,10 +123,14 @@ test('home page renders from browser', async ({ page }) => {
60
123
  const nav = page.locator('nav')
61
124
  await nav.getByRole('button', { name: /发现|Discover/i, exact: true }).click()
62
125
  await expect(page).toHaveURL(/\/discover$/)
63
- await page.reload()
64
- await expect(page).toHaveURL(/\/discover$/)
65
- await expect(page.getByRole('heading', { name: /发现|Discover/i })).toBeVisible()
66
- await nav.getByRole('button', { name: /首页|Home/i, exact: true }).click()
126
+ const home = nav.getByRole('button', { name: /首页|Home/i, exact: true })
127
+ await whileClientBootPaused(page, async () => {
128
+ await page.reload({ waitUntil: 'commit' })
129
+ await expect(page).toHaveURL(/\/discover$/)
130
+ await expect(page.getByRole('heading', { name: /发现|Discover/i })).toBeVisible()
131
+ await expect(home).toBeDisabled()
132
+ })
133
+ await home.click()
67
134
  await expect(page).toHaveURL(/\/$/)
68
135
  expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
69
136
  {{/if_eq}}
@@ -77,7 +144,11 @@ test('home page renders from browser', async ({ page }) => {
77
144
  const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
78
145
  await mkdir(evidence, { recursive: true })
79
146
  await page.evaluate(() => document.fonts.ready)
80
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-home.png'), fullPage: true, animations: 'disabled' })
147
+ await page.screenshot({
148
+ path: path.join(evidence, '{{projectName}}-home.png'),
149
+ fullPage: true,
150
+ animations: 'disabled',
151
+ })
81
152
  })
82
153
  {{#if features.iam}}
83
154
 
@@ -96,21 +167,30 @@ test('configured IAM registration and password login', async ({ page, context })
96
167
  await expect(page.locator('#login-username')).toBeVisible()
97
168
  await expect(page.locator('header').first()).toBeVisible()
98
169
  await expect(page.locator('footer')).toBeVisible()
99
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-login.png'), fullPage: true })
170
+ await page.screenshot({
171
+ path: path.join(evidence, '{{projectName}}-login.png'),
172
+ fullPage: true,
173
+ })
100
174
  await page.getByRole('{{#if isCapacitorApp}}button{{else}}link{{/if}}', { name: /立即注册|Register now/i }).click()
101
175
  {{#unless isCapacitorApp}}
102
176
  await expect(page).toHaveURL(/\/auth\/register(?:\?|$)/)
103
177
  {{/unless}}
104
178
  const registerForm = page.locator('.register-form')
105
179
  await expect(registerForm.locator('#register-username')).toBeVisible()
106
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-register.png'), fullPage: true })
180
+ await page.screenshot({
181
+ path: path.join(evidence, '{{projectName}}-register.png'),
182
+ fullPage: true,
183
+ })
107
184
  if (process.env.IAM_NOTIFICATION_URL) {
108
185
  // 协议弹窗留在认证页,关闭后继续填写注册表单。
109
186
  await registerForm.getByRole('button', { name: /用户协议|Terms of Service/i, exact: true }).click()
110
187
  await expect(page.getByRole('dialog')).toBeVisible()
111
188
  await expect(page.getByRole('dialog').getByRole('heading', { name: 'Configured registration agreement' })).toBeVisible()
112
189
  await expect(page.locator('iframe')).toHaveCount(0)
113
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-agreement.png'), fullPage: true })
190
+ await page.screenshot({
191
+ path: path.join(evidence, '{{projectName}}-agreement.png'),
192
+ fullPage: true,
193
+ })
114
194
  await page.keyboard.press('Escape')
115
195
  await expect(page.getByRole('dialog')).not.toBeVisible()
116
196
  }
@@ -120,12 +200,30 @@ test('configured IAM registration and password login', async ({ page, context })
120
200
  await registerForm.locator('#register-confirm-password').fill(password)
121
201
  await registerForm.locator('button[type="submit"]').click()
122
202
  await expect(page.locator('.register-form')).not.toBeVisible()
123
- {{#unless isCapacitorApp}}
203
+ {{#if isCapacitorApp}}
204
+ // 浏览器预览通过刷新 Cookie 恢复会话,不能把页面重载当作退出。
205
+ await page.reload()
206
+ await expect(page.getByRole('navigation')).toBeVisible()
207
+ await page.getByRole('navigation').getByRole('button', { name: /我的|Profile|プロフィール/, exact: true }).click()
208
+ const appearance = page.getByTestId('profile-theme')
209
+ await appearance.getByRole('button', { name: /选择主题|Select theme|テーマを選択/, exact: true }).click()
210
+ await appearance.getByRole('button', { name: /护眼|Eye care|目にやさしい/, exact: true }).click()
211
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'eye-care')
212
+ await page.reload()
213
+ await expect(page.getByTestId('profile-theme')).toBeVisible()
214
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'eye-care')
215
+ await expect.poll(() => page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--color-base-100').trim())).toBe('#e8f0df')
216
+ await page.screenshot({
217
+ path: path.join(evidence, '{{projectName}}-eye-care.png'),
218
+ fullPage: true,
219
+ animations: 'disabled',
220
+ })
221
+ {{else}}
124
222
  // 通过页面 fetch 复用 Cookie 与已安装的传输加密,避免 HTTP 辅助客户端绕过浏览器协议。
125
223
  const me = await page.evaluate(async url => (await fetch(url)).status, new URL('./api/auth/me', test.info().project.use.baseURL).href)
126
224
  expect(me).toBe(200)
225
+ {{/if}}
127
226
  await context.clearCookies()
128
- {{/unless}}
129
227
  await page.reload()
130
228
  await page.goto(authUrl)
131
229
  await expect(page.locator('#login-username')).toBeVisible()
@@ -141,7 +239,10 @@ test('configured IAM registration and password login', async ({ page, context })
141
239
  await page.reload()
142
240
  await page.goto(authUrl)
143
241
  await page.getByRole('button', { name: /邮件验证码|Email code/i, exact: true }).click()
144
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-email.png'), fullPage: true })
242
+ await page.screenshot({
243
+ path: path.join(evidence, '{{projectName}}-email.png'),
244
+ fullPage: true,
245
+ })
145
246
  const email = `${username}@example.com`
146
247
  await page.locator('#login-username').fill(email)
147
248
  await page.getByRole('button', { name: /发送验证码|Send code/i, exact: true }).click()
@@ -160,34 +261,26 @@ test('configured IAM registration and password login', async ({ page, context })
160
261
  await page.reload()
161
262
  await page.goto(authUrl)
162
263
  await page.getByRole('button', { name: /忘记密码|Forgot password/i }).click()
163
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-forgot.png'), fullPage: true })
264
+ await page.screenshot({
265
+ path: path.join(evidence, '{{projectName}}-forgot.png'),
266
+ fullPage: true,
267
+ })
164
268
  await page.locator('input[type="email"]').fill(email)
165
269
  await page.locator('button[type="submit"]').click()
166
270
  await expect(page.locator('.hai-toast-item[data-variant="success"]')).toBeVisible()
167
271
  const reset = await received('password_reset')
168
272
  expect(Boolean(reset?.token)).toBe(true)
169
273
  await page.getByRole('button', { name: /已有重置令牌|I have a reset token/i }).click()
170
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-reset.png'), fullPage: true })
274
+ await page.screenshot({
275
+ path: path.join(evidence, '{{projectName}}-reset.png'),
276
+ fullPage: true,
277
+ })
171
278
  const resetUrl = `{{#if isCapacitorApp}}/{{else}}/auth/reset-password{{/if}}?token=${encodeURIComponent(reset?.token ?? '')}`
172
279
  {{#if_eq appType 'h5'}}
173
- // 延迟客户端脚本,确定性覆盖 SSR 页面已显示、事件绑定尚未完成的窗口。
174
- let resumeClient = () => {}
175
- const clientBoot = new Promise<void>((resolve) => {
176
- resumeClient = resolve
177
- })
178
- await page.route('**/*.js', async (route) => {
179
- await clientBoot
180
- await route.continue()
181
- })
182
- try {
280
+ await whileClientBootPaused(page, async () => {
183
281
  await page.goto(resetUrl, { waitUntil: 'commit' })
184
282
  await expect(page.locator('input[type="password"]').nth(0)).toBeDisabled()
185
- }
186
- finally {
187
- resumeClient()
188
- // 等待正在恢复的路由完成,避免提前移除拦截导致 continue 被重复处理。
189
- await page.unrouteAll({ behavior: 'wait' })
190
- }
283
+ })
191
284
  {{else}}
192
285
  await page.goto(resetUrl)
193
286
  {{/if_eq}}
@@ -204,8 +297,9 @@ test('configured IAM registration and password login', async ({ page, context })
204
297
  expect(new URL(page.url()).searchParams.has('token')).toBe(false)
205
298
  await page.reload()
206
299
  {{#if isCapacitorApp}}
207
- // Web 预览采用内存 TokenStore,刷新后应回到登录,而不是已消费令牌的重置页面。
208
- await expect(page.locator('#login-username')).toBeVisible()
300
+ // 已消费的重置令牌不再参与启动;刷新 Cookie 恢复同一已登录会话。
301
+ await expect(page.getByRole('navigation')).toBeVisible()
302
+ await expect(page.locator('#login-username')).not.toBeVisible()
209
303
  {{else}}
210
304
  expect(await page.evaluate(async url => (await fetch(url)).status, new URL('./api/auth/me', test.info().project.use.baseURL).href)).toBe(200)
211
305
  {{/if}}
@@ -11,35 +11,54 @@ export const haiQualityConfig = {
11
11
  rules: {
12
12
  'no-console': ['error', {}],
13
13
  'ts/no-explicit-any': 'error',
14
- 'ts/ban-ts-comment': ['error', {
15
- 'ts-ignore': true,
16
- 'ts-nocheck': true,
17
- 'ts-check': false,
18
- 'ts-expect-error': 'allow-with-description',
19
- 'minimumDescriptionLength': 10,
20
- }],
14
+ 'ts/ban-ts-comment': [
15
+ 'error',
16
+ {
17
+ 'ts-ignore': true,
18
+ 'ts-nocheck': true,
19
+ 'ts-check': false,
20
+ 'ts-expect-error': 'allow-with-description',
21
+ 'minimumDescriptionLength': 10,
22
+ },
23
+ ],
21
24
  'eslint-comments/no-unlimited-disable': 'error',
22
25
  'eslint-comments/require-description': 'error',
23
26
  // 只禁内部源码/构建目录,公开的 /client、/adapter 等 exports 子路径可用。
24
- 'no-restricted-imports': ['error', {
25
- patterns: [{
26
- group: ['@h-ai/*/src', '@h-ai/*/src/**', '@h-ai/*/dist', '@h-ai/*/dist/**'],
27
+ 'no-restricted-imports': [
28
+ 'error',
29
+ {
30
+ patterns: [
31
+ {
32
+ group: [
33
+ '@h-ai/*/src',
34
+ '@h-ai/*/src/**',
35
+ '@h-ai/*/dist',
36
+ '@h-ai/*/dist/**',
37
+ ],
38
+ message: 'Use the public exports of hai packages instead of their src/dist internals.',
39
+ },
40
+ ],
41
+ },
42
+ ],
43
+ 'no-restricted-syntax': [
44
+ 'error',
45
+ {
46
+ selector: ':matches(TSAsExpression, TSTypeAssertion)[expression.typeAnnotation.type="TSUnknownKeyword"]',
47
+ message: 'Use the real upstream type or narrow unknown. Explain necessary third-party workarounds with a scoped lint exception.',
48
+ },
49
+ {
50
+ selector: 'ImportExpression[source.value=/^@h-ai\\/[^/]+\\/(src|dist)(\\/|$)/]',
27
51
  message: 'Use the public exports of hai packages instead of their src/dist internals.',
28
- }],
29
- }],
30
- 'no-restricted-syntax': ['error', {
31
- selector: ':matches(TSAsExpression, TSTypeAssertion)[expression.typeAnnotation.type="TSUnknownKeyword"]',
32
- message: 'Use the real upstream type or narrow unknown. Explain necessary third-party workarounds with a scoped lint exception.',
33
- }, {
34
- selector: 'ImportExpression[source.value=/^@h-ai\\/[^/]+\\/(src|dist)(\\/|$)/]',
35
- message: 'Use the public exports of hai packages instead of their src/dist internals.',
36
- }, {
52
+ },
53
+ {
37
54
  // 仅识别明确的静态敏感键;变量键、别名及业务敏感程度仍需审查。
38
- selector: 'CallExpression[callee.property.name="setItem"][callee.object.name="localStorage"][arguments.0.value=/^(token|password|secret|api[_-]?key|(access|refresh|auth|session)[_-]?token)$/i]',
39
- message: 'Keep sensitive tokens in memory, httpOnly cookies or a secure native TokenStore instead of localStorage.',
40
- }, {
41
- selector: 'CallExpression[callee.property.name="setItem"][callee.object.property.name="localStorage"][arguments.0.value=/^(token|password|secret|api[_-]?key|(access|refresh|auth|session)[_-]?token)$/i]',
42
- message: 'Keep sensitive tokens in memory, httpOnly cookies or a secure native TokenStore instead of localStorage.',
43
- }],
55
+ selector: 'CallExpression[callee.property.name="setItem"][callee.object.name="localStorage"][arguments.0.value=/^(token|password|secret|api[_-]?key|(access|refresh|auth|session)[_-]?token)$/i]',
56
+ message: 'Keep sensitive tokens in memory, httpOnly cookies or a secure native TokenStore instead of localStorage.',
57
+ },
58
+ {
59
+ selector: 'CallExpression[callee.property.name="setItem"][callee.object.property.name="localStorage"][arguments.0.value=/^(token|password|secret|api[_-]?key|(access|refresh|auth|session)[_-]?token)$/i]',
60
+ message: 'Keep sensitive tokens in memory, httpOnly cookies or a secure native TokenStore instead of localStorage.',
61
+ },
62
+ ],
44
63
  },
45
64
  }
@@ -9,7 +9,9 @@ import { haiQualityConfig } from './eslint-rules.js'
9
9
 
10
10
  export default antfu({
11
11
  // unused-imports 使用 TS 规则,JS 同样使用 TS parser 以保持 scope 契约一致。
12
- typescript: { files: [GLOB_SRC] },
12
+ typescript: {
13
+ files: [GLOB_SRC],
14
+ },
13
15
  formatters: true,
14
16
  ignores: [
15
17
  {{#unless isSvelteOnlyApp}}