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

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 (141) hide show
  1. package/README.en.md +66 -251
  2. package/README.ja.md +67 -252
  3. package/README.md +66 -251
  4. package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
  5. package/dist/chunk-7DSURTJ4.js.map +1 -0
  6. package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
  7. package/dist/chunk-AOQ4N657.js.map +1 -0
  8. package/dist/cli-config-templates-6AHNUGBN.js +5 -0
  9. package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
  10. package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
  11. package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
  12. package/dist/index.js +644 -391
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
  15. package/dist/src-2V6VP4I5.js.map +1 -0
  16. package/package.json +8 -8
  17. package/templates/README.md +48 -145
  18. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
  19. package/templates/apps/api/.dockerignore.hbs +18 -12
  20. package/templates/apps/api/.gitignore.hbs +9 -1
  21. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
  23. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
  24. package/templates/apps/api/hai.build.json.hbs +1 -0
  25. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  26. package/templates/apps/api/package.json.hbs +10 -8
  27. package/templates/apps/fullstack/.dockerignore.hbs +18 -14
  28. package/templates/apps/fullstack/.gitignore.hbs +9 -1
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  33. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
  39. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  40. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
  41. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
  42. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
  43. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
  44. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  45. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  46. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  47. package/templates/apps/fullstack/package.json.hbs +7 -12
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
  53. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
  56. package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
  57. package/templates/apps/mobile-app/index.html.hbs +1 -0
  58. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  59. package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
  60. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  61. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  62. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  63. package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
  64. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  65. package/templates/base/.dockerignore.hbs +18 -13
  66. package/templates/base/.gitignore.hbs +9 -1
  67. package/templates/base/e2e/app.spec.ts.hbs +10 -0
  68. package/templates/base/hai.build.json.hbs +1 -0
  69. package/templates/base/images/app.Dockerfile.hbs +64 -0
  70. package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
  71. package/templates/base/package.json.hbs +20 -19
  72. package/templates/base/svelte.config.js.hbs +2 -0
  73. package/templates/base/vite.config.ts.hbs +9 -3
  74. package/templates/shared/.gitattributes +11 -0
  75. package/templates/shared/container-runtime-config.sh +30 -0
  76. package/templates/shared/run-e2e.mjs +65 -0
  77. package/templates/shared/vite-runtime-config.ts +54 -0
  78. package/templates/skills/AGENTS.md +3 -3
  79. package/templates/skills/bridges/admin/AGENTS.md +1 -1
  80. package/templates/skills/bridges/api/AGENTS.md +1 -1
  81. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
  82. package/templates/skills/bridges/h5/AGENTS.md +1 -1
  83. package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
  84. package/templates/skills/bridges/website/AGENTS.md +1 -1
  85. package/templates/skills/hai-api-client/SKILL.md +14 -161
  86. package/templates/skills/hai-api-client/reference.md +166 -0
  87. package/templates/skills/hai-api-contract/SKILL.md +15 -273
  88. package/templates/skills/hai-api-contract/reference.md +278 -0
  89. package/templates/skills/hai-app-create/SKILL.md +2 -2
  90. package/templates/skills/hai-app-create/development.md +6 -4
  91. package/templates/skills/hai-app-create/reference.md +5 -4
  92. package/templates/skills/hai-app-review/SKILL.md +1 -1
  93. package/templates/skills/hai-app-tests/SKILL.md +4 -3
  94. package/templates/skills/hai-app-tests/reference.md +2 -2
  95. package/templates/skills/hai-app-tests/validation.md +31 -0
  96. package/templates/skills/hai-audit/SKILL.md +17 -142
  97. package/templates/skills/hai-audit/reference.md +145 -0
  98. package/templates/skills/hai-build/SKILL.md +24 -3
  99. package/templates/skills/hai-build/reference.md +10 -9
  100. package/templates/skills/hai-cache/SKILL.md +15 -182
  101. package/templates/skills/hai-cache/reference.md +187 -0
  102. package/templates/skills/hai-capacitor/SKILL.md +4 -3
  103. package/templates/skills/hai-capacitor/reference.md +6 -4
  104. package/templates/skills/hai-ci/SKILL.md +2 -0
  105. package/templates/skills/hai-core/SKILL.md +3 -3
  106. package/templates/skills/hai-crypto/SKILL.md +16 -252
  107. package/templates/skills/hai-crypto/reference.md +256 -0
  108. package/templates/skills/hai-datapipe/SKILL.md +15 -196
  109. package/templates/skills/hai-datapipe/reference.md +202 -0
  110. package/templates/skills/hai-deploy/SKILL.md +28 -271
  111. package/templates/skills/hai-deploy/middleware.md +6 -18
  112. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  113. package/templates/skills/hai-framework-sync/sync.md +1 -1
  114. package/templates/skills/hai-iam/SKILL.md +1 -3
  115. package/templates/skills/hai-kit/SKILL.md +12 -167
  116. package/templates/skills/hai-kit/reference.md +172 -0
  117. package/templates/skills/hai-payment/SKILL.md +19 -251
  118. package/templates/skills/hai-payment/reference.md +256 -0
  119. package/templates/skills/hai-pr-review/SKILL.md +4 -4
  120. package/templates/skills/hai-reach/SKILL.md +2 -2
  121. package/templates/skills/hai-reldb/SKILL.md +1 -1
  122. package/templates/skills/hai-scheduler/SKILL.md +15 -195
  123. package/templates/skills/hai-scheduler/reference.md +202 -0
  124. package/templates/skills/hai-serv/SKILL.md +2 -2
  125. package/templates/skills/hai-spec-implement/SKILL.md +1 -1
  126. package/templates/skills/hai-spec-implement/workflow.md +3 -3
  127. package/templates/skills/hai-storage/SKILL.md +17 -238
  128. package/templates/skills/hai-storage/reference.md +243 -0
  129. package/templates/skills/hai-ui/SKILL.md +1 -1
  130. package/templates/skills/hai-ui/reference.md +11 -5
  131. package/templates/skills/hai-vecdb/SKILL.md +2 -2
  132. package/dist/chunk-47V5OT22.js.map +0 -1
  133. package/dist/chunk-IPG3QNYK.js.map +0 -1
  134. package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
  135. package/dist/src-6PPBKQ6Q.js.map +0 -1
  136. package/templates/apps/api/Dockerfile.hbs +0 -48
  137. package/templates/apps/fullstack/Dockerfile.hbs +0 -69
  138. package/templates/base/Dockerfile.hbs +0 -79
  139. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  140. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  141. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
