@h-ai/cli 0.1.0-alpha.20 → 0.1.0-alpha.22

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 (182) hide show
  1. package/README.md +10 -7
  2. package/dist/{chunk-GSMXGUYF.js → chunk-5IGMJVV5.js} +56 -38
  3. package/dist/chunk-5IGMJVV5.js.map +1 -0
  4. package/dist/{chunk-3BBXDQ4Y.js → chunk-CFCXOKGV.js} +24 -2
  5. package/dist/chunk-CFCXOKGV.js.map +1 -0
  6. package/dist/cli-config-templates-5J5VKWWD.js +5 -0
  7. package/dist/{cli-config-templates-IGGO7P45.js.map → cli-config-templates-5J5VKWWD.js.map} +1 -1
  8. package/dist/cli-skill-templates-JMSV2SVW.js +5 -0
  9. package/dist/{cli-skill-templates-IMYZPQRG.js.map → cli-skill-templates-JMSV2SVW.js.map} +1 -1
  10. package/dist/index.js +567 -75
  11. package/dist/index.js.map +1 -1
  12. package/package.json +3 -2
  13. package/templates/README.md +40 -29
  14. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +16 -0
  15. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +38 -0
  16. package/templates/apps/api/apps/{{projectName}}-contract/src/app-contract.ts.hbs +27 -0
  17. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +33 -0
  18. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +3 -0
  19. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +12 -0
  20. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +18 -0
  21. package/templates/apps/api/apps/{{projectName}}-contract/tsconfig.json.hbs +12 -0
  22. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +11 -0
  23. package/templates/apps/api/apps/{{projectName}}-contract/vitest.config.ts.hbs +9 -0
  24. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +17 -0
  25. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +19 -0
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +26 -0
  27. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +55 -0
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +98 -0
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +21 -0
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +184 -0
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs +49 -0
  32. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +18 -0
  33. package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +168 -0
  34. package/templates/apps/api/apps/{{projectName}}-service/tsconfig.json.hbs +16 -0
  35. package/templates/apps/api/apps/{{projectName}}-service/tsup.config.ts.hbs +11 -0
  36. package/templates/apps/api/apps/{{projectName}}-service/vitest.config.ts.hbs +9 -0
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +18 -0
  38. package/templates/apps/api/eslint.config.js +32 -0
  39. package/templates/apps/api/package.json.hbs +29 -0
  40. package/templates/apps/api/playwright.config.ts.hbs +58 -0
  41. package/templates/apps/api/pnpm-workspace.yaml.hbs +14 -0
  42. package/templates/apps/api/tsconfig.base.json +15 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-app/.env.example.hbs +10 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -7
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +18 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +1 -1
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +16 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +6 -0
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +6 -0
  50. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +51 -29
  51. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +186 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +2 -0
  53. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +0 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +18 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +14 -0
  56. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +1 -5
  57. package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +10 -4
  58. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +4 -3
  59. package/templates/apps/fullstack/apps/{{projectName}}-desktop/.env.example.hbs +6 -0
  60. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +20 -6
  61. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +1 -1
  62. package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +15 -0
  63. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +33 -30
  64. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +152 -0
  65. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +1 -2
  66. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/main.ts.hbs +13 -0
  67. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +22 -0
  68. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/build.rs.hbs +6 -0
  69. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/capabilities/default.json.hbs +12 -0
  70. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128.png +0 -0
  71. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128@2x.png +0 -0
  72. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/32x32.png +0 -0
  73. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.icns +0 -0
  74. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.ico +0 -0
  75. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +16 -0
  76. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/main.rs.hbs +8 -0
  77. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +41 -0
  78. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +2 -8
  79. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +9 -4
  80. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +44 -21
  81. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +15 -6
  82. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +1 -1
  83. package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +15 -0
  84. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +25 -27
  85. package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +153 -0
  86. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +0 -1
  87. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +14 -0
  88. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +1 -5
  89. package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +10 -4
  90. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -2
  91. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +2 -1
  92. package/templates/apps/fullstack/eslint.config.js +0 -1
  93. package/templates/apps/fullstack/package.json.hbs +18 -17
  94. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +3 -3
  95. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
  96. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +2 -2
  97. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -1
  98. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/{server-app.test.ts.hbs → app-server.test.ts.hbs} +1 -1
  99. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +9 -8
  100. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +1 -1
  101. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +5 -1
  102. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +1 -1
  103. package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +1 -1
  104. package/templates/apps/fullstack/playwright.config.ts.hbs +41 -4
  105. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +15 -0
  106. package/templates/apps/mobile-app/.env.hbs +3 -0
  107. package/templates/apps/{android-app → mobile-app}/capacitor.config.ts.hbs +2 -2
  108. package/templates/apps/mobile-app/index.html.hbs +13 -0
  109. package/templates/apps/mobile-app/messages/en-US.json +36 -0
  110. package/templates/apps/mobile-app/messages/zh-CN.json +36 -0
  111. package/templates/apps/mobile-app/src/App.svelte.hbs +162 -0
  112. package/templates/apps/mobile-app/src/lib/api.ts.hbs +54 -0
  113. package/templates/apps/{android-app → mobile-app}/src/lib/capacitor.ts +5 -6
  114. package/templates/apps/mobile-app/src/main.ts.hbs +11 -0
  115. package/templates/base/.gitignore.hbs +16 -0
  116. package/templates/base/{eslint.config.js → eslint.config.js.hbs} +4 -1
  117. package/templates/base/package.json.hbs +52 -33
  118. package/templates/base/playwright.config.ts +30 -3
  119. package/templates/base/pnpm-workspace.yaml.hbs +14 -0
  120. package/templates/base/src/app.d.ts.hbs +3 -0
  121. package/templates/base/src/app.html.hbs +3 -0
  122. package/templates/base/svelte.config.js.hbs +43 -3
  123. package/templates/base/tests/scaffold.test.ts.hbs +7 -0
  124. package/templates/base/tsconfig.json.hbs +50 -0
  125. package/templates/base/vite.config.ts.hbs +10 -0
  126. package/templates/skills/AGENTS.md +57 -24
  127. package/templates/skills/CLAUDE.md +18 -7
  128. package/templates/skills/bridges/admin/AGENTS.md +66 -0
  129. package/templates/skills/bridges/admin/CLAUDE.md +19 -0
  130. package/templates/skills/bridges/admin/copilot-instructions.md +46 -0
  131. package/templates/skills/bridges/api/AGENTS.md +65 -0
  132. package/templates/skills/bridges/api/CLAUDE.md +19 -0
  133. package/templates/skills/bridges/api/copilot-instructions.md +46 -0
  134. package/templates/skills/bridges/fullstack/AGENTS.md +84 -0
  135. package/templates/skills/bridges/fullstack/CLAUDE.md +19 -0
  136. package/templates/skills/bridges/fullstack/copilot-instructions.md +54 -0
  137. package/templates/skills/bridges/h5/AGENTS.md +65 -0
  138. package/templates/skills/bridges/h5/CLAUDE.md +19 -0
  139. package/templates/skills/bridges/h5/copilot-instructions.md +44 -0
  140. package/templates/skills/bridges/mobile-app/AGENTS.md +77 -0
  141. package/templates/skills/bridges/mobile-app/CLAUDE.md +19 -0
  142. package/templates/skills/bridges/mobile-app/copilot-instructions.md +45 -0
  143. package/templates/skills/bridges/website/AGENTS.md +65 -0
  144. package/templates/skills/bridges/website/CLAUDE.md +19 -0
  145. package/templates/skills/bridges/website/copilot-instructions.md +44 -0
  146. package/templates/skills/copilot-instructions.md +27 -64
  147. package/templates/skills/hai-app-create/SKILL.md +1 -0
  148. package/templates/skills/hai-app-review/SKILL.md +1 -1
  149. package/templates/skills/hai-app-tests/SKILL.md +28 -3
  150. package/templates/skills/hai-capacitor/SKILL.md +14 -22
  151. package/templates/skills/hai-ui/SKILL.md +8 -2
  152. package/templates/skills/opencode.json +1 -6
  153. package/dist/chunk-3BBXDQ4Y.js.map +0 -1
  154. package/dist/chunk-GSMXGUYF.js.map +0 -1
  155. package/dist/cli-config-templates-IGGO7P45.js +0 -5
  156. package/dist/cli-skill-templates-IMYZPQRG.js +0 -5
  157. package/templates/apps/android-app/.env.hbs +0 -3
  158. package/templates/apps/android-app/messages/en-US.json +0 -24
  159. package/templates/apps/android-app/messages/zh-CN.json +0 -24
  160. package/templates/apps/android-app/src/lib/api.ts.hbs +0 -32
  161. package/templates/apps/android-app/src/routes/+layout.svelte.hbs +0 -43
  162. package/templates/apps/android-app/src/routes/+layout.ts +0 -7
  163. package/templates/apps/android-app/src/routes/+page.svelte.hbs +0 -38
  164. package/templates/apps/api/src/routes/+layout.svelte +0 -14
  165. package/templates/apps/api/src/routes/+page.svelte +0 -11
  166. package/templates/apps/api/src/routes/api/v1/health/+server.ts +0 -14
  167. package/templates/apps/api/src/routes/api/v1/items/+server.ts +0 -37
  168. package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +0 -30
  169. package/templates/apps/api/tests/health.test.ts.hbs +0 -26
  170. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +0 -14
  171. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +0 -18
  172. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +0 -162
  173. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +0 -14
  174. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +0 -18
  175. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +0 -162
  176. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +0 -14
  177. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +0 -18
  178. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +0 -162
  179. package/templates/apps/fullstack/pnpm-workspace.yaml +0 -7
  180. package/templates/base/tsconfig.json +0 -20
  181. /package/templates/apps/fullstack/packages/{{projectName}}-serv/src/{server-app.ts.hbs → app-server.ts.hbs} +0 -0
  182. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/{theme.svelte.ts.hbs → theme-store.svelte.ts.hbs} +0 -0
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.20",
4
+ "version": "0.1.0-alpha.22",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.1.0",
35
35
  "prompts": "^2.4.2",
