@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
@@ -0,0 +1,34 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
3
+ {
4
+ "$schema": "https://inlang.com/schema/inlang-message-format",
5
+ "page_title": "{name} · Web",
6
+ "platform_name": "Web",
7
+ "hero_badge": "Web 工作台",
8
+ "hero_title": "{name} Web 控制台",
9
+ "hero_subtitle": "面向浏览器的产品首页,使用本端 messages 管理页面文案,并复用 shared 的 Shell、主题、语言与 API 客户端。",
10
+ "hero_primary_action": "查看服务状态",
11
+ "hero_secondary_action": "了解如何自定义",
12
+ "preview_title": "响应式 Web 体验",
13
+ "preview_body": "适合官网、控制台与运营后台入口",
14
+ "preview_metric_shared": "共享组件与 API 客户端",
15
+ "preview_metric_local": "Web 自有 messages",
16
+ "preview_metric_theme": "主题可配置",
17
+ "status_title": "服务联通状态",
18
+ "status_description": "页面会调用后端服务,确认 contract、service、shared 与 Web 前端已经连通。",
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": "把跨端一致的 Shell、主题、语言切换、API client 与通用组件放在 packages/{{projectName}}-shared。",
26
+ "custom_local_title": "Web 层放产品文案",
27
+ "custom_local_body": "Web 独有页面文案写在 apps/{{projectName}}-web/messages,再由本端 project.inlang 编译。",
28
+ "flow_title": "协同边界",
29
+ "flow_contract": "Contract 定义接口真相源",
30
+ "flow_service": "Service 实现业务接口",
31
+ "flow_shared": "Shared 提供跨端 UI/API 能力",
32
+ "flow_frontend": "Web 组合页面与本端文案"
33
+ }
34
+ {{/if}}
@@ -0,0 +1,50 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
3
+ {
4
+ "name": "{{projectName}}-web",
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.web}}",
12
+ "build": "vite build",
13
+ "preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.web}} --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.web}}
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.web}}
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.web}}
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.web}}
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.web}}
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="web" onLocaleChange={syncLocale}>
16
+ {@render children()}
17
+ </AppShell>
18
+ {{/if}}
@@ -0,0 +1,162 @@
1
+ {{! @skipIfEmpty }}
2
+ {{#if fullstack.frontends.web}}
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.web}}
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.web}}
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: ['web'],
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.web}}
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.web}}
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,30 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { APP_API_ROUTES } from '{{fullstack.contractPackageName}}'
3
+
4
+ const apiBaseUrl = 'http://127.0.0.1:3000'
5
+
6
+ test('service health endpoint is ready', async ({ request }) => {
7
+ const response = await request.get(apiBaseUrl + APP_API_ROUTES.health)
8
+ expect(response.ok()).toBe(true)
9
+ await expect(response).toBeOK()
10
+ })
11
+
12
+ test('service echo endpoint returns HaiResult', async ({ request }) => {
13
+ const response = await request.post(apiBaseUrl + APP_API_ROUTES.echo, {
14
+ data: { message: 'hello e2e' },
15
+ })
16
+ expect(response.ok()).toBe(true)
17
+
18
+ const body = await response.json()
19
+ expect(body.success).toBe(true)
20
+ expect(body.data.message).toBe('hello e2e')
21
+ expect(body.data.requestId).toBeTruthy()
22
+ })
23
+ {{#if fullstack.e2eFrontend}}
24
+
25
+ test('frontend loads service status from page', async ({ page }) => {
26
+ await page.goto('/')
27
+ await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
28
+ await expect(page.getByTestId('service-status')).toContainText('{{fullstack.servPackageName}}')
29
+ })
30
+ {{/if}}
@@ -0,0 +1,44 @@
1
+ import antfu from '@antfu/eslint-config'
2
+
3
+ export default antfu({
4
+ formatters: true,
5
+ ignores: [
6
+ '**/.svelte-kit/**',
7
+ '**/build/**',
8
+ '**/dist/**',
9
+ '**/src/lib/paraglide/**',
10
+ '**/project.inlang/**',
11
+ '**/*.d.ts',
12
+ '**/.agents/**',
13
+ '**/.github/**',
14
+ '**/AGENTS.md',
15
+ '**/CLAUDE.md',
16
+ '**/README.md',
17
+ '**/opencode.json',
18
+ '**/package.json',
19
+ ],
20
+ svelte: true,
21
+ }, {
22
+ files: ['**/*.ts', '**/*.svelte'],
23
+ rules: {
24
+ 'no-console': 'error',
25
+ 'ts/no-explicit-any': 'error',
26
+ },
27
+ }, {
28
+ files: ['**/*.svelte'],
29
+ rules: {
30
+ 'svelte/html-quotes': 'off',
31
+ 'svelte/indent': 'off',
32
+ 'perfectionist/sort-imports': 'off',
33
+ },
34
+ }, {
35
+ files: ['**/tsconfig.json', '**/tsconfig.*.json'],
36
+ rules: {
37
+ 'jsonc/sort-keys': 'off',
38
+ },
39
+ }, {
40
+ files: ['**/index.ts'],
41
+ rules: {
42
+ 'perfectionist/sort-exports': 'off',
43
+ },
44
+ })
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "packageManager": "pnpm@9.15.0",
7
+ "description": "Full-stack hai workspace with separated contract, service and frontends.",
8
+ "scripts": {
9
+ "i18n:compile": "pnpm --filter {{fullstack.sharedPackageName}} paraglide:compile{{#each fullstack.frontendApps}} && pnpm --filter {{packageName}} paraglide:compile{{/each}}",
10
+ "postinstall": "pnpm i18n:compile",
11
+ "dev": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev",
12
+ "build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
13
+ "typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
14
+ "lint": "pnpm -r --if-present lint",
15
+ "test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present test",
16
+ "test:e2e": "pnpm build && playwright test",
17
+ "clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
18
+ },
19
+ "dependencies": {
20
+ "{{fullstack.contractPackageName}}": "workspace:*",
21
+ "{{fullstack.sharedPackageName}}": "workspace:*"
22
+ },
23
+ "devDependencies": {
24
+ "@antfu/eslint-config": "{{fullstack.versions.antfuEslintConfig}}",
25
+ "@playwright/test": "{{fullstack.versions.playwright}}",
26
+ "@sveltejs/adapter-auto": "{{fullstack.versions.adapterAuto}}",
27
+ "@sveltejs/kit": "{{fullstack.versions.svelteKit}}",
28
+ "@sveltejs/vite-plugin-svelte": "{{fullstack.versions.sveltePlugin}}",
29
+ "@types/node": "{{fullstack.versions.nodeTypes}}",
30
+ "eslint": "{{fullstack.versions.eslint}}",
31
+ "eslint-plugin-format": "{{fullstack.versions.eslintPluginFormat}}",
32
+ "eslint-plugin-svelte": "{{fullstack.versions.eslintPluginSvelte}}",
33
+ "rimraf": "{{fullstack.versions.rimraf}}",
34
+ "svelte": "{{fullstack.versions.svelte}}",
35
+ "svelte-check": "{{fullstack.versions.svelteCheck}}",
36
+ "svelte-eslint-parser": "{{fullstack.versions.svelteEslintParser}}",
37
+ "tsup": "{{fullstack.versions.tsup}}",
38
+ "typescript": "{{fullstack.versions.typescript}}",
39
+ "vite": "{{fullstack.versions.vite}}",
40
+ "vitest": "{{fullstack.versions.vitest}}"
41
+ }
42
+ }
@@ -0,0 +1,15 @@
1
+ # {{fullstack.contractPackageName}}
2
+
3
+ 共享 API 契约包,包含前后端共用的路径常量、Zod Schema、`apiContract.route()` 路由定义与工程级 contract。
4
+
5
+ ## 骨架说明
6
+
7
+ - `src/app-routes.ts`:HTTP 路径常量,供 service、frontend 与 E2E 复用。
8
+ - `src/app-schemas.ts`:输入输出 Schema 与类型。
9
+ - `src/app-contract.ts`:app 领域 contract 路由定义。
10
+ - `src/{{projectName}}-contract.ts`:工程级 contract,供 `@h-ai/serv` 与 `@h-ai/api-client` 共用。
11
+
12
+ ## 开发
13
+
14
+ - `pnpm --filter {{fullstack.contractPackageName}} typecheck`
15
+ - `pnpm --filter {{fullstack.contractPackageName}} test`
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "{{fullstack.contractPackageName}}",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "typecheck": "tsc --noEmit",
17
+ "lint": "eslint .",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "clean": "rimraf dist .turbo"
21
+ },
22
+ "dependencies": {
23
+ "@h-ai/api-contract": "{{fullstack.versions.apiContract}}",
24
+ "@h-ai/core": "{{fullstack.versions.core}}",
25
+ "zod": "{{fullstack.versions.zod}}"
26
+ }
27
+ }
@@ -0,0 +1,21 @@
1
+ import { apiContract } from '@h-ai/api-contract'
2
+ import { APP_CONTRACT_ROUTES } from './app-routes.js'
3
+ import { AppEchoInputSchema, AppEchoOutputSchema, AppInfoOutputSchema } from './app-schemas.js'
4
+
5
+ /** app 领域 API contract:路径、方法、输入输出 Schema 的单一来源。 */
6
+ export const appContract = {
7
+ info: apiContract.route({
8
+ method: 'POST',
9
+ path: APP_CONTRACT_ROUTES.info,
10
+ operationId: 'app.info',
11
+ tags: ['app'],
12
+ summary: 'Get application runtime information',
13
+ }).output(AppInfoOutputSchema),
14
+ echo: apiContract.route({
15
+ method: 'POST',
16
+ path: APP_CONTRACT_ROUTES.echo,
17
+ operationId: 'app.echo',
18
+ tags: ['app'],
19
+ summary: 'Echo a message through the generated service route',
20
+ }).input(AppEchoInputSchema).output(AppEchoOutputSchema),
21
+ }
@@ -0,0 +1,27 @@
1
+ /** HTTP 与业务 API 路径常量,前端、服务端与 E2E 共用。 */
2
+ export const APP_HTTP_ROUTES = {
3
+ apiPrefix: '/api/v1',
4
+ health: '/health',
5
+ ready: '/ready',
6
+ openapi: '/openapi.json',
7
+ docs: '/docs',
8
+ } as const
9
+
10
+ /** app 领域 contract 内部路径,最终会挂载到 APP_HTTP_ROUTES.apiPrefix 下。 */
11
+ export const APP_CONTRACT_ROUTES = {
12
+ info: '/app/info',
13
+ echo: '/app/echo',
14
+ } as const
15
+
16
+ /** 已拼接 apiPrefix 的实际 HTTP 路径,便于测试和非 typed client 场景使用。 */
17
+ export const APP_API_ROUTES = {
18
+ health: APP_HTTP_ROUTES.health,
19
+ ready: APP_HTTP_ROUTES.ready,
20
+ openapi: APP_HTTP_ROUTES.openapi,
21
+ docs: APP_HTTP_ROUTES.docs,
22
+ info: `${APP_HTTP_ROUTES.apiPrefix}${APP_CONTRACT_ROUTES.info}`,
23
+ echo: `${APP_HTTP_ROUTES.apiPrefix}${APP_CONTRACT_ROUTES.echo}`,
24
+ } as const
25
+
26
+ /** 当前工程启用的前端目标。 */
27
+ export const ENABLED_FRONTENDS = {{{fullstack.selectedFrontendsJson}}} as const