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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.en.md +40 -4
  2. package/README.ja.md +40 -4
  3. package/README.md +40 -4
  4. package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +920 -336
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/apps/admin/messages/ja-JP.json +38 -0
  21. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  22. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  25. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  27. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  32. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  33. package/templates/apps/api/package.json.hbs +1 -1
  34. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  35. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
  36. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  38. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  42. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  46. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
  47. package/templates/apps/fullstack/package.json.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  49. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  50. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  54. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  55. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  57. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  58. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  59. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  60. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  61. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  62. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  63. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
  64. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  65. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  66. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  68. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  70. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
  71. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  72. package/templates/apps/h5/messages/ja-JP.json +40 -0
  73. package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
  74. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  75. package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
  76. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  77. package/templates/apps/website/messages/ja-JP.json +32 -0
  78. package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
  79. package/templates/base/e2e/app.spec.ts.hbs +13 -8
  80. package/templates/base/package.json.hbs +4 -1
  81. package/templates/base/playwright.config.ts +15 -4
  82. package/templates/base/src/hooks.server.ts.hbs +15 -6
  83. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  84. package/templates/base/vite.config.ts.hbs +2 -2
  85. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  86. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  87. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  88. package/templates/i18n/project.inlang/settings.json +1 -1
  89. package/templates/shared/e2e-env.ts +98 -0
  90. package/templates/shared/run-e2e.mjs +51 -12
  91. package/templates/skills/AGENTS.md +4 -0
  92. package/templates/skills/bridges/admin/AGENTS.md +5 -1
  93. package/templates/skills/bridges/api/AGENTS.md +2 -0
  94. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
  95. package/templates/skills/bridges/h5/AGENTS.md +5 -1
  96. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
  97. package/templates/skills/bridges/website/AGENTS.md +5 -1
  98. package/templates/skills/hai-ai/SKILL.md +8 -2
  99. package/templates/skills/hai-ai/guide.md +134 -60
  100. package/templates/skills/hai-ai/reference.md +51 -11
  101. package/templates/skills/hai-api-client/SKILL.md +2 -0
  102. package/templates/skills/hai-api-client/reference.md +17 -5
  103. package/templates/skills/hai-api-contract/SKILL.md +1 -0
  104. package/templates/skills/hai-api-contract/reference.md +4 -2
  105. package/templates/skills/hai-app-create/SKILL.md +4 -0
  106. package/templates/skills/hai-app-create/development.md +20 -4
  107. package/templates/skills/hai-app-create/reference.md +2 -1
  108. package/templates/skills/hai-app-review/SKILL.md +6 -2
  109. package/templates/skills/hai-app-review/differential.md +34 -0
  110. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  111. package/templates/skills/hai-app-tests/reference.md +4 -4
  112. package/templates/skills/hai-app-tests/validation.md +10 -3
  113. package/templates/skills/hai-audit/SKILL.md +10 -8
  114. package/templates/skills/hai-audit/reference.md +3 -0
  115. package/templates/skills/hai-build/reference.md +6 -4
  116. package/templates/skills/hai-cache/SKILL.md +9 -3
  117. package/templates/skills/hai-cache/reference.md +53 -11
  118. package/templates/skills/hai-capacitor/SKILL.md +3 -1
  119. package/templates/skills/hai-capacitor/reference.md +9 -5
  120. package/templates/skills/hai-core/SKILL.md +4 -0
  121. package/templates/skills/hai-core/reference.md +25 -5
  122. package/templates/skills/hai-crypto/SKILL.md +10 -2
  123. package/templates/skills/hai-crypto/reference.md +64 -10
  124. package/templates/skills/hai-datapipe/SKILL.md +2 -0
  125. package/templates/skills/hai-datapipe/reference.md +3 -1
  126. package/templates/skills/hai-deploy/SKILL.md +9 -4
  127. package/templates/skills/hai-deploy/middleware.md +51 -37
  128. package/templates/skills/hai-iam/SKILL.md +24 -8
  129. package/templates/skills/hai-iam/reference.md +181 -154
  130. package/templates/skills/hai-kit/SKILL.md +23 -7
  131. package/templates/skills/hai-kit/reference.md +42 -24
  132. package/templates/skills/hai-payment/SKILL.md +13 -11
  133. package/templates/skills/hai-payment/reference.md +80 -41
  134. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  135. package/templates/skills/hai-reach/SKILL.md +5 -2
  136. package/templates/skills/hai-reach/reference.md +83 -22
  137. package/templates/skills/hai-reldb/SKILL.md +5 -2
  138. package/templates/skills/hai-reldb/reference.md +68 -25
  139. package/templates/skills/hai-scheduler/SKILL.md +8 -5
  140. package/templates/skills/hai-scheduler/reference.md +38 -24
  141. package/templates/skills/hai-serv/SKILL.md +23 -8
  142. package/templates/skills/hai-serv/reference.md +123 -87
  143. package/templates/skills/hai-spec-implement/SKILL.md +4 -0
  144. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  145. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  146. package/templates/skills/hai-spec-implement/templates.md +5 -0
  147. package/templates/skills/hai-spec-implement/workflow.md +2 -0
  148. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  149. package/templates/skills/hai-storage/SKILL.md +8 -2
  150. package/templates/skills/hai-storage/reference.md +64 -25
  151. package/templates/skills/hai-ui/SKILL.md +16 -2
  152. package/templates/skills/hai-ui/guide.md +3 -3
  153. package/templates/skills/hai-ui/reference.md +17 -6
  154. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  155. package/templates/skills/hai-ui-design/patterns.md +26 -0
  156. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  157. package/templates/skills/hai-ui-review/checklist.md +22 -0
  158. package/templates/skills/hai-vecdb/SKILL.md +12 -8
  159. package/templates/skills/hai-vecdb/reference.md +33 -3
  160. package/dist/chunk-7DSURTJ4.js.map +0 -1
  161. package/dist/chunk-AOQ4N657.js +0 -640
  162. package/dist/chunk-AOQ4N657.js.map +0 -1
  163. package/dist/cli-config-templates-6AHNUGBN.js +0 -5
  164. package/dist/src-2V6VP4I5.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@h-ai/cli",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.67",
