@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +86 -241
- package/README.ja.md +86 -241
- package/README.md +86 -241
- package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
- package/dist/chunk-FI56GONY.js.map +1 -0
- package/dist/chunk-MBPB37FR.js +1348 -0
- package/dist/chunk-MBPB37FR.js.map +1 -0
- package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +1304 -532
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/src-DSJJXM2F.js +3849 -0
- package/dist/src-DSJJXM2F.js.map +1 -0
- package/package.json +9 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/messages/ja-JP.json +38 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
- package/templates/apps/api/.dockerignore.hbs +14 -7
- package/templates/apps/api/.gitignore.hbs +7 -0
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
- package/templates/apps/api/hai.build.json.hbs +1 -1
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +6 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/.dockerignore.hbs +14 -9
- package/templates/apps/fullstack/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
- package/templates/apps/fullstack/hai.build.json.hbs +2 -2
- 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 +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
- package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
- package/templates/apps/h5/messages/ja-JP.json +40 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +14 -8
- package/templates/base/.gitignore.hbs +7 -0
- package/templates/base/e2e/app.spec.ts.hbs +23 -8
- package/templates/base/hai.build.json.hbs +1 -1
- package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
- package/templates/base/package.json.hbs +11 -3
- package/templates/base/playwright.config.ts +15 -4
- package/templates/base/src/hooks.server.ts.hbs +15 -6
- package/templates/base/src/lib/server/init.ts.hbs +139 -30
- package/templates/base/vite.config.ts.hbs +5 -3
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
- package/templates/i18n/project.inlang/settings.json +1 -1
- package/templates/shared/.gitattributes +6 -0
- package/templates/shared/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +7 -3
- package/templates/skills/bridges/admin/AGENTS.md +6 -2
- package/templates/skills/bridges/api/AGENTS.md +3 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
- package/templates/skills/bridges/h5/AGENTS.md +6 -2
- package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
- package/templates/skills/bridges/website/AGENTS.md +6 -2
- package/templates/skills/hai-ai/SKILL.md +8 -2
- package/templates/skills/hai-ai/guide.md +134 -60
- package/templates/skills/hai-ai/reference.md +51 -11
- package/templates/skills/hai-api-client/SKILL.md +15 -160
- package/templates/skills/hai-api-client/reference.md +178 -0
- package/templates/skills/hai-api-contract/SKILL.md +16 -273
- package/templates/skills/hai-api-contract/reference.md +280 -0
- package/templates/skills/hai-app-create/SKILL.md +6 -2
- package/templates/skills/hai-app-create/development.md +24 -8
- package/templates/skills/hai-app-create/reference.md +7 -5
- package/templates/skills/hai-app-review/SKILL.md +7 -3
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +6 -3
- package/templates/skills/hai-app-tests/reference.md +5 -5
- package/templates/skills/hai-app-tests/validation.md +38 -0
- package/templates/skills/hai-audit/SKILL.md +24 -147
- package/templates/skills/hai-audit/reference.md +148 -0
- package/templates/skills/hai-build/SKILL.md +14 -8
- package/templates/skills/hai-build/reference.md +16 -13
- package/templates/skills/hai-cache/SKILL.md +20 -181
- package/templates/skills/hai-cache/reference.md +229 -0
- package/templates/skills/hai-capacitor/SKILL.md +7 -4
- package/templates/skills/hai-capacitor/reference.md +15 -9
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +7 -3
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +22 -250
- package/templates/skills/hai-crypto/reference.md +310 -0
- package/templates/skills/hai-datapipe/SKILL.md +16 -195
- package/templates/skills/hai-datapipe/reference.md +204 -0
- package/templates/skills/hai-deploy/SKILL.md +24 -54
- package/templates/skills/hai-deploy/middleware.md +51 -43
- 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 +25 -11
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +35 -174
- package/templates/skills/hai-kit/reference.md +190 -0
- package/templates/skills/hai-payment/SKILL.md +33 -263
- package/templates/skills/hai-payment/reference.md +295 -0
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +6 -3
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +6 -3
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +18 -195
- package/templates/skills/hai-scheduler/reference.md +216 -0
- package/templates/skills/hai-serv/SKILL.md +25 -10
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +5 -1
- package/templates/skills/hai-spec-implement/evaluation.md +18 -0
- package/templates/skills/hai-spec-implement/handoff.md +47 -0
- package/templates/skills/hai-spec-implement/templates.md +5 -0
- package/templates/skills/hai-spec-implement/workflow.md +5 -3
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +23 -238
- package/templates/skills/hai-storage/reference.md +282 -0
- package/templates/skills/hai-ui/SKILL.md +17 -3
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +26 -11
- package/templates/skills/hai-ui-design/SKILL.md +31 -0
- package/templates/skills/hai-ui-design/patterns.md +26 -0
- package/templates/skills/hai-ui-review/SKILL.md +30 -0
- package/templates/skills/hai-ui-review/checklist.md +22 -0
- package/templates/skills/hai-vecdb/SKILL.md +14 -10
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-FWN5B54L.js +0 -468
- package/dist/chunk-FWN5B54L.js.map +0 -1
- package/dist/chunk-QYX6V7H2.js.map +0 -1
- package/dist/cli-config-templates-3XSFIXBP.js +0 -5
- package/dist/src-RDQ2EIBT.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -35
- package/templates/apps/fullstack/Dockerfile.hbs +0 -57
- /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
- /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { EncryptedPayload } from '@h-ai/crypto'
|
|
2
1
|
import { HaiApiClientError } from '@h-ai/api-client'
|
|
3
|
-
import { crypto, TRANSPORT_PROTOCOL } from '@h-ai/crypto'
|
|
2
|
+
import { authenticateTransportRequest, createInMemoryKeyStore, crypto, TRANSPORT_PROTOCOL } from '@h-ai/crypto'
|
|
4
3
|
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
|
5
4
|
import { initializeManagementApi, managementApi, resolveAvatarUrl } from '../src/lib/api/api-client.js'
|
|
6
5
|
|
|
@@ -16,7 +15,10 @@ const runtimeInfo = {
|
|
|
16
15
|
|
|
17
16
|
/** 使用真实 crypto 公共入口模拟对端,覆盖握手及双向加解密。 */
|
|
18
17
|
function createServerFetch() {
|
|
19
|
-
const
|
|
18
|
+
const key = crypto.asymmetric.generateKeyPair()
|
|
19
|
+
if (!key.success)
|
|
20
|
+
throw new Error(key.error.message)
|
|
21
|
+
const created = crypto.transport.createServer({ serverKeyRing: { activeKeyId: 'test-v1', keys: { 'test-v1': key.data } }, keyStore: createInMemoryKeyStore() })
|
|
20
22
|
if (!created.success)
|
|
21
23
|
throw new Error(created.error.message)
|
|
22
24
|
const server = created.data
|
|
@@ -29,25 +31,22 @@ function createServerFetch() {
|
|
|
29
31
|
if (url.pathname.endsWith('/_hai/key-exchange')) {
|
|
30
32
|
const body = await request.json() as { clientPublicKey: string }
|
|
31
33
|
const clientId = await server.registerClientKey(body.clientPublicKey)
|
|
32
|
-
return Response.json({ serverPublicKey: server.getServerPublicKey(), clientId })
|
|
34
|
+
return Response.json({ serverKeyId: server.getServerKeyId(), serverPublicKey: server.getServerPublicKey(), clientId })
|
|
33
35
|
}
|
|
34
36
|
const clientId = request.headers.get(TRANSPORT_PROTOCOL.CLIENT_ID_HEADER)
|
|
35
37
|
if (!clientId)
|
|
36
38
|
return Response.json({ success: false }, { status: 400 })
|
|
39
|
+
const verified = await authenticateTransportRequest(server, request)
|
|
40
|
+
if (!verified.success)
|
|
41
|
+
throw new Error(verified.error.message)
|
|
37
42
|
let data: unknown = runtimeInfo
|
|
38
43
|
if (url.pathname.endsWith('/app/echo')) {
|
|
39
44
|
expect(request.headers.get(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER)).toBe(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)
|
|
40
|
-
const
|
|
41
|
-
expect(JSON.stringify(encrypted)).not.toContain('private message')
|
|
42
|
-
const decrypted = server.decryptRequest(encrypted)
|
|
43
|
-
expect(decrypted.success).toBe(true)
|
|
44
|
-
if (!decrypted.success)
|
|
45
|
-
throw new Error(decrypted.error.message)
|
|
46
|
-
const payload = JSON.parse(decrypted.data) as { message: string }
|
|
45
|
+
const payload = JSON.parse(verified.data.plaintext) as { message: string }
|
|
47
46
|
messages.push(payload.message)
|
|
48
47
|
data = { message: payload.message, echoedAt: '2026-01-01T00:00:00.000Z', requestId: 'test-request' }
|
|
49
48
|
}
|
|
50
|
-
const response = await server.encryptResponse(
|
|
49
|
+
const response = await server.encryptResponse(verified.data.context, JSON.stringify({ success: true, data }), 200)
|
|
51
50
|
if (!response.success)
|
|
52
51
|
throw new Error(response.error.message)
|
|
53
52
|
return Response.json(response.data, { headers: { [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE } })
|
|
@@ -75,11 +74,11 @@ describe('shared API transport', () => {
|
|
|
75
74
|
expect(server.requests.at(-1)?.headers.get('x-request-client')).toBe('configured-client')
|
|
76
75
|
})
|
|
77
76
|
|
|
78
|
-
it('uses the API origin
|
|
77
|
+
it.each([false, true])('uses the injected API origin when native configuration is required: %s', async (requireApiBase) => {
|
|
79
78
|
Object.assign(globalThis, { __HAI_PUBLIC_CONFIG__: { apiBase: 'https://runtime.example.com' } })
|
|
80
79
|
const server = createServerFetch()
|
|
81
80
|
try {
|
|
82
|
-
expect((await initializeManagementApi({ fetchFn: server.fetchFn })).success).toBe(true)
|
|
81
|
+
expect((await initializeManagementApi({ fetchFn: server.fetchFn, requireApiBase })).success).toBe(true)
|
|
83
82
|
expect((await managementApi.app.info()).success).toBe(true)
|
|
84
83
|
expect(server.requests.every(request => request.url.startsWith('https://runtime.example.com/api/v1/'))).toBe(true)
|
|
85
84
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
import { defineConfig } from '@playwright/test'
|
|
3
|
+
import { createE2eNotificationEnv, createE2eServerEnv, createE2eTransportRing } from './scripts/e2e-env.js'
|
|
3
4
|
|
|
4
5
|
const defaultBaseURL = '{{#if fullstack.e2eFrontend}}http://127.0.0.1:{{fullstack.e2eFrontend.port}}{{else}}http://127.0.0.1:3000{{/if}}'
|
|
5
6
|
const defaultServiceBaseURL = 'http://127.0.0.1:3000'
|
|
@@ -49,32 +50,39 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
|
|
|
49
50
|
|
|
50
51
|
export default defineConfig({
|
|
51
52
|
testDir: './e2e',
|
|
53
|
+
// 多个界面验收共用管理员资料与引导状态,保留串行以免互相覆盖。
|
|
52
54
|
fullyParallel: false,
|
|
53
55
|
forbidOnly: !!process.env.CI,
|
|
54
|
-
retries:
|
|
56
|
+
retries: 0,
|
|
55
57
|
workers: 1,
|
|
56
58
|
reporter: 'list',
|
|
57
59
|
timeout: 30_000,
|
|
58
60
|
use: {
|
|
59
61
|
baseURL,
|
|
60
62
|
channel: 'chrome',
|
|
61
|
-
trace: 'on-
|
|
63
|
+
trace: 'retain-on-failure',
|
|
62
64
|
screenshot: 'only-on-failure',
|
|
63
65
|
},
|
|
64
66
|
webServer: [
|
|
65
67
|
{
|
|
66
68
|
name: '{{fullstack.servPackageName}}',
|
|
67
|
-
command: 'pnpm --filter {{fullstack.servPackageName}}
|
|
68
|
-
env: {
|
|
69
|
+
command: 'pnpm --filter {{fullstack.servPackageName}} exec node dist/index.js',
|
|
70
|
+
env: createE2eServerEnv({
|
|
71
|
+
...createE2eNotificationEnv(),
|
|
72
|
+
HAI_DB: JSON.stringify({ type: 'sqlite', database: `${process.env.HAI_E2E_DATA_ROOT}/fullstack.db` }),
|
|
73
|
+
HAI_CACHE: JSON.stringify({ type: 'memory' }),
|
|
74
|
+
HAI_STORAGE: JSON.stringify({ type: 'local', root: `${process.env.HAI_E2E_DATA_ROOT}/uploads` }),
|
|
75
|
+
HAI_VECDB: JSON.stringify({ type: 'lancedb', path: `${process.env.HAI_E2E_DATA_ROOT}/vecdb` }),
|
|
76
|
+
HAI_CRYPTO_TRANSPORT_SERVERKEYRING: createE2eTransportRing(),
|
|
69
77
|
HOST: serviceHost,
|
|
70
78
|
PORT: servicePort,
|
|
71
79
|
// 仅供脚手架验收:生产启动必须由部署环境提供初始管理员密码。
|
|
72
80
|
HAI_ADMIN_DEFAULT_PASSWORD: process.env.E2E_ADMIN_PASSWORD || 'Scaffold-test-Admin123!',
|
|
73
81
|
HAI_CORE_NAME: process.env.HAI_CORE_NAME || 'Configured scaffold',
|
|
74
82
|
HAI_CORE_VERSION: process.env.HAI_CORE_VERSION || '9.8.7',
|
|
75
|
-
HAI_CORE_ENV:
|
|
83
|
+
HAI_CORE_ENV: 'test',
|
|
76
84
|
HAI_CORE_LOGGING_LEVEL: 'warn',
|
|
77
|
-
},
|
|
85
|
+
}),
|
|
78
86
|
url: `${serviceBaseURL}/health`,
|
|
79
87
|
reuseExistingServer: false,
|
|
80
88
|
timeout: 120_000,
|
|
@@ -84,6 +92,7 @@ export default defineConfig({
|
|
|
84
92
|
return {
|
|
85
93
|
name: frontend.name,
|
|
86
94
|
command: `pnpm --filter ${frontend.name} exec vite preview --host ${resolveHostFromBaseUrl(url, '127.0.0.1')} --port ${resolvePortFromBaseUrl(url, '3000')} --strictPort`,
|
|
95
|
+
env: createE2eServerEnv({ PUBLIC_API_BASE: serviceBaseURL }),
|
|
87
96
|
url,
|
|
88
97
|
reuseExistingServer: false,
|
|
89
98
|
timeout: 120_000,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app_title": "H5アプリ",
|
|
3
|
+
"app_description": "hai Agent Frameworkで構築したH5モバイルアプリ",
|
|
4
|
+
"nav_home": "ホーム",
|
|
5
|
+
"nav_discover": "見つける",
|
|
6
|
+
"nav_profile": "プロフィール",
|
|
7
|
+
"page_home_title": "ホーム",
|
|
8
|
+
"page_home_desc": "H5アプリへようこそ",
|
|
9
|
+
"page_home_search": "検索...",
|
|
10
|
+
"page_home_feature1": "機能1",
|
|
11
|
+
"page_home_feature2": "機能2",
|
|
12
|
+
"page_home_feature3": "機能3",
|
|
13
|
+
"page_home_feature4": "機能4",
|
|
14
|
+
"page_home_latest": "最新情報",
|
|
15
|
+
"page_home_welcome": "H5アプリへようこそ",
|
|
16
|
+
"page_discover_title": "見つける",
|
|
17
|
+
"page_discover_desc": "さらに多くのコンテンツを探す。",
|
|
18
|
+
"page_profile_title": "プロフィール",
|
|
19
|
+
"page_profile_desc": "個人情報を管理します",
|
|
20
|
+
"page_profile_username": "ユーザー名",
|
|
21
|
+
"page_profile_login_hint": "タップしてログイン",
|
|
22
|
+
"page_profile_info": "個人情報",
|
|
23
|
+
"page_profile_settings": "設定",
|
|
24
|
+
"page_profile_about": "会社概要",
|
|
25
|
+
"auth_login_title": "ログイン",
|
|
26
|
+
"auth_login_submit": "ログイン",
|
|
27
|
+
"auth_login_loading": "ログイン中...",
|
|
28
|
+
"auth_login_failed": "ログインに失敗しました",
|
|
29
|
+
"auth_login_to_register": "アカウントをお持ちでない方は新規登録",
|
|
30
|
+
"auth_register_title": "登録",
|
|
31
|
+
"auth_register_submit": "登録",
|
|
32
|
+
"auth_register_loading": "登録中...",
|
|
33
|
+
"auth_register_failed": "登録に失敗しました",
|
|
34
|
+
"auth_register_to_login": "既にアカウントをお持ちの方はログイン",
|
|
35
|
+
"auth_label_username": "ユーザー名",
|
|
36
|
+
"auth_label_password": "パスワード",
|
|
37
|
+
"auth_label_confirm_password": "パスワードの確認",
|
|
38
|
+
"auth_error_network": "ネットワークエラー",
|
|
39
|
+
"auth_error_password_mismatch": "パスワードが一致しません"
|
|
40
|
+
}
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
import { page } from '$app/state'
|
|
14
14
|
{{#if hasI18n}}
|
|
15
15
|
import * as m from '$lib/paraglide/messages'
|
|
16
|
-
import { getLocale } from '$lib/paraglide/runtime.js'
|
|
17
|
-
import { AppBar, BottomNav, setGlobalLocale } from '@h-ai/ui'
|
|
16
|
+
import { getLocale, setLocale } from '$lib/paraglide/runtime.js'
|
|
17
|
+
import { AppBar, BottomNav, LanguageSwitch, setGlobalLocale } from '@h-ai/ui'
|
|
18
18
|
{{else}}
|
|
19
19
|
import { AppBar, BottomNav } from '@h-ai/ui'
|
|
20
20
|
{{/if}}
|
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
}
|
|
50
50
|
{{#if hasI18n}}
|
|
51
51
|
|
|
52
|
+
function handleLanguageChange(language: string) {
|
|
53
|
+
setGlobalLocale(language)
|
|
54
|
+
setLocale(language as Parameters<typeof setLocale>[0])
|
|
55
|
+
}
|
|
56
|
+
|
|
52
57
|
if (browser) {
|
|
53
58
|
const paraglideLocale = getLocale()
|
|
54
59
|
setGlobalLocale(paraglideLocale)
|
|
@@ -61,14 +66,20 @@
|
|
|
61
66
|
</svelte:head>
|
|
62
67
|
|
|
63
68
|
<div class='hai-mobile-viewport'>
|
|
64
|
-
<div class='hai-mobile-shell
|
|
65
|
-
<AppBar title={page.data.appConfig.name} safeArea fixed={false}
|
|
69
|
+
<div class='hai-mobile-shell min-w-0'>
|
|
70
|
+
<AppBar title={page.data.appConfig.name} safeArea fixed={false}>
|
|
71
|
+
{{#if hasI18n}}
|
|
72
|
+
{#snippet trailing()}
|
|
73
|
+
<LanguageSwitch currentLanguage={getLocale()} onchange={handleLanguageChange} compact />
|
|
74
|
+
{/snippet}
|
|
75
|
+
{{/if}}
|
|
76
|
+
</AppBar>
|
|
66
77
|
<main class='hai-mobile-main'>{@render children()}</main>
|
|
67
78
|
<footer class='border-t border-base-300 bg-base-100 p-2 text-center text-xs text-base-content/60'>
|
|
68
79
|
{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}
|
|
69
80
|
</footer>
|
|
70
81
|
{#if !isAuthPage}
|
|
71
|
-
<BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='
|
|
82
|
+
<BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='none' safeArea />
|
|
72
83
|
{/if}
|
|
73
84
|
</div>
|
|
74
85
|
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://inlang.com/schema/inlang-message-format",
|
|
3
|
+
"app_title": "モバイルワークスペース",
|
|
4
|
+
"app_description": "hai Agent Framework + Capacitor + Svelte 5で構築したモバイルアプリ",
|
|
5
|
+
"boot_failed_title": "起動に失敗しました",
|
|
6
|
+
"common_retry": "再試行",
|
|
7
|
+
"common_refresh": "更新",
|
|
8
|
+
"common_enabled": "有効",
|
|
9
|
+
"common_disabled": "無効",
|
|
10
|
+
"nav_home": "ワークスペース",
|
|
11
|
+
"nav_tasks": "タスク",
|
|
12
|
+
"nav_profile": "プロフィール",
|
|
13
|
+
"hero_eyebrow": "モバイルワークスペース",
|
|
14
|
+
"hero_title": "モバイルを中心にした実用的な出発点",
|
|
15
|
+
"hero_subtitle": "Capacitorのネイティブ機能、安全なトークン保存、共通APIクライアント、@h-ai/uiのモバイルコンポーネントを組み込んでいます。",
|
|
16
|
+
"feature_native_title": "ネイティブ対応",
|
|
17
|
+
"feature_native_desc": "ステータスバー、セーフエリア、デバイスランタイムを設定済み。プッシュ通知やカメラなどのプラグインを追加できます。",
|
|
18
|
+
"feature_api_title": "共通API",
|
|
19
|
+
"feature_api_desc": "PUBLIC_API_BASEでバックエンドに接続します。ネイティブ端末ではトークンを安全なストレージに保存します。",
|
|
20
|
+
"status_title": "実行状態",
|
|
21
|
+
"status_api_label": "APIベースURL",
|
|
22
|
+
"status_runtime_label": "実行環境",
|
|
23
|
+
"status_runtime_native": "ネイティブアプリ",
|
|
24
|
+
"status_runtime_web": "ブラウザープレビュー",
|
|
25
|
+
"status_token_label": "トークン保存",
|
|
26
|
+
"status_token_native": "安全なストレージ",
|
|
27
|
+
"status_token_memory": "メモリー内保存",
|
|
28
|
+
"tasks_title": "本日のタスク",
|
|
29
|
+
"tasks_sync": "バックエンドAPIを接続",
|
|
30
|
+
"tasks_polish": "モバイルUXを改善",
|
|
31
|
+
"tasks_release": "ネイティブのパッケージ化を準備",
|
|
32
|
+
"profile_title": "アプリ設定",
|
|
33
|
+
"profile_language": "言語はParaglideに従います",
|
|
34
|
+
"profile_theme": "テーマは@h-ai/uiに従います",
|
|
35
|
+
"profile_secure": "機密トークンはlocalStorageに保存しません"
|
|
36
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
{{#if features.iam}}
|
|
7
7
|
import { apiClient } from '@h-ai/api-client'
|
|
8
8
|
{{/if}}
|
|
9
|
-
import { applyTheme, getSavedTheme, setGlobalLocale } from '@h-ai/ui'
|
|
9
|
+
import { applyTheme, getSavedTheme, LanguageSwitch, setGlobalLocale } from '@h-ai/ui'
|
|
10
10
|
{{#if features.iam}}
|
|
11
11
|
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
12
12
|
{{/if}}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { initCapacitor, isNativeApp } from './lib/capacitor.js'
|
|
18
18
|
import { HashRouter } from './lib/navigation/hash-router.svelte.js'
|
|
19
19
|
import * as m from './lib/paraglide/messages.js'
|
|
20
|
-
import { getLocale } from './lib/paraglide/runtime.js'
|
|
20
|
+
import { getLocale, setLocale } from './lib/paraglide/runtime.js'
|
|
21
21
|
|
|
22
22
|
{{#if features.iam}}
|
|
23
23
|
const authActions = apiClient.createAuthActions(apiClient)
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
document.title = appConfig.name
|
|
58
58
|
applyTheme(getSavedTheme(), false)
|
|
59
59
|
setGlobalLocale(getLocale())
|
|
60
|
+
document.documentElement.lang = getLocale()
|
|
60
61
|
await initCapacitor()
|
|
61
62
|
await initApi()
|
|
62
63
|
{{#if features.iam}}
|
|
@@ -80,6 +81,11 @@
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
function handleLanguageChange(language: string): void {
|
|
85
|
+
setGlobalLocale(language)
|
|
86
|
+
setLocale(language as Parameters<typeof setLocale>[0])
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
/** 切换底部导航;例如 handleTabChange('tasks') 会写入 #/tasks。 */
|
|
84
90
|
function handleTabChange(id: string): void {
|
|
85
91
|
router.navigate(`/${id}`)
|
|
@@ -100,8 +106,12 @@
|
|
|
100
106
|
</script>
|
|
101
107
|
|
|
102
108
|
<div class='hai-mobile-viewport'>
|
|
103
|
-
<div class='hai-mobile-shell
|
|
104
|
-
<AppBar title={appConfig.name} safeArea fixed={false} class='bg-base-100/95 backdrop-blur border-base-content/8'
|
|
109
|
+
<div class='hai-mobile-shell min-w-0'>
|
|
110
|
+
<AppBar title={appConfig.name} safeArea fixed={false} class='bg-base-100/95 backdrop-blur border-base-content/8'>
|
|
111
|
+
{#snippet trailing()}
|
|
112
|
+
<LanguageSwitch currentLanguage={getLocale()} onchange={handleLanguageChange} compact />
|
|
113
|
+
{/snippet}
|
|
114
|
+
</AppBar>
|
|
105
115
|
<main class='hai-mobile-main'>
|
|
106
116
|
{#if booting}
|
|
107
117
|
<div class='grid min-h-64 place-items-center'><span class='loading loading-spinner loading-lg'></span></div>
|
|
@@ -129,7 +139,7 @@
|
|
|
129
139
|
{{#if features.iam}}
|
|
130
140
|
{#if authenticated && !booting && !bootError}
|
|
131
141
|
{{/if}}
|
|
132
|
-
<BottomNav items={navItems} active={router.path.slice(1)} onchange={handleTabChange} safeArea centered maxWidth='
|
|
142
|
+
<BottomNav items={navItems} active={router.path.slice(1)} onchange={handleTabChange} safeArea centered maxWidth='none' />
|
|
133
143
|
{{#if features.iam}}
|
|
134
144
|
{/if}
|
|
135
145
|
{{/if}}
|
|
@@ -1,24 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* {{projectName}} — API 客户端初始化。
|
|
3
3
|
* Web 预览仅使用内存 TokenStore;原生端使用 Capacitor 安全存储。
|
|
4
|
+
* 原生存储失败会拒绝 Promise;登录/退出调用方须处理 auth.setTokens/clear 失败并释放界面 loading。
|
|
4
5
|
*/
|
|
5
6
|
import { apiClient } from '@h-ai/api-client'
|
|
6
7
|
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
8
|
+
{{#if features.crypto}}
|
|
9
|
+
import { crypto } from '@h-ai/crypto'
|
|
10
|
+
{{/if}}
|
|
7
11
|
|
|
8
12
|
const DEFAULT_API_BASE = 'http://localhost:3000/api/v1'
|
|
9
13
|
|
|
10
14
|
let initialized = false
|
|
15
|
+
let initializing: Promise<void> | undefined
|
|
16
|
+
let closing: Promise<void> | undefined
|
|
17
|
+
{{#if features.crypto}}
|
|
18
|
+
// 只关闭本客户端实际初始化的 crypto,保留外部调用方已有实例。
|
|
19
|
+
let ownsCrypto = false
|
|
11
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 解析客户端传输选项;仅显式 off 连接匹配的明文 API,握手失败不降级。
|
|
23
|
+
* @returns 绑定实际 crypto 与公开协商子路径的选项,或显式明文模式的 undefined。
|
|
24
|
+
* @example 模块内部上下文
|
|
25
|
+
* ```ts
|
|
26
|
+
* const transport = resolveTransport()
|
|
27
|
+
* if (transport) {
|
|
28
|
+
* await initApi()
|
|
29
|
+
* try {
|
|
30
|
+
* if (!apiClient.config?.transport) throw new Error('Transport configuration missing')
|
|
31
|
+
* }
|
|
32
|
+
* finally { await closeApi() }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function resolveTransport() {
|
|
37
|
+
const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { apiTransport?: string, apiKeyExchangePath?: string } }).__HAI_PUBLIC_CONFIG__
|
|
38
|
+
if ((runtime?.apiTransport || import.meta.env.PUBLIC_API_TRANSPORT) === 'off')
|
|
39
|
+
return undefined
|
|
40
|
+
return { crypto, keyExchangePath: runtime?.apiKeyExchangePath || import.meta.env.PUBLIC_API_KEY_EXCHANGE_PATH || '/_hai/key-exchange' }
|
|
41
|
+
}
|
|
42
|
+
{{/if}}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 按公开运行配置、构建配置、本地示例地址的顺序解析含 API 前缀的 baseUrl。
|
|
46
|
+
* @returns API baseUrl;服务端私钥和连接配置不属于该公开地址。
|
|
47
|
+
* @example 模块内部上下文
|
|
48
|
+
* ```ts
|
|
49
|
+
* const base = new URL(resolveApiBase())
|
|
50
|
+
* const apiOrigin = base.origin
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
12
53
|
export function resolveApiBase(): string {
|
|
13
54
|
const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { apiBase?: string } }).__HAI_PUBLIC_CONFIG__
|
|
14
55
|
return runtime?.apiBase || import.meta.env.PUBLIC_API_BASE || DEFAULT_API_BASE
|
|
15
56
|
}
|
|
16
57
|
|
|
58
|
+
/**
|
|
59
|
+
* 查询实际 Capacitor 平台,决定安全原生存储或 Web 内存存储。
|
|
60
|
+
* @returns 是否处于 Capacitor 原生平台;浏览器预览返回 false。
|
|
61
|
+
* @throws Error - 平台模块加载或检测失败时拒绝 Promise,不伪装为 Web 模式。
|
|
62
|
+
* @example 模块内部上下文
|
|
63
|
+
* ```ts
|
|
64
|
+
* if (await usesNativeTokenStorage()) {
|
|
65
|
+
* const storage = createCapacitorTokenStorage()
|
|
66
|
+
* const accessToken = await storage.getAccessToken() // 只用于认证,不输出令牌。
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
17
70
|
export async function usesNativeTokenStorage(): Promise<boolean> {
|
|
18
71
|
const { Capacitor } = await import('@capacitor/core')
|
|
19
72
|
return Capacitor.isNativePlatform()
|
|
20
73
|
}
|
|
21
74
|
|
|
75
|
+
/**
|
|
76
|
+
* 按真实平台创建 TokenStorage;绑定 client 后通过 client.auth 变更身份。
|
|
77
|
+
* @returns 原生安全存储或 Web 内存存储;其方法的读写故障继续拒绝 Promise。
|
|
78
|
+
* @throws Error - 平台检测或存储工厂失败时拒绝,不改用较弱的持久化方式。
|
|
79
|
+
* @example 模块内部上下文
|
|
80
|
+
* ```ts
|
|
81
|
+
* const storage = await createTokenStorage()
|
|
82
|
+
* const accessToken = await storage.getAccessToken() // null 表示未认证,不记录令牌。
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
22
85
|
async function createTokenStorage() {
|
|
23
86
|
if (await usesNativeTokenStorage())
|
|
24
87
|
return createCapacitorTokenStorage()
|
|
@@ -26,29 +89,125 @@ async function createTokenStorage() {
|
|
|
26
89
|
return apiClient.tokenStorage.memory()
|
|
27
90
|
}
|
|
28
91
|
|
|
92
|
+
/**
|
|
93
|
+
* 合并在途初始化并等待关闭;安全存储、所选 crypto 与 typed client 依次就绪。
|
|
94
|
+
* @returns 客户端已准备;重复调用复用同一客户端。
|
|
95
|
+
* @throws Error - 安全存储、crypto 或 client 初始化失败,业务 HaiResult 在此边界明确转成拒绝。
|
|
96
|
+
* @throws AggregateError - 初始化和本次 crypto 清理同时失败,后续 closeApi 可重试。
|
|
97
|
+
* @example 模块内部上下文;目标 API 已提供标准 IAM 端点。
|
|
98
|
+
* ```ts
|
|
99
|
+
* await initApi()
|
|
100
|
+
* try {
|
|
101
|
+
* const current = await apiClient.iam.auth.currentUser()
|
|
102
|
+
* if (!current.success) throw new Error(current.error.message)
|
|
103
|
+
* }
|
|
104
|
+
* finally { await closeApi() }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
29
107
|
export async function initApi(): Promise<void> {
|
|
108
|
+
if (closing)
|
|
109
|
+
await closing
|
|
30
110
|
if (initialized)
|
|
31
111
|
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
112
|
+
if (initializing)
|
|
113
|
+
return initializing
|
|
114
|
+
{{#if features.crypto}}
|
|
115
|
+
if (ownsCrypto)
|
|
116
|
+
await closeApi()
|
|
117
|
+
{{/if}}
|
|
118
|
+
initializing = initialize().finally(() => {
|
|
119
|
+
initializing = undefined
|
|
38
120
|
})
|
|
121
|
+
return initializing
|
|
122
|
+
}
|
|
39
123
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
124
|
+
/**
|
|
125
|
+
* 在初始化屏障内装配客户端;安全存储先就绪,失败不退回明文并释放本次资源。
|
|
126
|
+
* @returns 当前客户端已初始化;由 initApi 管理并发屏障。
|
|
127
|
+
* @throws Error - 平台、所选 crypto 或 client 拒绝初始化。
|
|
128
|
+
* @throws AggregateError - 初始化失败且本次 crypto 未确认清理,保留关闭重试义务。
|
|
129
|
+
* @example 模块内部上下文;仅在初始化屏障内调用。
|
|
130
|
+
* ```ts
|
|
131
|
+
* await initialize()
|
|
132
|
+
* try {
|
|
133
|
+
* const current = await apiClient.iam.auth.currentUser()
|
|
134
|
+
* if (!current.success) throw new Error(current.error.message)
|
|
135
|
+
* }
|
|
136
|
+
* finally { await closeApi() }
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
async function initialize(): Promise<void> {
|
|
140
|
+
const storage = await createTokenStorage()
|
|
141
|
+
{{#if features.crypto}}
|
|
142
|
+
const transport = resolveTransport()
|
|
143
|
+
try {
|
|
144
|
+
if (transport && !crypto.isInitialized) {
|
|
145
|
+
const result = await crypto.init()
|
|
146
|
+
if (!result.success)
|
|
147
|
+
throw new Error(result.error.message)
|
|
148
|
+
ownsCrypto = true
|
|
149
|
+
}
|
|
150
|
+
const result = await apiClient.init({
|
|
151
|
+
baseUrl: resolveApiBase(),
|
|
152
|
+
auth: { storage },
|
|
153
|
+
...(transport ? { transport } : {}),
|
|
154
|
+
})
|
|
155
|
+
if (!result.success)
|
|
156
|
+
throw new Error(result.error.message)
|
|
157
|
+
initialized = true
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (ownsCrypto) {
|
|
161
|
+
try {
|
|
162
|
+
await crypto.close()
|
|
163
|
+
ownsCrypto = false
|
|
164
|
+
}
|
|
165
|
+
catch (cleanupError) {
|
|
166
|
+
throw new AggregateError([error, cleanupError], 'Mobile API initialization and crypto cleanup failed')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
throw error
|
|
170
|
+
}
|
|
171
|
+
{{else}}
|
|
172
|
+
const result = await apiClient.init({ baseUrl: resolveApiBase(), auth: { storage } })
|
|
173
|
+
if (!result.success)
|
|
174
|
+
throw new Error(result.error.message)
|
|
43
175
|
initialized = true
|
|
176
|
+
{{/if}}
|
|
44
177
|
}
|
|
45
178
|
|
|
179
|
+
/**
|
|
180
|
+
* 等待在途初始化,先销毁客户端 transport,再释放自身初始化的 crypto。
|
|
181
|
+
* @returns 所有本次资源确认关闭;无资源或并发关闭时安全复用。
|
|
182
|
+
* @throws Error - client 或 crypto 未确认关闭;保留状态供下一次 closeApi 重试。
|
|
183
|
+
* @example 模块内部上下文;目标 API 已提供标准 IAM 端点。
|
|
184
|
+
* ```ts
|
|
185
|
+
* await initApi()
|
|
186
|
+
* try {
|
|
187
|
+
* const current = await apiClient.iam.auth.currentUser()
|
|
188
|
+
* if (!current.success) throw new Error(current.error.message)
|
|
189
|
+
* }
|
|
190
|
+
* finally { await closeApi() } // 先 client,再本客户端初始化的 crypto。
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
46
193
|
export async function closeApi(): Promise<void> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
194
|
+
closing ??= (async () => {
|
|
195
|
+
if (initializing)
|
|
196
|
+
await initializing.catch(() => undefined)
|
|
197
|
+
if (initialized) {
|
|
198
|
+
await apiClient.close()
|
|
199
|
+
initialized = false
|
|
200
|
+
}
|
|
201
|
+
{{#if features.crypto}}
|
|
202
|
+
if (ownsCrypto) {
|
|
203
|
+
await crypto.close()
|
|
204
|
+
ownsCrypto = false
|
|
205
|
+
}
|
|
206
|
+
{{/if}}
|
|
207
|
+
})().finally(() => {
|
|
208
|
+
closing = undefined
|
|
209
|
+
})
|
|
210
|
+
return closing
|
|
52
211
|
}
|
|
53
212
|
|
|
54
213
|
export { apiClient }
|
|
@@ -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>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app_title": "企業サイト",
|
|
3
|
+
"app_description": "企業の公式サイト",
|
|
4
|
+
"meta_description": "企業の公式サイト",
|
|
5
|
+
"nav_home": "ホーム",
|
|
6
|
+
"nav_about": "会社概要",
|
|
7
|
+
"nav_services": "サービス",
|
|
8
|
+
"nav_contact": "担当者",
|
|
9
|
+
"page_home_title": "ようこそ",
|
|
10
|
+
"page_home_subtitle": "専門的で信頼できる企業向けサービス",
|
|
11
|
+
"page_home_cta": "お問い合わせ",
|
|
12
|
+
"page_home_advantages": "私たちの強み",
|
|
13
|
+
"page_home_team": "専門チーム",
|
|
14
|
+
"page_home_team_desc": "長年の業界経験を持つチームです。",
|
|
15
|
+
"page_home_service": "高品質なサービス",
|
|
16
|
+
"page_home_service_desc": "総合的で質の高いサポートを提供します。",
|
|
17
|
+
"page_home_tech": "革新的な技術",
|
|
18
|
+
"page_home_tech_desc": "業界をリードする革新的な技術ソリューション。",
|
|
19
|
+
"page_about_title": "会社概要",
|
|
20
|
+
"page_about_desc": "企業情報とこれまでの歩み。",
|
|
21
|
+
"page_services_title": "サービス",
|
|
22
|
+
"page_services_item1": "サービス1",
|
|
23
|
+
"page_services_item2": "サービス2",
|
|
24
|
+
"page_services_desc": "サービスの説明。",
|
|
25
|
+
"page_contact_title": "お問い合わせ",
|
|
26
|
+
"page_contact_desc": "ご質問はお気軽にお問い合わせください。",
|
|
27
|
+
"page_contact_name": "お名前",
|
|
28
|
+
"page_contact_email": "メールアドレス",
|
|
29
|
+
"page_contact_message": "メッセージ",
|
|
30
|
+
"page_contact_submit": "送信",
|
|
31
|
+
"footer_copyright": "Copyright © {year}. All rights reserved."
|
|
32
|
+
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import { page } from '$app/state'
|
|
12
12
|
{{#if hasI18n}}
|
|
13
13
|
import * as m from '$lib/paraglide/messages'
|
|
14
|
-
import { getLocale } from '$lib/paraglide/runtime.js'
|
|
15
|
-
import { setGlobalLocale } from '@h-ai/ui'
|
|
14
|
+
import { getLocale, setLocale } from '$lib/paraglide/runtime.js'
|
|
15
|
+
import { LanguageSwitch, setGlobalLocale } from '@h-ai/ui'
|
|
16
16
|
{{/if}}
|
|
17
17
|
import '../app.css'
|
|
18
18
|
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
] as const
|
|
38
38
|
{{#if hasI18n}}
|
|
39
39
|
|
|
40
|
+
function handleLanguageChange(language: string) {
|
|
41
|
+
setGlobalLocale(language)
|
|
42
|
+
setLocale(language as Parameters<typeof setLocale>[0])
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
if (browser) {
|
|
41
46
|
const paraglideLocale = getLocale()
|
|
42
47
|
setGlobalLocale(paraglideLocale)
|
|
@@ -49,7 +54,7 @@
|
|
|
49
54
|
</svelte:head>
|
|
50
55
|
|
|
51
56
|
<div class='min-h-screen flex flex-col'>
|
|
52
|
-
<header class='navbar bg-base-100 shadow-sm'>
|
|
57
|
+
<header class='navbar flex-wrap bg-base-100 shadow-sm'>
|
|
53
58
|
<div class='flex-1'>
|
|
54
59
|
{{#if hasI18n}}
|
|
55
60
|
<a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
|
|
@@ -57,13 +62,16 @@
|
|
|
57
62
|
<a href={resolve('/', {})} class='btn btn-ghost text-xl font-black tracking-tight'>{page.data.appConfig.name}</a>
|
|
58
63
|
{{/if}}
|
|
59
64
|
</div>
|
|
60
|
-
<nav class='flex-none'>
|
|
61
|
-
<ul class='menu menu-horizontal px-1'>
|
|
65
|
+
<nav class='w-full flex-none sm:w-auto'>
|
|
66
|
+
<ul class='menu menu-horizontal flex-wrap px-1'>
|
|
62
67
|
{#each navItems as item (item.path)}
|
|
63
68
|
<li><a href={resolve(item.path, {})}>{item.label}</a></li>
|
|
64
69
|
{/each}
|
|
65
70
|
</ul>
|
|
66
71
|
</nav>
|
|
72
|
+
{{#if hasI18n}}
|
|
73
|
+
<LanguageSwitch currentLanguage={getLocale()} onchange={handleLanguageChange} />
|
|
74
|
+
{{/if}}
|
|
67
75
|
</header>
|
|
68
76
|
|
|
69
77
|
<main class='flex-1'>
|
|
@@ -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}}
|