36
36
  "yaml": "^2.7.0",
37
- "@h-ai/core": "0.1.0-alpha.20"
37
+ "@h-ai/core": "0.1.0-alpha.22"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/fs-extra": "^11.0.4",
@@ -52,6 +52,7 @@
52
52
  "lint": "eslint .",
53
53
  "lint:fix": "eslint . --fix",
54
54
  "test": "vitest run",
55
+ "test:e2e": "pnpm test:scaffold-gates",
55
56
  "test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
56
57
  "test:watch": "vitest",
57
58
  "test:coverage": "vitest run --coverage",
@@ -9,13 +9,13 @@ templates/
9
9
  ├── base/ # 所有应用类型共用的骨架文件
10
10
  │ ├── package.json.hbs # 动态:按 features 注入依赖
11
11
  │ ├── vite.config.ts.hbs # 动态:按 hasI18n / hasUi 注入插件
12
- │ ├── tsconfig.json
13
- │ ├── svelte.config.js
14
- │ ├── .gitignore
12
+ │ ├── tsconfig.json.hbs # 动态:SvelteKit / 纯 Svelte 5 分支
13
+ │ ├── svelte.config.js.hbs # 动态:SvelteKit / 纯 Svelte 5 分支
14
+ │ ├── .gitignore.hbs # 动态:纯 Svelte 5 应用不输出 .svelte-kit
15
15
  │ ├── playwright.config.ts
