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

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 +8 -5
  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 +29 -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
@@ -8,14 +8,26 @@ export const APP_DUTIES = {
8
8
  admin: ['app.users.manage', 'app.roles.manage'],
9
9
  audit_admin: ['audit:read', 'audit:backup'],
10
10
  config_admin: ['app.config.manage'],
11
- business_operator: ['feedback:create', 'feedback:read', 'feedback:process', 'feedback:reopen', 'feedback:export'],
11
+ business_operator: [
12
+ 'feedback:create',
13
+ 'feedback:read',
14
+ 'feedback:process',
15
+ 'feedback:reopen',
16
+ 'feedback:export',
17
+ ],
12
18
  user: ['feedback:create'],
13
19
  } as const
14
20
 
15
21
  export async function seedAppDuties(): Promise<HaiResult<void>> {
16
22
  if (iam.config?.rbac.superAdminRole && iam.config.rbac.superAdminRole in APP_DUTIES)
17
23
  return err(HaiCommonError.VALIDATION_ERROR)
18
- for (const code of ['admin', 'audit_admin', 'config_admin', 'business_operator', 'user'] as const) {
24
+ for (const code of [
25
+ 'admin',
26
+ 'audit_admin',
27
+ 'config_admin',
28
+ 'business_operator',
29
+ 'user',
30
+ ] as const) {
19
31
  const permissionIds: string[] = []
20
32
  for (const permissionCode of APP_DUTIES[code]) {
21
33
  let permission = await iam.authz.getPermissionByCode(permissionCode)
@@ -25,8 +37,11 @@ export async function seedAppDuties(): Promise<HaiResult<void>> {
25
37
  permission = await iam.authz.createPermission({ code: permissionCode, name: permissionCode, type: 'api' })
26
38
  if (!permission.success) {
27
39
  permission = await iam.authz.getPermissionByCode(permissionCode)
28
- if (!permission.success || !permission.data)
29
- return permission.success ? err(HaiCommonError.INTERNAL_ERROR) : permission
40
+ if (!permission.success || !permission.data) {
41
+ return permission.success
42
+ ? err(HaiCommonError.INTERNAL_ERROR)
43
+ : permission
44
+ }
30
45
  }
31
46
  }
32
47
  if (permission.data)
@@ -36,16 +51,26 @@ export async function seedAppDuties(): Promise<HaiResult<void>> {
36
51
  if (!role.success)
37
52
  return role
38
53
  if (!role.data) {
39
- role = await iam.authz.createRole({ code, name: serverM(code), isSystem: true })
54
+ role = await iam.authz.createRole({
55
+ code,
56
+ name: serverM(code),
57
+ isSystem: true,
58
+ })
40
59
  if (!role.success) {
41
60
  role = await iam.authz.getRoleByCode(code)
42
- if (!role.success || !role.data)
43
- return role.success ? err(HaiCommonError.INTERNAL_ERROR) : role
61
+ if (!role.success || !role.data) {
62
+ return role.success
63
+ ? err(HaiCommonError.INTERNAL_ERROR)
64
+ : role
65
+ }
44
66
  }
45
67
  }
46
68
  if (!role.data)
47
69
  return err(HaiCommonError.INTERNAL_ERROR)
48
- const updated = await iam.authz.updateRole(role.data.id, { name: serverM(code), permissionIds })
70
+ const updated = await iam.authz.updateRole(role.data.id, {
71
+ name: serverM(code),
72
+ permissionIds,
73
+ })
49
74
  if (!updated.success)
50
75
  return updated
51
76
  }
@@ -58,11 +83,17 @@ export async function bootstrapDutyAccount(username: 'audit_admin' | 'config_adm
58
83
  if (!password)
59
84
  return ok(undefined)
60
85
  const created = await iam.user.createUser({ username, password })
61
- if (!created.success)
62
- return created.error.code === HaiIamError.USER_ALREADY_EXISTS.code ? ok(undefined) : created
86
+ if (!created.success) {
87
+ return created.error.code === HaiIamError.USER_ALREADY_EXISTS.code
88
+ ? ok(undefined)
89
+ : created
90
+ }
63
91
  const role = await iam.authz.getRoleByCode(username)
64
- if (!role.success || !role.data)
65
- return role.success ? err(HaiCommonError.INTERNAL_ERROR) : role
92
+ if (!role.success || !role.data) {
93
+ return role.success
94
+ ? err(HaiCommonError.INTERNAL_ERROR)
95
+ : role
96
+ }
66
97
  const assigned = await iam.authz.syncRoles(created.data.user.id, [role.data.id])
67
98
  if (!assigned.success) {
68
99
  const removed = await iam.user.deleteUser(created.data.user.id)
@@ -34,8 +34,17 @@ interface LoginData { user: { id: string }, tokens: { accessToken: string, refre
34
34
  async function call<T>(route: string, input?: object, token?: string, method = 'POST'): Promise<HaiResult<T>> {
35
35
  const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
36
36
  method,
37
- headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
38
- ...(input ? { body: JSON.stringify(input) } : {}),
37
+ headers: {
38
+ 'content-type': 'application/json',
39
+ ...(token
40
+ ? { authorization: `Bearer ${token}` }
41
+ : {}),
42
+ },
43
+ ...(input
44
+ ? {
45
+ body: JSON.stringify(input),
46
+ }
47
+ : {}),
39
48
  })
40
49
  return response.json() as Promise<HaiResult<T>>
41
50
  }
@@ -45,12 +54,14 @@ beforeAll(async () => {
45
54
  vi.stubEnv('HAI_DB_DATABASE', '":memory:"')
46
55
  vi.stubEnv('HAI_STORAGE_ROOT', storageRoot)
47
56
  vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', TEST_PASSWORD)
48
- core.init({ configDir: './config' })
49
57
  await initializeModules()
50
58
  const key = crypto.asymmetric.generateKeyPair()
51
59
  if (!key.success)
52
60
  throw new Error(key.error.message)
53
- vi.stubEnv('HAI_CRYPTO_TRANSPORT_SERVERKEYRING', JSON.stringify({ activeKeyId: 'test-v1', keys: { 'test-v1': key.data } }))
61
+ vi.stubEnv('HAI_CRYPTO_TRANSPORT_SERVERKEYRING', JSON.stringify({
62
+ activeKeyId: 'test-v1',
63
+ keys: { 'test-v1': key.data },
64
+ }))
54
65
  app = createServerApp()
55
66
  transport = crypto.transport.createClient({
56
67
  keyExchangeUrl: `http://localhost${PREFIX}/_hai/key-exchange`,
@@ -100,7 +111,12 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
100
111
  if (!result.success)
101
112
  return
102
113
  const config = core.config.getOrThrow<CoreConfig>('core')
103
- expect(result.data).toMatchObject({ name: config.name, version: config.version, env: config.env, defaultLocale: config.defaultLocale })
114
+ expect(result.data).toMatchObject({
115
+ name: config.name,
116
+ version: config.version,
117
+ env: config.env,
118
+ defaultLocale: config.defaultLocale,
119
+ })
104
120
  const invalid = await transport.encryptedFetch(`http://localhost${PREFIX}/app/echo`, {
105
121
  method: 'POST',
106
122
  headers: { 'content-type': 'application/json' },
@@ -130,7 +146,14 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
130
146
  it('mounts public auth routes while keeping default IAM administration routes private', async () => {
131
147
  const config = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/config`)
132
148
  expect(config.status).toBe(200)
133
- for (const route of ['/auth/register', '/auth/login/ldap', '/auth/login/otp', '/auth/otp/send', '/auth/password-reset/request', '/auth/password-reset/confirm']) {
149
+ for (const route of [
150
+ '/auth/register',
151
+ '/auth/login/ldap',
152
+ '/auth/login/otp',
153
+ '/auth/otp/send',
154
+ '/auth/password-reset/request',
155
+ '/auth/password-reset/confirm',
156
+ ]) {
134
157
  const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
135
158
  method: 'POST',
136
159
  headers: { 'content-type': 'application/json' },
@@ -145,14 +168,21 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
145
168
  const response = await transport.encryptedFetch(`http://localhost${PREFIX}${route}`, {
146
169
  method,
147
170
  headers: { 'content-type': 'application/json', 'authorization': `Bearer ${adminToken}` },
148
- ...(method === 'POST' ? { body: '{}' } : {}),
171
+ ...(method === 'POST'
172
+ ? { body: '{}' }
173
+ : {}),
149
174
  })
150
175
  expect(response.status).toBe(404)
151
176
  }
152
177
  })
153
178
 
154
179
  it('creates a normal user and enforces guards on every administration API', async () => {
155
- const created = await call<AdminUser>('/admin/users/create', { username: 'normal-user', password: TEST_PASSWORD, role: 'user', accountStatus: 'active' }, adminToken)
180
+ const created = await call<AdminUser>('/admin/users/create', {
181
+ username: 'normal-user',
182
+ password: TEST_PASSWORD,
183
+ role: 'user',
184
+ accountStatus: 'active',
185
+ }, adminToken)
156
186
  expect(created.success).toBe(true)
157
187
  if (!created.success)
158
188
  return
@@ -163,14 +193,35 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
163
193
  return
164
194
  userToken = loggedIn.data.tokens.accessToken
165
195
  for (const [route, input] of [
166
- ['/admin/users/list', { page: 1, pageSize: 20 }],
167
- ['/admin/users/create', { username: 'forbidden', password: TEST_PASSWORD }],
168
- ['/admin/users/update', { id: adminId, accountStatus: 'dormant' }],
169
- ['/admin/users/delete', { id: adminId }],
170
- ['/admin/users/avatar', { id: adminId, contentType: 'image/png', base64: PNG }],
196
+ [
197
+ '/admin/users/list',
198
+ { page: 1, pageSize: 20 },
199
+ ],
200
+ [
201
+ '/admin/users/create',
202
+ { username: 'forbidden', password: TEST_PASSWORD },
203
+ ],
204
+ [
205
+ '/admin/users/update',
206
+ { id: adminId, accountStatus: 'dormant' },
207
+ ],
208
+ [
209
+ '/admin/users/delete',
210
+ { id: adminId },
211
+ ],
212
+ [
213
+ '/admin/users/avatar',
214
+ { id: adminId, contentType: 'image/png', base64: PNG },
215
+ ],
171
216
  ['/admin/roles/list', undefined],
172
- ['/admin/roles/update', { id: 'missing', description: 'x' }],
173
- ['/admin/audit/list', { page: 1, pageSize: 20 }],
217
+ [
218
+ '/admin/roles/update',
219
+ { id: 'missing', description: 'x' },
220
+ ],
221
+ [
222
+ '/admin/audit/list',
223
+ { page: 1, pageSize: 20 },
224
+ ],
174
225
  ] as const) {
175
226
  const denied = await call(route, input, userToken)
176
227
  expect(denied.success).toBe(false)
@@ -180,8 +231,16 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
180
231
  })
181
232
 
182
233
  it('allows refresh-token body transport only for an identified trusted native origin', async () => {
183
- for (const [clientName, origin] of [['{{projectName}}-app', 'capacitor://localhost'], ['{{projectName}}-desktop', 'http://tauri.localhost']] as const) {
184
- const headers = { 'content-type': 'application/json', 'x-request-client': clientName, 'x-hai-token-transport': 'body', origin }
234
+ for (const [clientName, origin] of [
235
+ ['{{projectName}}-app', 'capacitor://localhost'],
236
+ ['{{projectName}}-desktop', 'http://tauri.localhost'],
237
+ ] as const) {
238
+ const headers = {
239
+ 'content-type': 'application/json',
240
+ 'x-request-client': clientName,
241
+ 'x-hai-token-transport': 'body',
242
+ origin,
243
+ }
185
244
  const login = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/login`, {
186
245
  method: 'POST',
187
246
  headers,
@@ -203,10 +262,18 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
203
262
  if (refreshed.success)
204
263
  expect(refreshed.data.tokens.refreshToken).not.toBe(result.data.tokens.refreshToken)
205
264
  }
206
- for (const [clientName, origin] of [['{{projectName}}-app', 'http://untrusted.example'], ['unrecognized-client', 'capacitor://localhost']] as const) {
265
+ for (const [clientName, origin] of [
266
+ ['{{projectName}}-app', 'http://untrusted.example'],
267
+ ['unrecognized-client', 'capacitor://localhost'],
268
+ ] as const) {
207
269
  const response = await transport.encryptedFetch(`http://localhost${PREFIX}/auth/login`, {
208
270
  method: 'POST',
209
- headers: { 'content-type': 'application/json', 'x-request-client': clientName, 'x-hai-token-transport': 'body', origin },
271
+ headers: {
272
+ 'content-type': 'application/json',
273
+ 'x-request-client': clientName,
274
+ 'x-hai-token-transport': 'body',
275
+ origin,
276
+ },
210
277
  body: JSON.stringify({ identifier: 'admin', password: TEST_PASSWORD }),
211
278
  })
212
279
  const result = await response.json() as HaiResult<LoginData>
@@ -223,7 +290,20 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
223
290
  expect(listed.data.items).toHaveLength(1)
224
291
  expect(listed.data.total).toBe(2)
225
292
  }
226
- for (const [route, input] of [['/admin/users/delete', { id: adminId }], ['/admin/users/update', { id: adminId, accountStatus: 'dormant' }], ['/admin/users/update', { id: adminId, role: 'user' }]] as const) {
293
+ for (const [route, input] of [
294
+ [
295
+ '/admin/users/delete',
296
+ { id: adminId },
297
+ ],
298
+ [
299
+ '/admin/users/update',
300
+ { id: adminId, accountStatus: 'dormant' },
301
+ ],
302
+ [
303
+ '/admin/users/update',
304
+ { id: adminId, role: 'user' },
305
+ ],
306
+ ] as const) {
227
307
  const denied = await call(route, input, adminToken)
228
308
  expect(denied.success).toBe(false)
229
309
  if (!denied.success)
@@ -231,7 +311,7 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
231
311
  }
232
312
  })
233
313
 
234
- it('updates personal preferences, validates images and serves only allowed avatar resources', async () => {
314
+ it('updates personal preferences and clears the email address', async () => {
235
315
  expect((await call('/profile/update', { displayName: 'Updated', locale: 'en-US', theme: '{{fullstack.theme.defaultTheme}}' }, userToken)).success).toBe(true)
236
316
  const current = await call<{ metadata: { locale: string, theme: string } }>('/auth/me', undefined, userToken, 'GET')
237
317
  expect(current.success).toBe(true)
@@ -242,16 +322,25 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
242
322
  expect(cleared.success).toBe(true)
243
323
  if (cleared.success)
244
324
  expect(cleared.data.email).toBe('')
325
+ })
326
+
327
+ it('rejects invalid images, oversized payloads and anonymous avatar uploads', async () => {
245
328
  const invalid = await call('/profile/avatar', { contentType: 'image/png', base64: 'PHN2Zz48L3N2Zz4=' }, userToken)
246
329
  expect(invalid.success).toBe(false)
247
330
  if (!invalid.success)
248
331
  expect(invalid.error.httpStatus).toBe(400)
249
- const oversized = await call('/profile/avatar', { contentType: 'image/png', base64: Buffer.alloc(512 * 1024 + 1).toString('base64') }, userToken)
332
+ const oversized = await call('/profile/avatar', {
333
+ contentType: 'image/png',
334
+ base64: Buffer.alloc(512 * 1024 + 1).toString('base64'),
335
+ }, userToken)
250
336
  expect(oversized.success).toBe(false)
251
337
  const anonymous = await call('/profile/avatar', { contentType: 'image/png', base64: PNG })
252
338
  expect(anonymous.success).toBe(false)
253
339
  if (!anonymous.success)
254
340
  expect(anonymous.error.httpStatus).toBe(401)
341
+ })
342
+
343
+ it('serves only uploaded avatar resources and restricts profile fields', async () => {
255
344
  const injected = await transport.encryptedFetch(`http://localhost${PREFIX}/profile/update`, {
256
345
  method: 'POST',
257
346
  headers: { 'content-type': 'application/json', 'authorization': `Bearer ${userToken}` },
@@ -291,7 +380,13 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
291
380
  const roles = await call<{ items: { id: string, code: string, userCount: number }[] }>('/admin/roles/list', undefined, adminToken)
292
381
  expect(roles.success).toBe(true)
293
382
  if (roles.success) {
294
- expect(roles.data.items.map(item => item.code).sort()).toEqual(['admin', 'audit_admin', 'business_operator', 'config_admin', 'user'])
383
+ expect(roles.data.items.map(item => item.code).sort()).toEqual([
384
+ 'admin',
385
+ 'audit_admin',
386
+ 'business_operator',
387
+ 'config_admin',
388
+ 'user',
389
+ ])
295
390
  const role = roles.data.items.find(item => item.code === 'user')!
296
391
  expect(role.userCount).toBe(1)
297
392
  expect((await call('/admin/roles/update', { id: role.id, description: 'Updated description' }, adminToken)).success).toBe(true)
@@ -319,7 +414,12 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
319
414
  const before = await iam.user.getUser(user.id)
320
415
  expect(before.success).toBe(true)
321
416
  const sync = vi.spyOn(iam.authz, 'syncRoles').mockResolvedValueOnce(err(HaiCommonError.API_ERROR, 'Injected role persistence failure'))
322
- const failed = await call('/admin/users/update', { id: user.id, role: 'admin', email: 'changed@example.com', displayName: 'Should not change' }, adminToken)
417
+ const failed = await call('/admin/users/update', {
418
+ id: user.id,
419
+ role: 'admin',
420
+ email: 'changed@example.com',
421
+ displayName: 'Should not change',
422
+ }, adminToken)
323
423
  sync.mockRestore()
324
424
  expect(failed.success).toBe(false)
325
425
  const after = await iam.user.getUser(user.id)
@@ -333,7 +433,7 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
333
433
  const log = vi.spyOn(audit, 'log').mockImplementation((input, tx) => input.action === 'update' && input.resource === 'profile'
334
434
  ? Promise.resolve(err(HaiCommonError.API_ERROR, 'Injected audit persistence failure'))
335
435
  : originalLog(input, tx))
336
- const logger = vi.spyOn(core.logger, 'error').mockImplementation(() => {})
436
+ const logger = vi.spyOn(core.logger, 'error').mockImplementation(() => { })
337
437
  try {
338
438
  const result = await call<AdminUser>('/profile/update', { displayName: 'Committed profile' }, userToken)
339
439
  expect(result.success).toBe(true)
@@ -369,9 +469,22 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
369
469
  const snapshot = await iam.session.get(userToken)
370
470
  if (!snapshot.success || !snapshot.data)
371
471
  throw new Error('Session unavailable')
372
- const stale = await iam.session.update(userToken, { roles: ['admin'], permissions: ['*'] }, snapshot.data.version)
472
+ const stale = await iam.session.update(userToken, {
473
+ roles: ['admin'],
474
+ permissions: ['*'],
475
+ }, snapshot.data.version)
373
476
  expect(stale.success).toBe(true)
374
- for (const [route, input] of [['/admin/users/list', { page: 1, pageSize: 20 }], ['/admin/roles/list', undefined], ['/admin/audit/list', { page: 1, pageSize: 20 }]] as const) {
477
+ for (const [route, input] of [
478
+ [
479
+ '/admin/users/list',
480
+ { page: 1, pageSize: 20 },
481
+ ],
482
+ ['/admin/roles/list', undefined],
483
+ [
484
+ '/admin/audit/list',
485
+ { page: 1, pageSize: 20 },
486
+ ],
487
+ ] as const) {
375
488
  const denied = await call(route, input, userToken)
376
489
  expect(denied.success).toBe(false)
377
490
  if (!denied.success)
@@ -410,8 +523,8 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
410
523
  expect(otherLogin.success).toBe(true)
411
524
  if (!otherLogin.success)
412
525
  return
413
- let resume: () => void = () => {}
414
- let entered: () => void = () => {}
526
+ let resume: () => void = () => { }
527
+ let entered: () => void = () => { }
415
528
  const paused = new Promise<void>((resolve) => {
416
529
  resume = resolve
417
530
  })
@@ -462,7 +575,10 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
462
575
  call('/admin/users/update', { id: adminId, accountStatus: 'dormant' }, loggedIn.data.tokens.accessToken),
463
576
  ])
464
577
  expect(results.filter(result => result.success)).toHaveLength(1)
465
- const admins = await iam.user.listUsers({ accountStatus: 'active', include: ['roles'] })
578
+ const admins = await iam.user.listUsers({
579
+ accountStatus: 'active',
580
+ include: ['roles'],
581
+ })
466
582
  expect(admins.success).toBe(true)
467
583
  if (admins.success) {
468
584
  const surviving = admins.data.items.filter(item => item.roles?.some(role => role.code === 'admin'))
@@ -15,7 +15,6 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest'
15
15
  import { closeModules, initializeModules } from '../src/server/runtime.js'
16
16
 
17
17
  let storageRoot: string
18
-
19
18
  beforeEach(async () => {
20
19
  storageRoot = await mkdtemp(path.join(os.tmpdir(), '{{projectName}}-bootstrap-'))
21
20
  vi.stubEnv('HAI_DB_DATABASE', '":memory:"')
@@ -23,22 +22,33 @@ beforeEach(async () => {
23
22
  vi.stubEnv('HAI_IAM_PASSWORD_REQUIREUPPERCASE', 'true')
24
23
  core.init({ configDir: './config' })
25
24
  })
26
-
27
25
  afterEach(async () => {
28
26
  await closeModules()
29
27
  vi.unstubAllEnvs()
30
28
  await rm(storageRoot, { recursive: true, force: true })
31
29
  })
32
-
33
30
  it.each([
34
31
  { password: 'abc' },
35
32
  { password: 'lowercase123!' },
36
33
  ])('startup explains the failed password policy and permits a clean retry: $password', async ({ password }) => {
37
34
  vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', password)
38
- await expect(initializeModules()).rejects.toMatchObject({ cause: { ext: { module: 'bootstrap', fields: ['HAI_ADMIN_DEFAULT_PASSWORD'] } } })
39
- for (const module of [audit, iam, storage, crypto, cache, reldb])
35
+ await expect(initializeModules()).rejects.toMatchObject({
36
+ cause: {
37
+ ext: {
38
+ module: 'bootstrap',
39
+ fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
40
+ },
41
+ },
42
+ })
43
+ for (const module of [
44
+ audit,
45
+ iam,
46
+ storage,
47
+ crypto,
48
+ cache,
49
+ reldb,
50
+ ])
40
51
  expect(module.isInitialized).toBe(false)
41
-
42
52
  // 失败后再次初始化,确认未保留半成品管理员或未释放的模块。
43
53
  vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', 'Bootstrap-retry-Admin123!')
44
54
  await initializeModules()
@@ -49,14 +59,27 @@ it.each([
49
59
  expect(users.data.items[0]?.username).toBe('admin')
50
60
  }
51
61
  })
52
-
53
62
  it.each(['', ' '])('startup identifies an unset administrator password without exposing its value', async (password) => {
54
63
  vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', password)
55
- await expect(initializeModules()).rejects.toMatchObject({ cause: { ext: { module: 'bootstrap', code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code, fields: ['HAI_ADMIN_DEFAULT_PASSWORD'] } } })
56
- for (const module of [audit, iam, storage, crypto, cache, reldb])
64
+ await expect(initializeModules()).rejects.toMatchObject({
65
+ cause: {
66
+ ext: {
67
+ module: 'bootstrap',
68
+ code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code,
69
+ fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
70
+ },
71
+ },
72
+ })
73
+ for (const module of [
74
+ audit,
75
+ iam,
76
+ storage,
77
+ crypto,
78
+ cache,
79
+ reldb,
80
+ ])
57
81
  expect(module.isInitialized).toBe(false)
58
82
  })
59
-
60
83
  {{#if features.feedback}}
61
84
  it('optional feedback failure does not roll back authentication and business modules', async () => {
62
85
  vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', 'Bootstrap-retry-Admin123!')