@@ -20,7 +20,6 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build": "tsup",
23
- "build:prod": "pnpm build",
24
23
  "check": "pnpm typecheck",
25
24
  "typecheck": "tsc --noEmit",
26
25
  "lint": "eslint .",
@@ -1,3 +1,10 @@
1
1
  node_modules
2
2
  .turbo
3
3
  src/lib/paraglide
4
+
5
+ # 构建器生成的安装输入快照
6
+ **/images/dependencies/
7
+
8
+ # 项目本地缓存
9
+ **/.pnpm-store/
10
+ **/.cache/
@@ -1,35 +1,48 @@
1
1
  # {{fullstack.sharedPackageName}}
2
2
 
3
+ ## Introduction
4
+
3
5
  Shared mini administration workspace for **{{projectName}}**. Web, Capacitor App and Tauri desktop use the same `AdminWorkspace`, responsive `AppShell`, typed API client and management store.
4
6
 
7
+ ## Core features
8
+
9
+ - Shared management pages, forms, navigation and application state.
10
+ - Typed encrypted API client and platform-specific token storage.
11
+ - Themes, language preferences, onboarding and help.
12
+
13
+ ## Getting started
14
+
15
+ ### Behavior and configuration
16
+
5
17
  The behavior below describes the initial example. Roles, permissions, workflows, limits, preferences and audit policies can change with the user's requirements. Update conflicting implementation, tests and guidance together; preserve the applicable public API contracts and server security checks.
6
18
 
7
19
  - Administrators manage users, upload user avatars, assign the built-in administrator/user roles, edit role descriptions and query audit logs.
8
20
  - By default, ordinary users update their own profile, avatar, language, appearance and password. The server enforces the actual business permissions on every request.
21
+ - The workspace uses the available window width. Tables scroll within their own area; settings panels form two columns on large screens and stack on phones.
9
22
  - `ManagementStore` owns API calls and business state; components render the state and forward user interactions.
10
23
  - Application name, version, environment and default locale come from the backend `app.info` contract, which reads `core` configuration. Do not duplicate these values in translations or frontend constants.
11
24
  - Requests, token refresh and JSON responses use the same `@h-ai/api-client` + `@h-ai/crypto` transport. Avatar uploads use encrypted JSON and validate PNG/JPEG/WebP content, with a 512 KiB limit.
12
25
  - Read metadata through the same initialized `managementApi.app.info()` client; inspect `success` before using `data`.
13
26
 
14
- All three Vite apps read the workspace root .env. Development and preview servers proxy /api to the local backend when no explicit API base is configured, so browser clients can use a single origin. In deployments, configure PUBLIC_API_BASE or the Web container runtime API origin for the actual service.
27
+ Vite development and preview servers read the workspace root environment files and proxy /api to the backend. Builds do not load deployment environment files. Web containers receive their public API address at startup; native apps follow the platform configuration below.
15
28
 
16
29
  An origin-only API base uses `/api/v1`. For a custom service `http.apiPrefix`, include that path in `PUBLIC_API_BASE` or the runtime API base; the client preserves the configured path and restricts avatar URLs to its matching resource prefix.
17
30
 
18
- ## Authentication and preferences
31
+ ### Authentication and preferences
19
32
 
20
33
  Web and browser previews keep access tokens in memory and refresh tokens in the server's httpOnly Cookie. Requests include credentials; reload restores the session through the encrypted refresh flow. Never store authentication tokens in localStorage.
21
34
 
