@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
@@ -2,6 +2,5 @@
2
2
  {{#if features.feedback}}
3
3
  /** 当前应用编译时烘焙的产物身份;运行时配置不能覆盖。 */
4
4
  declare const __HAI_BUILD_METADATA__: { appVersion: string, buildId: string, commit: string }
5
-
6
5
  export const buildMetadata = __HAI_BUILD_METADATA__
7
6
  {{/if}}
@@ -7,8 +7,7 @@ import { createServerApp } from './app-server.js'
7
7
  import { closeModules, initializeModules } from './server/runtime.js'
8
8
 
9
9
  async function startServer(): Promise<void> {
10
- // 加载配置后按依赖顺序初始化;日志设置沿用 _core.yml。
11
- core.init({ configDir: './config' })
10
+ // 运行时先注册模块 Schema 并加载配置,再按依赖顺序初始化;日志设置沿用 _core.yml。
12
11
  await initializeModules()
13
12
  try {
14
13
  // HOST/PORT 环境变量优先于 config/_serv.yml。
@@ -11,8 +11,18 @@ import { storage } from '@h-ai/storage'
11
11
  import { SUPPORTED_LOCALES } from '{{fullstack.contractPackageName}}'
12
12
  import { serverM } from './messages.js'
13
13
 
14
- const ROLE_CODES = new Set<AdminRoleCode>(['admin', 'audit_admin', 'config_admin', 'business_operator', 'user'])
15
- const SUPPORTED_THEMES = [{{#each fullstack.theme.themes}}'{{this}}'{{#unless @last}}, {{/unless}}{{/each}}] as readonly string[]
14
+ const ROLE_CODES = new Set<AdminRoleCode>([
15
+ 'admin',
16
+ 'audit_admin',
17
+ 'config_admin',
18
+ 'business_operator',
19
+ 'user',
20
+ ])
21
+ const SUPPORTED_THEMES = [
22
+ {{#each fullstack.theme.themes}}
23
+ '{{this}}',
24
+ {{/each}}
25
+ ] as readonly string[]
16
26
  let adminMutationActive = false
17
27
 
18
28
  /** 管理写操作串行化,锁内重新鉴权,防止两个管理员同时互相删除或降级。 */
@@ -42,7 +52,10 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
42
52
  void cache.lock.extend(key, 300, owner).then((renewed) => {
43
53
  if (!renewed.success || !renewed.data) {
44
54
  leaseLost = true
45
- core.logger.error('Admin mutation lease renewal failed', { owner, error: renewed.success ? 'lease_not_owned' : renewed.error.code })
55
+ core.logger.error('Admin mutation lease renewal failed', {
56
+ owner,
57
+ error: renewed.success ? 'lease_not_owned' : renewed.error.code,
58
+ })
46
59
  }
47
60
  }).catch(() => {
48
61
  leaseLost = true
@@ -55,7 +68,9 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
55
68
  const actor = await getAdminUser(context.session?.userId ?? '')
56
69
  if (!actor.success)
57
70
  return actor
58
- const permitted = await iam.authz.checkPermission(actor.data.id, operation.startsWith('iam.roles.') ? 'app.roles.manage' : 'app.users.manage')
71
+ const permitted = await iam.authz.checkPermission(actor.data.id, operation.startsWith('iam.roles.')
72
+ ? 'app.roles.manage'
73
+ : 'app.users.manage')
59
74
  if (!permitted.success)
60
75
  return permitted
61
76
  if (actor.data.accountStatus !== 'active' || (actor.data.accountType === 'temporary' && (!actor.data.accountExpiresAt || new Date(actor.data.accountExpiresAt).getTime() <= Date.now())) || !permitted.data)
@@ -73,8 +88,12 @@ export async function withAdminMutation<T>(context: ServContext, operation: stri
73
88
  try {
74
89
  if (acquired) {
75
90
  const released = await cache.lock.release(key, owner)
76
- if (!released.success || !released.data)
77
- core.logger.error('Failed to release admin mutation lock', { owner, error: released.success ? 'lease_not_owned' : released.error.code })
91
+ if (!released.success || !released.data) {
92
+ core.logger.error('Failed to release admin mutation lock', {
93
+ owner,
94
+ error: released.success ? 'lease_not_owned' : released.error.code,
95
+ })
96
+ }
78
97
  }
79
98
  }
80
99
  finally {
@@ -123,7 +142,12 @@ export async function createAdminUser(input: AdminCreateUser, context: ServConte
123
142
  return role
124
143
  if (!role.data)
125
144
  return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
126
- const registered = await iam.user.createUser({ ...input, accountExpiresAt: input.accountExpiresAt ? new Date(input.accountExpiresAt) : undefined })
145
+ const registered = await iam.user.createUser({
146
+ ...input,
147
+ accountExpiresAt: input.accountExpiresAt
148
+ ? new Date(input.accountExpiresAt)
149
+ : undefined,
150
+ })
127
151
  if (!registered.success)
128
152
  return registered
129
153
  const userId = registered.data.user.id
@@ -139,9 +163,17 @@ export async function createAdminUser(input: AdminCreateUser, context: ServConte
139
163
  /** 安全通知发送后发生未知结果时保留账号,不自动重试或删除已发出的邀请。 */
140
164
  export async function inviteAdminUser(input: AdminInviteUser, context: ServContext): Promise<HaiResult<AdminUser>> {
141
165
  const role = await iam.authz.getRoleByCode(input.role)
142
- if (!role.success || !role.data)
143
- return role.success ? err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale })) : role
144
- const created = await iam.user.inviteUser({ ...input, accountExpiresAt: input.accountExpiresAt ? new Date(input.accountExpiresAt) : undefined })
166
+ if (!role.success || !role.data) {
167
+ return role.success
168
+ ? err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
169
+ : role
170
+ }
171
+ const created = await iam.user.inviteUser({
172
+ ...input,
173
+ accountExpiresAt: input.accountExpiresAt
174
+ ? new Date(input.accountExpiresAt)
175
+ : undefined,
176
+ })
145
177
  if (!created.success)
146
178
  return created
147
179
  const assigned = await iam.authz.syncRoles(created.data.user.id, [role.data.id])
@@ -165,7 +197,9 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
165
197
  return existing
166
198
  if (!existing.data)
167
199
  return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
168
- const role = input.role ? await iam.authz.getRoleByCode(input.role) : undefined
200
+ const role = input.role
201
+ ? await iam.authz.getRoleByCode(input.role)
202
+ : undefined
169
203
  if (role && !role.success)
170
204
  return role
171
205
  if (role?.success && !role.data)
@@ -179,7 +213,14 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
179
213
  if (!assigned.success)
180
214
  return assigned
181
215
  }
182
- const updated = await iam.user.updateUser(id, { ...profile, accountExpiresAt: profile.accountExpiresAt === undefined ? undefined : profile.accountExpiresAt ? new Date(profile.accountExpiresAt) : null })
216
+ const updated = await iam.user.updateUser(id, {
217
+ ...profile,
218
+ accountExpiresAt: profile.accountExpiresAt === undefined
219
+ ? undefined
220
+ : profile.accountExpiresAt
221
+ ? new Date(profile.accountExpiresAt)
222
+ : null,
223
+ })
183
224
  if (!updated.success) {
184
225
  // 角色 ID 可精确还原,避免 undefined 资料字段被 updateUser 忽略后无法补偿。
185
226
  if (role?.success && role.data) {
@@ -189,7 +230,10 @@ export async function updateAdminUser(input: AdminUpdateUser, context: ServConte
189
230
  }
190
231
  return updated
191
232
  }
192
- await recordAudit(context, 'update', 'users', id, { fields: Object.keys(profile), role: input.role })
233
+ await recordAudit(context, 'update', 'users', id, {
234
+ fields: Object.keys(profile),
235
+ role: input.role,
236
+ })
193
237
  return getAdminUser(id)
194
238
  }
195
239
 
@@ -201,13 +245,15 @@ export async function getAdminRoles(): Promise<HaiResult<{ items: AdminRole[] }>
201
245
  const counts = await iam.authz.getRoleUserCounts(items.map(role => role.id))
202
246
  if (!counts.success)
203
247
  return counts
204
- return ok({ items: items.map(role => ({
205
- id: role.id,
206
- code: role.code as AdminRoleCode,
207
- name: role.name,
208
- description: role.description,
209
- userCount: counts.data.get(role.id) ?? 0,
210
- })) })
248
+ return ok({
249
+ items: items.map(role => ({
250
+ id: role.id,
251
+ code: role.code as AdminRoleCode,
252
+ name: role.name,
253
+ description: role.description,
254
+ userCount: counts.data.get(role.id) ?? 0,
255
+ })),
256
+ })
211
257
  }
212
258
 
213
259
  export async function updateProfile(input: ProfileUpdate, context: ServContext): Promise<HaiResult<AdminUser>> {
@@ -225,7 +271,9 @@ export async function updateProfile(input: ProfileUpdate, context: ServContext):
225
271
  const updated = await iam.user.updateCurrentUser(token, { displayName: input.displayName, email: input.email, metadata })
226
272
  if (!updated.success)
227
273
  return updated
228
- await recordAudit(context, 'update', 'profile', current.data.id, { fields: Object.keys(input) })
274
+ await recordAudit(context, 'update', 'profile', current.data.id, {
275
+ fields: Object.keys(input),
276
+ })
229
277
  return getAdminUser(current.data.id)
230
278
  }
231
279
 
@@ -5,15 +5,22 @@ import { err, HaiCommonError, ok } from '@h-ai/core'
5
5
  import { reldb } from '@h-ai/reldb'
6
6
  import { z } from 'zod'
7
7
 
8
- export const BusinessSettingsSchema = z.object({ notice: z.string().trim().max(200) }).strict()
8
+ export const BusinessSettingsSchema = z.object({
9
+ notice: z.string().trim().max(200),
10
+ }).strict()
9
11
 
10
12
  export function initBusinessSettings(): Promise<HaiResult<void>> {
11
- return reldb.ddl.createTable('app_business_settings', { id: { type: 'TEXT', primaryKey: true }, notice: { type: 'TEXT', notNull: true } })
13
+ return reldb.ddl.createTable('app_business_settings', {
14
+ id: { type: 'TEXT', primaryKey: true },
15
+ notice: { type: 'TEXT', notNull: true },
16
+ })
12
17
  }
13
18
 
14
19
  export async function readBusinessSettings(): Promise<HaiResult<{ notice: string }>> {
15
20
  const row = await reldb.sql.get<{ notice: string }>('SELECT notice FROM app_business_settings WHERE id = ?', ['public'])
16
- return row.success ? ok({ notice: row.data?.notice ?? '' }) : row
21
+ return row.success
22
+ ? ok({ notice: row.data?.notice ?? '' })
23
+ : row
17
24
  }
18
25
 
19
26
  export async function saveBusinessSettings(input: { notice: string }): Promise<HaiResult<{ notice: string }>> {
@@ -33,7 +40,14 @@ export async function saveBusinessSettings(input: { notice: string }): Promise<H
33
40
  await tx.rollback()
34
41
  return saved
35
42
  }
36
- const logged = await audit.capture({ action: 'business.configuration.update', resource: 'business-settings', category: 'business', details: { fields: ['notice'] } }, { tx })
43
+ const logged = await audit.capture({
44
+ action: 'business.configuration.update',
45
+ resource: 'business-settings',
46
+ category: 'business',
47
+ details: {
48
+ fields: ['notice'],
49
+ },
50
+ }, { tx })
37
51
  if (!logged.success) {
38
52
  await tx.rollback()
39
53
  return logged
@@ -41,7 +55,9 @@ export async function saveBusinessSettings(input: { notice: string }): Promise<H
41
55
  const committed = await tx.commit()
42
56
  if (!committed.success)
43
57
  await tx.rollback()
44
- return committed.success ? ok(parsed.data) : committed
58
+ return committed.success
59
+ ? ok(parsed.data)
60
+ : committed
45
61
  }
46
62
  catch (error) {
47
63
  await tx.rollback()
@@ -28,13 +28,27 @@ export function createAuthProcedures() {
28
28
  export function createAdminProcedures() {
29
29
  return serv.implement(adminContract).context<ServContext>().route('permissions.list').permission('app.roles.manage').handle(async ({ input }) => {
30
30
  const result = await iam.authz.getAllPermissions(input)
31
- return result.success ? ok({ total: result.data.total, items: result.data.items.map(item => ({ id: item.id, code: item.code, name: item.name, sensitiveOperations: item.sensitiveOperations ?? [] })) }) : result
31
+ return result.success
32
+ ? ok({
33
+ total: result.data.total,
34
+ items: result.data.items.map(item => ({
35
+ id: item.id,
36
+ code: item.code,
37
+ name: item.name,
38
+ sensitiveOperations: item.sensitiveOperations ?? [],
39
+ })),
40
+ })
41
+ : result
32
42
  }).route('permissions.update').permission('app.roles.manage').handle(({ input, context }) => withAdminMutation(context, 'iam.permissions.sensitiveOperations', async () => {
33
43
  const result = await iam.authz.setPermissionSensitiveOperations(input.id, input.sensitiveOperations)
34
- return result.success ? ok({ saved: true as const }) : result
44
+ return result.success
45
+ ? ok({ saved: true as const })
46
+ : result
35
47
  })).route('configuration.get').permission('app.config.manage').handle(() => readBusinessSettings()).route('configuration.update').permission('app.config.manage').handle(async ({ input, context }) => {
36
48
  const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'business.settings.update', context.request.headers.get('x-hai-reauthentication') ?? undefined)
37
- return checked.success ? saveBusinessSettings(input) : checked
49
+ return checked.success
50
+ ? saveBusinessSettings(input)
51
+ : checked
38
52
  }).route('users.list').permission('app.users.manage').handle(async ({ input }) => {
39
53
  const users = await iam.user.listUsers(input)
40
54
  if (!users.success)
@@ -42,7 +56,10 @@ export function createAdminProcedures() {
42
56
  const roles = await iam.authz.getUserRolesForMany(users.data.items.map(user => user.id))
43
57
  if (!roles.success)
44
58
  return roles
45
- return ok({ ...users.data, items: users.data.items.map(user => toAdminUser(user, roles.data.get(user.id) ?? [])) })
59
+ return ok({
60
+ ...users.data,
61
+ items: users.data.items.map(user => toAdminUser(user, roles.data.get(user.id) ?? [])),
62
+ })
46
63
  }).route('users.resetPassword').permission('app.users.manage').handle(({ input, context }) => withAdminMutation(context, 'iam.users.resetPassword', async () => {
47
64
  const result = await iam.user.adminResetPassword(input.id, input.newPassword)
48
65
  if (!result.success)
@@ -74,33 +91,68 @@ export function createAdminProcedures() {
74
91
  const result = roles.data.items.find(item => item.id === input.id)
75
92
  if (!result)
76
93
  return err(HaiCommonError.NOT_FOUND, serverM('notFound', { locale: context.locale }))
77
- await recordAudit(context, 'update', 'roles', input.id, { fields: ['description'] })
94
+ await recordAudit(context, 'update', 'roles', input.id, {
95
+ fields: ['description'],
96
+ })
78
97
  return ok(result)
79
98
  })).route('audit.backup').permission('audit:backup').handle(async ({ input, context }) => {
80
99
  const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'audit.backup', context.request.headers.get('x-hai-reauthentication') ?? undefined)
81
100
  if (!checked.success)
82
101
  return checked
83
102
  const result = await audit.backup.run({ ...input, signal: context.request.signal })
84
- return result.success ? ok({ ...result.data, createdAt: result.data.createdAt.toISOString(), cutoff: result.data.cutoff.toISOString() }) : result
103
+ return result.success
104
+ ? ok({
105
+ ...result.data,
106
+ createdAt: result.data.createdAt.toISOString(),
107
+ cutoff: result.data.cutoff.toISOString(),
108
+ })
109
+ : result
85
110
  }).route('audit.list').permission('audit:read').handle(async ({ input, context }) => {
86
111
  if (input.startDate && input.endDate && input.startDate > input.endDate)
87
112
  return err(HaiCommonError.VALIDATION_ERROR, serverM('invalidDateRange', { locale: context.locale }))
88
- const result = await audit.list({ ...input, startDate: input.startDate ? new Date(input.startDate) : undefined, endDate: input.endDate ? new Date(input.endDate) : undefined })
113
+ const result = await audit.list({
114
+ ...input,
115
+ startDate: input.startDate
116
+ ? new Date(input.startDate)
117
+ : undefined,
118
+ endDate: input.endDate
119
+ ? new Date(input.endDate)
120
+ : undefined,
121
+ })
89
122
  if (!result.success)
90
123
  return result
91
- const stats = await audit.getStats(7, { category: input.category, outcome: input.outcome, severity: input.severity, resource: input.resource, scopeId: input.scopeId, userId: input.userId })
124
+ const stats = await audit.getStats(7, {
125
+ category: input.category,
126
+ outcome: input.outcome,
127
+ severity: input.severity,
128
+ resource: input.resource,
129
+ scopeId: input.scopeId,
130
+ userId: input.userId,
131
+ })
92
132
  if (!stats.success)
93
133
  return stats
94
- return ok({ ...result.data, stats: stats.data, backupEnabled: AuditInitConfigSchema.parse(core.config.get('audit') ?? {}).backup.enabled, items: result.data.items.map(item => ({ ...item, createdAt: new Date(item.createdAt).toISOString() })) })
134
+ return ok({
135
+ ...result.data,
136
+ stats: stats.data,
137
+ backupEnabled: AuditInitConfigSchema.parse(core.config.get('audit') ?? {}).backup.enabled,
138
+ items: result.data.items.map(item => ({
139
+ ...item,
140
+ createdAt: new Date(item.createdAt).toISOString(),
141
+ })),
142
+ })
95
143
  }).build()
96
144
  }
97
145
 
98
146
  export function createProfileProcedures() {
99
147
  return serv.implement(profileContract).context<ServContext>().route('update').auth().handle(async ({ input, context }) => {
100
148
  const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'iam.profile.update', context.request.headers.get('x-hai-reauthentication') ?? undefined)
101
- return checked.success ? updateProfile(input, context) : checked
149
+ return checked.success
150
+ ? updateProfile(input, context)
151
+ : checked
102
152
  }).route('avatar').auth().handle(async ({ input, context }) => {
103
153
  const checked = await iam.auth.checkSensitiveOperation(context.accessToken ?? '', 'iam.profile.avatar', context.request.headers.get('x-hai-reauthentication') ?? undefined)
104
- return checked.success ? uploadAvatar(context.session.userId, input, context) : checked
154
+ return checked.success
155
+ ? uploadAvatar(context.session.userId, input, context)
156
+ : checked
105
157
  }).build()
106
158
  }
@@ -22,7 +22,12 @@ export function createAppProcedures(options: CreateAppProceduresOptions) {
22
22
  .context<ServContext>()
23
23
  .route('info', async () => {
24
24
  const settings = await readBusinessSettings()
25
- return settings.success ? ok({ ...createAppInfo(options), notice: settings.data.notice }) : settings
25
+ return settings.success
26
+ ? ok({
27
+ ...createAppInfo(options),
28
+ notice: settings.data.notice,
29
+ })
30
+ : settings
26
31
  })
27
32
  .route('echo', ({ input, context }) => {
28
33
  const data: AppEchoOutputData = {
@@ -17,7 +17,9 @@ export function createServerProcedures(options: CreateServerProceduresOptions) {
17
17
  return {
18
18
  // @hai-generator:procedures:entries
19
19
  app: createAppProcedures(options.app),
20
- iam: { auth: createAuthProcedures() },
20
+ iam: {
21
+ auth: createAuthProcedures(),
22
+ },
21
23
  admin: createAdminProcedures(),
22
24
  profile: createProfileProcedures(),
23
25
  {{#if features.feedback}}
@@ -10,12 +10,12 @@ import { cache, CacheConfigSchema } from '@h-ai/cache'
10
10
  import { core, HaiConfigError } from '@h-ai/core'
11
11
  import { crypto } from '@h-ai/crypto'
12
12
  {{#if features.feedback}}
13
- import { feedback } from '@h-ai/feedback'
13
+ import { feedback, FeedbackConfigSchema } from '@h-ai/feedback'
14
14
  {{/if}}
15
15
  import { iam, IamConfigSchema } from '@h-ai/iam'
16
16
  import { reach, ReachConfigSchema } from '@h-ai/reach'
17
17
  import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
18
- import { scheduler } from '@h-ai/scheduler'
18
+ import { scheduler, SchedulerConfigSchema } from '@h-ai/scheduler'
19
19
  import { ServConfigSchema } from '@h-ai/serv'
20
20
  import { storage, StorageConfigSchema } from '@h-ai/storage'
21
21
  {{#if features.vecdb}}
@@ -42,7 +42,10 @@ function startFeedback(): Promise<void> {
42
42
  feedbackClosing = false
43
43
  feedbackStarting = (async () => {
44
44
  try {
45
- const result = await feedback.init({ ...core.config.getOrThrow<import('@h-ai/feedback').FeedbackConfigInput>('feedback'), ...buildMetadata })
45
+ const result = await feedback.init({
46
+ ...core.config.getOrThrow<import('@h-ai/feedback').FeedbackConfigInput>('feedback'),
47
+ ...buildMetadata,
48
+ })
46
49
  if (!result.success) {
47
50
  if (feedbackFailure !== result.error.code)
48
51
  core.logger.warn('Optional feedback unavailable; business runtime remains ready', { module: 'feedback', code: result.error.code })
@@ -76,38 +79,127 @@ async function closeFeedback() {
76
79
  await feedbackStarting
77
80
  return feedback.close()
78
81
  }
79
-
80
82
  {{/if}}
81
83
  const runtime = core.module.createRuntime([
82
- { name: 'serv-config', start: () => core.config.validate('serv', ServConfigSchema), close: () => {} },
83
- { name: 'reldb', start: async () => {
84
- const config = ReldbConfigSchema.parse(core.config.getOrThrow('db'))
85
- if (config.type === 'sqlite' && config.database !== ':memory:')
86
- await mkdir(path.dirname(config.database), { recursive: true })
87
- return reldb.init(config)
88
- }, close: () => reldb.close() },
89
- { name: 'cache', start: () => cache.init(CacheConfigSchema.parse(core.config.getOrThrow('cache'))), close: () => cache.close() },
84
+ {
85
+ name: 'configuration',
86
+ start: () => {
87
+ core.init({
88
+ configDir: './config',
89
+ configSchemas: {
90
+ {{#if features.ai}}
91
+ ai: AIConfigSchema,
92
+ {{/if}}
93
+ audit: AuditInitConfigSchema,
94
+ cache: CacheConfigSchema,
95
+ db: ReldbConfigSchema,
96
+ {{#if features.feedback}}
97
+ feedback: FeedbackConfigSchema,
98
+ {{/if}}
99
+ iam: IamConfigSchema,
100
+ reach: ReachConfigSchema,
101
+ scheduler: SchedulerConfigSchema,
102
+ serv: ServConfigSchema,
103
+ storage: StorageConfigSchema,
90
104
  {{#if features.vecdb}}
91
- { name: 'vecdb', start: () => vecdb.init(VecdbConfigSchema.parse(core.config.getOrThrow('vecdb'))), close: () => vecdb.close() },
105
+ vecdb: VecdbConfigSchema,
92
106
  {{/if}}
93
- { name: 'crypto', start: () => crypto.init(), close: () => crypto.close() },
94
- { name: 'storage', start: () => storage.init(StorageConfigSchema.parse(core.config.getOrThrow('storage'))), close: () => storage.close() },
95
- { name: 'reach', enabled: () => Boolean(core.config.get('reach')), start: () => reach.init(ReachConfigSchema.parse(core.config.getOrThrow('reach'))), close: () => reach.close() },
96
- { name: 'audit', start: () => audit.init({ ...AuditInitConfigSchema.parse(core.config.get('audit') ?? {}), storage, reach }), close: () => audit.close() },
97
- { name: 'iam', start: () => iam.init({ ...IamConfigSchema.parse(core.config.getOrThrow('iam')), reach }), close: () => iam.close() },
98
- { name: 'audit-jobs', enabled: () => Boolean(audit.schedulerPlan), start: startAuditJobs, close: () => scheduler.close() },
107
+ },
108
+ })
109
+ },
110
+ close: () => { },
111
+ },
112
+ {
113
+ name: 'serv-config',
114
+ start: () => core.config.validate('serv', ServConfigSchema),
115
+ close: () => { },
116
+ },
117
+ {
118
+ name: 'reldb',
119
+ start: async () => {
120
+ const config = ReldbConfigSchema.parse(core.config.getOrThrow('db'))
121
+ if (config.type === 'sqlite' && config.database !== ':memory:')
122
+ await mkdir(path.dirname(config.database), { recursive: true })
123
+ return reldb.init(config)
124
+ },
125
+ close: () => reldb.close(),
126
+ },
127
+ {
128
+ name: 'cache',
129
+ start: () => cache.init(CacheConfigSchema.parse(core.config.getOrThrow('cache'))),
130
+ close: () => cache.close(),
131
+ },
132
+ {{#if features.vecdb}}
133
+ {
134
+ name: 'vecdb',
135
+ start: () => vecdb.init(VecdbConfigSchema.parse(core.config.getOrThrow('vecdb'))),
136
+ close: () => vecdb.close(),
137
+ },
138
+ {{/if}}
139
+ {
140
+ name: 'crypto',
141
+ start: () => crypto.init(),
142
+ close: () => crypto.close(),
143
+ },
144
+ {
145
+ name: 'storage',
146
+ start: () => storage.init(StorageConfigSchema.parse(core.config.getOrThrow('storage'))),
147
+ close: () => storage.close(),
148
+ },
149
+ {
150
+ name: 'reach',
151
+ enabled: () => Boolean(core.config.get('reach')),
152
+ start: () => reach.init(ReachConfigSchema.parse(core.config.getOrThrow('reach'))),
153
+ close: () => reach.close(),
154
+ },
155
+ {
156
+ name: 'audit',
157
+ start: () => audit.init({
158
+ ...AuditInitConfigSchema.parse(core.config.get('audit') ?? {}),
159
+ storage,
160
+ reach,
161
+ }),
162
+ close: () => audit.close(),
163
+ },
164
+ {
165
+ name: 'iam',
166
+ start: () => iam.init({
167
+ ...IamConfigSchema.parse(core.config.getOrThrow('iam')),
168
+ reach,
169
+ }),
170
+ close: () => iam.close(),
171
+ },
172
+ {
173
+ name: 'audit-jobs',
174
+ enabled: () => Boolean(audit.schedulerPlan),
175
+ start: startAuditJobs,
176
+ close: () => scheduler.close(),
177
+ },
99
178
  {{#if features.feedback}}
100
- { name: 'feedback', enabled: () => Boolean(core.config.get('feedback')), start: startFeedback, close: closeFeedback },
179
+ {
180
+ name: 'feedback',
181
+ enabled: () => Boolean(core.config.get('feedback')),
182
+ start: startFeedback,
183
+ close: closeFeedback,
184
+ },
101
185
  {{/if}}
102
186
  {{#if features.ai}}
103
- { name: 'ai', start: () => ai.init(AIConfigSchema.parse(core.config.getOrThrow('ai'))), close: () => ai.close() },
187
+ {
188
+ name: 'ai',
189
+ start: () => ai.init(AIConfigSchema.parse(core.config.getOrThrow('ai'))),
190
+ close: () => ai.close(),
191
+ },
104
192
  {{/if}}
105
- { name: 'bootstrap', start: async () => {
106
- requireInitialized(await initBusinessSettings(), 'business-settings')
107
- await ensureRolesAndAdmin()
108
- const app = core.config.getOrThrow<CoreConfig>('core')
109
- core.logger.info('Application initialized', { name: app.name, version: app.version, environment: app.env })
110
- }, close: () => {} },
193
+ {
194
+ name: 'bootstrap',
195
+ start: async () => {
196
+ requireInitialized(await initBusinessSettings(), 'business-settings')
197
+ await ensureRolesAndAdmin()
198
+ const app = core.config.getOrThrow<CoreConfig>('core')
199
+ core.logger.info('Application initialized', { name: app.name, version: app.version, environment: app.env })
200
+ },
201
+ close: () => { },
202
+ },
111
203
  ])
112
204
 
113
205
  /** 仅声明模块和业务钩子,失败回滚、并发及关闭重试由 core 负责。 */
@@ -125,15 +217,27 @@ export async function closeModules(): Promise<void> {
125
217
 
126
218
  /** 此函数仅用于进程启动边界,业务请求继续返回 HaiResult。 */
127
219
  function requireInitialized<T>(result: HaiResult<T>, moduleName: string): T {
128
- if (!result.success)
129
- throw Object.assign(new Error(moduleName), { code: result.error.code, ext: { fields: moduleName === 'iam-bootstrap' ? ['HAI_ADMIN_DEFAULT_PASSWORD'] : [] } })
220
+ if (!result.success) {
221
+ throw Object.assign(new Error(moduleName), {
222
+ code: result.error.code,
223
+ ext: {
224
+ fields: moduleName === 'iam-bootstrap'
225
+ ? ['HAI_ADMIN_DEFAULT_PASSWORD']
226
+ : [],
227
+ },
228
+ })
229
+ }
130
230
  return result.data
131
231
  }
132
232
 
133
233
  async function ensureRolesAndAdmin(): Promise<void> {
134
234
  requireInitialized(await seedAppDuties(), 'app-duties')
135
235
  await ensureInitialSystemAdmin()
136
- for (const [username, password] of [['audit_admin', process.env.HAI_AUDIT_ADMIN_PASSWORD], ['config_admin', process.env.HAI_CONFIG_ADMIN_PASSWORD], ['business_operator', process.env.HAI_BUSINESS_OPERATOR_PASSWORD]] as const)
236
+ for (const [username, password] of [
237
+ ['audit_admin', process.env.HAI_AUDIT_ADMIN_PASSWORD],
238
+ ['config_admin', process.env.HAI_CONFIG_ADMIN_PASSWORD],
239
+ ['business_operator', process.env.HAI_BUSINESS_OPERATOR_PASSWORD],
240
+ ] as const)
137
241
  requireInitialized(await bootstrapDutyAccount(username, password), 'app-duty-bootstrap')
138
242
  }
139
243
 
@@ -142,9 +246,19 @@ async function ensureInitialSystemAdmin(): Promise<void> {
142
246
  if (users.total > 0)
143
247
  return
144
248
  const password = process.env.HAI_ADMIN_DEFAULT_PASSWORD?.trim()
145
- if (!password)
146
- throw Object.assign(new Error('Administrator bootstrap configuration required'), { code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code, ext: { fields: ['HAI_ADMIN_DEFAULT_PASSWORD'] } })
147
- const registered = requireInitialized(await iam.user.createUser({ username: 'admin', password, displayName: serverM('admin') }), 'iam-bootstrap')
249
+ if (!password) {
250
+ throw Object.assign(new Error('Administrator bootstrap configuration required'), {
251
+ code: HaiConfigError.CONFIG_ENV_VAR_MISSING.code,
252
+ ext: {
253
+ fields: ['HAI_ADMIN_DEFAULT_PASSWORD'],
254
+ },
255
+ })
256
+ }
257
+ const registered = requireInitialized(await iam.user.createUser({
258
+ username: 'admin',
259
+ password,
260
+ displayName: serverM('admin'),
261
+ }), 'iam-bootstrap')
148
262
  const adminRole = requireInitialized(await iam.authz.getRoleByCode('admin'), 'iam-role')
149
263
  if (!adminRole)
150
264
  throw new Error('Administrator role is missing')