@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.21
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 +74 -154
- package/README.ja.md +74 -154
- package/README.md +74 -154
- package/dist/{chunk-AKV2CQZV.js → chunk-2ZL5ORQ2.js} +316 -97
- package/dist/chunk-2ZL5ORQ2.js.map +1 -0
- package/dist/{chunk-EF7WOFWZ.js → chunk-L53P43B2.js} +1312 -163
- package/dist/chunk-L53P43B2.js.map +1 -0
- package/dist/cli-config-templates-LWMD2I6V.js +5 -0
- package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
- package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
- package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30118 -3253
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +22 -18
- package/templates/README.md +45 -30
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
- package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
- package/templates/apps/api/.dockerignore.hbs +10 -0
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +8 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +6 -2
- package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +24 -2
- package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +74 -24
- package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +153 -37
- package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
- package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +106 -33
- package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
- package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
- package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
- package/templates/apps/api/eslint.config.js.hbs +3 -1
- package/templates/apps/api/images/serv.Dockerfile.hbs +15 -10
- package/templates/apps/api/playwright.config.ts.hbs +23 -19
- package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
- package/templates/apps/fullstack/.dockerignore.hbs +10 -0
- package/templates/apps/fullstack/.gitignore.hbs +7 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +14 -12
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +5 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -8
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +9 -9
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
- package/templates/apps/fullstack/docs/security-configuration.md.hbs +4 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +140 -28
- package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
- package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +19 -11
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +17 -8
- package/templates/apps/fullstack/package.json.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +7 -5
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +17 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +13 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +17 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +10 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +147 -33
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +146 -30
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +142 -26
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +48 -66
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
- package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
- package/templates/apps/fullstack/playwright.config.ts.hbs +25 -3
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -14
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +74 -30
- package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
- package/templates/apps/mobile-app/capacitor.config.ts.hbs +5 -2
- package/templates/apps/mobile-app/messages/en-US.json +2 -2
- package/templates/apps/mobile-app/messages/ja-JP.json +2 -2
- package/templates/apps/mobile-app/messages/zh-CN.json +2 -2
- package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +20 -6
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
- package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -1
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
- package/templates/apps/website/scripts/static-website.mjs.hbs +19 -6
- package/templates/apps/website/src/routes/+layout.svelte.hbs +56 -29
- package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
- package/templates/base/.dockerignore.hbs +10 -0
- package/templates/base/.gitignore.hbs +7 -2
- package/templates/base/e2e/app.spec.ts.hbs +130 -36
- package/templates/base/eslint-rules.js +44 -25
- package/templates/base/eslint.config.js.hbs +3 -1
- package/templates/base/images/web.Dockerfile.hbs +25 -14
- package/templates/base/package.json.hbs +13 -7
- package/templates/base/patches/daisyui-5.7.50.patch +8 -0
- package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
- package/templates/base/playwright.config.ts +3 -1
- package/templates/base/pnpm-workspace.yaml.hbs +33 -16
- package/templates/base/src/app.d.ts.hbs +1 -1
- package/templates/base/src/app.html.hbs +1 -0
- package/templates/base/src/hooks.server.ts.hbs +44 -33
- package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
- package/templates/base/src/lib/server/init.ts.hbs +146 -35
- package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
- package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
- package/templates/base/svelte.config.js.hbs +10 -100
- package/templates/base/tests/scaffold.test.ts.hbs +12 -3
- package/templates/base/tsconfig.json.hbs +7 -4
- package/templates/base/vite.config.ts.hbs +108 -14
- package/templates/base/vitest.config.ts.hbs +6 -2
- package/templates/features/ai/routes/api/chat/+server.ts +3 -1
- package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
- package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +14 -3
- package/templates/shared/browser-check/startup.js +115 -0
- package/templates/shared/browser-check/update.css +37 -0
- package/templates/shared/browser-check/update.html +19 -0
- package/templates/shared/browser-startup.spec.ts +23 -0
- package/templates/shared/build-metadata.ts +18 -4
- package/templates/shared/e2e-env.ts +22 -6
- package/templates/shared/native-name.d.mts +5 -0
- package/templates/shared/native-name.mjs +94 -0
- package/templates/shared/public-base-path.mjs +17 -4
- package/templates/shared/run-e2e.mjs +46 -12
- package/templates/shared/run-node.mjs +56 -0
- package/templates/shared/run-tauri.mjs +74 -0
- package/templates/shared/vite-runtime-config.ts +9 -3
- package/templates/skills/AGENTS.md +10 -73
- package/templates/skills/bridges/admin/AGENTS.md +9 -65
- package/templates/skills/bridges/api/AGENTS.md +8 -59
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
- package/templates/skills/bridges/h5/AGENTS.md +8 -63
- package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
- package/templates/skills/bridges/website/AGENTS.md +8 -63
- package/templates/skills/hai-ai/SKILL.md +6 -2
- package/templates/skills/hai-ai/acp.md +153 -0
- package/templates/skills/hai-ai/guide.md +27 -12
- package/templates/skills/hai-ai/reference.md +24 -15
- package/templates/skills/hai-ai/resources.md +69 -0
- package/templates/skills/hai-api-client/SKILL.md +1 -1
- package/templates/skills/hai-api-client/reference.md +3 -1
- package/templates/skills/hai-app-create/collaboration.md +7 -0
- package/templates/skills/hai-app-create/development.md +19 -10
- package/templates/skills/hai-app-create/project.md +33 -0
- package/templates/skills/hai-app-create/reference.md +10 -10
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-app-tests/validation.md +1 -0
- package/templates/skills/hai-build/SKILL.md +19 -9
- package/templates/skills/hai-build/paths.md +4 -2
- package/templates/skills/hai-build/reference.md +26 -8
- package/templates/skills/hai-cache/reference.md +6 -2
- package/templates/skills/hai-capacitor/SKILL.md +1 -0
- package/templates/skills/hai-capacitor/reference.md +24 -0
- package/templates/skills/hai-ci/SKILL.md +3 -0
- package/templates/skills/hai-core/SKILL.md +9 -4
- package/templates/skills/hai-core/reference.md +40 -10
- package/templates/skills/hai-core/request-access.md +2 -2
- package/templates/skills/hai-crypto/SKILL.md +2 -2
- package/templates/skills/hai-crypto/reference.md +7 -3
- package/templates/skills/hai-deploy/SKILL.md +41 -5
- package/templates/skills/hai-deploy/middleware.md +26 -14
- package/templates/skills/hai-framework-sync/SKILL.md +13 -9
- package/templates/skills/hai-framework-sync/sync.md +2 -0
- package/templates/skills/hai-iam/reference.md +2 -2
- package/templates/skills/hai-kit/SKILL.md +1 -1
- package/templates/skills/hai-kit/reference.md +19 -5
- package/templates/skills/hai-payment/reference.md +41 -17
- package/templates/skills/hai-reach/SKILL.md +3 -1
- package/templates/skills/hai-reach/reference.md +29 -9
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-reldb/reference.md +9 -2
- package/templates/skills/hai-scheduler/reference.md +1 -1
- package/templates/skills/hai-serv/SKILL.md +2 -1
- package/templates/skills/hai-serv/reference.md +26 -15
- package/templates/skills/hai-storage/reference.md +23 -6
- package/templates/skills/hai-ui/SKILL.md +11 -6
- package/templates/skills/hai-ui/charts.md +51 -0
- package/templates/skills/hai-ui/guide.md +1 -1
- package/templates/skills/hai-ui/reference.md +58 -38
- package/templates/skills/hai-vecdb/reference.md +8 -3
- package/dist/chunk-AKV2CQZV.js.map +0 -1
- package/dist/chunk-EF7WOFWZ.js.map +0 -1
- package/dist/chunk-MUHRBJSW.js +0 -29272
- package/dist/chunk-MUHRBJSW.js.map +0 -1
- package/dist/cli-config-templates-HHFHNXBN.js +0 -5
- package/dist/src-TEBHR5I5.js +0 -5
- package/dist/src-TEBHR5I5.js.map +0 -1
- package/templates/apps/fullstack/playwright.session-close.config.ts.hbs +0 -18
|
@@ -29,11 +29,23 @@ beforeAll(async () => {
|
|
|
29
29
|
expect((await reldb.init({ type: 'sqlite', database: ':memory:' })).success).toBe(true)
|
|
30
30
|
expect((await cache.init({ type: 'memory' })).success).toBe(true)
|
|
31
31
|
expect((await storage.init({ type: 'local', root: directory })).success).toBe(true)
|
|
32
|
-
expect((await audit.init({
|
|
32
|
+
expect((await audit.init({
|
|
33
|
+
storage,
|
|
34
|
+
backup: { enabled: true },
|
|
35
|
+
})).success).toBe(true)
|
|
33
36
|
expect((await iam.init({
|
|
34
37
|
initialCredentials: { enabled: true },
|
|
35
38
|
passwordReset: { enabled: true, mode: 'email' },
|
|
36
|
-
reauthentication: {
|
|
39
|
+
reauthentication: {
|
|
40
|
+
enabled: true,
|
|
41
|
+
operations: [
|
|
42
|
+
'iam.users.create',
|
|
43
|
+
'iam.users.invite',
|
|
44
|
+
'iam.users.resetPassword',
|
|
45
|
+
'audit.backup',
|
|
46
|
+
'business.settings.update',
|
|
47
|
+
],
|
|
48
|
+
},
|
|
37
49
|
onPasswordResetRequest: async (user, token) => {
|
|
38
50
|
invitations.set(user.username, token)
|
|
39
51
|
if (deliveryUnknown)
|
|
@@ -56,9 +68,19 @@ beforeAll(async () => {
|
|
|
56
68
|
}
|
|
57
69
|
const app = serv.createApp({
|
|
58
70
|
contract: {{fullstack.contractExportName}},
|
|
59
|
-
procedures: createServerProcedures({
|
|
71
|
+
procedures: createServerProcedures({
|
|
72
|
+
app: {
|
|
73
|
+
name: 'Security fixture',
|
|
74
|
+
version: 'test',
|
|
75
|
+
env: 'test',
|
|
76
|
+
defaultLocale: 'en-US',
|
|
77
|
+
supportedLocales: ['en-US'],
|
|
78
|
+
startedAt: new Date().toISOString(),
|
|
79
|
+
frontends: ['web'],
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
60
82
|
iam,
|
|
61
|
-
http: { openapi: false, docs: false
|
|
83
|
+
http: { openapi: false, docs: false },
|
|
62
84
|
})
|
|
63
85
|
server = serv.listen(app, { host: '127.0.0.1', port: 0 })
|
|
64
86
|
if (!server.server.listening)
|
|
@@ -100,23 +122,57 @@ async function proof(code: Duty, operation: string) {
|
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
function post(code: Duty, route: string, input: object = {}, ticket?: string) {
|
|
103
|
-
return fetch(`${base}${route}`, {
|
|
125
|
+
return fetch(`${base}${route}`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
'authorization': `Bearer ${actor(code).token}`,
|
|
130
|
+
...(ticket
|
|
131
|
+
? { 'x-hai-reauthentication': ticket }
|
|
132
|
+
: {}),
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify(input),
|
|
135
|
+
})
|
|
104
136
|
}
|
|
105
137
|
|
|
106
138
|
it('系统、审计、配置与业务职责的直接 HTTP 请求各有边界', async () => {
|
|
107
|
-
expect(await (await post('audit_admin', '/admin/users/list')).json()).toMatchObject({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
expect(await (await post('audit_admin', '/admin/users/list')).json()).toMatchObject({
|
|
140
|
+
success: false,
|
|
141
|
+
error: { httpStatus: 403 },
|
|
142
|
+
})
|
|
143
|
+
expect(await (await post('admin', '/admin/audit/list')).json()).toMatchObject({
|
|
144
|
+
success: false,
|
|
145
|
+
error: { httpStatus: 403 },
|
|
146
|
+
})
|
|
147
|
+
expect(await (await post('admin', '/admin/configuration/update', { notice: 'Rejected' })).json()).toMatchObject({
|
|
148
|
+
success: false,
|
|
149
|
+
error: { httpStatus: 403 },
|
|
150
|
+
})
|
|
151
|
+
expect(await (await post('business_operator', '/admin/users/list')).json()).toMatchObject({
|
|
152
|
+
success: false,
|
|
153
|
+
error: { httpStatus: 403 },
|
|
154
|
+
})
|
|
111
155
|
const ticket = await proof('config_admin', 'business.settings.update')
|
|
112
156
|
expect(await (await post('config_admin', '/admin/configuration/update', { notice: 'Authorized public notice' }, ticket)).json()).toMatchObject({ success: true })
|
|
113
157
|
const info = await fetch(`${base}/app/info`, { method: 'POST' })
|
|
114
|
-
expect(await info.json()).toMatchObject({
|
|
158
|
+
expect(await info.json()).toMatchObject({
|
|
159
|
+
success: true,
|
|
160
|
+
data: { notice: 'Authorized public notice' },
|
|
161
|
+
})
|
|
115
162
|
})
|
|
116
163
|
|
|
117
164
|
it('再鉴别按用途单次使用,临时账号状态变化立即阻止旧会话', async () => {
|
|
118
|
-
const input = {
|
|
119
|
-
|
|
165
|
+
const input = {
|
|
166
|
+
username: 'temporary-member',
|
|
167
|
+
password: 'Temporary-example-123!',
|
|
168
|
+
role: 'user',
|
|
169
|
+
accountType: 'temporary',
|
|
170
|
+
accountExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
171
|
+
}
|
|
172
|
+
expect(await (await post('admin', '/admin/users/create', input)).json()).toMatchObject({
|
|
173
|
+
success: false,
|
|
174
|
+
error: { code: 'hai:iam:027' },
|
|
175
|
+
})
|
|
120
176
|
const ticket = await proof('admin', 'iam.users.create')
|
|
121
177
|
const response = await post('admin', '/admin/users/create', input, ticket)
|
|
122
178
|
const created = apiContract.haiResultSchema(AdminUserSchema).parse(await response.json())
|
|
@@ -135,7 +191,12 @@ it('再鉴别按用途单次使用,临时账号状态变化立即阻止旧会
|
|
|
135
191
|
|
|
136
192
|
it('设密邀请可完成,投递未知时保留账号;系统管理员可创建其他职责账号', async () => {
|
|
137
193
|
const createdDuty = await post('admin', '/admin/users/create', { username: 'managed-auditor', password: 'Example-123!', role: 'audit_admin' }, await proof('admin', 'iam.users.create'))
|
|
138
|
-
expect(await createdDuty.json()).toMatchObject({
|
|
194
|
+
expect(await createdDuty.json()).toMatchObject({
|
|
195
|
+
success: true,
|
|
196
|
+
data: {
|
|
197
|
+
roles: ['audit_admin'],
|
|
198
|
+
},
|
|
199
|
+
})
|
|
139
200
|
const invite = await post('admin', '/admin/users/invite', { username: 'invited-member', email: 'member@example.test' }, await proof('admin', 'iam.users.invite'))
|
|
140
201
|
expect(await invite.json()).toMatchObject({ success: true })
|
|
141
202
|
const token = invitations.get('invited-member')
|
|
@@ -145,39 +206,94 @@ it('设密邀请可完成,投递未知时保留账号;系统管理员可创
|
|
|
145
206
|
deliveryUnknown = true
|
|
146
207
|
try {
|
|
147
208
|
const unknown = await post('admin', '/admin/users/invite', { username: 'delivery-unknown-member', email: 'unknown@example.test' }, await proof('admin', 'iam.users.invite'))
|
|
148
|
-
expect(await unknown.json()).toMatchObject({
|
|
209
|
+
expect(await unknown.json()).toMatchObject({
|
|
210
|
+
success: false,
|
|
211
|
+
error: { code: 'hai:iam:028' },
|
|
212
|
+
})
|
|
149
213
|
const found = await iam.user.listUsers({ search: 'delivery-unknown-member' })
|
|
150
|
-
expect(found).toMatchObject({
|
|
214
|
+
expect(found).toMatchObject({
|
|
215
|
+
success: true,
|
|
216
|
+
data: { total: 1 },
|
|
217
|
+
})
|
|
151
218
|
}
|
|
152
219
|
finally { deliveryUnknown = false }
|
|
153
220
|
})
|
|
154
221
|
|
|
155
222
|
it('只有审计职责可手动备份且私有归档可验证', async () => {
|
|
156
223
|
const id = crypto.randomUUID()
|
|
157
|
-
expect(await (await post('admin', '/admin/audit/backup', { id })).json()).toMatchObject({
|
|
224
|
+
expect(await (await post('admin', '/admin/audit/backup', { id })).json()).toMatchObject({
|
|
225
|
+
success: false,
|
|
226
|
+
error: { httpStatus: 403 },
|
|
227
|
+
})
|
|
158
228
|
const result = await post('audit_admin', '/admin/audit/backup', { id }, await proof('audit_admin', 'audit.backup'))
|
|
159
229
|
const body: unknown = await result.json()
|
|
160
|
-
expect(body).toMatchObject({
|
|
230
|
+
expect(body).toMatchObject({
|
|
231
|
+
success: true,
|
|
232
|
+
data: {
|
|
233
|
+
id,
|
|
234
|
+
key: expect.stringContaining('hai-private/audit-backups/'),
|
|
235
|
+
sha256: expect.stringMatching(/^[a-f\d]{64}$/),
|
|
236
|
+
},
|
|
237
|
+
})
|
|
161
238
|
const verified = await audit.backup.verify(`hai-private/audit-backups/${id}.jsonl`)
|
|
162
239
|
expect(verified.success).toBe(true)
|
|
163
240
|
})
|
|
164
241
|
|
|
165
|
-
it.each([
|
|
242
|
+
it.each([
|
|
243
|
+
'admin',
|
|
244
|
+
'audit_admin',
|
|
245
|
+
'config_admin',
|
|
246
|
+
'business_operator',
|
|
247
|
+
'user',
|
|
248
|
+
] as const)('系统管理员统一管理 %s 的资料、生命周期、角色和密码', async (role) => {
|
|
166
249
|
const username = `managed-${role}`
|
|
167
250
|
const created = apiContract.haiResultSchema(AdminUserSchema).parse(await (await post('admin', '/admin/users/create', { username, password: 'Managed-example-123!', role }, await proof('admin', 'iam.users.create'))).json())
|
|
168
|
-
expect(created).toMatchObject({
|
|
251
|
+
expect(created).toMatchObject({
|
|
252
|
+
success: true,
|
|
253
|
+
data: {
|
|
254
|
+
roles: [role],
|
|
255
|
+
accountStatus: 'active',
|
|
256
|
+
},
|
|
257
|
+
})
|
|
169
258
|
if (!created.success)
|
|
170
259
|
throw new Error('Managed account fixture failed')
|
|
171
|
-
const input = {
|
|
172
|
-
|
|
260
|
+
const input = {
|
|
261
|
+
id: created.data.id,
|
|
262
|
+
displayName: 'Managed profile',
|
|
263
|
+
accountStatus: 'dormant',
|
|
264
|
+
role: 'user',
|
|
265
|
+
}
|
|
266
|
+
expect(await (await post('admin', '/admin/users/update', input)).json()).toMatchObject({
|
|
267
|
+
success: true,
|
|
268
|
+
data: {
|
|
269
|
+
displayName: 'Managed profile',
|
|
270
|
+
roles: ['user'],
|
|
271
|
+
accountStatus: 'dormant',
|
|
272
|
+
},
|
|
273
|
+
})
|
|
173
274
|
const reset = { id: created.data.id, newPassword: 'Reset-example-456!' }
|
|
174
|
-
expect(await (await post('admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
275
|
+
expect(await (await post('admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
276
|
+
success: false,
|
|
277
|
+
error: { code: 'hai:iam:027' },
|
|
278
|
+
})
|
|
175
279
|
const ticket = await proof('admin', 'iam.users.resetPassword')
|
|
176
|
-
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({
|
|
280
|
+
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({
|
|
281
|
+
success: true,
|
|
282
|
+
data: { reset: true },
|
|
283
|
+
})
|
|
177
284
|
expect(await (await post('admin', '/admin/users/reset-password', reset, ticket)).json()).toMatchObject({ success: false })
|
|
178
|
-
expect(await iam.user.getUser(created.data.id)).toMatchObject({
|
|
179
|
-
|
|
285
|
+
expect(await iam.user.getUser(created.data.id)).toMatchObject({
|
|
286
|
+
success: true,
|
|
287
|
+
data: { accountStatus: 'dormant' },
|
|
288
|
+
})
|
|
289
|
+
expect(await iam.auth.login({ identifier: username, password: reset.newPassword })).toMatchObject({
|
|
290
|
+
success: false,
|
|
291
|
+
error: { code: 'hai:iam:002' },
|
|
292
|
+
})
|
|
180
293
|
expect(await (await post('admin', '/admin/users/update', { id: created.data.id, accountStatus: 'active' })).json()).toMatchObject({ success: true })
|
|
181
294
|
expect((await iam.auth.login({ identifier: username, password: reset.newPassword })).success).toBe(true)
|
|
182
|
-
expect(await (await post('audit_admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
295
|
+
expect(await (await post('audit_admin', '/admin/users/reset-password', reset)).json()).toMatchObject({
|
|
296
|
+
success: false,
|
|
297
|
+
error: { httpStatus: 403 },
|
|
298
|
+
})
|
|
183
299
|
})
|
|
@@ -6,13 +6,17 @@ import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
|
|
|
6
6
|
const buildMetadata = resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))
|
|
7
7
|
|
|
8
8
|
export default defineConfig({
|
|
9
|
-
define: {
|
|
9
|
+
define: {
|
|
10
|
+
__HAI_BUILD_METADATA__: JSON.stringify(buildMetadata),
|
|
11
|
+
},
|
|
10
12
|
async onSuccess() {
|
|
11
13
|
await writeFile(new URL('./dist/build-metadata.json', import.meta.url), JSON.stringify(buildMetadata))
|
|
12
14
|
},
|
|
13
15
|
entry: ['src/index.ts'],
|
|
14
16
|
format: ['esm'],
|
|
15
|
-
dts: {
|
|
17
|
+
dts: {
|
|
18
|
+
compilerOptions: { ignoreDeprecations: '6.0' },
|
|
19
|
+
},
|
|
16
20
|
sourcemap: true,
|
|
17
21
|
clean: true,
|
|
18
22
|
})
|
|
@@ -3,7 +3,9 @@ import { defineConfig } from 'vitest/config'
|
|
|
3
3
|
import { resolveBuildMetadata } from '../../scripts/build-metadata.js'
|
|
4
4
|
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
define: {
|
|
6
|
+
define: {
|
|
7
|
+
__HAI_BUILD_METADATA__: JSON.stringify(resolveBuildMetadata(fileURLToPath(new URL('.', import.meta.url)))),
|
|
8
|
+
},
|
|
7
9
|
test: {
|
|
8
10
|
globals: true,
|
|
9
11
|
environment: 'node',
|
|
@@ -1,97 +1,79 @@
|
|
|
1
1
|
# {{fullstack.sharedPackageName}}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
供{{projectName}}的Web、Capacitor App和Tauri Desktop复用的前端工作区,提供AdminWorkspace、AppShell、typed API、管理状态、语言主题、引导和帮助。它不单独部署,也不在浏览器中初始化服务端数据库/IAM。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 运行与接入
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
从工程根安装依赖,启动Serv,再启动所选前端。各端已经接入此共享包;增加页面优先复用现有状态和组件。公开入口包含AdminWorkspace/AppShell、initializeManagementApi/managementApi、AVAILABLE_THEMES/themeStore及语言消息与runtime。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Typed encrypted API client and platform-specific token storage.
|
|
11
|
-
- Themes, language preferences, onboarding and help.
|
|
9
|
+
以下示例用于已有前端宿主,地址替换为实际API;原有宿主已初始化时直接复用,不重复执行初始化:
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
```ts
|
|
12
|
+
import { initializeManagementApi, managementApi } from '{{fullstack.sharedPackageName}}'
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
const ready = await initializeManagementApi({ baseUrl: 'https://api.example.invalid/api/v1' })
|
|
15
|
+
if (!ready.success) throw new Error(ready.error.message)
|
|
16
|
+
const info = await managementApi.app.info()
|
|
17
|
+
if (!info.success) throw new Error(info.error.message)
|
|
18
|
+
// info.data包含后端Core名称/版本、环境、语言、公告及前端清单。
|
|
19
|
+
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
输入为可信API基址,输出为统一HaiResult。初始化装配认证刷新和传输加密,不能把请求结束当成功;应用入口统一处理安全错误与生命周期,退出时先停止当前身份的后台操作,再清凭据和关闭客户端。
|
|
18
22
|
|
|
19
|
-
|
|
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.
|
|
22
|
-
- `ManagementStore` owns API calls and business state; components render the state and forward user interactions.
|
|
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.
|
|
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.
|
|
25
|
-
- Read metadata through the same initialized `managementApi.app.info()` client; inspect `success` before using `data`.
|
|
23
|
+
## 功能与输入输出
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
| 功能 | 用户输入/调用场景 | 可观察输出与限制 |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| 元信息 | managementApi.app.info() | 后端Core名称、版本、环境及语言;不在前端重复硬编码 |
|
|
28
|
+
| 用户/角色管理 | 当前职责允许的列表、筛选、排序与修改 | 服务端授权后的数据与明确结果;隐藏按钮不代替服务端检查 |
|
|
29
|
+
| 个人资料/头像 | 资料值、允许的PNG/JPEG/WebP(不超过512KiB) | 成功保存后更新;失败保留草稿。头像key由服务端生成 |
|
|
30
|
+
| 审计 | 分类、结果、等级、排序或备份条件 | 当前权限范围内记录、近7天统计、私有备份key/数量/SHA256回执;不公开备份对象 |
|
|
31
|
+
| 公告 | 配置人员的受限正文输入 | 公告与审计同事务提交,失败回滚,不把客户端编辑当已发布 |
|
|
32
|
+
| 主题/语言 | 个人设置选择已编译主题/语言 | 保存到IAM用户metadata,登录后恢复,并同步shared与宿主runtime |
|
|
33
|
+
| 引导/帮助 | 首次使用邀请、稍后、关闭、完成或重播 | 按用户及引导版本保存偏好,帮助入口可再次打开,不作为业务完成证明 |
|
|
34
|
+
| 表单/提示 | 输入、失焦、提交与失败重试 | 统一Toast显示安全说明,控件保留错误边框和读屏关联;成功信号决定退出编辑 |
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
默认示例职责为系统管理、审计、配置、业务操作员和普通成员,实际规则由应用维护。临时账号、生命周期、邀请、敏感操作再认证及密码/会话限制通过服务端策略实现;取消再认证不发送对应敏感业务请求。
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
工作区按窗口扩展,表格内部滚动,宽屏表单可两列,窄屏收拢。FormField.colSpan可明确占列。页面和数据逻辑分别维护在组件与ManagementStore,错误恢复保留用户输入。
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
## 配置与扩展
|
|
34
41
|
|
|
35
|
-
###
|
|
42
|
+
### API地址、认证与平台存储
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
initializeManagementApi接受baseUrl、fetchFn、clientName、terminalType、tokenStorage、headers、requireApiBase和onSessionExpired。基址顺序为显式baseUrl→公开runtime-config→PUBLIC_API_BASE→当前origin;只给origin时补/api/v1,自定义http.apiPrefix必须明确包含。
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
- Web/浏览器预览使用内存access token和服务端HttpOnly刷新Cookie,刷新恢复会话;认证token不放localStorage。
|
|
47
|
+
- Capacitor原生使用createCapacitorTokenStorage及受信原生通道。编译/同步前在App的public/runtime-config.js设置公开API地址,不放服务端秘密。
|
|
48
|
+
- Tauri原生凭据只在内存,重启重新登录。启动安装程序前设置进程PUBLIC_API_BASE,Rust宿主注入地址;缺省http://localhost:3000/api/v1只适合本机服务。
|
|
49
|
+
- 原生请求声明clientName及body通道头,服务端还核验可信Origin;仅声明头不获得原生权限。
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
仅配置地址不证明设备可达。代理、HTTPS、凭据传递与认证状态恢复应在对应平台验证。
|
|
42
52
|
|
|
43
|
-
###
|
|
53
|
+
### IAM与通知
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
Serv的config/_iam.yml控制登录/注册/重置、协议与ui品牌,AuthPanel只使用安全公开配置。密码、验证码、LDAP及发送回调的完整配置见[生成应用IAM技能](../../.agents/skills/hai-iam/reference.md)。协议正文Markdown优先于URL并安全渲染;邮件/短信投递未知需查回执,不能自动重复发送。
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
页面离开撤销为可选且尽力而为;生产仍用共享数据库/Redis、闲置与绝对期限。管理写入路由和最后管理员边界按根README的实际部署方案执行。
|
|
48
58
|
|
|
49
|
-
###
|
|
59
|
+
### 消息、主题、组件与引导
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
共享文案在messages,前端专属文案放各端messages;启用语言及baseLocale一致后执行根 `pnpm i18n:compile`,不手改生成文件。插件来自已安装workspace依赖,不在编译时从CDN获取。
|
|
52
62
|
|
|
53
|
-
|
|
63
|
+
AVAILABLE_THEMES只暴露已编译CSS主题,eye-care为可选护眼主题;默认主题和显式白名单由宿主决定。外观缓存可保存theme,认证token不能复用该存储。修改AppShell、相邻管理组件或ManagementStore时同步三语与键盘/窄屏恢复流程。
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
引导定义在src/lib/stores/guide-store.ts,组件提供稳定data-guide属性;Web引导偏好用Cookie,App/Desktop用按当前用户与版本划分的localStorage,不存认证信息。设计、实现和复核分别使用根skills中的hai-ui-design、hai-ui、hai-ui-review。
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
{{#if features.feedback}}
|
|
68
|
+
### 用户反馈
|
|
58
69
|
|
|
59
|
-
|
|
70
|
+
反馈页面复用shared SDK预览、标注、保存草稿与导出轮询;提交受理编号不表示材料和通知全部完成。服务端 `_feedback.yml` 配置采集预算/业务采集超时/Webhook,密钥仅在服务端私有环境。退出身份清理草稿与观察器,导出文件通过受授权私有通道交付。App/Desktop按平台安全保存文件并清理临时资源。
|
|
71
|
+
{{/if}}
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
## 验证
|
|
62
74
|
|
|
63
|
-
|
|
75
|
+
从根目录运行 `pnpm typecheck`、`pnpm lint`、`pnpm build`、`pnpm test`、`pnpm test:e2e`。核对管理员与普通用户、跨资源拒绝、资料失败保留、头像、偏好恢复、密码/会话失效及窄屏布局。所有已选择前端共用流程,但浏览器测试不替代原生壳、设备权限或miniapp验收。
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
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. Their third context argument supplies expiresAt/idempotencyKey; senders durably deduplicate that key, and errors/timeouts mean unknown delivery with no automatic resend. 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.
|
|
68
|
-
|
|
69
|
-
Agreements accept Markdown text or a URL. Markdown takes precedence and opens in a safely rendered dialog.
|
|
70
|
-
|
|
71
|
-
See `.agents/skills/hai-iam/reference.md` in the generated application for the complete contract.
|
|
72
|
-
|
|
73
|
-
### Operation feedback
|
|
74
|
-
|
|
75
|
-
Operation results use the shared `toast` queue from the lightweight `@h-ai/ui/toast` entry. `AdminWorkspace` mounts one `ToastContainer` for Web, App and Desktop: notifications float at the bottom right by default, use UI-owned semantic colors for information, success, warning and error, with a soft tinted background, a narrow color border and a distinct icon, close automatically or manually, and appear one at a time in FIFO order. Waiting notifications do not expire before being shown. Set `position` in `toast.add` for any of the six supported positions. Field validation and persistent business state stay inline.
|
|
76
|
-
|
|
77
|
-
### Form layouts
|
|
78
|
-
|
|
79
|
-
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.
|
|
80
|
-
|
|
81
|
-
## 界面设计与验收
|
|
82
|
-
|
|
83
|
-
共享管理界面的 Web/App/Desktop 使用同一套 UI 组件和状态。设计、实现、审查分别参考根目录 `.agents/skills/hai-ui-design/`、`hai-ui/`、`hai-ui-review/`;正常、加载、空态、错误恢复、键盘、语言/主题和窄容器均按实际场景验收。组件的筛选、排序和分页仍由业务数据回调接入。
|
|
84
|
-
|
|
85
|
-
## 反馈现场与交付检查
|
|
86
|
-
|
|
87
|
-
feedback.init(config, { collectEvidence }) 支持等待业务 text/data 与文本/ZIP/GZIP 附件;_feedback.yml 提供业务采集超时和 Webhook URL/签名密钥/请求超时。密钥仅放服务端私有环境。Webhook 持久重试,详情展示状态并允许管理员重试。
|
|
88
|
-
|
|
89
|
-
反馈的 console/disabled/reach 独立选择;SDK getView/ratio/saveDraft 与 createFeedbackReview 统一预览、标注、保存及轮询。退出身份清理草稿和观察器。宿主生命周期由 core.module.createRuntime 处理,框架表升级前备份停旧写入并运行各模块 dryRun 预检。
|
|
90
|
-
|
|
91
|
-
## Development and testing
|
|
92
|
-
|
|
93
|
-
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.
|
|
94
|
-
|
|
95
|
-
## License
|
|
77
|
+
## 许可证
|
|
96
78
|
|
|
97
79
|
Apache-2.0
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs
CHANGED
|
@@ -40,7 +40,12 @@ export async function initializeManagementApi(options: ManagementApiOptions = {}
|
|
|
40
40
|
clientName: options.clientName ?? '{{fullstack.sharedPackageName}}',
|
|
41
41
|
fetch: createCredentialedFetch(options.fetchFn),
|
|
42
42
|
headers: options.headers,
|
|
43
|
-
auth: {
|
|
43
|
+
auth: {
|
|
44
|
+
recoverSession: sessionRecovery.recover,
|
|
45
|
+
terminalType: options.terminalType ?? 'web',
|
|
46
|
+
storage: options.tokenStorage,
|
|
47
|
+
onRefreshFailed: options.onSessionExpired,
|
|
48
|
+
},
|
|
44
49
|
transport: { crypto },
|
|
45
50
|
})
|
|
46
51
|
}
|
|
@@ -65,7 +70,9 @@ export function resolveAvatarUrl(value: string | undefined): string | undefined
|
|
|
65
70
|
function resolveDefaultApiOrigin(): string {
|
|
66
71
|
return resolveRuntimeApiOrigin()
|
|
67
72
|
|| import.meta.env.PUBLIC_API_BASE
|
|
68
|
-
|| (typeof globalThis.location === 'undefined'
|
|
73
|
+
|| (typeof globalThis.location === 'undefined'
|
|
74
|
+
? DEFAULT_API_ORIGIN
|
|
75
|
+
: globalThis.location.origin)
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
/** 跨端浏览器请求携带服务端管理的 httpOnly refresh Cookie。 */
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
{{#if features.feedback}}
|
|
3
3
|
import { createFeedbackClient, createFeedbackReview, saveFeedbackFile } from '@h-ai/feedback/client'
|
|
4
4
|
import { managementApi } from './api-client.js'
|
|
5
|
-
|
|
6
5
|
/** 仅装配共享采集器,不复制采集、脱敏、上传或离线打包逻辑。 */
|
|
7
6
|
export const browserFeedback = createFeedbackClient()
|
|
8
7
|
export const feedbackReview = createFeedbackReview(managementApi.feedback.reports)
|
|
9
8
|
export { feedbackDataBytes } from '@h-ai/feedback/client'
|
|
10
|
-
|
|
11
9
|
export async function initializeBrowserFeedback(metadata: { appVersion: string, buildId: string, commit: string }): Promise<void> {
|
|
12
10
|
const result = await browserFeedback.init({
|
|
13
11
|
appId: '{{projectName}}',
|
|
@@ -26,7 +24,6 @@ export async function initializeBrowserFeedback(metadata: { appVersion: string,
|
|
|
26
24
|
if (!result.success)
|
|
27
25
|
throw new Error(result.error.message, { cause: result.error })
|
|
28
26
|
}
|
|
29
|
-
|
|
30
27
|
/** 下载的内容来自受权 API 或本地采集器;对象 URL 在发起后释放。 */
|
|
31
28
|
export async function downloadFeedback(bytes: Uint8Array, name: string, contentType = 'application/zip'): Promise<void> {
|
|
32
29
|
const result = await saveFeedbackFile(bytes, name, contentType)
|
|
@@ -10,7 +10,19 @@ export const sensitiveActions = createSensitiveActions(authActions)
|
|
|
10
10
|
/** 用途凭据只传给本次 typed client 调用,不使用共享 header。 */
|
|
11
11
|
export async function requestSensitive<T>(operation: string, run: (options: { context: { reauthentication?: string, reauthorize: () => Promise<string | null | undefined> } }) => Promise<HaiResult<T>>): Promise<HaiResult<T>> {
|
|
12
12
|
const ticket = await sensitiveActions.authorize(operation)
|
|
13
|
-
if (ticket === null)
|
|
14
|
-
return err({
|
|
15
|
-
|
|
13
|
+
if (ticket === null) {
|
|
14
|
+
return err({
|
|
15
|
+
code: 'app:cancelled',
|
|
16
|
+
httpStatus: 400,
|
|
17
|
+
message: m.security_action_cancelled(),
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return run({
|
|
21
|
+
context: {
|
|
22
|
+
...(ticket
|
|
23
|
+
? { reauthentication: ticket }
|
|
24
|
+
: {}),
|
|
25
|
+
reauthorize: () => sensitiveActions.authorize(operation),
|
|
26
|
+
},
|
|
27
|
+
})
|
|
16
28
|
}
|
|
@@ -30,14 +30,38 @@
|
|
|
30
30
|
})
|
|
31
31
|
let menuOpen = $state(false)
|
|
32
32
|
const items = $derived([
|
|
33
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
{
|
|
34
|
+
id: 'users',
|
|
35
|
+
label: m.nav_users(),
|
|
36
|
+
icon: 'icon-[tabler--users]',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'roles',
|
|
40
|
+
label: m.nav_roles(),
|
|
41
|
+
icon: 'icon-[tabler--shield-lock]',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'configuration',
|
|
45
|
+
label: m.business_settings_title(),
|
|
46
|
+
icon: 'icon-[tabler--adjustments]',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'audit',
|
|
50
|
+
label: m.nav_audit(),
|
|
51
|
+
icon: 'icon-[tabler--file-text]',
|
|
52
|
+
},
|
|
37
53
|
{{#if features.feedback}}
|
|
38
|
-
{
|
|
54
|
+
{
|
|
55
|
+
id: 'feedback',
|
|
56
|
+
label: m.nav_feedback(),
|
|
57
|
+
icon: 'icon-[tabler--message-report]',
|
|
58
|
+
},
|
|
39
59
|
{{/if}}
|
|
40
|
-
{
|
|
60
|
+
{
|
|
61
|
+
id: 'settings',
|
|
62
|
+
label: m.settings_title(),
|
|
63
|
+
icon: 'icon-[tabler--settings]',
|
|
64
|
+
},
|
|
41
65
|
].filter(item => store.canOpenPage(item.id)))
|
|
42
66
|
const title = $derived(items.find(item => item.id === store.page)?.label ?? store.info?.name ?? '')
|
|
43
67
|
|
|
@@ -61,7 +85,9 @@
|
|
|
61
85
|
</div>
|
|
62
86
|
<nav class="flex-1 space-y-1 p-3" aria-label={m.navigation()}>
|
|
63
87
|
{#each items as item (item.id)}
|
|
64
|
-
<BareButton class="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm transition-colors {store.page === item.id
|
|
88
|
+
<BareButton class="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm transition-colors {store.page === item.id
|
|
89
|
+
? 'bg-primary/10 font-medium text-primary'
|
|
90
|
+
: 'text-base-content/60 hover:bg-base-content/4'}" onclick={() => navigate(item.id)} data-testid="nav-{item.id}">
|
|
65
91
|
<span class="{item.icon} size-4.5"></span>{item.label}
|
|
66
92
|
</BareButton>
|
|
67
93
|
{/each}
|
|
@@ -41,7 +41,13 @@
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
function input(data: FeedbackFormData) {
|
|
44
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
description: data.description,
|
|
46
|
+
category: data.type,
|
|
47
|
+
contact: data.contact,
|
|
48
|
+
files: data.files,
|
|
49
|
+
removedAttachmentIds: data.removedAttachmentIds,
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
52
|
async function submit(data: FeedbackFormData) {
|
|
47
53
|
const result = await browserFeedback.submit(input(data))
|
|
@@ -82,5 +88,13 @@
|
|
|
82
88
|
<Button variant='ghost' size='sm' loading={preparing} onclick={() => void prepare()} data-testid='feedback-open'>{m.nav_feedback()}</Button>
|
|
83
89
|
<FeedbackModal bind:open {attachments} budget={browserFeedback.budget} resetKey={draft?.submissionId} onsubmit={submit} oncapture={capturePage} onannotate={annotate} onexport={exportLocal} onfileschange={addFiles}
|
|
84
90
|
evidence={draft?.evidence ?? []}
|
|
85
|
-
evidenceSummary={draft?.missing.length
|
|
91
|
+
evidenceSummary={draft?.missing.length
|
|
92
|
+
? [
|
|
93
|
+
{
|
|
94
|
+
label: m.feedback_logs(),
|
|
95
|
+
status: 'partial',
|
|
96
|
+
detail: draft.missing.join(', '),
|
|
97
|
+
},
|
|
98
|
+
]
|
|
99
|
+
: []} />
|
|
86
100
|
{{/if}}
|