@h-ai/cli 0.1.0-alpha.66 → 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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  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-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  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-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  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/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -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}}
@@ -1 +1 @@
1
- { "images": [{ "name": "app", "target": "runtime", "containerPort": 3000 }] }
1
+ { "images": [{ "name": "app", "dockerfile": "images/app.Dockerfile", "containerPort": 3000 }] }
@@ -0,0 +1,35 @@
1
+ FROM node:24.22.1-bookworm-slim AS build
2
+ ARG SQLITE_PREBUILD_MIRROR=
3
+ ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
4
+
5
+ ENV PNPM_HOME=/pnpm
6
+ ENV PATH=$PNPM_HOME:$PATH
7
+ ENV CI=true
8
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
9
+ WORKDIR /workspace
10
+
11
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
12
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
13
+ COPY images/dependencies/ ./
14
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
15
+ COPY . .
16
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
17
+ RUN pnpm compile
18
+ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
19
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{api.servicePackageName}} --prod /prod/app
20
+
21
+ FROM node:24.22.1-bookworm-slim AS runtime-base
22
+
23
+ ENV NODE_ENV=production
24
+ ENV HOST=0.0.0.0
25
+ ENV PORT=3000
26
+ WORKDIR /app
27
+
28
+ COPY --from=build --chown=node:node /prod/app ./
29
+ RUN mkdir -p /app/data && chown node:node /app/data
30
+
31
+ USER node
32
+ EXPOSE 3000
33
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
34
+ CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
35
+ CMD ["node", "dist/index.js"]
@@ -13,9 +13,11 @@
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
+ {{#if features.deploy}}
18
19
  "deploy": "hai deploy apps/{{projectName}}-service",
20
+ {{/if}}
19
21
  "check": "pnpm typecheck",
20
22
  "typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
21
23
  "lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
@@ -30,6 +32,9 @@
30
32
  "{{api.contractPackageName}}": "workspace:*"
31
33
  },
32
34
  "devDependencies": {
35
+ {{#if features.deploy}}
36
+ "@h-ai/deploy": "{{haiPackageSpecifier}}",
37
+ {{/if}}
33
38
  "@h-ai/cli": "{{haiPackageSpecifier}}",
34
39
  "@h-ai/api-client": "{{haiPackageSpecifier}}",
35
40
  {{#if features.crypto}}
@@ -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,
@@ -1,18 +1,23 @@
1
- .git
1
+ **/.git
2
+ **/node_modules
3
+ **/.pnpm-store
4
+ **/.cache
5
+ **/.svelte-kit
6
+ **/.turbo
7
+ **/build
8
+ **/dist
9
+ **/coverage
10
+ **/playwright-report
11
+ **/test-results
12
+ **/src-tauri/target
13
+ **/hash-core/target
14
+ tmp
2
15
  **/*.tar
3
- Dockerfile
4
16
  .github
5
17
  .agents
6
18
  .claude
7
19
  .codex
8
20
  .opencode
9
- .turbo
10
- **/build
11
- **/dist
12
- **/node_modules
13
- node_modules
14
- playwright-report
15
- test-results
16
21
  **/data
17
22
  *.log
18
23
  # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
@@ -29,3 +29,10 @@ playwright-report/
29
29
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
30
30
  .env.local
31
31
  .env.*.local
32
+
33
+ # 构建器生成的安装输入快照
34
+ **/images/dependencies/
35
+
36
+ # 项目本地缓存
37
+ **/.pnpm-store/
38
+ **/.cache/
@@ -2,9 +2,19 @@
2
2
  {{#if fullstack.frontends.app}}
3
3
  # {{projectName}}-app
4
4
 
5
+ ## 简介
6
+
5
7
  App/H5 前端工程,复用 shared 的响应式 mini 后台,使用 Svelte 5 + Vite + Capacitor,可输出 Web 预览并同步 Android / iOS 原生工程。
6
8
 
7
- ## i18n 与 shared 协同
9
+ ## 核心功能清单
10
+
11
+ - 响应式 H5 页面与 Capacitor Android/iOS 原生壳。
12
+ - 共享管理界面、个人设置和接口客户端。
13
+ - 原生使用安全 TokenStorage,浏览器预览使用 Cookie 刷新。
14
+
15
+ ## 使用入门
16
+
17
+ ### i18n 与 shared 协同
8
18
 
9
19
  - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
10
20
  - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
@@ -13,7 +23,7 @@ App/H5 前端工程,复用 shared 的响应式 mini 后台,使用 Svelte 5 +
13
23
 
14
24
  自定义建议:移动端独有引导、运营位、按钮文案放在本目录 `messages/`;跨端通用组件放到 shared。
15
25
 
16
- ## 编译、打包与部署
26
+ ### 编译、打包与部署
17
27
 
18
28
  ```bash
19
29
  # 开发预览,默认端口 {{fullstack.ports.app}}
@@ -31,15 +41,23 @@ pnpm --filter {{projectName}}-app cap:build:android:release
31
41
  pnpm --filter {{projectName}}-app cap:build:ios
32
42
  ```
33
43
 
34
- 部署前确认 `PUBLIC_API_BASE` 在 `.env` 中指向可访问的后端服务。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000`。
44
+ 本地开发通过根 `.env` 的 `PUBLIC_API_BASE` 配置代理。原生打包前,在本端 `public/runtime-config.js` 中设置 `globalThis.__HAI_PUBLIC_CONFIG__ = { apiBase: 'https://api.example.com/api/v1' }`,再编译并同步原生工程;这里只能放公开地址,不能放密钥。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000/api/v1`。
35
45
 
36
- ## IAM 认证配置
46
+ ### IAM 认证配置
37
47
 
38
- 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
48
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
39
49
 
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。
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。
41
51
 
42
52
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
43
53
 
44
54
  完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
55
+
56
+ ## 开发测试
57
+
58
+ 从工程根执行 `pnpm typecheck`、`pnpm lint`、`pnpm build`、`pnpm test` 和 `pnpm test:e2e`。浏览器预览通过后,原生壳仍需在对应系统或设备单独验收。
59
+
60
+ ## 许可证
61
+
62
+ Apache-2.0
45
63
  {{/if}}
@@ -6,6 +6,7 @@
6
6
  <meta charset="UTF-8" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
8
8
  <meta name="theme-color" content="#2563eb" />
9
+ <script src="/runtime-config.js"></script>
9
10
  <title>{{html fullstack.appName}}</title>
10
11
  <link
11
12
  rel="icon"
@@ -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}}
@@ -0,0 +1,4 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ globalThis.__HAI_PUBLIC_CONFIG__ = {}
4
+ {{/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
@@ -51,7 +51,7 @@ export default defineConfig(({ mode, command }) => {
51
51
  },
52
52
  envPrefix: ['VITE_', 'PUBLIC_'],
53
53
  ssr: {
54
- noExternal: [/@h-ai\//, /{{fullstack.sharedPackageName}}/],
54
+ noExternal: ['@h-ai/ui', /{{fullstack.sharedPackageName}}/],
55
55
  },
56
56
  test: {
57
57
  environment: 'node',
@@ -2,9 +2,19 @@
2
2
  {{#if fullstack.frontends.desktop}}
3
3
  # {{projectName}}-desktop
4
4
 
5
+ ## 简介
6
+
5
7
  桌面端工程,参考 `apps/desktop-app` 使用 Tauri v2 + Svelte 5 + Vite,前端通过共享 contract 调用 `{{fullstack.servPackageName}}`。
6
8
 
7
- ## i18n 与 shared 协同
9
+ ## 核心功能清单
10
+
11
+ - Tauri v2 桌面壳与共享管理界面。
12
+ - 运行时配置 API 地址,无需按环境重复打包。
13
+ - 原生令牌仅存内存,重启后重新登录。
14
+
15
+ ## 使用入门
16
+
17
+ ### i18n 与 shared 协同
8
18
 
9
19
  - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
10
20
  - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
@@ -14,7 +24,7 @@
14
24
 
15
25
  自定义建议:桌面端菜单、状态、快捷操作文案放在本目录 `messages/`;跨端通用能力放到 shared。
16
26
 
17
- ## 编译、打包与部署
27
+ ### 编译、打包与部署
18
28
 
19
29
  ```bash
20
30
  # 仅前端开发,默认端口 {{fullstack.ports.desktop}}
@@ -32,13 +42,21 @@ pnpm build --target desktop --platform windows --arch x64
32
42
 
33
43
  打包前需要安装 Rust 工具链和 Tauri 平台依赖。构建不读取 .env.prod/.env.local;启动已安装程序前设置进程 PUBLIC_API_BASE(HTTP(S),不含凭据、查询和片段)。Rust 会在页面加载前注入公开地址,仅将该 Origin 加入 CSP;不需要为 test/prod 重打安装包。缺省连接 http://localhost:3000/api/v1。
34
44
 
35
- ## IAM 认证配置
45
+ ### IAM 认证配置
36
46
 
37
- 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
47
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
38
48
 
39
- 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。
40
50
 
41
51
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
42
52
 
43
53
  完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
54
+
55
+ ## 开发测试
56
+
57
+ 从工程根执行 `pnpm typecheck`、`pnpm lint`、`pnpm build`、`pnpm test` 和 `pnpm test:e2e`。浏览器预览通过后,原生壳仍需在对应系统或设备单独验收。
58
+
59
+ ## 许可证
60
+
61
+ Apache-2.0
44
62
  {{/if}}
@@ -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
@@ -2,14 +2,24 @@
2
2
  {{#if fullstack.hasMiniapp}}
3
3
  # {{projectName}}-miniapp
4
4
 
5
- 小程序端当前为预留占位,不参与 workspace 构建、单元测试或 E2E,但已预置 inlang 标准目录:
5
+ ## 简介
6
6
 
7
- - `project.inlang/settings.json`:小程序端 locale 配置
8
- - `messages/zh-CN.json`、`messages/en-US.json`:小程序端自有文案
7
+ 小程序预留目录,目前没有可运行的小程序应用。
9
8
 
10
- 消息格式插件复用 workspace 已安装的依赖,从根目录 `node_modules` 加载;生成后先在 workspace 根目录安装依赖,无需从 CDN 加载插件。
9
+ ## 核心功能清单
11
10
 
12
- 后续实现时建议复用 `packages/{{fullstack.contractPackageName}}` 中的 Schema 与路径常量,保持接口真相源一致。
11
+ - 已提供 `project.inlang/settings.json` 与中英文 `messages/`。
12
+ - 可复用 `packages/{{fullstack.contractPackageName}}` 的接口类型。
13
13
 
14
- 协同边界:跨端通用文案/组件放 `packages/{{fullstack.sharedPackageName}}`,小程序独有页面文案放本目录 `messages/`。
14
+ ## 使用入门
15
+
16
+ 选定小程序框架后再实现页面、请求适配和认证。小程序文案放本目录 `messages/`,跨端通用内容放 `packages/{{fullstack.sharedPackageName}}`;消息格式插件使用工程根已安装的依赖。
17
+
18
+ ## 开发测试
19
+
20
+ 本目录不参与 workspace 构建、单元测试或 E2E。接入实际框架后增加对应命令,并在目标平台验证登录、请求、导航与发布包。
21
+
22
+ ## 许可证
23
+
24
+ Apache-2.0
15
25
  {{/if}}
@@ -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}}
@@ -2,9 +2,19 @@
2
2
  {{#if fullstack.frontends.web}}
3
3
  # {{projectName}}-web
4
4
 
5
+ ## 简介
6
+
5
7
  响应式 mini 后台 Web 前端,通过 `packages/{{fullstack.sharedPackageName}}` 调用后端 API,并通过 `@h-ai/ui` 组件搭建页面。
6
8
 
7
- ## i18n 与 shared 协同
9
+ ## 核心功能清单
10
+
11
+ - 浏览器访问共享管理界面与个人设置。
12
+ - 与 API 共用接口类型、登录状态、主题和语言。
13
+ - 可单独构建 Web 镜像并增量更新。
14
+
15
+ ## 使用入门
16
+
17
+ ### i18n 与 shared 协同
8
18
 
9
19
  - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
10
20
  - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
@@ -12,7 +22,7 @@
12
22
 
13
23
  自定义建议:Web 独有文案放在本目录 `messages/`;跨端通用文案或组件放到 shared。
14
24
 
15
- ## 编译、打包与部署
25
+ ### 编译、打包与部署
16
26
 
17
27
  ```bash
18
28
  # 开发预览,默认端口 {{fullstack.ports.web}}
@@ -22,15 +32,23 @@ pnpm --filter {{projectName}}-web dev
22
32
  pnpm --filter {{projectName}}-web build
23
33
  ```
24
34
 
25
- 从工程根运行 `pnpm build --target docker` 构建并导出镜像。启用 deploy 功能后,运行根命令 `pnpm deploy --env test`,一次发布 API/Web;网络不通时用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。部署不重新构建。
35
+ 从工程根运行 `pnpm build --target docker` 导出镜像。默认带部署工具,可运行 `pnpm run deploy --env test --build` 一次完成构建发布,或 `pnpm run deploy --env prod --bundle-only` 交付已有镜像及跨平台脚本。完整包包含自建中间件,人工部署与按服务更新见工程根 README。
26
36
 
27
- ## IAM 认证配置
37
+ ### IAM 认证配置
28
38
 
29
- 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
39
+ 服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。单实例可使用 Memory;多实例需共享数据库与 Redis,并按工程根 README 配置写入路由和传输会话。
30
40
 
31
- 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。
32
42
 
33
43
  协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
34
44
 
35
45
  完整契约见生成应用的 `.agents/skills/hai-iam/reference.md`。
46
+
47
+ ## 开发测试
48
+
49
+ 从工程根执行 `pnpm typecheck`、`pnpm lint`、`pnpm build`、`pnpm test` 和 `pnpm test:e2e`。浏览器预览通过后,原生壳仍需在对应系统或设备单独验收。
50
+
51
+ ## 许可证
52
+
53
+ Apache-2.0
36
54
  {{/if}}