22
- Capacitor native uses `createCapacitorTokenStorage()` and the trusted native token channel. Tauri native uses memory token storage and requires a new login after restarting the application. Native builds require a reachable `PUBLIC_API_BASE` in the workspace root `.env`; missing configuration produces a connection configuration error. Both send their frontend `clientName` and `X-HAI-Token-Transport: body`; the service verifies the allowed native origin before returning a refresh token in JSON.
35
+ Capacitor native uses `createCapacitorTokenStorage()` and the trusted native token channel. Set its public API address in the App's `public/runtime-config.js` before compiling and syncing native assets. Tauri uses memory token storage and requires a new login after restart; set process `PUBLIC_API_BASE` before launching the installed application, and its Rust host injects the address. Both send `clientName` and `X-HAI-Token-Transport: body`; the service verifies the trusted native origin before returning a refresh token in JSON. Missing API configuration is an error, not a successful connection.
23
36
 
24
37
  Language and theme preferences are saved in IAM user metadata, restored after login and synchronized with the shared/host Paraglide runtimes and `core.i18n`. The small reactive theme store delegates DOM updates and safe appearance caching to `@h-ai/ui/theme` (`applyTheme` / `getSavedTheme`); the framework uses the `theme` localStorage key. Appearance selectors live in personal settings and save through the authenticated profile contract.
25
38
 
26
- ## Onboarding and help
39
+ ### Onboarding and help
27
40
 
28
41
  AdminWorkspace mounts the shared `@h-ai/ui/guide` controller and GuideHost after authentication. AppShell includes a help button; first-use invitations offer start, later and dismiss. Web stores preferences in Cookies, while App/Desktop use localStorage, scoped by the current user and tour version. Help articles link to the overview and profile tours.
29
42
 
30
43
  Extend `src/lib/stores/guide-store.ts` and the shared Chinese/English messages, then add stable data-guide attributes to existing components. Navigation uses the same ManagementStore. Keep business text as i18n callbacks and preserve the host's locale/scope binding. Browser tests cover completion, reload, replay, settings navigation and mobile help.
31
44
 
32
- ## i18n and themes
45
+ ### i18n and themes
33
46
 
34
47
  Shared page copy belongs in `messages/{zh-CN,en-US}.json`. Frontend-specific copy belongs in each app's own messages directory. Keep `baseLocale` and `locales` aligned across all enabled projects, then run root `pnpm i18n:compile`; do not edit generated Paraglide files.
35
48
 
@@ -37,22 +50,30 @@ All frontend and shared projects load the installed message-format plugin from t
37
50
 
38
51
  The scaffold theme configuration determines the themes compiled into frontend CSS. Personal settings offer only those available themes. Customize the common layout in `src/lib/components/AppShell.svelte`, the management pages in adjacent components, and business behavior in `src/lib/stores/management-store.svelte.ts`.
39
52
 
40
- ## Validation
53
+ ### Validation
41
54
 
42
- Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation. Set `HAI_AUTH_EVIDENCE_DIR` to keep screenshots outside the test output directory. These browser checks do not replace native-shell or miniapp device acceptance.
55
+ Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation. Clean screenshots, reports, temporary projects, containers and helper processes in finally/teardown, including failure paths. These browser checks do not replace native-shell or miniapp device acceptance.
43
56
 
44
- ## IAM configuration
57
+ ### IAM configuration
45
58
 
46
- The service's `config/_iam.yml` controls login methods, registration, password reset, agreements and `ui` branding. Email and SMS use providers/templates in `_reach.yml` or custom callbacks. `AuthPanel` uses the public configuration and framework auth actions to manage tokens, resend countdowns and errors. All service nodes must share the same database and Redis; memory cache is for a single process.
59
+ The service's `config/_iam.yml` controls login methods, registration, password reset, agreements and `ui` branding. Email and SMS use providers/templates in `_reach.yml` or custom callbacks. `AuthPanel` uses the public configuration and framework auth actions to manage tokens, resend countdowns and errors. A single process can use memory cache. Multiple instances need shared database/cache, write routing and transport session affinity as described in the project README.
47
60
 
48
61
  Authentication defaults to a split layout. Set `ui.variant`, `title`, `subtitle`, `brandTitle`, `brandText`, `logoUrl` and `illustrationUrl` to customize login and registration. Without a `brandTitle`, the displayed application name comes from `app.info` and follows `core` configuration.
49
62
 
50
63
  LDAP and Reach are optional. Install ldapts in the IAM service workspace only when using its default LDAP client, then enable login.ldap and set connection settings. Custom onOtpSendEmail/onOtpSendSms/onPasswordResetRequest callbacks work without Reach and must reject on delivery failure. Agreements open in a Modal. LoginForm/RegisterForm support header/extraFields/footer snippets; AuthPanel forwards loginHeader/loginFields/loginFooter and registerHeader/registerFields/registerFooter. See the generated hai-iam/hai-ui skills for configuration and examples.
51
64
 
52
- 协议支持配置中的 userAgreementMarkdown/privacyPolicyMarkdown 正文及 URL;同主题优先 Markdown,由安全渲染器在弹窗显示。
65
+ Agreements accept Markdown text or a URL. Markdown takes precedence and opens in a safely rendered dialog.
53
66
 
54
67
  See `.agents/skills/hai-iam/reference.md` in the generated application for the complete contract.
55
68
 
