@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,17 +4,29 @@ import type { AddressInfo } from 'node:net'
4
4
  import { mkdtempSync, rmSync } from 'node:fs'
5
5
  import { tmpdir } from 'node:os'
6
6
  import { join } from 'node:path'
7
- {{#if features.ai}}import { ai } from '@h-ai/ai'
8
- {{/if}}import { apiClient } from '@h-ai/api-client'
9
- {{#if features.cache}}import { cache } from '@h-ai/cache'
10
- {{/if}}import { core } from '@h-ai/core'
11
- {{#if features.iam}}import { iam } from '@h-ai/iam'
12
- {{/if}}{{#if features.db}}import { reldb } from '@h-ai/reldb'
13
- {{/if}}import { serv } from '@h-ai/serv'
14
- {{#if features.storage}}import { storage } from '@h-ai/storage'
15
- {{/if}}{{#if features.vecdb}}import { vecdb } from '@h-ai/vecdb'
16
- {{/if}}import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
17
- import { afterAll, beforeAll, describe, expect, it } from 'vitest'
7
+ {{#if features.ai}}
8
+ import { ai } from '@h-ai/ai'
9
+ {{/if}}
10
+ import { apiClient } from '@h-ai/api-client'
11
+ {{#if features.cache}}
12
+ import { cache } from '@h-ai/cache'
13
+ {{/if}}
14
+ import { core } from '@h-ai/core'
15
+ {{#if features.iam}}
16
+ import { iam } from '@h-ai/iam'
17
+ {{/if}}
18
+ {{#if features.db}}
19
+ import { reldb } from '@h-ai/reldb'
20
+ {{/if}}
21
+ import { serv } from '@h-ai/serv'
22
+ {{#if features.storage}}
23
+ import { storage } from '@h-ai/storage'
24
+ {{/if}}
25
+ {{#if features.vecdb}}
26
+ import { vecdb } from '@h-ai/vecdb'
27
+ {{/if}}
28
+ import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
29
+ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
18
30
 
19
31
  import { {{api.serviceAppFactoryName}} } from '../src/app.js'
20
32
 
@@ -23,10 +35,12 @@ let baseUrl = ''
23
35
  let client: ReturnType<typeof apiClient.create<typeof {{api.contractExportName}}>>
24
36
  let server: ReturnType<typeof serv.listen>
25
37
  let servConfig: ServConfig
26
- {{#if features.storage}}let storageRoot = ''
27
- {{/if}}{{#if features.vecdb}}let vecdbRoot = ''
38
+ {{#if features.storage}}
39
+ let storageRoot = ''
40
+ {{/if}}
41
+ {{#if features.vecdb}}
42
+ let vecdbRoot = ''
28
43
  {{/if}}
29
-
30
44
  {{#if features.iam}}
31
45
  function uniqueUser(prefix = 'api') {
32
46
  const suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(-8)
@@ -40,10 +54,14 @@ function uniqueUser(prefix = 'api') {
40
54
 
41
55
  beforeAll(async () => {
42
56
  tempRoot = mkdtempSync(join(tmpdir(), '{{projectName}}-service-test-'))
43
- {{#if features.storage}} storageRoot = join(tempRoot, 'storage')
44
- {{/if}}{{#if features.vecdb}} vecdbRoot = join(tempRoot, 'vecdb')
57
+ {{#if features.storage}}
58
+ storageRoot = join(tempRoot, 'storage')
59
+ {{/if}}
60
+ {{#if features.vecdb}}
61
+ vecdbRoot = join(tempRoot, 'vecdb')
45
62
  {{/if}}
46
63
 
64
+ vi.stubEnv('HAI_SERV_CORS_NATIVEORIGINS', undefined)
47
65
  core.init({
48
66
  configDir: './config',
49
67
  logging: { level: 'warn' },
@@ -54,27 +72,32 @@ beforeAll(async () => {
54
72
  const dbResult = await reldb.init({ type: 'sqlite', database: ':memory:' })
55
73
  if (!dbResult.success)
56
74
  throw new Error(`reldb: ${dbResult.error.message}`)
57
- {{/if}}{{#if features.cache}}
75
+ {{/if}}
76
+ {{#if features.cache}}
58
77
 
59
78
  const cacheResult = await cache.init({ type: 'memory' })
60
79
  if (!cacheResult.success)
61
80
  throw new Error(`cache: ${cacheResult.error.message}`)
62
- {{/if}}{{#if features.vecdb}}
81
+ {{/if}}
82
+ {{#if features.vecdb}}
63
83
 
64
84
  const vecdbResult = await vecdb.init({ type: 'lancedb', path: vecdbRoot })
65
85
  if (!vecdbResult.success)
66
86
  throw new Error(`vecdb: ${vecdbResult.error.message}`)
67
- {{/if}}{{#if features.iam}}
87
+ {{/if}}
88
+ {{#if features.iam}}
68
89
 
69
90
  const iamResult = await iam.init({})
70
91
  if (!iamResult.success)
71
92
  throw new Error(`iam: ${iamResult.error.message}`)
72
- {{/if}}{{#if features.storage}}
93
+ {{/if}}
94
+ {{#if features.storage}}
73
95
 
74
96
  const storageResult = await storage.init({ type: 'local', root: storageRoot })
75
97
  if (!storageResult.success)
76
98
  throw new Error(`storage: ${storageResult.error.message}`)
77
- {{/if}}{{#if features.ai}}
99
+ {{/if}}
100
+ {{#if features.ai}}
78
101
 
79
102
  const aiResult = await ai.init({})
80
103
  if (!aiResult.success)
@@ -82,8 +105,7 @@ beforeAll(async () => {
82
105
  {{/if}}
83
106
 
84
107
  const app = {{api.serviceAppFactoryName}}({
85
- transport: 'disabled',{{#if features.iam}}
86
- refreshCookie: 'disabled',{{/if}}
108
+ transport: 'disabled',
87
109
  })
88
110
  const listening = new Promise<AddressInfo>((resolve) => {
89
111
  server = serv.listen(app, {
@@ -98,7 +120,11 @@ beforeAll(async () => {
98
120
  client = apiClient.create({{api.contractExportName}})
99
121
  const initResult = await client.init({
100
122
  baseUrl: `${baseUrl}${servConfig.http.apiPrefix}`,
101
- auth: { storage: apiClient.tokenStorage.memory() },
123
+ clientName: 'hai-mobile-app',
124
+ headers: { 'origin': 'capacitor://localhost', 'x-hai-token-transport': 'body' },
125
+ auth: {
126
+ storage: apiClient.tokenStorage.memory(),
127
+ },
102
128
  })
103
129
  if (!initResult.success)
104
130
  throw new Error(`client.init: ${initResult.error.message}`)
@@ -107,13 +133,26 @@ beforeAll(async () => {
107
133
  afterAll(async () => {
108
134
  await client?.close()
109
135
  await server?.close()
110
- {{#if features.ai}} await ai.close()
111
- {{/if}}{{#if features.storage}} await storage.close()
112
- {{/if}}{{#if features.iam}} await iam.close()
113
- {{/if}}{{#if features.vecdb}} await vecdb.close()
114
- {{/if}}{{#if features.cache}} await cache.close()
115
- {{/if}}{{#if features.db}} await reldb.close()
116
- {{/if}} rmSync(tempRoot, { recursive: true, force: true })
136
+ {{#if features.ai}}
137
+ await ai.close()
138
+ {{/if}}
139
+ {{#if features.storage}}
140
+ await storage.close()
141
+ {{/if}}
142
+ {{#if features.iam}}
143
+ await iam.close()
144
+ {{/if}}
145
+ {{#if features.vecdb}}
146
+ await vecdb.close()
147
+ {{/if}}
148
+ {{#if features.cache}}
149
+ await cache.close()
150
+ {{/if}}
151
+ {{#if features.db}}
152
+ await reldb.close()
153
+ {{/if}}
154
+ rmSync(tempRoot, { recursive: true, force: true })
155
+ vi.unstubAllEnvs()
117
156
  })
118
157
 
119
158
  describe('{{api.servPackageName}}', () => {
@@ -132,7 +171,12 @@ describe('{{api.servPackageName}}', () => {
132
171
  expect(result.success).toBe(true)
133
172
  if (result.success) {
134
173
  const config = core.config.getOrThrow<CoreConfig>('core')
135
- expect(result.data).toMatchObject({ name: config.name, version: config.version, env: config.env, defaultLocale: config.defaultLocale })
174
+ expect(result.data).toMatchObject({
175
+ name: config.name,
176
+ version: config.version,
177
+ env: config.env,
178
+ defaultLocale: config.defaultLocale,
179
+ })
136
180
  expect(typeof result.data.transportEnabled).toBe('boolean')
137
181
  }
138
182
  })
@@ -142,8 +186,37 @@ describe('{{api.servPackageName}}', () => {
142
186
 
143
187
  expect(result.success).toBe(false)
144
188
  })
145
-
146
189
  {{#if features.iam}}
190
+ it.each([
191
+ ['capacitor://localhost', 'hai-mobile-app'],
192
+ ['https://localhost', 'hai-mobile-app'],
193
+ ['http://tauri.localhost', 'hai-desktop-app'],
194
+ ['https://tauri.localhost', 'hai-desktop-app'],
195
+ ['tauri://localhost', 'hai-desktop-app'],
196
+ ])('refreshes an expired access token at the default native Origin %s', async (origin, clientName) => {
197
+ const native = apiClient.create({{api.contractExportName}})
198
+ try {
199
+ expect((await native.init({
200
+ baseUrl: `${baseUrl}${servConfig.http.apiPrefix}`,
201
+ clientName,
202
+ headers: { 'origin': origin, 'x-hai-token-transport': 'body' },
203
+ auth: {
204
+ storage: apiClient.tokenStorage.memory(),
205
+ },
206
+ })).success).toBe(true)
207
+ const registration = await apiClient.createAuthActions(native).register(uniqueUser('refresh'))
208
+ expect(registration.success).toBe(true)
209
+ if (!registration.success)
210
+ return
211
+ expect(registration.data.tokens.refreshToken).toBeTruthy()
212
+ await native.auth.setTokens({ ...registration.data.tokens, accessToken: 'expired-access-for-test' })
213
+ expect((await native.iam.auth.currentUser()).success).toBe(true)
214
+ }
215
+ finally {
216
+ await native.close()
217
+ }
218
+ })
219
+
147
220
  it('registers a user and returns authenticated echo context', async () => {
148
221
  const user = uniqueUser('svc')
149
222
  const authActions = apiClient.createAuthActions(client)
@@ -6,7 +6,9 @@ import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
6
6
  const buildMetadata = resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))
7
7
 
8
8
  export default defineConfig({
9
- define: { __HAI_BUILD_METADATA__: JSON.stringify(buildMetadata) },
9
+ define: {
10
+ __HAI_BUILD_METADATA__: JSON.stringify(buildMetadata),
11
+ },
10
12
  async onSuccess() {
11
13
  await writeFile(new URL('./dist/build-metadata.json', import.meta.url), JSON.stringify(buildMetadata))
12
14
  },
@@ -3,7 +3,9 @@ import { defineConfig } from 'vitest/config'
3
3
  import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
4
4
 
5
5
  export default defineConfig({
6
- define: { __HAI_BUILD_METADATA__: JSON.stringify(resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))) },
6
+ define: {
7
+ __HAI_BUILD_METADATA__: JSON.stringify(resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))),
8
+ },
7
9
  test: {
8
10
  globals: true,
9
11
  environment: 'node',
@@ -16,7 +16,9 @@ test.beforeAll(async () => {
16
16
  const result = await client.init({
17
17
  baseUrl: `${process.env.BASE_URL || 'http://127.0.0.1:3000'}${API_PREFIX}`,
18
18
  {{#if features.iam}}
19
- auth: { storage: apiClient.tokenStorage.memory() },
19
+ auth: {
20
+ storage: apiClient.tokenStorage.memory(),
21
+ },
20
22
  {{/if}}
21
23
  {{#if features.crypto}}
22
24
  transport: { crypto },
@@ -105,9 +107,24 @@ test('feedback intake is idempotent and creator can append evidence without admi
105
107
  expect(duplicate.success).toBe(true)
106
108
  if (duplicate.success)
107
109
  expect(duplicate.data.id).toBe(created.data.id)
108
- const evidence = await client.feedback.reports.appendEvidence({ id: created.data.id, evidence: [{ id: 'event-1', source: 'api-test', sequence: 1, kind: 'business', timestamp: Date.now(), data: { action: 'api-feedback' } }] })
110
+ const evidence = await client.feedback.reports.appendEvidence({
111
+ id: created.data.id,
112
+ evidence: [
113
+ {
114
+ id: 'event-1',
115
+ source: 'api-test',
116
+ sequence: 1,
117
+ kind: 'business',
118
+ timestamp: Date.now(),
119
+ data: { action: 'api-feedback' },
120
+ },
121
+ ],
122
+ })
109
123
  expect(evidence.success).toBe(true)
110
- expect((await client.feedback.reports.completeCollection({ id: created.data.id, missing: ['browser-not-applicable'] })).success).toBe(true)
124
+ expect((await client.feedback.reports.completeCollection({
125
+ id: created.data.id,
126
+ missing: ['browser-not-applicable'],
127
+ })).success).toBe(true)
111
128
  expect((await client.feedback.reports.list({})).success).toBe(false)
112
129
  })
113
130
  {{/if}}
@@ -15,7 +15,10 @@ export default async function captureEvidence() {
15
15
  expect(response?.status()).toBe(200)
16
16
  await expect(page.locator('body')).toContainText('ok')
17
17
  await mkdir(evidence, { recursive: true })
18
- await page.screenshot({ path: path.join(evidence, '{{projectName}}-health.png'), fullPage: true })
18
+ await page.screenshot({
19
+ path: path.join(evidence, '{{projectName}}-health.png'),
20
+ fullPage: true,
21
+ })
19
22
  }
20
23
  finally {
21
24
  await browser.close()
@@ -3,7 +3,9 @@ import { haiQualityConfig } from './eslint-rules.js'
3
3
 
4
4
  export default antfu({
5
5
  // unused-imports 使用 TS 规则,JS 同样使用 TS parser 以保持 scope 契约一致。
6
- typescript: { files: [GLOB_SRC] },
6
+ typescript: {
7
+ files: [GLOB_SRC],
8
+ },
7
9
  formatters: true,
8
10
  ignores: [
9
11
  '**/dist/**',
@@ -1,6 +1,5 @@
1
- FROM node:24.22.1-bookworm-slim AS build
2
- ARG SQLITE_PREBUILD_MIRROR=
3
- ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
1
+ ARG HAI_NODE_IMAGE=node:24.22.1-bookworm-slim
2
+ FROM ${HAI_NODE_IMAGE} AS build
4
3
 
5
4
  ENV PNPM_HOME=/pnpm
6
5
  ENV PATH=$PNPM_HOME:$PATH
@@ -8,19 +7,25 @@ ENV CI=true
8
7
  ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
9
8
  WORKDIR /workspace
10
9
 
11
- RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
10
+ ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
11
+ ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
12
+ ARG npm_config_registry=$HAI_NPM_REGISTRY
13
+ ARG pnpm_config_registry=$HAI_NPM_REGISTRY
14
+ RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
12
15
  COPY pnpm-lock.yaml pnpm-workspace.yaml ./
13
16
  COPY images/dependencies/ ./
14
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
17
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
18
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
15
19
  COPY . .
16
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
17
20
  ARG HAI_BUILD_ID=
18
21
  ARG HAI_BUILD_COMMIT=unknown
19
22
  RUN HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" pnpm compile
20
- 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
21
- RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{api.servPackageName}} --prod /prod/app
23
+ # SQLite 13 的原生二进制随 npm 包交付,在构建时验证实际加载。
24
+ RUN if [ -d node_modules/better-sqlite3 ]; then node -e "const Database=require('better-sqlite3');const db=new Database(':memory:');db.prepare('SELECT 1').get();db.close()"; fi
25
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{api.servPackageName}} --prod /prod/app
26
+ RUN rm -f /prod/app/README* && find /prod/app/dist -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' \) -delete
22
27
 
23
- FROM node:24.22.1-bookworm-slim AS runtime-base
28
+ FROM ${HAI_NODE_IMAGE} AS runtime-base
24
29
 
25
30
  ENV NODE_ENV=production
26
31
  ENV HOST=0.0.0.0
@@ -33,5 +38,5 @@ RUN mkdir -p /app/data && chown node:node /app/data
33
38
  USER node
34
39
  EXPOSE 3000
35
40
  HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
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))"
41
+ CMD node -e "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config', configSchemas: { serv: ServConfigSchema } }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"
37
42
  CMD ["node", "dist/index.js"]
@@ -24,7 +24,9 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
24
24
  return url.port
25
25
  }
26
26
 
27
- return url.protocol === 'https:' ? '443' : '80'
27
+ return url.protocol === 'https:'
28
+ ? '443'
29
+ : '80'
28
30
  }
29
31
  catch {
30
32
  return fallbackPort
@@ -46,22 +48,24 @@ export default defineConfig({
46
48
  trace: 'retain-on-failure',
47
49
  screenshot: 'only-on-failure',
48
50
  },
49
- webServer: [{
50
- name: '{{api.servPackageName}}',
51
- command: 'pnpm --filter {{api.servPackageName}} exec node dist/index.js',
52
- env: createE2eServerEnv({
53
- ...createE2eNotificationEnv(),
54
- HAI_CORE_ENV: 'test',
55
- HAI_DB: JSON.stringify({ type: 'sqlite', database: `${process.env.HAI_E2E_DATA_ROOT}/api.db` }),
56
- HAI_CACHE: JSON.stringify({ type: 'memory' }),
57
- HAI_STORAGE: JSON.stringify({ type: 'local', root: `${process.env.HAI_E2E_DATA_ROOT}/uploads` }),
58
- HAI_VECDB: JSON.stringify({ type: 'lancedb', path: `${process.env.HAI_E2E_DATA_ROOT}/vecdb` }),
59
- HAI_CRYPTO_TRANSPORT_SERVERKEYRING: createE2eTransportRing(),
60
- HOST: serviceHost,
61
- PORT: servicePort,
62
- }),
63
- url: `${baseURL}/health`,
64
- reuseExistingServer: false,
65
- timeout: 120_000,
66
- }],
51
+ webServer: [
52
+ {
53
+ name: '{{api.servPackageName}}',
54
+ command: 'pnpm --filter {{api.servPackageName}} exec node dist/index.js',
55
+ env: createE2eServerEnv({
56
+ ...createE2eNotificationEnv(),
57
+ HAI_CORE_ENV: 'test',
58
+ HAI_DB: JSON.stringify({ type: 'sqlite', database: `${process.env.HAI_E2E_DATA_ROOT}/api.db` }),
59
+ HAI_CACHE: JSON.stringify({ type: 'memory' }),
60
+ HAI_STORAGE: JSON.stringify({ type: 'local', root: `${process.env.HAI_E2E_DATA_ROOT}/uploads` }),
61
+ HAI_VECDB: JSON.stringify({ type: 'lancedb', path: `${process.env.HAI_E2E_DATA_ROOT}/vecdb` }),
62
+ HAI_CRYPTO_TRANSPORT_SERVERKEYRING: createE2eTransportRing(),
63
+ HOST: serviceHost,
64
+ PORT: servicePort,
65
+ }),
66
+ url: `${baseURL}/health`,
67
+ reuseExistingServer: false,
68
+ timeout: 120_000,
69
+ },
70
+ ],
67
71
  })
@@ -1,5 +1,5 @@
1
1
  {{! @skipIfEmpty }}
2
- shellEmulator: true
2
+ minimumReleaseAgeExcludePrune: true
3
3
 
4
4
  trustPolicy: no-downgrade
5
5
  # 上游构建工具仍要求 4.x;4.0.3 tarball 已核对原 lockfile 的 SHA-512。
@@ -8,33 +8,44 @@ trustPolicyExclude:
8
8
 
9
9
  # pnpm 12 配置;原生依赖仅运行明确列出的安装脚本。
10
10
  nodeLinker: hoisted
11
- allowBuilds:
12
- cpu-features: true
13
- onnxruntime-node: true
14
- protobufjs: true
15
- ssh2: true
16
- vue-demi: true
17
- better-sqlite3: true
18
- esbuild: true
19
- core-js: false
11
+ shellEmulator: true
20
12
 
13
+ packages:
14
+ - apps/*
15
+
16
+ # 构建与浏览器依赖的安全修复;版本与框架工作区同步维护。
21
17
  overrides:
22
- "@ai-sdk/provider-utils@>=4.0.0 <4.0.33": 4.0.57
18
+ '@ai-sdk/provider-utils@>=4.0.0 <4.0.33': 4.0.57
19
+ '@types/node': {{versions.nodeTypes}}
20
+ body-parser@>=2 <2.4.0: 2.4.0
23
21
  cookie@<0.7.0: 0.7.2
22
+ dompurify@>=3 <3.4.17: 3.4.17
24
23
  esbuild@<0.28.1: 0.28.2
24
+ fast-uri@>=3 <3.1.9: 3.1.9
25
+ follow-redirects@>=1.16.0 <1.16.2: 1.16.2
26
+ glob@>=9 <=14.0.1: 14.0.4
27
+ ip-address@<=10.7.3: 10.7.6
25
28
  katex@<0.18.2: 0.18.10
29
+ minimatch@>=7 <11.0.1: 11.0.5
30
+ parse5@>=8 <8.1.0: 8.1.0
31
+ qs@>=6.7 <6.16.1: 6.17.1
32
+ runed@<0.38.0: 0.38.0
26
33
  sharp@<0.35.5: 0.35.5
27
34
  tinypool@<2.1.2: 2.2.0
28
- '@qdrant/js-client-rest>undici': 7.30.0
29
35
  xcode>uuid: 11.1.1
30
- "@types/node": "{{versions.nodeTypes}}"
31
36
 
32
- packages:
33
- - apps/*
37
+ allowBuilds:
38
+ core-js: false
39
+ cpu-features: true
40
+ esbuild: true
41
+ onnxruntime-node: true
42
+ protobufjs: true
43
+ ssh2: true
44
+ vue-demi: true
34
45
 
35
46
  {{#if useCatalogProtocol}}
36
47
  catalog:
37
48
  {{#each haiCatalogPackages}}
38
- '{{@key}}': {{this}}
49
+ {{{yaml @key this}}}
39
50
  {{/each}}
40
51
  {{/if}}
@@ -23,3 +23,13 @@ tmp
23
23
  # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
24
24
  **/.env.*
25
25
  **/.hai
26
+ # 部署证书和测试材料不进入镜像构建上下文。
27
+ **/*.pem
28
+ **/*.key
29
+ **/*.p12
30
+ **/*.pfx
31
+ **/tests
32
+ **/e2e
33
+ **/*.test.*
34
+ **/*.spec.*
35
+ docs
@@ -3,8 +3,13 @@ node_modules
3
3
  build
4
4
  dist
5
5
  .turbo
6
- android
7
- ios
6
+ **/android/.gradle/
7
+ **/android/**/build/
8
+ **/ios/Pods/
9
+ **/ios/App/Pods/
10
+ *.keystore
11
+ *.jks
12
+ *.p12
8
13
  **/src/lib/paraglide
9
14
  **/src-tauri/gen/
10
15
  **/src-tauri/target/
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 简介
6
6
 
7
+ `android` / `ios` 是需要维护的原生工程,应提交 Git;签名秘密单独忽略。普通 `clean` 保留这些目录,只清除构建产物。
8
+
7
9
  App/H5 前端工程,复用 shared 的响应式 mini 后台,使用 Svelte 5 + Vite + Capacitor,可输出 Web 预览并同步 Android / iOS 原生工程。
8
10
 
9
11
  ## 核心功能清单
@@ -14,8 +16,12 @@ App/H5 前端工程,复用 shared 的响应式 mini 后台,使用 Svelte 5 +
14
16
 
15
17
  ## 使用入门
16
18
 
19
+ `capacitor.config.ts` 的 `appName` 动态读取 `packages/{{fullstack.servPackageName}}/config/_core.yml` 的 `name`,环境文件从工程根加载,支持 `HAI_CORE_NAME` 及 Core 的统一优先级。正式打包默认 `prod`,进程 `HAI_CORE_ENV` 可选择模式。名称在原生构建时固定;已有 Android/iOS 工程还需同步平台显示名(Android `strings.xml` 的 `app_name` / `title_activity_main`,iOS `CFBundleDisplayName`),再重新打包。
20
+
17
21
  ### i18n 与 shared 协同
18
22
 
23
+ 先从工程根安装依赖并启动Serv。浏览器预览输入有效账号应进入共享界面,原生包还要配置设备真实可达API。上传头像的输入为允许类型且不超过512KiB的图片,输出为服务端生成资源标识和展示结果;不把本机任意路径当上传key。主题/语言改变后应在重新登录时恢复。
24
+
19
25
  - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
20
26
  - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
21
27
  - 语言切换通过 shared runtime 与本端 `src/lib/paraglide/runtime.js` 同步。
@@ -35,25 +41,21 @@ pnpm --filter {{projectName}}-app build
35
41
  # 同步原生工程;首次使用前按需执行 cap:add:android / cap:add:ios
36
42
  pnpm --filter {{projectName}}-app cap:sync
37
43
 
38
- # Android / iOS 原生打包
39
- pnpm --filter {{projectName}}-app cap:build:android:debug
40
- pnpm --filter {{projectName}}-app package:android
41
- pnpm --filter {{projectName}}-app package:ios
44
+ # 工程根的原生打包入口
45
+ pnpm build --target app --platform android --runtime auto --debug
46
+ pnpm build --target app --platform android --runtime auto
47
+ pnpm build --target app --platform ios
42
48
  ```
43
49
 
44
50
  本地开发通过根 `.env` 的 `PUBLIC_API_BASE` 配置代理。原生打包前,在本端 `public/runtime-config.js` 中设置 `globalThis.__HAI_PUBLIC_CONFIG__ = { apiBase: 'https://api.example.com/api/v1' }`,再编译并同步原生工程;这里只能放公开地址,不能放密钥。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000/api/v1`。
45
51
 
46
- 发布入口实际调用 hai build --target app,缺平台工程、工具链、签名配置或新的 APK/IPA 均失败。package 默认 Android;iOS 必须在 macOS/Xcode 上执行。CAPACITOR_SERVER_URL 仅开发使用,发行打包过滤并核验不进入原生配置。调试 APK 命令为 Windows Gradle 调试入口,不作为发行成功证据。
47
-
48
- ### IAM 认证配置
49
-
50
- 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。Memory 仅用于本地开发和显式测试;生产环境即使只有一个实例,也须使用框架支持的共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
52
+ Android 容器会安装依赖、编译 workspace 并打包。调试时可临时生成缺失的 Android 工程。发布时使用已有 Android 工程和私有签名配置,见 [Android 容器打包](../../.agents/skills/hai-capacitor/reference.md#android-容器打包)。iOS 使用 macOS/Xcode 和相应签名。`CAPACITOR_SERVER_URL` 仅供开发热更新。成功后 CLI 打印 `.hai/build/app/<平台>/<构建类型>/build-{{projectName}}-app-<平台>-<类型>-YYYYMMDD-HHmmss-<随机串>-<版本>/` 中的安装包路径;APK 如 `{{projectName}}-android-debug-<版本>.apk`,使用工程标识,显示名仍取 Core。
51
53
 
52
- LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise;回调第三参数 context 提供 expiresAt/idempotencyKey,发送服务按稳定键持久化去重,异常/超时为结果未知且不自动重发。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
54
+ ### 认证与模块导航
53
55
 
54
- 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
56
+ 认证方式、注册、密码策略、协议与品牌由Serv的 `_iam.yml` 控制。前端只接收安全公开配置,使用共享AuthPanel和加密typed client;服务端再次核验权限。LDAP、通知回调、用途证明和会话限制在服务端接入,不把私密配置复制到客户端。
55
57
 
56
- 完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
58
+ 所有前端共用的偏好、认证存储、表单、审计与帮助行为见[共享工作区](../../packages/{{fullstack.sharedPackageName}}/README.md),详细配置见[生成应用IAM技能](../../.agents/skills/hai-iam/reference.md)。
57
59
 
58
60
  启用反馈时,shared SDK 负责图片预览/比例标注/导出轮询;原生保存使用 Filesystem 缓存与 Share,完成/取消清理临时文件。首次添加 iOS 平台后按 Filesystem 插件要求配置 PrivacyInfo.xcprivacy。
59
61
 
@@ -1,20 +1,22 @@
1
1
  {{! @skipIfEmpty }}
2
2
  {{#if fullstack.frontends.app}}
3
3
  import type { CapacitorConfig } from '@capacitor/cli'
4
+ import { resolve } from 'node:path'
4
5
  import process from 'node:process'
6
+ import { resolveNativeName } from '../../scripts/native-name.mjs'
5
7
 
8
+ const envDir = resolve(process.cwd(), '../..')
6
9
  const config: CapacitorConfig = {
7
10
  appId: 'com.hai.{{fullstack.nativeAppIdSegment}}.app',
8
- appName: {{jsString fullstack.appName}},
11
+ appName: resolveNativeName(envDir, resolve(envDir, 'packages/{{fullstack.servPackageName}}/config/_core.yml')),
9
12
  webDir: 'dist',
10
13
  server: process.env.NODE_ENV === 'production'
11
14
  ? undefined
12
15
  : {
13
- // 仅用于 Capacitor live reload 前端页面;API 地址由前端配置或 shared client 参数控制。
16
+ // 仅用于 Capacitor live reload 前端页面;API 地址由前端配置或 shared client 参数控制。
14
17
  url: process.env.CAPACITOR_SERVER_URL,
15
18
  cleartext: process.env.CAPACITOR_SERVER_URL?.startsWith('http://') ?? false,
16
19
  },
17
20
  }
18
-
19
21
  export default config
20
22
  {{/if}}
@@ -5,9 +5,17 @@ import { haiQualityConfig } from '../../eslint-rules.js'
5
5
 
6
6
  export default antfu({
7
7
  // unused-imports 使用 TS 规则,JS 同样使用 TS parser 以保持 scope 契约一致。
8
- typescript: { files: [GLOB_SRC] },
8
+ typescript: {
9
+ files: [GLOB_SRC],
10
+ },
9
11
  formatters: true,
10
- ignores: ['**/build/**', '**/dist/**', '**/*.d.ts', '**/project.inlang/**', '**/src/lib/paraglide/**'],
12
+ ignores: [
13
+ '**/build/**',
14
+ '**/dist/**',
15
+ '**/*.d.ts',
16
+ '**/project.inlang/**',
17
+ '**/src/lib/paraglide/**',
18
+ ],
11
19
  svelte: true,
12
20
  }, haiQualityConfig, {
13
21
  files: ['**/*.svelte'],
@@ -23,7 +23,7 @@
23
23
  "lint:fix": "eslint . --fix",
24
24
  "test": "pnpm paraglide:compile && vitest run --passWithNoTests",
25
25
  "test:watch": "pnpm paraglide:compile && vitest",
26
- "clean": "rimraf dist build android ios .turbo src/lib/paraglide",
26
+ "clean": "rimraf dist build .turbo src/lib/paraglide",
27
27
  "cap:add:android": "cap add android",
28
28
  "cap:add:ios": "cap add ios",
29
29
  "cap:sync": "pnpm build && cap sync",
@@ -30,7 +30,13 @@
30
30
  apiOptions = {
31
31
  clientName: '{{projectName}}-app',
32
32
  terminalType: 'app',
33
- ...(capacitor.isNative() ? { requireApiBase: true, tokenStorage: createCapacitorTokenStorage(), headers: { 'X-HAI-Token-Transport': 'body' } } : {}),
33
+ ...(capacitor.isNative()
34
+ ? {
35
+ requireApiBase: true,
36
+ tokenStorage: createCapacitorTokenStorage(),
37
+ headers: { 'X-HAI-Token-Transport': 'body' },
38
+ }
39
+ : {}),
34
40
  }
35
41
  }
36
42
  onMount(() => {
@@ -42,7 +48,9 @@
42
48
  })
43
49
  }
44
50
  })
45
- useToastFeedback(() => startupFailed ? messages.operation_failed() : '')
51
+ useToastFeedback(() => startupFailed
52
+ ? messages.operation_failed()
53
+ : '')
46
54
  </script>
47
55
 
48
56
  {#if startupFailed}