@h-ai/cli 0.1.0-alpha.15 → 0.1.0-alpha.18

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 (116) hide show
  1. package/dist/{chunk-LANIDJC7.js → chunk-BPO34NRL.js} +3 -3
  2. package/dist/chunk-BPO34NRL.js.map +1 -0
  3. package/dist/{chunk-YV7WPUI6.js → chunk-GSMXGUYF.js} +6 -2
  4. package/dist/chunk-GSMXGUYF.js.map +1 -0
  5. package/dist/cli-config-templates-JCS7T3NU.js +5 -0
  6. package/dist/cli-config-templates-JCS7T3NU.js.map +1 -0
  7. package/dist/cli-skill-templates-IMYZPQRG.js +5 -0
  8. package/dist/cli-skill-templates-IMYZPQRG.js.map +1 -0
  9. package/dist/index.js +391 -58
  10. package/dist/index.js.map +1 -1
  11. package/dist/{src-RZ4DPYNJ.js → src-N4NPS7AG.js} +1239 -493
  12. package/dist/src-N4NPS7AG.js.map +1 -0
  13. package/package.json +8 -7
  14. package/templates/README.md +10 -3
  15. package/templates/apps/api/tests/health.test.ts.hbs +26 -0
  16. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +21 -0
  17. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +23 -0
  18. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +34 -0
  19. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +34 -0
  20. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +50 -0
  21. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +17 -0
  22. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +18 -0
  23. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +9 -0
  24. package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +14 -0
  25. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +18 -0
  26. package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +162 -0
  27. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +20 -0
  28. package/templates/apps/fullstack/apps/{{projectName}}-app/tests/api.test.ts.hbs +28 -0
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +17 -0
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +29 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +21 -0
  32. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +23 -0
  33. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/en-US.json.hbs +34 -0
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/zh-CN.json.hbs +34 -0
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +50 -0
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +17 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.css.hbs +18 -0
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +9 -0
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +14 -0
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +18 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +162 -0
  42. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +20 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tests/api.test.ts.hbs +28 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +17 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +29 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +13 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/en-US.json.hbs +12 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/zh-CN.json.hbs +12 -0
  49. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +17 -0
  50. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +21 -0
  51. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +23 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/en-US.json.hbs +34 -0
  53. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/zh-CN.json.hbs +34 -0
  54. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +50 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +17 -0
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.css.hbs +18 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +9 -0
  58. package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +14 -0
  59. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +18 -0
  60. package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +162 -0
  61. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +20 -0
  62. package/templates/apps/fullstack/apps/{{projectName}}-web/tests/api.test.ts.hbs +28 -0
  63. package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +17 -0
  64. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +29 -0
  65. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -0
  66. package/templates/apps/fullstack/eslint.config.js +44 -0
  67. package/templates/apps/fullstack/package.json.hbs +42 -0
  68. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +15 -0
  69. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +27 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-contract.ts.hbs +21 -0
  71. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-routes.ts.hbs +27 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/app-schemas.ts.hbs +34 -0
  73. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +4 -0
  74. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +9 -0
  75. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +48 -0
  76. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsconfig.json.hbs +9 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +9 -0
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +14 -0
  79. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +29 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +15 -0
  81. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +38 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +13 -0
  83. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +42 -0
  84. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/server-app.test.ts.hbs +49 -0
  85. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsconfig.json.hbs +10 -0
  86. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +9 -0
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +3 -0
  88. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +39 -0
  89. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +24 -0
  90. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +24 -0
  91. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +44 -0
  92. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +11 -0
  93. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +8 -0
  94. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +49 -0
  95. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +60 -0
  96. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/LanguageSwitcher.svelte.hbs +27 -0
  97. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +18 -0
  98. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme.svelte.ts.hbs +55 -0
  99. package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +17 -0
  100. package/templates/apps/fullstack/playwright.config.ts.hbs +34 -0
  101. package/templates/apps/fullstack/pnpm-workspace.yaml +7 -0
  102. package/templates/apps/fullstack/tsconfig.base.json +15 -0
  103. package/templates/base/e2e/app.spec.ts.hbs +7 -0
  104. package/templates/base/eslint.config.js +6 -0
  105. package/templates/base/package.json.hbs +42 -36
  106. package/templates/base/src/app.css.hbs +1 -0
  107. package/templates/base/src/lib/server/init.ts.hbs +3 -3
  108. package/templates/base/tests/scaffold.test.ts.hbs +27 -0
  109. package/templates/skills/hai-serv/SKILL.md +24 -11
  110. package/dist/chunk-LANIDJC7.js.map +0 -1
  111. package/dist/chunk-YV7WPUI6.js.map +0 -1
  112. package/dist/config-templates-S5RI2MC6.js +0 -5
  113. package/dist/config-templates-S5RI2MC6.js.map +0 -1
  114. package/dist/skill-templates-N5DU5FNX.js +0 -5
  115. package/dist/skill-templates-N5DU5FNX.js.map +0 -1
  116. package/dist/src-RZ4DPYNJ.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.15",