56
- ## Form layouts
69
+ ### Form layouts
70
+
71
+ The user editor uses `FormLayout columns={2} labelPosition="left"`. Personal settings fill the available width and use two columns on wide screens, with stacked labels and a single avatar editor. Narrow containers automatically stack cards, fields and labels. UI controls and CRUD forms show client validation on blur, update errors while correcting input and validate untouched fields on submit. Field errors follow the current UI language. The management store syncs locale through the lightweight `@h-ai/ui/i18n` entry, without loading components or tour CSS. Configure `colSpan` on FormField to override automatic spans for textareas, uploads and multiple choices.
72
+
73
+ ## Development and testing
74
+
75
+ Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Browser checks cover shared workflows; test native shells on their target platforms separately.
76
+
77
+ ## License
57
78
 
58
- The user editor uses `FormLayout columns={2} labelPosition="left"`. Personal settings use a bounded page width, stacked labels and a single avatar editor. Narrow containers automatically stack fields and labels. UI controls and CRUD forms show client validation on blur, update errors while correcting input and validate untouched fields on submit. Field errors follow the current UI language. The management store syncs locale through the lightweight `@h-ai/ui/i18n` entry, without loading components or tour CSS. Configure `colSpan` on FormField to override automatic spans for textareas, uploads and multiple choices.
79
+ Apache-2.0
@@ -34,7 +34,7 @@
34
34
  {#if menuOpen}
35
35
  <BareButton class="fixed inset-0 z-30 bg-black/30 lg:hidden" ariaLabel={m.close_menu()} onclick={() => { menuOpen = false }} />
36
36
  {/if}
37
- <aside class="fixed inset-y-0 left-0 z-40 flex w-60 flex-col border-r border-base-content/8 bg-base-100 transition-transform duration-200 lg:sticky lg:top-0 lg:h-dvh lg:translate-x-0 {menuOpen ? 'translate-x-0' : '-translate-x-full'}">
37
+ <aside class="fixed inset-y-0 left-0 z-40 flex w-60 shrink-0 flex-col border-r border-base-content/8 bg-base-100 transition-transform duration-200 lg:sticky lg:top-0 lg:h-dvh lg:translate-x-0 {menuOpen ? 'translate-x-0' : '-translate-x-full'}">
38
38
  <div class="flex h-16 items-center gap-3 border-b border-base-content/8 px-5">
39
39
  <span class="grid size-8 shrink-0 place-items-center rounded-lg bg-primary text-primary-content"><span class="icon-[tabler--hexagon-letter-h] size-5"></span></span>
40
40
  <span class="truncate text-sm font-semibold" data-testid="app-name">{store.info?.name}</span>
@@ -66,7 +66,7 @@
66
66
  <Button variant="ghost" size="sm" loading={store.saving} onclick={() => void store.logout()} data-testid="logout">{m.logout()}</Button>
67
67
  </div>
68
68
  </header>
69
- <main class="mx-auto w-full max-w-7xl flex-1 p-4 pb-8 sm:p-6 lg:p-8" data-guide="admin-content">{@render children()}</main>
69
+ <main class="w-full min-w-0 flex-1 p-4 pb-8 sm:p-6 lg:p-8" data-guide="admin-content">{@render children()}</main>
70
70
  <footer class="border-t border-base-content/8 px-6 py-3 text-center text-xs text-base-content/35">{store.info?.name} · v{store.info?.version}</footer>
71
71
  </div>
72
72
  </div>
@@ -14,13 +14,13 @@
14
14
  const themeOptions = AVAILABLE_THEMES.map(theme => ({ value: theme, label: getThemeInfo(theme)?.name ?? theme }))
15
15
  </script>
16
16
 
17
- <section class='mx-auto w-full max-w-4xl space-y-6' data-testid='settings-page'>
18
- <div>
17
+ <section class='grid w-full min-w-0 gap-6 xl:grid-cols-2' data-testid='settings-page'>
18
+ <div class='xl:col-span-2'>
19
19
  <h1 class='text-xl font-semibold tracking-tight'>{m.settings_title()}</h1>
20
20
  <p class='mt-1 text-sm text-base-content/50'>{m.settings_description()}</p>
21
21
  </div>
22
22
  {#if store.user}
23
- <Card title={m.profile_title()} bordered shadow={false} data-guide='admin-profile-form'>
23
+ <Card class='xl:row-span-2' title={m.profile_title()} bordered shadow={false} data-guide='admin-profile-form'>
24
24
  <div class='mb-5 flex flex-wrap items-center gap-3 border-b border-base-content/8 pb-4'>
25
25
  <p class='min-w-0 break-all text-sm text-base-content/55'>@{store.user.username}</p>
26
26
  <Badge size='sm' variant='ghost'>{store.isAdmin ? m.role_admin() : m.role_user()}</Badge>
@@ -75,11 +75,11 @@ describe('shared API transport', () => {
75
75
  expect(server.requests.at(-1)?.headers.get('x-request-client')).toBe('configured-client')
76
76
  })
77
77
 
78
- it('uses the API origin injected at container startup', async () => {
78
+ it.each([false, true])('uses the injected API origin when native configuration is required: %s', async (requireApiBase) => {
79
79
  Object.assign(globalThis, { __HAI_PUBLIC_CONFIG__: { apiBase: 'https://runtime.example.com' } })
80
80
  const server = createServerFetch()
81
81
  try {
82
- expect((await initializeManagementApi({ fetchFn: server.fetchFn })).success).toBe(true)
82
+ expect((await initializeManagementApi({ fetchFn: server.fetchFn, requireApiBase })).success).toBe(true)
83
83
  expect((await managementApi.app.info()).success).toBe(true)
84
84
  expect(server.requests.every(request => request.url.startsWith('https://runtime.example.com/api/v1/'))).toBe(true)
85
85
  }
@@ -61,14 +61,14 @@
61
61
  </svelte:head>
62
62
 
63
63
  <div class='hai-mobile-viewport'>
64
- <div class='hai-mobile-shell max-w-lg'>
64
+ <div class='hai-mobile-shell min-w-0'>
65
65
  <AppBar title={page.data.appConfig.name} safeArea fixed={false} />
66
66
  <main class='hai-mobile-main'>{@render children()}</main>
67
67
  <footer class='border-t border-base-300 bg-base-100 p-2 text-center text-xs text-base-content/60'>
68
68
  {page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}
69
69
  </footer>
70
70
  {#if !isAuthPage}
71
- <BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='lg' safeArea />
71
+ <BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='none' safeArea />
72
72
  {/if}
73
73
  </div>
74
74
  </div>
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
6
  <meta name="theme-color" content="#2563eb" />
7
7
  <title></title>
8
+ <script src="/runtime-config.js"></script>
8
9
  </head>
9
10
  <body>
10
11
  <div id="app"></div>
@@ -0,0 +1 @@
1
+ globalThis.__HAI_PUBLIC_CONFIG__ ??= {}
@@ -100,7 +100,7 @@
100
100
  </script>
101
101
 
102
102
  <div class='hai-mobile-viewport'>
103
- <div class='hai-mobile-shell max-w-lg'>
103
+ <div class='hai-mobile-shell min-w-0'>
104
104
  <AppBar title={appConfig.name} safeArea fixed={false} class='bg-base-100/95 backdrop-blur border-base-content/8' />
105
105
  <main class='hai-mobile-main'>
106
106
  {#if booting}
@@ -129,7 +129,7 @@
129
129
  {{#if features.iam}}
130
130
  {#if authenticated && !booting && !bootError}
131
131
  {{/if}}
132
- <BottomNav items={navItems} active={router.path.slice(1)} onchange={handleTabChange} safeArea centered maxWidth='lg' />
132
+ <BottomNav items={navItems} active={router.path.slice(1)} onchange={handleTabChange} safeArea centered maxWidth='none' />
133
133
  {{#if features.iam}}
134
134
  {/if}
135
135
  {{/if}}
@@ -10,7 +10,8 @@ const DEFAULT_API_BASE = 'http://localhost:3000/api/v1'
10
10
  let initialized = false
11
11
 
12
12
  export function resolveApiBase(): string {
13
- return import.meta.env.PUBLIC_API_BASE ?? DEFAULT_API_BASE
13
+ const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { apiBase?: string } }).__HAI_PUBLIC_CONFIG__
14
+ return runtime?.apiBase || import.meta.env.PUBLIC_API_BASE || DEFAULT_API_BASE
14
15
  }
15
16
 
16
17
  export async function usesNativeTokenStorage(): Promise<boolean> {
@@ -5,8 +5,9 @@ declare const __APP_CORE_CONFIG__: Pick<CoreConfig, 'name' | 'version' | 'env' |
5
5
  logging?: Pick<NonNullable<CoreConfig['logging']>, 'level' | 'format'>
6
6
  }
7
7
 
8
- /** Vite 经 core 加载后注入的公开配置,修改 YAML 后重新构建客户端。 */
9
- export const appConfig = __APP_CORE_CONFIG__
8
+ /** 构建包含公开默认值;预览或容器启动时的公开配置覆盖默认值,不重建静态资源。 */
9
+ const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { core?: Partial<typeof __APP_CORE_CONFIG__> } }).__HAI_PUBLIC_CONFIG__?.core
10
+ export const appConfig = { ...__APP_CORE_CONFIG__, ...runtime, logging: { ...__APP_CORE_CONFIG__.logging, ...runtime?.logging } }
10
11
 
11
12
  export function initClientCore(): void {
12
13
  core.init({ logging: appConfig.logging })
@@ -12,8 +12,8 @@
12
12
  const { nativeRuntime, nativeTokenStorage }: Props = $props()
13
13
  </script>
14
14
 
15
- <section class='space-y-4 p-4 pb-6'>
16
- <div class='bg-primary text-primary-content rounded-2xl p-5 shadow-sm'>
15
+ <section class='grid gap-4 p-4 pb-6 lg:grid-cols-2'>
16
+ <div class='lg:col-span-2 bg-primary text-primary-content rounded-2xl p-5 shadow-sm'>
17
17
  <p class='text-sm opacity-75'>{m.hero_eyebrow()}</p>
18
18
  <h1 class='mt-1 text-2xl font-bold'>{m.hero_title()}</h1>
19
19
  <p class='mt-2 text-sm opacity-80'>{m.hero_subtitle()}</p>
@@ -49,7 +49,7 @@
49
49
  </svelte:head>
50
50
 
51
51
  <div class='min-h-screen flex flex-col'>
52
- <header class='navbar bg-base-100 shadow-sm'>
52
+ <header class='navbar flex-wrap bg-base-100 shadow-sm'>
53
53
  <div class='flex-1'>
54
54
  {{#if hasI18n}}
55
55
  <a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
@@ -57,8 +57,8 @@
57
57
  <a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
58
58
  {{/if}}
59
59
  </div>
60
- <nav class='flex-none'>
61
- <ul class='menu menu-horizontal px-1'>
60
+ <nav class='w-full flex-none sm:w-auto'>
61
+ <ul class='menu menu-horizontal flex-wrap px-1'>
62
62
  {#each navItems as item (item.path)}
63
63
  <li><a href={resolve(item.path, {})}>{item.label}</a></li>
64
64
  {/each}
@@ -31,7 +31,7 @@
31
31
  {{/if}}
32
32
  </svelte:head>
33
33
 
34
- <section class='py-16 px-4 max-w-4xl mx-auto'>
34
+ <section class='mx-auto w-full max-w-[120rem] px-4 py-16 sm:px-6 lg:px-8'>
35
35
  {{#if hasI18n}}
36
36
  <PageHeader title={m.page_services_title()} description={m.page_services_desc()} />
37
37
  {{else}}
@@ -1,22 +1,27 @@
1
- .git
1
+ **/.git
2
+ **/node_modules
3
+ **/.pnpm-store
4
+ **/.cache
5
+ **/.svelte-kit
6
+ **/.turbo
7
+ **/build
8
+ **/dist
9
+ **/coverage
10
+ **/playwright-report
11
+ **/test-results
12
+ **/src-tauri/target
13
+ **/hash-core/target
14
+ tmp
15
+ **/*.tar
2
16
  .github
3
17
  .agents
4
18
  .claude
5
19
  .codex
6
20
  .opencode
7
- .svelte-kit
8
- .turbo
9
21
  android
10
- build
11
- dist
12
22
  ios
13
- node_modules
14
- playwright-report
15
- test-results
16
23
  **/data
17
24
  *.log
18
- # 仅排除本机私有配置,与 Git 处理一致;公共 .env 与 dev/test/prod 随上下文构建。
19
- **/.env.local
20
- **/.env.*.local
21
- **/.env.runtime
22
- **/.hai/deploy
25
+ # 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
26
+ **/.env.*
27
+ **/.hai
@@ -25,9 +25,17 @@ playwright-report/
25
25
  # 本地数据目录
26
26
  **/data/
27
27
 
28
- # hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
28
+ # 构建与部署产物(镜像 / Compose tar / 私有运行配置)
29
29
  .hai/
30
+ *.tar
30
31
 
31
32
  # 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
32
33
  .env.local
33
34
  .env.*.local
35
+
36
+ # 构建器生成的安装输入快照
37
+ **/images/dependencies/
38
+
39
+ # 项目本地缓存
40
+ **/.pnpm-store/
41
+ **/.cache/
@@ -23,6 +23,16 @@ test('home page renders from browser', async ({ page }) => {
23
23
  await expect(page.locator('main, section').first()).toBeVisible()
24
24
  await expect(page.locator('header').first()).toBeVisible()
25
25
  await expect(page.locator('footer').first()).toBeAttached()
26
+ // 大屏使用可用空间,窄屏由组件内部滚动,不能撑宽整个页面。
27
+ for (const width of [390, 1440, 1920, 2560, 3840]) {
28
+ await page.setViewportSize({ width, height: 960 })
29
+ const availableWidth = await page.evaluate(() => document.documentElement.clientWidth)
30
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
31
+ const shell = page.locator('.hai-mobile-shell')
32
+ if (await shell.count())
33
+ expect((await shell.boundingBox())!.width).toBeGreaterThan(availableWidth - 2)
34
+ }
35
+ await page.setViewportSize({ width: 390, height: 844 })
26
36
  {{#if_eq appType 'h5'}}
27
37
  // 在手机宽度验证导航真正切换路由,避免样式失效但首页渲染测试仍通过。
28
38
  const nav = page.locator('nav')
@@ -0,0 +1 @@
1
+ { "images": [{ "name": "app", "dockerfile": "images/app.Dockerfile", "containerPort": {{#if isSvelteOnlyApp}}8080{{else}}3000{{/if}} }] }
@@ -0,0 +1,64 @@
1
+ {{#if isSvelteOnlyApp}}
2
+ FROM node:24.22.1-bookworm-slim AS build
3
+
4
+ ENV PNPM_HOME=/pnpm
5
+ ENV PATH=$PNPM_HOME:$PATH
6
+ ENV CI=true
7
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
8
+ WORKDIR /workspace
9
+
10
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
11
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
12
+ COPY images/dependencies/ ./
13
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
14
+ COPY . .
15
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
16
+ RUN pnpm compile
17
+
18
+ FROM nginxinc/nginx-unprivileged:1.29.8-alpine AS runtime
19
+
20
+ COPY --chmod=755 images/web/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
21
+ COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
22
+ COPY --from=build /workspace/dist /usr/share/nginx/html
23
+
24
+ EXPOSE 8080
25
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
26
+ CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
27
+ {{else}}
28
+ FROM node:24.22.1-bookworm-slim AS build
29
+ ARG SQLITE_PREBUILD_MIRROR=
30
+ ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
31
+
32
+ ENV PNPM_HOME=/pnpm
33
+ ENV PATH=$PNPM_HOME:$PATH
34
+ ENV CI=true
35
+ ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
36
+ WORKDIR /workspace
37
+
38
+ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
39
+ COPY pnpm-lock.yaml pnpm-workspace.yaml ./
40
+ COPY images/dependencies/ ./
41
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
42
+ COPY . .
43
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
44
+ RUN pnpm compile
45
+ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
46
+ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{projectName}} --prod /prod/app
47
+
48
+ FROM node:24.22.1-bookworm-slim AS runtime-base
49
+
50
+ ENV NODE_ENV=production
51
+ ENV HOST=0.0.0.0
52
+ ENV PORT=3000
53
+ WORKDIR /app
54
+
55
+ COPY --from=build --chown=node:node /prod/app ./
56
+ RUN mkdir -p /app/data && chown node:node /app/data
57
+
58
+ USER node
59
+ EXPOSE 3000
60
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
61
+ CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
62
+ CMD ["node", "build"]
63
+
64
+ {{/if}}
@@ -9,6 +9,12 @@ server {
9
9
  try_files $uri $uri/ /index.html;
10
10
  }
11
11
 
12
+ location = /runtime-config.js {
13
+ alias /tmp/hai-runtime-config/runtime-config.js;
14
+ add_header Cache-Control "no-store";
15
+ default_type application/javascript;
16
+ }
17
+
12
18
  location = /health {
13
19
  access_log off;
14
20
  add_header Content-Type text/plain;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
3
3
  "name": "{{projectName}}",
4
- "version": "0.1.0-alpha.65",
4
+ "version": "0.1.0-alpha.67",
5
5
  "private": true,
6
6
  "packageManager": "pnpm@12.11.2",
7
7
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
@@ -23,8 +23,11 @@
23
23
  "dev": "vite dev",
24
24
  "dev:test": "vite dev --mode test",
25
25
  "dev:prod": "vite dev --mode prod",
26
- "build": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
27
- "build:prod": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build --mode prod",
26
+ "compile": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
27
+ "build": "hai build",
28
+ {{#if features.deploy}}
29
+ "deploy": "hai deploy",
30
+ {{/if}}
28
31
  {{#unless isSvelteOnlyApp}}
29
32
  "start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js",
30
33
  "start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local build/index.js",
@@ -37,26 +40,20 @@
37
40
  "lint:fix": "eslint . --fix",
38
41
  "test": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vitest run",
39
42
  "test:watch": "vitest",
40
- "test:e2e": "playwright test",
41
- "clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}",
42
- "docker:build": "docker build --target runtime -t {{projectName}}:latest .",
43
- {{#unless isSvelteOnlyApp}}
44
- "docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest .",
45
- {{/unless}}
46
- "podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest ."{{#unless isSvelteOnlyApp}},
47
- "podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest ."{{/unless}}{{#if isCapacitorApp}},
43
+ "test:e2e": "node scripts/run-e2e.mjs",
44
+ "clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}"{{#if isCapacitorApp}},
48
45
  "cap:add:android": "cap add android",
49
46
  "cap:add:ios": "cap add ios",
50
- "cap:sync": "pnpm build && cap sync",
51
- "cap:sync:android": "pnpm build && cap sync android",
52
- "cap:sync:ios": "pnpm build && cap sync ios",
47
+ "cap:sync": "pnpm compile && cap sync",
48
+ "cap:sync:android": "pnpm compile && cap sync android",
49
+ "cap:sync:ios": "pnpm compile && cap sync ios",
53
50
  "cap:android": "cap open android",
54
51
  "cap:ios": "cap open ios",
55
52
  "cap:run:android": "cap run android",
56
53
  "cap:run:ios": "cap run ios",
57
- "cap:build:android:debug": "pnpm build && cap sync android && cd android && gradlew.bat assembleDebug",
58
- "cap:build:android:release": "pnpm build && cap sync android && cap build android --androidreleasetype APK",
59
- "cap:build:ios": "pnpm build && cap sync ios && cap build ios"{{/if}}
54
+ "cap:build:android:debug": "pnpm compile && cap sync android && cd android && gradlew.bat assembleDebug",
55
+ "cap:build:android:release": "pnpm compile && cap sync android && cap build android --androidreleasetype APK",
56
+ "cap:build:ios": "pnpm compile && cap sync ios && cap build ios"{{/if}}
60
57
  },
61
58
  "dependencies": {
62
59
  {{#if isSvelteOnlyApp}}
@@ -78,16 +75,20 @@
78
75
  "@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
79
76
  "@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
80
77
  "@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
81
- "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
82
- "@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
78
+ "@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.api-client}},
83
79
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/if}}{{#if features.capacitor}},
84
80
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/if}}{{#if isCapacitorApp}}{{#unless features.api-client}},
85
81
  "@h-ai/api-client": "{{haiPackageSpecifier}}"{{/unless}}{{#unless features.capacitor}},
86
82
  "@h-ai/capacitor": "{{haiPackageSpecifier}}"{{/unless}}{{/if}},
83
+ "sm-crypto": "0.5.7",
87
84
  "zod": "{{versions.zod}}"
88
85
  {{/if}}
89
86
  },
90
87
  "devDependencies": {
88
+ {{#if features.deploy}}
89
+ "@h-ai/deploy": "{{haiPackageSpecifier}}",
90
+ {{/if}}
91
+ "@h-ai/cli": "{{haiPackageSpecifier}}",
91
92
  {{#if isSvelteOnlyApp}}
92
93
  "@h-ai/core": "{{haiPackageSpecifier}}",
93
94
  {{/if}}
@@ -56,6 +56,8 @@ const config = {
56
56
  runes: true,
57
57
  },
58
58
  kit: {
59
+ // 环境文件由 Vite 的 core loader 统一加载;Kit 不再二次读取根目录私有配置。
60
+ env: { dir: './.svelte-kit' },
59
61
  {{#if isCapacitorApp}}
60
62
  adapter: adapter({
61
63
  pages: 'build',
@@ -22,9 +22,12 @@ import { sveltekit } from '@sveltejs/kit/vite'
22
22
  import tailwindcss from '@tailwindcss/vite'
23
23
  {{/if}}
24
24
  import { defineConfig } from 'vite'
25
+ {{#if isSvelteOnlyApp}}
26
+ import { publicRuntimeConfig } from './scripts/vite-runtime-config.js'
27
+ {{/if}}
25
28
 
26
- export default defineConfig(({ mode }) => {
27
- const envResult = core.config.loadEnvFiles({ mode, envDir: process.cwd() })
29
+ export default defineConfig(({ mode, command }) => {
30
+ const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir: process.cwd() })
28
31
  if (!envResult.success)
29
32
  throw new Error(envResult.error.message)
30
33
  const env = envResult.data
@@ -50,6 +53,7 @@ export default defineConfig(({ mode }) => {
50
53
  {{/if}}
51
54
  plugins: [
52
55
  {{#if isSvelteOnlyApp}}
56
+ publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, core: appConfig }),
53
57
  svelte(),
54
58
  {{else}}
55
59
  sveltekit(),
@@ -76,7 +80,9 @@ export default defineConfig(({ mode }) => {
76
80
  {{/if}}
77
81
  {{#unless isSvelteOnlyApp}}
78
82
  ssr: {
79
- noExternal: [/@h-ai\//],
83
+ noExternal: ['@h-ai/ui', '@h-ai/kit'],
84
+ // sm-crypto 同时列入生产依赖,避免 adapter-node 再次打包其动态 require。
85
+ external: ['@h-ai/core', 'sm-crypto'],
80
86
  },
81
87
  {{/unless}}
82
88
  server: {
@@ -0,0 +1,11 @@
1
+ # Windows 构建机交付 Linux 启动脚本时必须保留 LF,避免 /bin/sh 解释器失效。
2
+ *.sh text eol=lf
3
+ *.sh.hbs text eol=lf
4
+ *.envsh text eol=lf
5
+ *.envsh.hbs text eol=lf
6
+
7
+ # Linux 容器引擎读取忽略规则和构建文件时统一使用 LF。
8
+ .dockerignore text eol=lf
9
+ *.dockerignore text eol=lf
10
+ Dockerfile text eol=lf
11
+ *.Dockerfile text eol=lf
@@ -0,0 +1,30 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # 只编码公开白名单;Base64 不会把环境值中的引号或换行解释为 JavaScript。
5
+ encode() { printf '%s' "$1" | base64 | tr -d '\n'; }
6
+ mkdir -p /tmp/hai-runtime-config
7
+ cat > /tmp/hai-runtime-config/runtime-config.js <<EOF
8
+ {
9
+ const decode = value => new TextDecoder().decode(Uint8Array.from(atob(value), character => character.charCodeAt(0)));
10
+ const nonempty = object => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ''));
11
+ const core = nonempty({
12
+ name: decode('$(encode "${HAI_CORE_NAME:-}")'),
13
+ version: decode('$(encode "${HAI_CORE_VERSION:-}")'),
14
+ env: decode('$(encode "${HAI_CORE_ENV:-}")'),
15
+ defaultLocale: decode('$(encode "${HAI_CORE_DEFAULTLOCALE:-}")')
16
+ });
17
+ const logging = nonempty({
18
+ level: decode('$(encode "${HAI_CORE_LOGGING_LEVEL:-}")'),
19
+ format: decode('$(encode "${HAI_CORE_LOGGING_FORMAT:-}")')
20
+ });
21
+ if (Object.keys(logging).length) core.logging = logging;
22
+ const config = nonempty({
23
+ apiBase: decode('$(encode "${PUBLIC_API_BASE:-}")'),
24
+ apiTransport: decode('$(encode "${PUBLIC_API_TRANSPORT:-}")'),
25
+ apiKeyExchangePath: decode('$(encode "${PUBLIC_API_KEY_EXCHANGE_PATH:-}")')
26
+ });
27
+ if (Object.keys(core).length) config.core = core;
28
+ globalThis.__HAI_PUBLIC_CONFIG__ = { ...globalThis.__HAI_PUBLIC_CONFIG__, ...config };
29
+ }
30
+ EOF