@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.
- package/README.en.md +66 -251
- package/README.ja.md +67 -252
- package/README.md +66 -251
- package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
- package/dist/chunk-7DSURTJ4.js.map +1 -0
- package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
- package/dist/chunk-AOQ4N657.js.map +1 -0
- package/dist/cli-config-templates-6AHNUGBN.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
- package/dist/index.js +644 -391
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
- package/dist/src-2V6VP4I5.js.map +1 -0
- package/package.json +8 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
- package/templates/apps/api/.dockerignore.hbs +18 -12
- package/templates/apps/api/.gitignore.hbs +9 -1
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +10 -8
- package/templates/apps/fullstack/.dockerignore.hbs +18 -14
- package/templates/apps/fullstack/.gitignore.hbs +9 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +7 -12
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +18 -13
- package/templates/base/.gitignore.hbs +9 -1
- package/templates/base/e2e/app.spec.ts.hbs +10 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/images/app.Dockerfile.hbs +64 -0
- package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
- package/templates/base/package.json.hbs +20 -19
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +9 -3
- package/templates/shared/.gitattributes +11 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/AGENTS.md +3 -3
- package/templates/skills/bridges/admin/AGENTS.md +1 -1
- package/templates/skills/bridges/api/AGENTS.md +1 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
- package/templates/skills/bridges/h5/AGENTS.md +1 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
- package/templates/skills/bridges/website/AGENTS.md +1 -1
- package/templates/skills/hai-api-client/SKILL.md +14 -161
- package/templates/skills/hai-api-client/reference.md +166 -0
- package/templates/skills/hai-api-contract/SKILL.md +15 -273
- package/templates/skills/hai-api-contract/reference.md +278 -0
- package/templates/skills/hai-app-create/SKILL.md +2 -2
- package/templates/skills/hai-app-create/development.md +6 -4
- package/templates/skills/hai-app-create/reference.md +5 -4
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-app-tests/SKILL.md +4 -3
- package/templates/skills/hai-app-tests/reference.md +2 -2
- package/templates/skills/hai-app-tests/validation.md +31 -0
- package/templates/skills/hai-audit/SKILL.md +17 -142
- package/templates/skills/hai-audit/reference.md +145 -0
- package/templates/skills/hai-build/SKILL.md +24 -3
- package/templates/skills/hai-build/reference.md +10 -9
- package/templates/skills/hai-cache/SKILL.md +15 -182
- package/templates/skills/hai-cache/reference.md +187 -0
- package/templates/skills/hai-capacitor/SKILL.md +4 -3
- package/templates/skills/hai-capacitor/reference.md +6 -4
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +3 -3
- package/templates/skills/hai-crypto/SKILL.md +16 -252
- package/templates/skills/hai-crypto/reference.md +256 -0
- package/templates/skills/hai-datapipe/SKILL.md +15 -196
- package/templates/skills/hai-datapipe/reference.md +202 -0
- package/templates/skills/hai-deploy/SKILL.md +28 -271
- package/templates/skills/hai-deploy/middleware.md +6 -18
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +1 -3
- package/templates/skills/hai-kit/SKILL.md +12 -167
- package/templates/skills/hai-kit/reference.md +172 -0
- package/templates/skills/hai-payment/SKILL.md +19 -251
- package/templates/skills/hai-payment/reference.md +256 -0
- package/templates/skills/hai-pr-review/SKILL.md +4 -4
- package/templates/skills/hai-reach/SKILL.md +2 -2
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-scheduler/SKILL.md +15 -195
- package/templates/skills/hai-scheduler/reference.md +202 -0
- package/templates/skills/hai-serv/SKILL.md +2 -2
- package/templates/skills/hai-spec-implement/SKILL.md +1 -1
- package/templates/skills/hai-spec-implement/workflow.md +3 -3
- package/templates/skills/hai-storage/SKILL.md +17 -238
- package/templates/skills/hai-storage/reference.md +243 -0
- package/templates/skills/hai-ui/SKILL.md +1 -1
- package/templates/skills/hai-ui/reference.md +11 -5
- package/templates/skills/hai-vecdb/SKILL.md +2 -2
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -48
- package/templates/apps/fullstack/Dockerfile.hbs +0 -69
- package/templates/base/Dockerfile.hbs +0 -79
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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>
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs
CHANGED
|
@@ -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='
|
|
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>
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs
CHANGED
|
@@ -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
|
|
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
|
|
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='
|
|
71
|
+
<BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='none' safeArea />
|
|
72
72
|
{/if}
|
|
73
73
|
</div>
|
|
74
74
|
</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
|
|
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='
|
|
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
|
-
|
|
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
|
-
/**
|
|
9
|
-
|
|
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='
|
|
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='
|
|
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
|
-
|
|
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
|
-
#
|
|
19
|
-
**/.env
|
|
20
|
-
**/.
|
|
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
|
-
#
|
|
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.
|
|
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
|
-
"
|
|
27
|
-
"build
|
|
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": "
|
|
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
|
|
51
|
-
"cap:sync:android": "pnpm
|
|
52
|
-
"cap:sync:ios": "pnpm
|
|
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
|
|
58
|
-
"cap:build:android:release": "pnpm
|
|
59
|
-
"cap:build:ios": "pnpm
|
|
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.
|
|
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}}
|
|
@@ -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: [
|
|
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
|