4
+ "version": "0.1.0-alpha.68",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -30,7 +30,7 @@
30
30
  "node": "^22.22.2 || ^24.15.0 || >=26.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "@h-ai/core": "0.1.0-alpha.67",
33
+ "@h-ai/core": "0.1.0-alpha.68",
34
34
  "cac": "^7.0.1",
35
35
  "chalk": "^6.0.1",
36
36
  "fast-wrap-ansi": "^0.2.2",
@@ -41,12 +41,13 @@
41
41
  "yaml": "^2.9.1"
42
42
  },
43
43
  "devDependencies": {
44
- "@h-ai/cache": "0.1.0-alpha.67",
45
- "@h-ai/crypto": "0.1.0-alpha.67",
46
- "@h-ai/deploy": "0.1.0-alpha.67",
47
- "@h-ai/reach": "0.1.0-alpha.67",
48
- "@h-ai/reldb": "0.1.0-alpha.67",
49
- "@h-ai/storage": "0.1.0-alpha.67",
44
+ "@h-ai/api-client": "0.1.0-alpha.68",
45
+ "@h-ai/cache": "0.1.0-alpha.68",
46
+ "@h-ai/crypto": "0.1.0-alpha.68",
47
+ "@h-ai/deploy": "0.1.0-alpha.68",
48
+ "@h-ai/reach": "0.1.0-alpha.68",
49
+ "@h-ai/reldb": "0.1.0-alpha.68",
50
+ "@h-ai/storage": "0.1.0-alpha.68",
50
51
  "@types/fs-extra": "^11.0.4",
51
52
  "@types/node": "^26.6.5",
52
53
  "@types/prompts": "^2.4.9",
@@ -0,0 +1,38 @@
1
+ {
2
+ "app_title": "管理コンソール",
3
+ "app_description": "hai Agent Frameworkで構築した管理コンソール",
4
+ "meta_description": "管理コンソール — 企業管理システム",
5
+ "nav_home": "ホーム",
6
+ "nav_dashboard": "ダッシュボード",
7
+ "nav_settings": "設定",
8
+ "nav_admin": "管理画面へ",
9
+ "page_home_title": "管理コンソール",
10
+ "page_home_subtitle": "hai Agent Frameworkで構築",
11
+ "page_home_badge": "Powered by hai",
12
+ "page_dashboard_title": "ダッシュボード",
13
+ "page_dashboard_subtitle": "システム稼働状況の概要",
14
+ "page_dashboard_total_users": "総ユーザー数",
15
+ "page_dashboard_today_active": "本日のアクティブユーザー",
16
+ "page_dashboard_revenue": "月間売上",
17
+ "page_dashboard_orders": "未処理の注文",
18
+ "page_settings_title": "システム設定",
19
+ "page_settings_subtitle": "システム設定を管理",
20
+ "page_settings_basic": "基本設定",
21
+ "page_settings_desc": "ここでシステムの設定を管理します。",
22
+ "auth_login_title": "ログイン",
23
+ "auth_login_submit": "ログイン",
24
+ "auth_login_loading": "ログイン中...",
25
+ "auth_login_failed": "ログインに失敗しました",
26
+ "auth_login_to_register": "アカウントをお持ちでない方は新規登録",
27
+ "auth_register_title": "登録",
28
+ "auth_register_submit": "登録",
29
+ "auth_register_loading": "登録中...",
30
+ "auth_register_failed": "登録に失敗しました",
31
+ "auth_register_to_login": "既にアカウントをお持ちの方はログイン",
32
+ "auth_label_username": "ユーザー名",
33
+ "auth_label_email": "メールアドレス",
34
+ "auth_label_password": "パスワード",
35
+ "auth_label_confirm_password": "パスワードの確認",
36
+ "auth_error_network": "ネットワークエラー",
37
+ "auth_error_password_mismatch": "パスワードが一致しません"
38
+ }
@@ -9,8 +9,8 @@
9
9
  {{/if}}
10
10
  import { page } from '$app/state'
11
11
  {{#if hasI18n}}
12
- import { getLocale } from '$lib/paraglide/runtime.js'
13
- import { setGlobalLocale } from '@h-ai/ui'
12
+ import { getLocale, setLocale } from '$lib/paraglide/runtime.js'
13
+ import { LanguageSwitch, setGlobalLocale } from '@h-ai/ui'
14
14
  {{/if}}
15
15
  import '../app.css'
16
16
 
@@ -21,6 +21,11 @@
21
21
  const { children }: Props = $props()
22
22
  {{#if hasI18n}}
23
23
 
24
+ function handleLanguageChange(language: string) {
25
+ setGlobalLocale(language)
26
+ setLocale(language as Parameters<typeof setLocale>[0])
27
+ }
28
+
24
29
  if (browser) {
25
30
  const paraglideLocale = getLocale()
26
31
  setGlobalLocale(paraglideLocale)
@@ -32,8 +37,13 @@
32
37
  <title>{page.data.appConfig.name}</title>
33
38
  </svelte:head>
34
39
 
35
- <div class='min-h-screen flex flex-col bg-base-200'>
36
- <header class='navbar border-b border-base-300 bg-base-100 px-6'><span class='text-lg font-semibold'>{page.data.appConfig.name}</span></header>
37
- <div class='flex-1'>{@render children()}</div>
38
- <footer class='border-t border-base-300 bg-base-100 p-3 text-center text-xs text-base-content/60'>{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}</footer>
40
+ <div class='h-dvh flex flex-col overflow-hidden bg-base-200'>
41
+ <header class='navbar shrink-0 border-b border-base-300 bg-base-100 px-6'>
42
+ <span class='flex-1 text-lg font-semibold'>{page.data.appConfig.name}</span>
43
+ {{#if hasI18n}}
44
+ <LanguageSwitch currentLanguage={getLocale()} onchange={handleLanguageChange} />
45
+ {{/if}}
46
+ </header>
47
+ <div class='flex min-h-0 flex-1 flex-col overflow-auto'>{@render children()}</div>
48
+ <footer class='shrink-0 border-t border-base-300 bg-base-100 p-3 text-center text-xs text-base-content/60'>{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}</footer>
39
49
  </div>
@@ -27,10 +27,10 @@
27
27
  ] as const)
28
28
  </script>
29
29
 
30
- <div class='drawer lg:drawer-open'>
30
+ <div class='drawer min-h-0 flex-1 lg:drawer-open'>
31
31
  <input id='admin-drawer' type='checkbox' class='drawer-toggle' />
32
- <div class='drawer-content min-w-0 p-4 sm:p-6'>
33
- <label for='admin-drawer' class='btn btn-ghost btn-sm drawer-button lg:hidden mb-4'>
32
+ <div class='drawer-content flex min-h-0 min-w-0 flex-col overflow-auto p-4 sm:p-6'>
33
+ <label for='admin-drawer' class='btn btn-ghost btn-sm drawer-button shrink-0 self-start lg:hidden mb-4'>
34
34
  <span class='icon-[tabler--menu-2] size-5'></span>
35
35
  </label>
36
36
  {@render children()}
@@ -1,3 +1,5 @@
1
1
  export * from './app-contract.js'
2
2
  export * from './app-schemas.js'
3
3
  export * from './{{projectName}}-contract.js'
4
+
5
+ // @hai-generator:contracts:exports
@@ -1,12 +1,15 @@
1
1
  import { apiContract } from '@h-ai/api-contract'
2
2
  import { appContract } from './app-contract.js'
3
3
 
4
+ // @hai-generator:contracts:imports
5
+
4
6
  /** 工程级应用 contract。 */
5
7
  export const {{api.contractExportName}} = apiContract.create({
6
8
  {{#if features.iam}} iam: apiContract.iam,
7
9
  {{/if}}{{#if features.storage}} storage: apiContract.storage,
8
10
  {{/if}}{{#if features.ai}} ai: apiContract.ai,
9
- {{/if}} app: appContract,
11
+ {{/if}} // @hai-generator:contracts:entries
12
+ app: appContract,
10
13
  })
11
14
 
12
15
  export type ApiWorkspaceContract = typeof {{api.contractExportName}}
@@ -27,6 +27,10 @@ pnpm --filter {{api.servicePackageName}} test
27
27
 
28
28
  ### 关键目录
29
29
 
30
+ {{#if features.crypto}}启用 transport 时,`serverKeyRing` 与客户端 `keyStore` 都必须显式提供。CLI 在本服务被忽略的 `.env.local` 中一次性生成独立开发 ring,变量为 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`;运行时不生成密钥。部署使用 Secret Manager 向所有节点分发相同受管 ring,配合共享数据库 registry。公钥/版本参与协商,私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发新旧版本再切 activeKeyId,过渡期后撤销旧 ID;无需会话粘性。
31
+ {{#unless features.db}}本模板未选择 db,registry 仅在 development/test 显式使用内存;production/staging 会拒绝启动 transport。选择 db 并配置共享数据库后再部署,或显式关闭 transport。
32
+ {{/unless}}{{/if}}
33
+
30
34
  - `config/`:service 配置
31
35
  - `src/app.ts`:HTTP App 装配
32
36
  - `src/lib/server/init.ts`:模块初始化与关闭
@@ -37,7 +41,7 @@ pnpm --filter {{api.servicePackageName}} test
37
41
 
38
42
  服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。客户端可通过安全公开配置和框架认证动作接入 `AuthPanel`。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
39
43
 
40
- LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
44
+ 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。
41
45
 
42
46
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
43
47
 
@@ -5,7 +5,6 @@
5
5
  name: {{projectName}} API Service
6
6
  version: 0.1.0
7
7
  env: ${HAI_CORE_ENV:development}
8
- debug: ${HAI_CORE_DEBUG:false}
9
8
 
10
9
  logging:
11
10
  level: info
@@ -16,12 +15,3 @@ logging:
16
15
  - secret
17
16
 
18
17
  defaultLocale: {{defaultLocale}}
19
- supportedLocales:
20
- {{#if_eq defaultLocale 'en-US'}} - en-US
21
- - zh-CN
22
- {{else}} - zh-CN
23
- - en-US
24
- {{/if_eq}}
25
-
26
- features:
27
- apiAccess: true
@@ -27,11 +27,13 @@ cors:
27
27
  - Content-Type
28
28
  {{#if features.crypto}} - X-Client-Id
29
29
  - X-Encrypted
30
+ - X-Hai-Transport
30
31
  {{/if}} - X-Hai-Locale
31
32
  - X-Request-Id
32
33
  - X-Requested-With
33
34
  exposedHeaders:
34
35
  {{#if features.crypto}} - X-Encrypted
36
+ - X-Hai-Transport
35
37
  {{/if}} - X-Request-Id
36
38
 
37
39
  {{#if features.crypto}}transport:
@@ -42,6 +44,5 @@ cors:
42
44
  - /openapi.json
43
45
  - /docs
44
46
  - /_hai/scalar.js
45
- maxClients: 10000
46
47
  {{else}}transport: false
47
48
  {{/if}}
@@ -1,9 +1,12 @@
1
1
  import type { CoreConfig } from '@h-ai/core'
2
- import type { ServConfig } from '@h-ai/serv'
2
+ import type { ServConfig{{#if features.crypto}}, ServTransportConfig{{/if}} } from '@h-ai/serv'
3
+ {{#if features.crypto}}import process from 'node:process'
4
+ {{/if}}
3
5
  import { core } from '@h-ai/core'
4
- {{#if features.crypto}}import { crypto } from '@h-ai/crypto'
6
+ {{#if features.crypto}}import { {{#if features.db}}createReldbTransportKeyStore{{else}}createInMemoryKeyStore{{/if}}, crypto, parseTransportServerKeyRing } from '@h-ai/crypto'
5
7
  {{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
6
- {{/if}}import { serv } from '@h-ai/serv'
8
+ {{/if}}{{#if features.crypto}}{{#if features.db}}import { reldb } from '@h-ai/reldb'
9
+ {{/if}}{{/if}}import { serv } from '@h-ai/serv'
7
10
  import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
8
11
  import { {{api.serviceProceduresFactoryName}} } from './server/procedures/index.js'
9
12
 
@@ -24,14 +27,24 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
24
27
  nativeOrigins: servConfig.cors.nativeOrigins,
25
28
  })
26
29
  {{#if features.crypto}} const transportMode = _options.transport ?? 'config'
27
- const transport = transportMode === 'disabled' || servConfig.transport === false
28
- ? undefined
29
- : {
30
- crypto,
31
- keyExchangePath: servConfig.transport.keyExchangePath,
32
- excludePaths: [...servConfig.transport.excludePaths],
33
- maxClients: servConfig.transport.maxClients,
34
- }
30
+ let transport: ServTransportConfig | undefined
31
+ if (transportMode !== 'disabled' && servConfig.transport !== false) {
32
+ {{#unless features.db}} if (coreConfig.env !== 'development' && coreConfig.env !== 'test')
33
+ throw new Error('Production transport requires a shared registry; enable the db feature and configure a shared database')
34
+ {{/unless}}
35
+ const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
36
+ if (!ring.success)
37
+ throw new Error(ring.error.message)
38
+ transport = {
39
+ crypto,
40
+ serverKeyRing: ring.data,
41
+ {{#if features.db}} keyStore: createReldbTransportKeyStore({ reldb, ttlSeconds: 3600 }),
42
+ {{else}} keyStore: createInMemoryKeyStore(), // 无 db 的模板仅在 development/test 使用内存 registry。
43
+ {{/if}}
44
+ keyExchangePath: servConfig.transport.keyExchangePath,
45
+ excludePaths: [...servConfig.transport.excludePaths],
46
+ }
47
+ }
35
48
  {{else}} const transport = undefined
36
49
  {{/if}}
37
50
  {{#if features.iam}}
@@ -21,7 +21,14 @@ async function main(): Promise<void> {
21
21
  })
22
22
  }
23
23
 
24
- main().catch((error: unknown) => {
24
+ main().catch(async (error: unknown) => {
25
25
  core.logger.error('API service failed to start', { error })
26
26
  process.exitCode = 1
27
+ try {
28
+ // 包括初始化后的 app/transport 创建失败,均须释放已建立的连接。
29
+ await closeApp()
30
+ }
31
+ catch (cleanupError) {
32
+ core.logger.error('API service startup cleanup failed', { error: cleanupError })
33
+ }
27
34
  })
@@ -21,23 +21,47 @@
21
21
 
22
22
  let initialized = false
23
23
  let initPromise: Promise<void> | null = null
24
+ /** 记录启动/关闭后未确认释放的资源,使失败清理可以重试。 */
25
+ let cleanupPending = false
26
+ let closePromise: Promise<void> | null = null
24
27
  {{#if features.crypto}}let cryptoInitialized = false
25
28
  {{/if}}
26
29
 
30
+ /**
31
+ * 合并并发启动;任一步失败先逆序清理,未释放的资源会在下次启动前重试。
32
+ * @throws Error | AggregateError - 启动失败,或启动与资源清理同时失败。
33
+ * @example
34
+ * await initApp()
35
+ */
27
36
  export async function initApp(): Promise<void> {
37
+ if (closePromise)
38
+ await closePromise
28
39
  if (initialized)
29
40
  return
30
41
  if (initPromise)
31
42
  return initPromise
43
+ if (cleanupPending) {
44
+ await closeApp()
45
+ return await initApp()
46
+ }
32
47
 
33
- initPromise = doInit()
34
- try {
35
- await initPromise
48
+ cleanupPending = true
49
+ initPromise = doInit().then(() => {
36
50
  initialized = true
37
- }
38
- finally {
51
+ cleanupPending = false
52
+ }).catch(async (error: unknown) => {
53
+ // 私有清理不等待 initPromise,避免启动失败时等待自身。
54
+ try {
55
+ await cleanupModules()
56
+ }
57
+ catch (cleanupError) {
58
+ throw new AggregateError([error, cleanupError], 'Application startup and cleanup failed')
59
+ }
60
+ throw error
61
+ }).finally(() => {
39
62
  initPromise = null
40
- }
63
+ })
64
+ return await initPromise
41
65
  }
42
66
 
43
67
  async function doInit(): Promise<void> {
@@ -160,16 +184,38 @@ async function doInit(): Promise<void> {
160
184
  core.logger.info('API service initialized')
161
185
  }
162
186
 
187
+ /**
188
+ * 等待启动并关闭所有模块;失败时保留重试入口。
189
+ * @throws AggregateError - 任一资源未确认释放。
190
+ * @example
191
+ * await closeApp()
192
+ */
163
193
  export async function closeApp(): Promise<void> {
164
- if (!initialized && !initPromise)
194
+ closePromise ??= doCloseApp().finally(() => {
195
+ closePromise = null
196
+ })
197
+ return await closePromise
198
+ }
199
+
200
+ /** 等待启动结束后逐一释放;HaiResult 失败与异常都会聚合报告并保留重试入口。 */
201
+ async function doCloseApp(): Promise<void> {
202
+ if (!initialized && !initPromise && !cleanupPending)
165
203
  return
166
204
 
205
+ if (initPromise)
206
+ await initPromise.catch(() => undefined)
207
+ await cleanupModules()
208
+ }
209
+
210
+ /** 不等待启动屏障,供启动失败与显式关闭共用的逆序清理。 */
211
+ async function cleanupModules(): Promise<void> {
212
+ const failures: unknown[] = []
167
213
  const closers: Array<readonly [string, () => unknown]> = [
168
214
  {{#if features.ai}} ['ai', () => ai.close()],
169
215
  {{/if}}{{#if features.storage}} ['storage', () => storage.close()],
170
216
  {{/if}}{{#if features.iam}} ['iam', () => iam.close()],
171
- {{/if}}{{#if features.reach}} ['reach', () => reach.close()],
172
217
  {{/if}}{{#if features.vecdb}} ['vecdb', () => vecdb.close()],
218
+ {{/if}}{{#if features.reach}} ['reach', () => reach.close()],
173
219
  {{/if}}{{#if features.cache}} ['cache', () => cache.close()],
174
220
  {{/if}}{{#if features.db}} ['reldb', () => reldb.close()],
175
221
  {{/if}} ]
@@ -180,14 +226,25 @@ export async function closeApp(): Promise<void> {
180
226
 
181
227
  for (const [name, close] of closers) {
182
228
  try {
183
- await close()
229
+ const result = await close()
230
+ if (result && typeof result === 'object' && 'success' in result && result.success === false) {
231
+ failures.push('error' in result ? result.error : new Error('Module close failed'))
232
+ core.logger.warn(`Module close failed: ${name}`)
233
+ continue
234
+ }
235
+ {{#if features.crypto}} if (name === 'crypto')
236
+ cryptoInitialized = false
237
+ {{/if}}
184
238
  }
185
239
  catch (error) {
240
+ failures.push(error)
186
241
  core.logger.warn(`Module close failed: ${name}`, { error })
187
242
  }
188
243
  }
189
244
 
190
245
  initialized = false
191
- {{#if features.crypto}} cryptoInitialized = false
192
- {{/if}} core.logger.info('API service closed')
246
+ cleanupPending = failures.length > 0
247
+ if (cleanupPending)
248
+ throw new AggregateError(failures, 'Application resource cleanup failed; retry closeApp()')
249
+ core.logger.info('API service closed')
193
250
  }
@@ -7,12 +7,20 @@ import type { AppProcedureDeps } from './app-procedures.js'
7
7
  import { storage } from '@h-ai/storage'
8
8
  {{/if}}import { createAppProcedures } from './app-procedures.js'
9
9
 
10
+ // @hai-generator:procedures:imports
11
+
10
12
  /** 创建应用级 procedures 聚合。 */
11
13
  export function {{api.serviceProceduresFactoryName}}(deps: { app: AppProcedureDeps }) {
12
14
  return {
15
+ // @hai-generator:procedures:entries
13
16
  {{#if features.iam}} iam: createIamProcedures({ iam }),
14
- {{/if}}{{#if features.storage}} storage: createStorageProcedures({ storage }),
15
- {{/if}}{{#if features.ai}} ai: createAiProcedures({ ai }),
17
+ {{/if}}{{#if features.storage}} storage: createStorageProcedures({
18
+ storage,
19
+ authorize: ({ context, keys }) => keys.every(key =>
20
+ key.startsWith(`users/${encodeURIComponent(context.session.userId)}/`)),
21
+ }),
22
+ {{/if}}{{#if features.ai}} // AI 默认绑定可信 session.userId,跨主体访问需另行提供 authorizeObject 策略。
23
+ ai: createAiProcedures({ ai }),
16
24
  {{/if}} app: createAppProcedures(deps.app),
17
25
  }
18
26
  }
@@ -19,7 +19,7 @@ test.beforeAll(async () => {
19
19
  auth: { storage: apiClient.tokenStorage.memory() },
20
20
  {{/if}}
21
21
  {{#if features.crypto}}
22
- transport: process.env.HAI_SERV_TRANSPORT === 'false' ? undefined : { crypto },
22
+ transport: { crypto },
23
23
  {{/if}}
24
24
  })
25
25
  expect(result.success).toBe(true)
@@ -46,7 +46,7 @@ test('service app.info endpoint returns HaiResult', async () => {
46
46
  return
47
47
  expect(body.data.name).toBe(process.env.HAI_CORE_NAME ?? '{{projectName}} API Service')
48
48
  expect(body.data.version).toBe(process.env.HAI_CORE_VERSION ?? '0.1.0')
49
- expect(body.data.env).toBe(process.env.HAI_CORE_ENV ?? 'development')
49
+ expect(body.data.env).toBe('test')
50
50
  expect(typeof body.data.transportEnabled).toBe('boolean')
51
51
  })
52
52
  {{#if features.iam}}
@@ -13,7 +13,7 @@
13
13
  "start:prod": "pnpm --filter {{api.servicePackageName}} start:prod",
14
14
  "dev:test": "pnpm --filter {{api.servicePackageName}} dev:test",
15
15
  "dev:prod": "pnpm --filter {{api.servicePackageName}} dev:prod",
16
- "compile": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
16
+ "compile": "pnpm -r --if-present build",
17
17
  "build": "hai build",
18
18
  {{#if features.deploy}}
19
19
  "deploy": "hai deploy apps/{{projectName}}-service",
@@ -1,5 +1,6 @@
1
1
  import process from 'node:process'
2
2
  import { defineConfig } from '@playwright/test'
3
+ import { createE2eNotificationEnv, createE2eServerEnv, createE2eTransportRing } from './scripts/e2e-env.js'
3
4
 
4
5
  const defaultBaseURL = 'http://127.0.0.1:3000'
5
6
  const baseURL = process.env.BASE_URL || defaultBaseURL
@@ -33,25 +34,32 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
33
34
  export default defineConfig({
34
35
  testDir: './e2e',
35
36
  globalSetup: './e2e/capture-evidence.ts',
36
- fullyParallel: false,
37
+ fullyParallel: true,
37
38
  forbidOnly: !!process.env.CI,
38
- retries: process.env.CI ? 2 : 0,
39
- workers: 1,
39
+ retries: 0,
40
+ workers: 2,
40
41
  reporter: 'list',
41
42
  timeout: 30_000,
42
43
  use: {
43
44
  baseURL,
44
45
  channel: 'chrome',
45
- trace: 'on-first-retry',
46
+ trace: 'retain-on-failure',
46
47
  screenshot: 'only-on-failure',
47
48
  },
48
49
  webServer: [{
49
50
  name: '{{api.servicePackageName}}',
50
- command: 'pnpm --filter {{api.servicePackageName}} start',
51
- env: {
51
+ command: 'pnpm --filter {{api.servicePackageName}} 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(),
52
60
  HOST: serviceHost,
53
61
  PORT: servicePort,
54
- },
62
+ }),
55
63
  url: `${baseURL}/health`,
56
64
  reuseExistingServer: false,
57
65
  timeout: 120_000,
@@ -47,7 +47,7 @@ pnpm --filter {{projectName}}-app cap:build:ios
47
47
 
48
48
  服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
49
49
 
50
- LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
50
+ 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。
51
51
 
52
52
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
53
53
 
@@ -0,0 +1,7 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name}"
6
+ }
7
+ {{/if}}
@@ -3,6 +3,7 @@
3
3
  <script lang="ts">
4
4
  import type { ManagementApiOptions } from '{{fullstack.sharedPackageName}}'
5
5
  import { capacitor, createCapacitorTokenStorage } from '@h-ai/capacitor'
6
+ import { core } from '@h-ai/core'
6
7
  import { Alert } from '@h-ai/ui/components/compounds/index'
7
8
  import { Button, Spinner } from '@h-ai/ui/components/primitives/index'
8
9
  import { onMount } from 'svelte'
@@ -29,7 +30,12 @@
29
30
  }
30
31
  onMount(() => {
31
32
  void initialize()
32
- return () => void capacitor.close()
33
+ return () => {
34
+ void capacitor.close().then((closed) => {
35
+ if (!closed.success)
36
+ core.logger.warn('Native listener cleanup failed; retry on next initialization')
37
+ })
38
+ }
33
39
  })
34
40
  </script>
35
41
 
@@ -12,7 +12,7 @@ import { defineConfig } from 'vitest/config'
12
12
  const envDir = fileURLToPath(new URL('../../', import.meta.url))
13
13
 
14
14
  export default defineConfig(({ mode, command }) => {
15
- const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
15
+ const envResult = command === 'build' || process.env.HAI_E2E === '1' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
16
16
  if (!envResult.success)
17
17
  throw new Error(envResult.error.message)
18
18
  const env = envResult.data
@@ -46,7 +46,7 @@ pnpm build --target desktop --platform windows --arch x64
46
46
 
47
47
  服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
48
48
 
49
- LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
49
+ 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。
50
50
 
51
51
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
52
52
 
@@ -0,0 +1,7 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.desktop}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name}"
6
+ }
7
+ {{/if}}
@@ -13,7 +13,7 @@ const host = process.env.TAURI_DEV_HOST
13
13
  const envDir = fileURLToPath(new URL('../../', import.meta.url))
14
14
 
15
15
  export default defineConfig(({ mode, command }) => {
16
- const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
16
+ const envResult = command === 'build' || process.env.HAI_E2E === '1' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
17
17
  if (!envResult.success)
18
18
  throw new Error(envResult.error.message)
19
19
  const env = envResult.data
@@ -0,0 +1,12 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.hasMiniapp}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "mini_title": "{{projectName}} ミニアプリ",
6
+ "mini_status": "予約済みのプレースホルダー",
7
+ "mini_description": "ミニアプリの対象では、まだビルド可能なパッケージは生成しません。標準のinlang project.inlangとmessagesを用意しているため、後でミニアプリ用フレームワークに組み込めます。",
8
+ "mini_shared_contract": "packages/{{fullstack.contractPackageName}}のスキーマとルート定数を再利用してください。",
9
+ "mini_own_messages": "ミニアプリ固有の文案はapps/{{projectName}}-miniapp/messagesに配置します。",
10
+ "mini_customize": "端末間の言語切り替えを揃えるため、言語一覧をsharedと一致させてください。"
11
+ }
12
+ {{/if}}
@@ -38,7 +38,7 @@ pnpm --filter {{projectName}}-web build
38
38
 
39
39
  服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
40
40
 
41
- LDAP/Reach 均可选:基础认证无需安装。默认 LDAP 客户端需在 IAM 服务端 workspace 安装 ldapts,再配置 login.ldap 与连接信息。自定义 onOtpSendEmail/onOtpSendSms/onPasswordResetRequest 回调可独立于 Reach 使用,投递失败须拒绝 Promise。协议在 Modal 显示;LoginForm/RegisterForm 提供 header/extraFields/footer,AuthPanel 透传 loginHeader/loginFields/loginFooter 和 registerHeader/registerFields/registerFooter。配置及例子见生成项目的 hai-iam/hai-ui skills。
41
+ 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。
42
42
 
43
43
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
44
44
 
@@ -0,0 +1,7 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name}"
6
+ }
7
+ {{/if}}