16
16
  │ └── src/
17
- │ ├── app.html.hbs # 动态:i18n 时输出 %lang% 占位符
18
- │ ├── app.d.ts
17
+ │ ├── app.html.hbs # 动态:SvelteKit 应用输出 app shell
18
+ │ ├── app.d.ts.hbs # 动态:SvelteKit 应用输出 App namespace
19
19
  │ ├── app.css
20
20
  │ ├── hooks.server.ts.hbs # 动态:按 features / hasI18n 组装 handle 序列
21
21
  │ └── lib/server/
@@ -33,6 +33,10 @@ templates/
33
33
  │ │ └── src/routes/
34
34
  │ ├── api/ # 纯 API 服务(无 UI / 无 i18n)
35
35
  │ │ └── src/routes/
36
+ │ ├── mobile-app/ # Svelte 5 + Vite + Capacitor 移动应用
37
+ │ │ ├── index.html.hbs
38
+ │ │ ├── messages/
39
+ │ │ └── src/App.svelte.hbs
36
40
  │ └── fullstack/ # 前后端分离多包工程(contract / serv / web / app / desktop / miniapp 占位)
37
41
  │ ├── packages/ # 共享 contract 与后端 serv 包
38
42
  │ ├── apps/ # 多前端目标,按用户选择条件生成
@@ -52,17 +56,20 @@ templates/
52
56
  │ └── project.inlang/
53
57
  │ └── settings.json # Paraglide 项目配置
54
58
  │