4
+ "version": "0.1.0-alpha.18",
5
5
  "description": "Hai Framework CLI for project scaffolding and code generation.",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
@@ -34,16 +34,16 @@
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.15"
37
+ "@h-ai/core": "0.1.0-alpha.18"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/fs-extra": "^11.0.4",
41
- "@types/node": "^25.1.0",
41
+ "@types/node": "^25.9.1",
42
42
  "@types/prompts": "^2.4.9",
43
- "rimraf": "^6.0.1",
44
- "tsup": "^8.3.5",
45
- "typescript": "^6.0.2",
46
- "vitest": "^4.0.18"
43
+ "rimraf": "^6.1.3",
44
+ "tsup": "^8.5.1",
45
+ "typescript": "^6.0.3",
46
+ "vitest": "^4.1.7"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsup",
@@ -52,6 +52,7 @@
52
52
  "lint": "eslint .",
53
53
  "lint:fix": "eslint . --fix",
54
54
  "test": "vitest run",
55
+ "test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
55
56
  "test:watch": "vitest",
56
57
  "test:coverage": "vitest run --coverage",
57
58
  "clean": "rimraf dist .turbo"
@@ -31,8 +31,12 @@ templates/
31
31
  │ ├── h5/ # H5 移动端
32
32
  │ │ ├── messages/
33
33
  │ │ └── src/routes/
34
- │ └── api/ # 纯 API 服务(无 UI / 无 i18n)
35
- │ └── src/routes/
34
+ │ ├── api/ # 纯 API 服务(无 UI / 无 i18n)
35
+ │ │ └── src/routes/
36
+ │ └── fullstack/ # 前后端分离多包工程(contract / serv / web / app / desktop / miniapp 占位)
37
+ │ ├── packages/ # 共享 contract 与后端 serv 包
38
+ │ ├── apps/ # 多前端目标,按用户选择条件生成
39
+ │ └── e2e/ # 服务端 + 页面级 Playwright 流程
36
40
  │
37
41
  ├── features/ # 可选 feature 路由(叠加到 src/routes/)
38
42
  │ ├── iam/
@@ -81,6 +85,7 @@ templates/
81
85
  | `{{hasI18n}}` | `boolean` | 是否启用 i18n(非 `api` 类型为 `true`) |
82
86
  | `{{defaultLocale}}` | `string` | 默认语言,如 `zh-CN` |
83
87
  | `{{packageManager}}` | `string` | 包管理器:`pnpm` / `npm` / `yarn` |
88
+ | `{{fullstack.*}}` | `object` | fullstack 类型专用上下文 |
84
89
  | `{{features.iam}}` | `boolean` | 是否选中 iam feature |
85
90
  | `{{features.db}}` | `boolean` | 是否选中 db feature |
86
91
  | `{{features.cache}}` | `boolean` | 是否选中 cache feature |
@@ -88,6 +93,8 @@ templates/
88
93
  | `{{features.storage}}` | `boolean` | 是否选中 storage feature |
89
94
  | `{{features.ai}}` | `boolean` | 是否选中 ai feature |
90
95
 
96
+ `fullstack.*` 包含包名、前端选择、依赖版本和 `contractExportName`,用于渲染 contract / serv / 多端 UI 的模板。
97
+
91
98
  ### 常用条件写法
92
99
 
93
100
  ```handlebars
@@ -123,4 +130,4 @@ import { iam } from '@h-ai/iam'
123
130
 
124
131
  ### 新增 feature 路由
125
132
 
126
- 在 `features/{featureId}/routes-shared/`(API 路由)或 `features/{featureId}/routes-{appType}/`(UI 页面)下创建文件,然后在 `template-engine.ts` 的 `FEATURE_ROUTE_DIRS` / `FEATURE_APP_ROUTE_DIRS` 中注册该目录。
133
+ 在 `features/{featureId}/routes-shared/`(API 路由)或 `features/{featureId}/routes-{appType}/`(UI 页面)下创建文件,然后在 `cli-template-engine.ts` 的 `FEATURE_ROUTE_DIRS` / `FEATURE_APP_ROUTE_DIRS` 中注册该目录。
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { GET } from '../src/routes/api/v1/health/+server'
3
+
4
+ interface HealthResponseBody {
5
+ success: boolean
6
+ data?: {
7
+ status?: string
8
+ service?: string
9
+ }
10
+ requestId?: string
11
+ }
12
+
13
+ describe('api health endpoint', () => {
14
+ it('returns a successful HaiResult response', async () => {
15
+ const response = await GET({
16
+ locals: { requestId: 'test-request' },
17
+ } as Parameters<typeof GET>[0])
18
+ const body = await response.json() as HealthResponseBody
19
+
20
+ expect(response.status).toBe(200)
21
+ expect(body.success).toBe(true)
22
+ expect(body.data?.status).toBe('ok')
23
+ expect(body.data?.service).toBe('api-service')
24
+ expect(body.requestId).toBe('test-request')
25
+ })
26
+ })
@@ -0,0 +1,21 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ # {{projectName}}-app
4
+
5
+ App/H5 前端工程,使用 `@h-ai/api-client` 绑定共享 contract 调用后端 API,并通过 `@h-ai/ui` 组件搭建页面。
6
+
7
+ ## i18n 与 shared 协同
8
+
9
+ - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
10
+ - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
11
+ - 语言切换由 shared 的 `LanguageSwitcher` 触发,并同步本端 `$lib/paraglide/runtime.js`
12
+
13
+ 自定义建议:移动端独有引导、运营位、按钮文案放在本目录 `messages/`;跨端通用组件放到 shared。
14
+
15
+ ## 开发
16
+
17
+ - `pnpm i18n:compile`:在工作区根目录统一编译 shared + 各前端 Paraglide 输出
18
+ - `pnpm --filter {{projectName}}-app dev`,默认端口 4174
19
+ - `pnpm --filter {{projectName}}-app paraglide:compile`:只编译本端 messages
20
+ - `pnpm --filter {{projectName}}-app test`
21
+ {{/if}}
@@ -0,0 +1,23 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ import antfu from '@antfu/eslint-config'
4
+
5
+ export default antfu({
6
+ formatters: true,
7
+ ignores: ['**/.svelte-kit/**', '**/build/**', '**/dist/**', '**/*.d.ts', '**/project.inlang/**', '**/src/lib/paraglide/**'],
8
+ svelte: true,
9
+ }, {
10
+ files: ['**/*.ts', '**/*.svelte'],
11
+ rules: {
12
+ 'no-console': 'error',
13
+ 'ts/no-explicit-any': 'error',
14
+ },
15
+ }, {
16
+ files: ['**/*.svelte'],
17
+ rules: {
18
+ 'svelte/html-quotes': 'off',
19
+ 'svelte/indent': 'off',
20
+ 'perfectionist/sort-imports': 'off',
21
+ },
22
+ })
23
+ {{/if}}
@@ -0,0 +1,34 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name} · App",
6
+ "platform_name": "App/H5",
7
+ "hero_badge": "Mobile workspace",
8
+ "hero_title": "{name} App Experience",
9
+ "hero_subtitle": "A mobile and H5 oriented entry point. This app keeps its own messages so onboarding and campaign copy can evolve independently.",
10
+ "hero_primary_action": "View service status",
11
+ "hero_secondary_action": "Customize the starter",
12
+ "preview_title": "Mobile-first interface",
13
+ "preview_body": "Ready for H5, Capacitor or embedded WebViews",
14
+ "preview_metric_shared": "Shared shell and API",
15
+ "preview_metric_local": "App-owned messages",
16
+ "preview_metric_theme": "Theme from config",
17
+ "status_title": "Service connectivity",
18
+ "status_description": "The mobile page calls the same backend API to verify cross-platform contract and shared API client wiring.",
19
+ "status_online": "{name} is connected",
20
+ "status_version": "Service version",
21
+ "status_started": "Started at: {time}",
22
+ "status_frontends": "Service frontends: {list}",
23
+ "status_frontends_count": "{count} frontend targets",
24
+ "custom_shared_title": "Shared owns common capabilities",
25
+ "custom_shared_body": "Keep cross-platform layout, locale, theme and API access in packages/{{projectName}}-shared.",
26
+ "custom_local_title": "App owns mobile copy",
27
+ "custom_local_body": "Write App-specific copy in apps/{{projectName}}-app/messages so mobile UX can iterate independently.",
28
+ "flow_title": "Collaboration boundary",
29
+ "flow_contract": "Contract constrains requests/responses",
30
+ "flow_service": "Service exposes unified APIs",
31
+ "flow_shared": "Shared reuses cross-platform capabilities",
32
+ "flow_frontend": "App composes the mobile experience"
33
+ }
34
+ {{/if}}
@@ -0,0 +1,34 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name} · App",
6
+ "platform_name": "App/H5",
7
+ "hero_badge": "移动端工作台",
8
+ "hero_title": "{name} App 体验",
9
+ "hero_subtitle": "面向移动端与 H5 场景的轻量入口,保留本端 messages 以便独立调整文案、引导语和运营内容。",
10
+ "hero_primary_action": "查看服务状态",
11
+ "hero_secondary_action": "了解如何自定义",
12
+ "preview_title": "移动优先界面",
13
+ "preview_body": "适合 H5、Capacitor 或内嵌 WebView",
14
+ "preview_metric_shared": "共享 Shell 与接口",
15
+ "preview_metric_local": "App 自有 messages",
16
+ "preview_metric_theme": "主题跟随配置",
17
+ "status_title": "服务联通状态",
18
+ "status_description": "移动端页面会调用同一套后端 API,验证跨端 contract 与 shared API client 正常工作。",
19
+ "status_online": "{name} 已连接",
20
+ "status_version": "服务版本",
21
+ "status_started": "启动时间:{time}",
22
+ "status_frontends": "服务声明的前端:{list}",
23
+ "status_frontends_count": "{count} 个前端目标",
24
+ "custom_shared_title": "共享层放通用能力",
25
+ "custom_shared_body": "跨端一致的布局、语言、主题和 API 访问都放在 packages/{{projectName}}-shared。",
26
+ "custom_local_title": "App 层放移动文案",
27
+ "custom_local_body": "App 独有文案写在 apps/{{projectName}}-app/messages,便于移动端独立迭代。",
28
+ "flow_title": "协同边界",
29
+ "flow_contract": "Contract 约束请求响应",
30
+ "flow_service": "Service 提供统一 API",
31
+ "flow_shared": "Shared 复用跨端能力",
32
+ "flow_frontend": "App 组织移动端体验"
33
+ }
34
+ {{/if}}
@@ -0,0 +1,50 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "name": "{{projectName}}-app",
5
+ "type": "module",
6
+ "version": "0.1.0",
7
+ "private": true,
8
+ "scripts": {
9
+ "paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
10
+ "paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
11
+ "dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.app}}",
12
+ "build": "vite build",
13
+ "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.app}} --strictPort",
14
+ "typecheck": "pnpm paraglide:compile && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
15
+ "lint": "eslint .",
16
+ "test": "vitest run",
17
+ "test:watch": "vitest",
18
+ "clean": "rimraf .svelte-kit build .turbo src/lib/paraglide"
19
+ },
20
+ "dependencies": {
21
+ "@h-ai/api-client": "{{fullstack.versions.apiClient}}",
22
+ "@h-ai/ui": "{{fullstack.versions.ui}}",
23
+ "{{fullstack.contractPackageName}}": "workspace:*",
24
+ "{{fullstack.sharedPackageName}}": "workspace:*"
25
+ },
26
+ "devDependencies": {
27
+ "@antfu/eslint-config": "{{fullstack.versions.antfuEslintConfig}}",
28
+ "@iconify-json/tabler": "{{fullstack.versions.iconifyJsonTabler}}",
29
+ "@iconify/tailwind4": "{{fullstack.versions.iconifyTailwind}}",
30
+ "@inlang/paraglide-js": "{{fullstack.versions.inlangParaglide}}",
31
+ "@inlang/plugin-message-format": "{{fullstack.versions.inlangMessageFormat}}",
32
+ "@sveltejs/adapter-auto": "{{fullstack.versions.adapterAuto}}",
33
+ "@sveltejs/kit": "{{fullstack.versions.svelteKit}}",
34
+ "@sveltejs/vite-plugin-svelte": "{{fullstack.versions.sveltePlugin}}",
35
+ "@tailwindcss/vite": "{{fullstack.versions.tailwindcssVite}}",
36
+ "bits-ui": "{{fullstack.versions.bitsUi}}",
37
+ "daisyui": "{{fullstack.versions.daisyui}}",
38
+ "eslint": "{{fullstack.versions.eslint}}",
39
+ "eslint-plugin-format": "{{fullstack.versions.eslintPluginFormat}}",
40
+ "eslint-plugin-svelte": "{{fullstack.versions.eslintPluginSvelte}}",
41
+ "svelte": "{{fullstack.versions.svelte}}",
42
+ "svelte-check": "{{fullstack.versions.svelteCheck}}",
43
+ "svelte-eslint-parser": "{{fullstack.versions.svelteEslintParser}}",
44
+ "tailwindcss": "{{fullstack.versions.tailwindcss}}",
45
+ "typescript": "{{fullstack.versions.typescript}}",
46
+ "vite": "{{fullstack.versions.vite}}",
47
+ "vitest": "{{fullstack.versions.vitest}}"
48
+ }
49
+ }
50
+ {{/if}}
@@ -0,0 +1,17 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/project-settings",
5
+ "baseLocale": "{{fullstack.i18n.baseLocale}}",
6
+ "locales": [
7
+ {{#each fullstack.i18n.locales}}"{{this}}"{{#unless @last}},{{/unless}}
8
+ {{/each}}
9
+ ],
10
+ "modules": [
11
+ "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
12
+ ],
13
+ "plugin.inlang.messageFormat": {
14
+ "pathPattern": "./messages/{locale}.json"
15
+ }
16
+ }
17
+ {{/if}}
@@ -0,0 +1,18 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ @import 'tailwindcss';
4
+ @import '@h-ai/ui/styles/global.css';
5
+ @import '@h-ai/ui/styles/theme.css';
6
+ @source "../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
7
+ @source "../../../node_modules/@h-ai/ui/dist/**/*.{svelte,js,ts}";
8
+ @source "../../../packages/{{fullstack.sharedPackageName}}/src/**/*.{svelte,js,ts}";
9
+
10
+ @plugin "daisyui" {
11
+ themes:
12
+ {{#each fullstack.theme.themes}}{{this}}{{#if_eq this ../fullstack.theme.defaultTheme}} --default{{/if_eq}}{{#unless @last}},
13
+ {{/unless}}{{/each}};
14
+ }
15
+ @plugin "@iconify/tailwind4" {
16
+ prefixes: tabler;
17
+ }
18
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ /// <reference types="@sveltejs/kit" />
4
+ /// <reference types="vite/client" />
5
+
6
+ import '@h-ai/ui/auto-import'
7
+
8
+ declare module '*.css'
9
+ {{/if}}
@@ -0,0 +1,14 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ <!doctype html>
4
+ <html lang="%paraglide.lang%">
5
+ <head>
6
+ <meta charset="utf-8" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ %sveltekit.head%
9
+ </head>
10
+ <body data-sveltekit-preload-data="hover">
11
+ <div style="display: contents">%sveltekit.body%</div>
12
+ </body>
13
+ </html>
14
+ {{/if}}
@@ -0,0 +1,18 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ <script lang="ts">
4
+ import { setLocale } from '$lib/paraglide/runtime.js'
5
+ import { AppShell } from '{{fullstack.sharedPackageName}}'
6
+ import '../app.css'
7
+
8
+ const { children } = $props()
9
+
10
+ function syncLocale(locale: string) {
11
+ setLocale(locale as Parameters<typeof setLocale>[0])
12
+ }
13
+ </script>
14
+
15
+ <AppShell platform="app" onLocaleChange={syncLocale}>
16
+ {@render children()}
17
+ </AppShell>
18
+ {{/if}}
@@ -0,0 +1,162 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ <script lang="ts">
4
+ import type { AppInfo } from '{{fullstack.sharedPackageName}}'
5
+ import * as appM from '$lib/paraglide/messages.js'
6
+ import { Alert, Button, Card, Skeleton } from '@h-ai/ui'
7
+ import { onMount } from 'svelte'
8
+ import { fetchAppInfo, messages as sharedM } from '{{fullstack.sharedPackageName}}'
9
+
10
+ let info = $state<AppInfo | null>(null)
11
+ let loading = $state(true)
12
+ let errorMessage = $state('')
13
+
14
+ async function refreshStatus() {
15
+ loading = true
16
+ errorMessage = ''
17
+ try {
18
+ info = await fetchAppInfo()
19
+ }
20
+ catch {
21
+ info = null
22
+ errorMessage = sharedM.home_error()
23
+ }
24
+ finally {
25
+ loading = false
26
+ }
27
+ }
28
+
29
+ onMount(() => {
30
+ void refreshStatus()
31
+ })
32
+ </script>
33
+
34
+ <svelte:head>
35
+ <title>{appM.page_title({ name: sharedM.app_name() })}</title>
36
+ </svelte:head>
37
+
38
+ <section class="space-y-8">
39
+ <div class="grid gap-6 lg:grid-cols-[1.2fr_0.8fr]">
40
+ <section class="relative overflow-hidden rounded-[2rem] border border-base-300/70 bg-gradient-to-br from-primary/15 via-base-100 to-secondary/15 p-6 shadow-2xl shadow-primary/10 lg:p-10">
41
+ <div class="absolute right-8 top-8 hidden size-28 rounded-full bg-primary/20 blur-3xl sm:block"></div>
42
+ <div class="relative z-10 max-w-2xl space-y-6">
43
+ <span class="badge badge-primary badge-lg gap-2">
44
+ <span class="icon-[tabler--rocket] size-4"></span>
45
+ {appM.hero_badge()}
46
+ </span>
47
+ <div class="space-y-4">
48
+ <h1 class="text-4xl font-black tracking-tight text-base-content sm:text-5xl">
49
+ {appM.hero_title({ name: sharedM.app_name() })}
50
+ </h1>
51
+ <p class="max-w-xl text-lg leading-8 text-base-content/70">
52
+ {appM.hero_subtitle()}
53
+ </p>
54
+ </div>
55
+ <div class="flex flex-wrap gap-3">
56
+ <a class="btn btn-primary shadow-lg shadow-primary/20" href="#status">
57
+ <span class="icon-[tabler--activity-heartbeat] size-5"></span>
58
+ {appM.hero_primary_action()}
59
+ </a>
60
+ <a class="btn btn-ghost" href="#customize">
61
+ <span class="icon-[tabler--adjustments] size-5"></span>
62
+ {appM.hero_secondary_action()}
63
+ </a>
64
+ </div>
65
+ </div>
66
+ </section>
67
+
68
+ <aside class="rounded-[2rem] border border-base-300/70 bg-base-100/80 p-5 shadow-xl backdrop-blur">
69
+ <div class="mockup-window border border-base-300 bg-base-200/70">
70
+ <div class="space-y-4 bg-base-100 p-5">
71
+ <div class="flex items-center justify-between">
72
+ <div>
73
+ <p class="text-sm font-semibold">{appM.preview_title()}</p>
74
+ <p class="text-xs text-base-content/55">{appM.preview_body()}</p>
75
+ </div>
76
+ <span class="badge badge-success badge-outline">{appM.platform_name()}</span>
77
+ </div>
78
+ <div class="grid gap-3 text-sm">
79
+ <div class="rounded-2xl bg-primary/10 p-4">
80
+ <span class="icon-[tabler--components] mb-2 size-6 text-primary"></span>
81
+ <p class="font-semibold">{appM.preview_metric_shared()}</p>
82
+ </div>
83
+ <div class="grid grid-cols-2 gap-3">
84
+ <div class="rounded-2xl bg-secondary/10 p-4">
85
+ <span class="icon-[tabler--language] mb-2 size-6 text-secondary"></span>
86
+ <p class="font-semibold">{appM.preview_metric_local()}</p>
87
+ </div>
88
+ <div class="rounded-2xl bg-accent/10 p-4">
89
+ <span class="icon-[tabler--palette] mb-2 size-6 text-accent"></span>
90
+ <p class="font-semibold">{appM.preview_metric_theme()}</p>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </aside>
97
+ </div>
98
+
99
+ <div class="grid gap-6 lg:grid-cols-[0.95fr_1.05fr]">
100
+ <div id="status">
101
+ <Card title={appM.status_title()} bordered shadow="md" class="bg-base-100/90 backdrop-blur">
102
+ <p class="mb-5 text-sm text-base-content/65">{appM.status_description()}</p>
103
+ {#if loading}
104
+ <Skeleton variant="title" class="mb-3" />
105
+ <Skeleton count={3} />
106
+ <p class="sr-only" data-testid="service-status">{sharedM.home_loading()}</p>
107
+ {:else if info}
108
+ <Alert variant="success" title={appM.status_online({ name: info.name })}>
109
+ <p data-testid="service-status">{info.name}</p>
110
+ </Alert>
111
+ <dl class="mt-5 grid gap-3 sm:grid-cols-2 text-sm">
112
+ <div class="rounded-box border border-base-300/70 bg-base-200/60 p-4">
113
+ <dt class="text-xs text-base-content/55">{appM.status_version()}</dt>
114
+ <dd class="mt-1 font-semibold">{info.version}</dd>
115
+ </div>
116
+ <div class="rounded-box border border-base-300/70 bg-base-200/60 p-4">
117
+ <dt class="text-xs text-base-content/55">{appM.status_started({ time: info.startedAt })}</dt>
118
+ <dd class="mt-1 font-semibold">{appM.status_frontends_count({ count: String(info.frontends.length) })}</dd>
119
+ </div>
120
+ <div class="rounded-box border border-base-300/70 bg-base-200/60 p-4 sm:col-span-2">
121
+ <dt class="text-xs text-base-content/55">{appM.status_frontends({ list: info.frontends.join(' / ') })}</dt>
122
+ <dd class="mt-1 font-semibold">{sharedM.home_subtitle()}</dd>
123
+ </div>
124
+ </dl>
125
+ {:else}
126
+ <Alert variant="error" title={sharedM.home_error()}>
127
+ <p data-testid="service-status">{errorMessage}</p>
128
+ </Alert>
129
+ {/if}
130
+
131
+ <div class="mt-5">
132
+ <Button type="button" variant="primary" loading={loading} onclick={refreshStatus}>
133
+ {sharedM.home_refresh()}
134
+ </Button>
135
+ </div>
136
+ </Card>
137
+ </div>
138
+
139
+ <section id="customize" class="grid gap-4 sm:grid-cols-2">
140
+ <div class="rounded-[1.5rem] border border-base-300/70 bg-base-100/90 p-5 shadow-md">
141
+ <span class="icon-[tabler--package] mb-4 size-7 text-primary"></span>
142
+ <h2 class="text-lg font-bold">{appM.custom_shared_title()}</h2>
143
+ <p class="mt-2 text-sm leading-6 text-base-content/65">{appM.custom_shared_body()}</p>
144
+ </div>
145
+ <div class="rounded-[1.5rem] border border-base-300/70 bg-base-100/90 p-5 shadow-md">
146
+ <span class="icon-[tabler--message-language] mb-4 size-7 text-secondary"></span>
147
+ <h2 class="text-lg font-bold">{appM.custom_local_title()}</h2>
148
+ <p class="mt-2 text-sm leading-6 text-base-content/65">{appM.custom_local_body()}</p>
149
+ </div>
150
+ <div class="rounded-[1.5rem] border border-base-300/70 bg-base-100/90 p-5 shadow-md sm:col-span-2">
151
+ <h2 class="text-lg font-bold">{appM.flow_title()}</h2>
152
+ <ol class="mt-4 grid gap-3 md:grid-cols-4">
153
+ <li class="rounded-box bg-base-200/70 p-4 text-sm">{appM.flow_contract()}</li>
154
+ <li class="rounded-box bg-base-200/70 p-4 text-sm">{appM.flow_service()}</li>
155
+ <li class="rounded-box bg-base-200/70 p-4 text-sm">{appM.flow_shared()}</li>
156
+ <li class="rounded-box bg-base-200/70 p-4 text-sm">{appM.flow_frontend()}</li>
157
+ </ol>
158
+ </div>
159
+ </section>
160
+ </div>
161
+ </section>
162
+ {{/if}}
@@ -0,0 +1,20 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ import { autoImportHaiUi } from '@h-ai/ui/auto-import'
4
+ import adapter from '@sveltejs/adapter-auto'
5
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
6
+
7
+ /** @type {import('@sveltejs/kit').Config} */
8
+ const config = {
9
+ preprocess: [autoImportHaiUi(), vitePreprocess()],
10
+ compilerOptions: {
11
+ // 使用 Svelte 5 Runes,和 @h-ai/ui 组件库保持一致。
12
+ runes: true,
13
+ },
14
+ kit: {
15
+ adapter: adapter(),
16
+ },
17
+ }
18
+
19
+ export default config
20
+ {{/if}}
@@ -0,0 +1,28 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ import { fetchAppInfo } from '{{fullstack.sharedPackageName}}'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ describe('fetchAppInfo', () => {
7
+ it('parses shared contract response', async () => {
8
+ const fetchFn: typeof fetch = async () => new Response(
9
+ JSON.stringify({
10
+ success: true,
11
+ data: {
12
+ name: '{{fullstack.servPackageName}}',
13
+ version: '0.1.0',
14
+ startedAt: '2026-01-01T00:00:00.000Z',
15
+ frontends: ['app'],
16
+ },
17
+ }),
18
+ {
19
+ headers: { 'content-type': 'application/json' },
20
+ },
21
+ )
22
+
23
+ const info = await fetchAppInfo({ fetchFn, baseUrl: 'http://127.0.0.1:3000' })
24
+
25
+ expect(info.name).toBe('{{fullstack.servPackageName}}')
26
+ })
27
+ })
28
+ {{/if}}
@@ -0,0 +1,17 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ {
4
+ "extends": "./.svelte-kit/tsconfig.json",
5
+ "compilerOptions": {
6
+ "moduleResolution": "bundler",
7
+ "allowImportingTsExtensions": true,
8
+ "allowJs": true,
9
+ "checkJs": false,
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true,
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src/**/*", "src/**/*.svelte", "tests/**/*"],
15
+ "exclude": ["node_modules", ".svelte-kit", "build"]
16
+ }
17
+ {{/if}}
@@ -0,0 +1,29 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.app}}
3
+ import { paraglideVitePlugin } from '@inlang/paraglide-js'
4
+ import { sveltekit } from '@sveltejs/kit/vite'
5
+ import tailwindcss from '@tailwindcss/vite'
6
+ import { defineConfig } from 'vitest/config'
7
+
8
+ export default defineConfig({
9
+ plugins: [
10
+ paraglideVitePlugin({
11
+ project: './project.inlang',
12
+ outdir: './src/lib/paraglide',
13
+ strategy: ['cookie', 'baseLocale'],
14
+ }),
15
+ sveltekit(),
16
+ tailwindcss(),
17
+ ],
18
+ optimizeDeps: {
19
+ exclude: ['bits-ui'],
20
+ },
21
+ ssr: {
22
+ noExternal: [/@h-ai\//, /{{fullstack.sharedPackageName}}/],
23
+ },
24
+ test: {
25
+ environment: 'node',
26
+ include: ['tests/**/*.test.ts'],
27
+ },
28
+ })
29
+ {{/if}}
@@ -0,0 +1,21 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.desktop}}
3
+ # {{projectName}}-desktop
4
+
5
+ 桌面端前端工程,使用 `@h-ai/api-client` 绑定共享 contract 调用后端 API,并通过 `@h-ai/ui` 组件搭建页面;后续可按需接入 Tauri。
6
+
7
+ ## i18n 与 shared 协同
8
+
9
+ - 本端页面文案:`messages/{locale}.json` + `project.inlang/settings.json`
10
+ - 共享 Shell/主题/语言/API:`packages/{{fullstack.sharedPackageName}}`
11
+ - 语言切换由 shared 的 `LanguageSwitcher` 触发,并同步本端 `$lib/paraglide/runtime.js`
12
+
13
+ 自定义建议:桌面端菜单、状态、快捷操作文案放在本目录 `messages/`;跨端通用能力放到 shared。
14
+
15
+ ## 开发
16
+
17
+ - `pnpm i18n:compile`:在工作区根目录统一编译 shared + 各前端 Paraglide 输出
18
+ - `pnpm --filter {{projectName}}-desktop dev`,默认端口 4175
19
+ - `pnpm --filter {{projectName}}-desktop paraglide:compile`:只编译本端 messages
20
+ - `pnpm --filter {{projectName}}-desktop test`
21
+ {{/if}}