55
- └── skills/ # AI Skill 单一来源模板(分发到 .agents/skills/)
59
+ └── skills/ # AI Skill 与各 appType 桥接指引
60
+ └── bridges/ # admin / api / website / h5 / mobile-app / fullstack 专属 AGENTS/Copilot/Claude
56
61
  ```
57
62
 
58
63
  ## AI Skill 模板(Single Source of Truth)
59
64
 
60
65
  `templates/skills/` 是 CLI 生成共享 AI 上下文时的单一来源,同时用于 `hai create` 与 `hai add`。
61
66
 
62
- - 每个 Skill 目录会复制到 `.agents/skills/<skill>/`
63
- - `copilot-instructions.md` 会复制到 `.github/copilot-instructions.md`,并引导 Copilot/其他助手读取 `.agents/skills/`
64
- - `AGENTS.md`、`CLAUDE.md`、`opencode.json` 会复制到项目根目录
65
- - `opencode.json` 使用 OpenCode 的 `instructions` 与 `skills.paths` 配置,将项目级指令与 Skill 目录显式指向 `AGENTS.md` 和 `.agents/skills/`
67
+ - `hai create` 会把 `templates/skills/` 下所有 `hai-*` Skill 目录复制到 `.agents/skills/<skill>/`,再按 appType 排除互斥项(例如 fullstack 不复制 `hai-kit`;admin / website / h5 不复制 `hai-serv`、`hai-api-contract`、`hai-api-client`、`hai-capacitor`;mobile-app 不复制 `hai-core`、`hai-kit`、`hai-serv`、`hai-api-contract`)
68
+ - `bridges/<appType>/copilot-instructions.md` 会复制到 `.github/copilot-instructions.md`;没有专属文件时回退到通用桥接文件
69
+ - `bridges/<appType>/AGENTS.md`、`bridges/<appType>/CLAUDE.md` 会复制到项目根目录,确保不同样板工程使用自己的 AI 指引
70
+ - `opencode.json` 会复制到项目根目录
71
+ - `templates/skills/AGENTS.md`、`CLAUDE.md`、`copilot-instructions.md` 与 `opencode.json` 不是多余副本:它们是 generic fallback,主要服务于缺少 appType 上下文时的桥接复制与 `hai add` 回填 AI 支持
72
+ - `opencode.json` 使用 OpenCode 的 `instructions` 配置复用 `.github/copilot-instructions.md`;`.agents/skills/` 由 OpenCode 原生发现,不再写入过期的 `skills.paths`
66
73
  - `CLAUDE.md` 作为 Claude Code 的原生项目指引入口,并通过 `@AGENTS.md` 复用共享规范;`.agents/skills/` 仍是共享参考目录,而非 Claude Code 的原生 project skills 目录
67
74
  - `hai add` 只补齐缺失的 AI 支持文件,不覆盖用户已自定义的桥接文件,也不会自动删除遗留的 `.github/skills/`
68
75
 
@@ -77,23 +84,27 @@ templates/
77
84
 
78
85
  所有 `.hbs` 文件可使用以下变量:
79
86
 
80
- | 变量 | 类型 | 说明 |
81
- | ---------------------- | --------- | -------------------------------------------- |
82
- | `{{projectName}}` | `string` | 项目名称(如 `my-app`) |
83
- | `{{appType}}` | `string` | 应用类型:`admin` / `website` / `h5` / `api` |
84
- | `{{hasUi}}` | `boolean` | 是否包含 UI(非 `api` 类型为 `true`) |
85
- | `{{hasI18n}}` | `boolean` | 是否启用 i18n(非 `api` 类型为 `true`) |
86
- | `{{defaultLocale}}` | `string` | 默认语言,如 `zh-CN` |
87
- | `{{packageManager}}` | `string` | 包管理器:`pnpm` / `npm` / `yarn` |
88
- | `{{fullstack.*}}` | `object` | fullstack 类型专用上下文 |
89
- | `{{features.iam}}` | `boolean` | 是否选中 iam feature |
90
- | `{{features.db}}` | `boolean` | 是否选中 db feature |
91
- | `{{features.cache}}` | `boolean` | 是否选中 cache feature |
92
- | `{{features.crypto}}` | `boolean` | 是否选中 crypto feature |
93
- | `{{features.storage}}` | `boolean` | 是否选中 storage feature |
94
- | `{{features.ai}}` | `boolean` | 是否选中 ai feature |
95
-
96
- `fullstack.*` 包含包名、前端选择、依赖版本和 `contractExportName`,用于渲染 contract / serv / 多端 UI 的模板。
87
+ | 变量 | 类型 | 说明 |
88
+ | ---------------------- | --------- | ----------------------------------------- |
89
+ | `{{projectName}}` | `string` | 项目名称(如 `my-app`) |
90
+ | `{{appType}}` | `string` | 应用类型 |
91
+ | `{{hasUi}}` | `boolean` | 是否包含 UI(非 `api` 类型为 `true`) |
92
+ | `{{hasI18n}}` | `boolean` | 是否启用 i18n(非 `api` 类型为 `true`) |
93
+ | `{{isSvelteOnlyApp}}` | `boolean` | 是否为不依赖 SvelteKit 的纯 Svelte 5 应用 |
94
+ | `{{isCapacitorApp}}` | `boolean` | 是否为 Capacitor 移动应用 |
95
+ | `{{defaultLocale}}` | `string` | 默认语言,如 `zh-CN` |
96
+ | `{{packageManager}}` | `string` | 包管理器:`pnpm` / `npm` / `yarn` |
97
+ | `{{fullstack.*}}` | `object` | fullstack 类型专用上下文 |
98
+ | `{{features.iam}}` | `boolean` | 是否选中 iam feature |
99
+ | `{{features.db}}` | `boolean` | 是否选中 db feature |
100
+ | `{{features.cache}}` | `boolean` | 是否选中 cache feature |
101
+ | `{{features.crypto}}` | `boolean` | 是否选中 crypto feature |
102
+ | `{{features.storage}}` | `boolean` | 是否选中 storage feature |
103
+ | `{{features.ai}}` | `boolean` | 是否选中 ai feature |
104
+
105
+ `appType` 支持 `admin` / `website` / `h5` / `api` / `mobile-app` / `fullstack`。
106
+
107
+ `fullstack.*` 包含包名、前端选择、依赖版本、`contractExportName` 和原生壳 `nativeAppIdSegment`,用于渲染 contract / serv / 多端 UI / Capacitor / Tauri 模板。
97
108
 
98
109
  ### 常用条件写法
99
110
 
@@ -116,8 +127,8 @@ import { iam } from '@h-ai/iam'
116
127
  模板引擎执行 6 个步骤:
117
128
 
118
129
  1. **复制 `base/` 静态文件** → 项目根
119
- 2. **复制 `apps/{appType}/` 路由** → 项目根(`messages/` 仅 `hasI18n` 时复制)
120
- 3. **叠加 feature 静态路由** → `src/routes/`
130
+ 2. **复制 `apps/{appType}/` 专属文件** → 项目根(`messages/` 仅 `hasI18n` 时复制)
131
+ 3. **叠加 feature 静态路由** → `src/routes/`(纯 Svelte 5 应用跳过 SvelteKit 路由)
121
132
  4. **渲染所有 `.hbs` 文件** — `base/` + `apps/{appType}/` 输出到项目根;`features/*/routes*/` 输出到 `src/routes/`
122
133
  5. **复制 `i18n/` 脚手架** → 项目根(仅 `hasI18n` 时)
123
134
  6. **确保 `static/` 目录存在**
@@ -0,0 +1,16 @@
1
+ # {{api.contractPackageName}}
2
+
3
+ {{projectName}} API service 的共享 contract 包。
4
+
5
+ ## 包含内容
6
+
7
+ - `app-contract.ts`:应用级路由定义
8
+ - `app-schemas.ts`:输入输出 Zod schema
9
+ - `{{projectName}}-contract.ts`:工程级 contract 聚合入口
10
+
11
+ ## 常用命令
12
+
13
+ ```bash
14
+ pnpm --filter {{api.contractPackageName}} build
15
+ pnpm --filter {{api.contractPackageName}} test
16
+ ```
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "{{api.contractPackageName}}",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "eslint .",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "clean": "rimraf dist .turbo"
26
+ },
27
+ "dependencies": {
28
+ "@h-ai/api-contract": "{{haiPackageSpecifier}}",
29
+ "zod": "{{api.packageSpecifiers.zod}}"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "{{api.packageSpecifiers.nodeTypes}}",
33
+ "rimraf": "{{api.packageSpecifiers.rimraf}}",
34
+ "tsup": "{{api.packageSpecifiers.tsup}}",
35
+ "typescript": "{{api.packageSpecifiers.typescript}}",
36
+ "vitest": "{{api.packageSpecifiers.vitest}}"
37
+ }
38
+ }
@@ -0,0 +1,27 @@
1
+ import { apiContract } from '@h-ai/api-contract'
2
+ import { AppEchoInputSchema, AppEchoOutputSchema, AppInfoOutputSchema } from './app-schemas.js'
3
+
4
+ /** 应用自有 app contract。 */
5
+ export const appContract = {
6
+ info: apiContract
7
+ .route({
8
+ method: 'POST',
9
+ path: '/app/info',
10
+ operationId: 'app.info',
11
+ summary: 'Get service info',
12
+ tags: ['app'],
13
+ })
14
+ .output(AppInfoOutputSchema),
15
+ echo: apiContract
16
+ .route({
17
+ method: 'POST',
18
+ path: '/app/echo',
19
+ operationId: 'app.echo',
20
+ summary: '{{#if features.iam}}Echo a message (auth required){{else}}Echo a message{{/if}}',
21
+ tags: ['app'],
22
+ })
23
+ .input(AppEchoInputSchema)
24
+ .output(AppEchoOutputSchema),
25
+ }
26
+
27
+ export type AppContract = typeof appContract
@@ -0,0 +1,33 @@
1
+ import { apiContract } from '@h-ai/api-contract'
2
+ import { z } from 'zod'
3
+
4
+ /** 服务元信息响应数据。 */
5
+ export const AppInfoOutputDataSchema = z.object({
6
+ name: z.string(),
7
+ version: z.string(),
8
+ uptimeMs: z.number().int().nonnegative(),
9
+ transportEnabled: z.boolean(),
10
+ })
11
+
12
+ /** app.info 标准 HaiResult 输出。 */
13
+ export const AppInfoOutputSchema = apiContract.haiResultSchema(AppInfoOutputDataSchema)
14
+
15
+ /** 输入:单条文本消息(演示用,限制最大长度防止误用)。 */
16
+ export const AppEchoInputSchema = z.object({
17
+ message: z.string().min(1).max(2000),
18
+ })
19
+
20
+ /** 输出:原样回显 + 调用者快照。 */
21
+ export const AppEchoOutputDataSchema = z.object({
22
+ message: z.string(),
23
+ requestId: z.string(),
24
+ timestamp: z.string(),
25
+ userId: z.string().optional(),
26
+ })
27
+
28
+ /** app.echo 标准 HaiResult 输出。 */
29
+ export const AppEchoOutputSchema = apiContract.haiResultSchema(AppEchoOutputDataSchema)
30
+
31
+ export type AppInfoOutputData = z.infer<typeof AppInfoOutputDataSchema>
32
+ export type AppEchoInput = z.infer<typeof AppEchoInputSchema>
33
+ export type AppEchoOutputData = z.infer<typeof AppEchoOutputDataSchema>
@@ -0,0 +1,3 @@
1
+ export * from './app-contract.js'
2
+ export * from './app-schemas.js'
3
+ export * from './{{projectName}}-contract.js'
@@ -0,0 +1,12 @@
1
+ import { apiContract } from '@h-ai/api-contract'
2
+ import { appContract } from './app-contract.js'
3
+
4
+ /** 工程级应用 contract。 */
5
+ export const {{api.contractExportName}} = apiContract.create({
6
+ {{#if features.iam}} iam: apiContract.iam,
7
+ {{/if}}{{#if features.storage}} storage: apiContract.storage,
8
+ {{/if}}{{#if features.ai}} ai: apiContract.ai,
9
+ {{/if}} app: appContract,
10
+ })
11
+
12
+ export type ApiWorkspaceContract = typeof {{api.contractExportName}}
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { AppEchoInputSchema, {{api.contractExportName}} } from '../src/index.js'
3
+
4
+ describe('{{api.contractPackageName}}', () => {
5
+ it('includes generated contract routes', () => {
6
+ expect({{api.contractExportName}}.app.info).toBeDefined()
7
+ expect({{api.contractExportName}}.app.echo).toBeDefined()
8
+ {{#if features.iam}} expect({{api.contractExportName}}.iam.auth.login).toBeDefined()
9
+ {{/if}}{{#if features.storage}} expect({{api.contractExportName}}.storage.presignedUrls.createUpload).toBeDefined()
10
+ {{/if}}{{#if features.ai}} expect({{api.contractExportName}}.ai.chats.createCompletion).toBeDefined()
11
+ {{/if}} })
12
+
13
+ it('validates app.echo input length', () => {
14
+ expect(AppEchoInputSchema.safeParse({ message: 'hello' }).success).toBe(true)
15
+ expect(AppEchoInputSchema.safeParse({ message: '' }).success).toBe(false)
16
+ expect(AppEchoInputSchema.safeParse({ message: 'x'.repeat(2001) }).success).toBe(false)
17
+ })
18
+ })
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {},
4
+ "include": [
5
+ "src/**/*"
6
+ ],
7
+ "exclude": [
8
+ "node_modules",
9
+ "dist",
10
+ "tests"
11
+ ]
12
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: { index: 'src/index.ts' },
5
+ format: ['esm'],
6
+ target: 'node22',
7
+ sourcemap: true,
8
+ clean: true,
9
+ dts: true,
10
+ external: ['@h-ai/api-contract', 'zod'],
11
+ })
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['tests/**/*.test.ts'],
8
+ },
9
+ })
@@ -0,0 +1,17 @@
1
+ # {{api.servicePackageName}}
2
+
3
+ {{projectName}} API service 包,负责模块初始化、procedure 装配与 HTTP 入口。
4
+
5
+ ## 常用命令
6
+
7
+ ```bash
8
+ pnpm --filter {{api.servicePackageName}} dev
9
+ pnpm --filter {{api.servicePackageName}} test
10
+ ```
11
+
12
+ ## 关键目录
13
+
14
+ - `config/`:service 配置
15
+ - `src/app.ts`:HTTP App 装配
16
+ - `src/lib/server/init.ts`:模块初始化与关闭
17
+ - `src/server/procedures/`:应用级 procedures
@@ -0,0 +1,19 @@
1
+ # =============================================================================
2
+ # {{projectName}} API Service - 应用配置
3
+ # =============================================================================
4
+
5
+ name: {{projectName}} API Service
6
+ version: 0.1.0
7
+ env: ${HAI_ENV:development}
8
+ debug: false
9
+
10
+ defaultLocale: {{defaultLocale}}
11
+ supportedLocales:
12
+ {{#if_eq defaultLocale 'en-US'}} - en-US
13
+ - zh-CN
14
+ {{else}} - zh-CN
15
+ - en-US
16
+ {{/if_eq}}
17
+
18
+ features:
19
+ apiAccess: true
@@ -0,0 +1,26 @@
1
+ # =============================================================================
2
+ # {{projectName}} API Service - Serv / HTTP 入口配置
3
+ # =============================================================================
4
+
5
+ http:
6
+ apiPrefix: /api/v1
7
+ openapi:
8
+ path: /openapi.json
9
+ docs:
10
+ path: /docs
11
+ health:
12
+ path: /health
13
+ readyPath: /ready
14
+ rpc: false
15
+
16
+ {{#if features.crypto}}transport:
17
+ keyExchangePath: /_hai/key-exchange
18
+ excludePaths:
19
+ - /health
20
+ - /ready
21
+ - /openapi.json
22
+ - /docs
23
+ - /_hai/scalar.js
24
+ maxClients: 10000
25
+ {{else}}transport: false
26
+ {{/if}}
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "{{api.servicePackageName}}",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "eslint .",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "clean": "rimraf dist .turbo",
26
+ "dev": "pnpm build && node dist/index.js",
27
+ "start": "node dist/index.js"
28
+ },
29
+ "dependencies": {
30
+ "@h-ai/core": "{{haiPackageSpecifier}}",
31
+ "@h-ai/serv": "{{haiPackageSpecifier}}",
32
+ "{{api.contractPackageName}}": "workspace:*"{{#if features.ai}},
33
+ "@h-ai/ai": "{{haiPackageSpecifier}}"{{/if}}{{#if features.audit}},
34
+ "@h-ai/audit": "{{haiPackageSpecifier}}"{{/if}}{{#if features.cache}},
35
+ "@h-ai/cache": "{{haiPackageSpecifier}}"{{/if}}{{#if features.crypto}},
36
+ "@h-ai/crypto": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
37
+ "@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
38
+ "@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.iam}},
39
+ "@h-ai/iam": "{{haiPackageSpecifier}}"{{/if}}{{#if features.payment}},
40
+ "@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.reach}},
41
+ "@h-ai/reach": "{{haiPackageSpecifier}}"{{/if}}{{#if features.db}},
42
+ "@h-ai/reldb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
43
+ "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.storage}},
44
+ "@h-ai/storage": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
45
+ "@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}
46
+ },
47
+ "devDependencies": {
48
+ "@h-ai/api-client": "{{haiPackageSpecifier}}",
49
+ "@types/node": "{{api.packageSpecifiers.nodeTypes}}",
50
+ "rimraf": "{{api.packageSpecifiers.rimraf}}",
51
+ "tsup": "{{api.packageSpecifiers.tsup}}",
52
+ "typescript": "{{api.packageSpecifiers.typescript}}",
53
+ "vitest": "{{api.packageSpecifiers.vitest}}"
54
+ }
55
+ }
@@ -0,0 +1,98 @@
1
+ import type { ServConfig } from '@h-ai/serv'
2
+ import { core } from '@h-ai/core'
3
+ {{#if features.crypto}}import { crypto, TRANSPORT_PROTOCOL } from '@h-ai/crypto'
4
+ {{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
5
+ {{/if}}import { serv } from '@h-ai/serv'
6
+ import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
7
+ import pkg from '../package.json' with { type: 'json' }
8
+ import { {{api.serviceProceduresFactoryName}} } from './server/procedures/index.js'
9
+
10
+ const startedAt = Date.now()
11
+
12
+ interface CreateApiServiceAppOptions {
13
+ transport?: 'config' | 'disabled'
14
+ refreshCookie?: 'enabled' | 'disabled'
15
+ }
16
+
17
+ const LOOPBACK_ORIGIN_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/
18
+ const WEBVIEW_ORIGINS = new Set([
19
+ 'capacitor://localhost',
20
+ 'http://localhost',
21
+ 'https://tauri.localhost',
22
+ 'tauri://localhost',
23
+ ])
24
+ const DEFAULT_CORS_ALLOWED_HEADERS = [
25
+ 'Authorization',
26
+ 'Content-Type',
27
+ {{#if features.crypto}} TRANSPORT_PROTOCOL.CLIENT_ID_HEADER,
28
+ TRANSPORT_PROTOCOL.ENCRYPTED_HEADER,
29
+ {{/if}} 'X-Hai-Locale',
30
+ 'X-Request-Id',
31
+ 'X-Requested-With',
32
+ ]
33
+
34
+ /** 创建 API Service HTTP 应用。 */
35
+ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptions = {}) {
36
+ const servConfig = core.config.getOrThrow<ServConfig>('serv')
37
+ {{#if features.crypto}} const transportMode = _options.transport ?? 'config'
38
+ const transport = transportMode === 'disabled' || servConfig.transport === false
39
+ ? undefined
40
+ : {
41
+ crypto,
42
+ keyExchangePath: servConfig.transport.keyExchangePath,
43
+ excludePaths: [...servConfig.transport.excludePaths],
44
+ maxClients: servConfig.transport.maxClients,
45
+ }
46
+ {{else}} const transport = undefined
47
+ {{/if}}
48
+ {{#if features.iam}}
49
+ const refreshCookie = _options.refreshCookie === 'disabled' ? undefined : {}
50
+ {{/if}}
51
+
52
+ const appOptions: Parameters<typeof serv.createApp>[0] = {
53
+ contract: {{api.contractExportName}},
54
+ procedures: {{api.serviceProceduresFactoryName}}({
55
+ app: {
56
+ info: {
57
+ name: pkg.name,
58
+ version: pkg.version,
59
+ transportEnabled: transport !== undefined,
60
+ },
61
+ startedAt,
62
+ },
63
+ }),
64
+ http: servConfig.http,
65
+ {{#if features.iam}}
66
+ iam,
67
+ {{/if}}
68
+ middlewares: [
69
+ {
70
+ middleware: serv.cors({
71
+ origin: origin => resolveCorsOrigin(origin) !== null,
72
+ {{#if features.iam}} credentials: true,
73
+ {{/if}} allowedHeaders: DEFAULT_CORS_ALLOWED_HEADERS,{{#if features.crypto}}
74
+ exposedHeaders: [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, 'X-Request-Id'],{{/if}}
75
+ }),
76
+ },
77
+ ],
78
+ }
79
+ {{#if features.iam}}
80
+
81
+ appOptions.refreshCookie = refreshCookie
82
+ {{/if}}{{#if features.crypto}}
83
+ if (transport)
84
+ appOptions.transport = transport
85
+ {{/if}}
86
+
87
+ return serv.createApp(appOptions)
88
+ }
89
+
90
+ function resolveCorsOrigin(origin: string | undefined): string | null {
91
+ if (!origin)
92
+ return null
93
+ if (WEBVIEW_ORIGINS.has(origin))
94
+ return origin
95
+ if (LOOPBACK_ORIGIN_RE.test(origin))
96
+ return origin
97
+ return null
98
+ }
@@ -0,0 +1,21 @@
1
+ import process from 'node:process'
2
+ import { core } from '@h-ai/core'
3
+ import { serv } from '@h-ai/serv'
4
+ import { {{api.serviceAppFactoryName}} } from './app.js'
5
+ import { closeApp, initApp } from './lib/server/init.js'
6
+
7
+ const logger = core.logger.child({ app: '{{api.servicePackageName}}', scope: 'entrypoint' })
8
+
9
+ async function main(): Promise<void> {
10
+ await initApp()
11
+
12
+ serv.listen({{api.serviceAppFactoryName}}(), {
13
+ onListening: info => logger.info('API service listening', { address: info.address, port: info.port }),
14
+ onClose: closeApp,
15
+ })
16
+ }
17
+
18
+ main().catch((error: unknown) => {
19
+ logger.error('API service failed to start', { error })
20
+ process.exitCode = 1
21
+ })