@h-ai/cli 0.1.0-alpha.67 → 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 +40 -4
- package/README.ja.md +40 -4
- package/README.md +40 -4
- package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
- 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-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +920 -336
- 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/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/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 +5 -1
- 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/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/package.json.hbs +1 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -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 +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
- 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/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
- 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 +1 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
- package/templates/apps/fullstack/package.json.hbs +1 -1
- 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 +5 -2
- 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/README.md.hbs +9 -4
- 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 +5 -5
- 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 +1 -1
- 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 +11 -12
- 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 +14 -3
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
- package/templates/base/e2e/app.spec.ts.hbs +13 -8
- package/templates/base/package.json.hbs +4 -1
- 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 +2 -2
- 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/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +4 -0
- package/templates/skills/bridges/admin/AGENTS.md +5 -1
- package/templates/skills/bridges/api/AGENTS.md +2 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
- package/templates/skills/bridges/h5/AGENTS.md +5 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
- package/templates/skills/bridges/website/AGENTS.md +5 -1
- 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 +2 -0
- package/templates/skills/hai-api-client/reference.md +17 -5
- package/templates/skills/hai-api-contract/SKILL.md +1 -0
- package/templates/skills/hai-api-contract/reference.md +4 -2
- package/templates/skills/hai-app-create/SKILL.md +4 -0
- package/templates/skills/hai-app-create/development.md +20 -4
- package/templates/skills/hai-app-create/reference.md +2 -1
- package/templates/skills/hai-app-review/SKILL.md +6 -2
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +3 -1
- package/templates/skills/hai-app-tests/reference.md +4 -4
- package/templates/skills/hai-app-tests/validation.md +10 -3
- package/templates/skills/hai-audit/SKILL.md +10 -8
- package/templates/skills/hai-audit/reference.md +3 -0
- package/templates/skills/hai-build/reference.md +6 -4
- package/templates/skills/hai-cache/SKILL.md +9 -3
- package/templates/skills/hai-cache/reference.md +53 -11
- package/templates/skills/hai-capacitor/SKILL.md +3 -1
- package/templates/skills/hai-capacitor/reference.md +9 -5
- package/templates/skills/hai-core/SKILL.md +4 -0
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +10 -2
- package/templates/skills/hai-crypto/reference.md +64 -10
- package/templates/skills/hai-datapipe/SKILL.md +2 -0
- package/templates/skills/hai-datapipe/reference.md +3 -1
- package/templates/skills/hai-deploy/SKILL.md +9 -4
- package/templates/skills/hai-deploy/middleware.md +51 -37
- package/templates/skills/hai-iam/SKILL.md +24 -8
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +23 -7
- package/templates/skills/hai-kit/reference.md +42 -24
- package/templates/skills/hai-payment/SKILL.md +13 -11
- package/templates/skills/hai-payment/reference.md +80 -41
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +5 -2
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +5 -2
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +8 -5
- package/templates/skills/hai-scheduler/reference.md +38 -24
- package/templates/skills/hai-serv/SKILL.md +23 -8
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +4 -0
- 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 +2 -0
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +8 -2
- package/templates/skills/hai-storage/reference.md +64 -25
- package/templates/skills/hai-ui/SKILL.md +16 -2
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +17 -6
- 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 +12 -8
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-7DSURTJ4.js.map +0 -1
- package/dist/chunk-AOQ4N657.js +0 -640
- package/dist/chunk-AOQ4N657.js.map +0 -1
- package/dist/cli-config-templates-6AHNUGBN.js +0 -5
- package/dist/src-2V6VP4I5.js.map +0 -1
|
@@ -33,7 +33,7 @@ import type { AIConfigInput } from '@h-ai/ai'
|
|
|
33
33
|
{{#if features.audit}}
|
|
34
34
|
import type { AuditInitConfigInput } from '@h-ai/audit'
|
|
35
35
|
{{/if}}
|
|
36
|
-
import type { CoreConfig } from '@h-ai/core'
|
|
36
|
+
import type { CoreConfig, HaiResult } from '@h-ai/core'
|
|
37
37
|
{{#if features.iam}}
|
|
38
38
|
import type { IamConfigSettingsInput } from '@h-ai/iam'
|
|
39
39
|
{{/if}}
|
|
@@ -44,7 +44,7 @@ import type { ReachConfigInput } from '@h-ai/reach'
|
|
|
44
44
|
import type { StorageConfigInput } from '@h-ai/storage'
|
|
45
45
|
{{/if}}
|
|
46
46
|
{{#if features.ai}}
|
|
47
|
-
import { ai } from '@h-ai/ai'
|
|
47
|
+
import { ai, AIConfigSchema } from '@h-ai/ai'
|
|
48
48
|
{{/if}}
|
|
49
49
|
{{#if features.audit}}
|
|
50
50
|
import { audit, AuditInitConfigSchema } from '@h-ai/audit'
|
|
@@ -68,6 +68,9 @@ import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
|
68
68
|
{{#if features.storage}}
|
|
69
69
|
import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
70
70
|
{{/if}}
|
|
71
|
+
{{#if features.vecdb}}
|
|
72
|
+
import { vecdb, VecdbConfigSchema } from '@h-ai/vecdb'
|
|
73
|
+
{{/if}}
|
|
71
74
|
|
|
72
75
|
{{#if features.db}}
|
|
73
76
|
type DbConfigInput = Parameters<typeof reldb.init>[0]
|
|
@@ -77,14 +80,46 @@ type CacheConfigInput = Parameters<typeof cache.init>[0]
|
|
|
77
80
|
{{/if}}
|
|
78
81
|
|
|
79
82
|
let initialized = false
|
|
83
|
+
let initializing: Promise<void> | undefined
|
|
84
|
+
let closing: Promise<void> | undefined
|
|
85
|
+
let cleanupPending = false
|
|
80
86
|
|
|
81
87
|
/**
|
|
82
88
|
* 初始化应用
|
|
83
89
|
*/
|
|
84
90
|
export async function initApp(): Promise<void> {
|
|
91
|
+
if (closing)
|
|
92
|
+
await closing
|
|
85
93
|
if (initialized)
|
|
86
94
|
return
|
|
95
|
+
if (!initializing) {
|
|
96
|
+
initializing = (async () => {
|
|
97
|
+
if (cleanupPending)
|
|
98
|
+
await cleanupModules()
|
|
99
|
+
cleanupPending = true
|
|
100
|
+
try {
|
|
101
|
+
await doInit()
|
|
102
|
+
initialized = true
|
|
103
|
+
cleanupPending = false
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
try {
|
|
107
|
+
await cleanupModules()
|
|
108
|
+
}
|
|
109
|
+
catch (cleanupError) {
|
|
110
|
+
throw new AggregateError([error, cleanupError], 'Application startup and cleanup failed')
|
|
111
|
+
}
|
|
112
|
+
throw error
|
|
113
|
+
}
|
|
114
|
+
})().finally(() => {
|
|
115
|
+
initializing = undefined
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return initializing
|
|
119
|
+
}
|
|
87
120
|
|
|
121
|
+
/** 实际启动只有一个调用者执行,失败由外层检查清理结果后再抛出。 */
|
|
122
|
+
async function doInit(): Promise<void> {
|
|
88
123
|
// 1. 加载配置
|
|
89
124
|
core.init({
|
|
90
125
|
configDir: './config',
|
|
@@ -115,6 +150,16 @@ export async function initApp(): Promise<void> {
|
|
|
115
150
|
if (!auditValidation.success)
|
|
116
151
|
throw new Error(`Audit config invalid: ${auditValidation.error.message}`)
|
|
117
152
|
{{/if}}
|
|
153
|
+
{{#if features.vecdb}}
|
|
154
|
+
const vectorValidation = core.config.validate('vecdb', VecdbConfigSchema)
|
|
155
|
+
if (!vectorValidation.success)
|
|
156
|
+
throw new Error(`VecDB config invalid: ${vectorValidation.error.message}`)
|
|
157
|
+
{{/if}}
|
|
158
|
+
{{#if features.ai}}
|
|
159
|
+
const aiValidation = core.config.validate('ai', AIConfigSchema)
|
|
160
|
+
if (!aiValidation.success)
|
|
161
|
+
throw new Error(`AI config invalid: ${aiValidation.error.message}`)
|
|
162
|
+
{{/if}}
|
|
118
163
|
|
|
119
164
|
{{#if features.db}}
|
|
120
165
|
// ─── 获取配置 ───
|
|
@@ -130,13 +175,16 @@ export async function initApp(): Promise<void> {
|
|
|
130
175
|
const auditConfig = core.config.getOrThrow<AuditInitConfigInput>('audit')
|
|
131
176
|
{{/if}}
|
|
132
177
|
{{#if features.storage}}
|
|
133
|
-
const storageConfig = core.config.
|
|
178
|
+
const storageConfig = core.config.getOrThrow<StorageConfigInput>('storage')
|
|
134
179
|
{{/if}}
|
|
135
180
|
{{#if features.ai}}
|
|
136
|
-
const aiConfig = core.config.
|
|
181
|
+
const aiConfig = core.config.getOrThrow<AIConfigInput>('ai')
|
|
182
|
+
{{/if}}
|
|
183
|
+
{{#if features.vecdb}}
|
|
184
|
+
const vectorConfig = core.config.getOrThrow<Parameters<typeof vecdb.init>[0]>('vecdb')
|
|
137
185
|
{{/if}}
|
|
138
186
|
{{#if features.reach}}
|
|
139
|
-
const reachConfig = core.config.
|
|
187
|
+
const reachConfig = core.config.getOrThrow<ReachConfigInput>('reach')
|
|
140
188
|
{{/if}}
|
|
141
189
|
|
|
142
190
|
{{#if features.db}}
|
|
@@ -166,26 +214,25 @@ export async function initApp(): Promise<void> {
|
|
|
166
214
|
{{/if}}
|
|
167
215
|
|
|
168
216
|
{{#if features.storage}}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
217
|
+
const storageValidation = core.config.validate('storage', StorageConfigSchema)
|
|
218
|
+
if (!storageValidation.success)
|
|
219
|
+
throw new Error(`Storage config invalid: ${storageValidation.error.message}`)
|
|
220
|
+
const storageResult = await storage.init(storageConfig)
|
|
221
|
+
if (!storageResult.success)
|
|
222
|
+
throw new Error(`Storage initialization failed: ${storageResult.error.message}`)
|
|
223
|
+
{{/if}}
|
|
224
|
+
|
|
225
|
+
{{#if features.vecdb}}
|
|
226
|
+
// 共享向量存储必须在 AI 选择默认 Store 之前就绪。
|
|
227
|
+
const vectorResult = await vecdb.init(vectorConfig)
|
|
228
|
+
if (!vectorResult.success)
|
|
229
|
+
throw new Error(`VecDB initialization failed: ${vectorResult.error.message}`)
|
|
179
230
|
{{/if}}
|
|
180
231
|
|
|
181
232
|
{{#if features.reach}}
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
if (!reachResult.success) {
|
|
186
|
-
core.logger.warn('Reach initialization failed', { error: reachResult.error.message })
|
|
187
|
-
}
|
|
188
|
-
}
|
|
233
|
+
const reachResult = await reach.init(reachConfig)
|
|
234
|
+
if (!reachResult.success)
|
|
235
|
+
throw new Error(`Reach initialization failed: ${reachResult.error.message}`)
|
|
189
236
|
{{/if}}
|
|
190
237
|
|
|
191
238
|
{{#if features.iam}}
|
|
@@ -205,15 +252,77 @@ export async function initApp(): Promise<void> {
|
|
|
205
252
|
{{/if}}
|
|
206
253
|
|
|
207
254
|
{{#if features.ai}}
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
core.logger.warn('AI initialization failed', { error: aiResult.error.message })
|
|
213
|
-
}
|
|
214
|
-
}
|
|
255
|
+
// 显式启用的 AI 不得在持久 Store 不可用时降级为启动成功。
|
|
256
|
+
const aiResult = await ai.init(aiConfig)
|
|
257
|
+
if (!aiResult.success)
|
|
258
|
+
throw new Error(`AI initialization failed: ${aiResult.error.message}`)
|
|
215
259
|
{{/if}}
|
|
216
260
|
|
|
217
|
-
initialized = true
|
|
218
261
|
core.logger.info('Application initialized', { app: core.config.getOrThrow<CoreConfig>('core').name })
|
|
219
262
|
}
|
|
263
|
+
|
|
264
|
+
/** 逆序关闭所有应用拥有的模块;保留失败义务以便下一次重试。 */
|
|
265
|
+
async function cleanupModules(): Promise<void> {
|
|
266
|
+
const failures: unknown[] = []
|
|
267
|
+
const closers: Array<() => Promise<HaiResult<void> | void>> = [
|
|
268
|
+
{{#if features.ai}}
|
|
269
|
+
() => ai.close(),
|
|
270
|
+
{{/if}}
|
|
271
|
+
{{#if features.audit}}
|
|
272
|
+
() => audit.close(),
|
|
273
|
+
{{/if}}
|
|
274
|
+
{{#if features.iam}}
|
|
275
|
+
() => iam.close(),
|
|
276
|
+
{{/if}}
|
|
277
|
+
{{#if features.reach}}
|
|
278
|
+
() => reach.close(),
|
|
279
|
+
{{/if}}
|
|
280
|
+
{{#if features.vecdb}}
|
|
281
|
+
() => vecdb.close(),
|
|
282
|
+
{{/if}}
|
|
283
|
+
{{#if features.storage}}
|
|
284
|
+
() => storage.close(),
|
|
285
|
+
{{/if}}
|
|
286
|
+
{{#if features.cache}}
|
|
287
|
+
() => cache.close(),
|
|
288
|
+
{{/if}}
|
|
289
|
+
{{#if features.db}}
|
|
290
|
+
() => reldb.close(),
|
|
291
|
+
{{/if}}
|
|
292
|
+
{{#if features.crypto}}
|
|
293
|
+
() => crypto.close(),
|
|
294
|
+
{{/if}}
|
|
295
|
+
]
|
|
296
|
+
for (const close of closers) {
|
|
297
|
+
try {
|
|
298
|
+
const result = await close()
|
|
299
|
+
if (result && !result.success)
|
|
300
|
+
failures.push(result.error)
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
failures.push(error)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
initialized = false
|
|
307
|
+
cleanupPending = failures.length > 0
|
|
308
|
+
if (cleanupPending)
|
|
309
|
+
throw new AggregateError(failures, 'Application cleanup failed; retry closeApp()')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 关闭应用模块并与并发启动/关闭合并;清理失败抛错并允许重试。
|
|
314
|
+
* @example `await closeApp()`
|
|
315
|
+
*/
|
|
316
|
+
export async function closeApp(): Promise<void> {
|
|
317
|
+
if (!closing) {
|
|
318
|
+
const pending = initializing
|
|
319
|
+
closing = (async () => {
|
|
320
|
+
if (pending)
|
|
321
|
+
await pending.catch(() => undefined)
|
|
322
|
+
await cleanupModules()
|
|
323
|
+
})().finally(() => {
|
|
324
|
+
closing = undefined
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
return closing
|
|
328
|
+
}
|
|
@@ -27,7 +27,7 @@ import { publicRuntimeConfig } from './scripts/vite-runtime-config.js'
|
|
|
27
27
|
{{/if}}
|
|
28
28
|
|
|
29
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() })
|
|
30
|
+
const envResult = command === 'build' || process.env.HAI_E2E === '1' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir: process.cwd() })
|
|
31
31
|
if (!envResult.success)
|
|
32
32
|
throw new Error(envResult.error.message)
|
|
33
33
|
const env = envResult.data
|
|
@@ -53,7 +53,7 @@ export default defineConfig(({ mode, command }) => {
|
|
|
53
53
|
{{/if}}
|
|
54
54
|
plugins: [
|
|
55
55
|
{{#if isSvelteOnlyApp}}
|
|
56
|
-
publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, core: appConfig }),
|
|
56
|
+
publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, {{#if features.crypto}}apiTransport: process.env.PUBLIC_API_TRANSPORT, apiKeyExchangePath: process.env.PUBLIC_API_KEY_EXCHANGE_PATH, {{/if}}core: appConfig }),
|
|
57
57
|
svelte(),
|
|
58
58
|
{{else}}
|
|
59
59
|
sveltekit(),
|
|
@@ -7,11 +7,16 @@ import { kit } from '@h-ai/kit'
|
|
|
7
7
|
|
|
8
8
|
export const POST: RequestHandler = kit.handler(async ({ cookies, locals }) => {
|
|
9
9
|
const token = cookies.get('session_token')
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
try {
|
|
11
|
+
if (token) {
|
|
12
|
+
const result = await iam.auth.logout(token)
|
|
13
|
+
if (!result.success)
|
|
14
|
+
return kit.response.fromError(result.error, locals.requestId)
|
|
15
|
+
}
|
|
16
|
+
return kit.response.ok({ authenticated: false }, locals.requestId)
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
// 本地Cookie清理不代表服务端撤销成功,失败响应必须保留。
|
|
20
|
+
cookies.delete('session_token', { path: '/' })
|
|
14
21
|
}
|
|
15
|
-
cookies.delete('session_token', { path: '/' })
|
|
16
|
-
return kit.response.ok({ authenticated: false }, locals.requestId)
|
|
17
22
|
})
|
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 创建支付订单 API
|
|
3
|
-
*/
|
|
1
|
+
/** 创建支付参数 API;渠道、场景与 openid 必须通过显式校验,禁止回落到其它支付模式。 */
|
|
4
2
|
import type { RequestHandler } from './$types'
|
|
5
3
|
import { kit } from '@h-ai/kit'
|
|
6
4
|
import { payment } from '@h-ai/payment'
|
|
5
|
+
import { z } from 'zod'
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|| tradeType === 'mini_program'
|
|
20
|
-
? tradeType
|
|
21
|
-
: 'jsapi'
|
|
7
|
+
/** 本模板的业务字段;服务端仍需从可信订单重新核对金额、描述、订单号和所属用户。 */
|
|
8
|
+
const CreatePaymentSchema = z.object({
|
|
9
|
+
provider: z.enum(['wechat', 'alipay', 'stripe']),
|
|
10
|
+
tradeType: z.enum(['jsapi', 'h5']),
|
|
11
|
+
outTradeNo: z.string().min(1),
|
|
12
|
+
totalAmount: z.number().int().positive(),
|
|
13
|
+
subject: z.string().min(1),
|
|
14
|
+
userId: z.string().trim().min(1).max(128).optional(),
|
|
15
|
+
metadata: z.record(z.string(), z.string()).optional(),
|
|
16
|
+
notifyUrl: z.url({ protocol: /^https?$/ }).optional(),
|
|
17
|
+
}).strict().refine(input => input.tradeType === (input.provider === 'wechat' ? 'jsapi' : 'h5'), { path: ['tradeType'] }).refine(input => input.provider !== 'wechat' || input.userId !== undefined, { path: ['userId'] })
|
|
22
18
|
|
|
19
|
+
export const POST: RequestHandler = async ({ request, locals }) => {
|
|
20
|
+
const { provider, tradeType, outTradeNo, totalAmount, subject, notifyUrl, userId, metadata } = await kit.validate.body(request, CreatePaymentSchema)
|
|
23
21
|
const result = await payment.createOrder(provider, {
|
|
24
22
|
orderNo: outTradeNo,
|
|
25
|
-
amount:
|
|
23
|
+
amount: totalAmount,
|
|
26
24
|
description: subject,
|
|
27
|
-
tradeType
|
|
25
|
+
tradeType,
|
|
28
26
|
userId,
|
|
29
27
|
metadata,
|
|
30
28
|
notifyUrl: notifyUrl ?? `${new URL(request.url).origin}/api/v1/payment/notify/${provider}`,
|
|
31
29
|
})
|
|
32
|
-
|
|
33
|
-
if (!result.success) {
|
|
34
|
-
return kit.response.internalError(result.error.message)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return kit.response.ok(result.data, locals.requestId)
|
|
30
|
+
return kit.response.fromResult(result, locals.requestId)
|
|
38
31
|
}
|
|
@@ -30,6 +30,10 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
|
|
30
30
|
return new Response('FAIL', { status: 500 })
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// 此模板仅确认已验签解析;success 不代表已付款,Stripe 延迟付款会返回 pending。
|
|
34
|
+
// 业务接入时须在响应成功前,用共享数据库核对订单/金额/币种,按 eventId 去重并防止状态倒退;
|
|
35
|
+
// 仅 status=paid 可触发履约,不在进程内 Set/Map 中维护通知去重。
|
|
36
|
+
|
|
33
37
|
// 微信支付要求返回 JSON,支付宝要求返回 "success"
|
|
34
38
|
if (provider === 'wechat') {
|
|
35
39
|
return new Response(JSON.stringify({ code: 'SUCCESS', message: '' }), {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createECDH } from 'node:crypto'
|
|
2
|
+
import { isAbsolute } from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 仅给 Playwright 的服务子进程装配环境;不修改测试进程和 pnpm/Turbo 的环境。
|
|
7
|
+
* @param profile - 本轮测试明确选择的模块配置和可信控制值。
|
|
8
|
+
* @returns Playwright webServer.env;undefined 项由 Node spawn 从子进程环境中移除。
|
|
9
|
+
* @throws 缺少绝对数据目录,或 API 指向非本轮 loopback HTTP 夹具时抛错。
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // 测试内部上下文;数据目录由 run-e2e 为本轮分配。
|
|
13
|
+
* const env = createE2eServerEnv({
|
|
14
|
+
* HAI_E2E_DATA_ROOT: process.cwd(),
|
|
15
|
+
* HAI_CACHE: JSON.stringify({ type: 'memory' }),
|
|
16
|
+
* })
|
|
17
|
+
* if (env.HAI_E2E !== '1') throw new Error('E2E profile was not applied')
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function createE2eServerEnv(profile: Record<string, string>): Record<string, string> {
|
|
21
|
+
const dataRoot = profile.HAI_E2E_DATA_ROOT || process.env.HAI_E2E_DATA_ROOT
|
|
22
|
+
if (!dataRoot || !isAbsolute(dataRoot))
|
|
23
|
+
throw new Error('Run E2E through the managed entrypoint with its own absolute HAI_E2E_DATA_ROOT')
|
|
24
|
+
if (profile.PUBLIC_API_BASE) {
|
|
25
|
+
const api = new URL(profile.PUBLIC_API_BASE)
|
|
26
|
+
if (api.protocol !== 'http:' || !['127.0.0.1', 'localhost', '[::1]'].includes(api.hostname) || api.username || api.password)
|
|
27
|
+
throw new Error('E2E API must use an unauthenticated loopback HTTP fixture')
|
|
28
|
+
}
|
|
29
|
+
const environment: Record<string, string | undefined> = Object.fromEntries(
|
|
30
|
+
Object.keys(process.env).filter(key => /^(?:HAI_|PUBLIC_|VITE_)/i.test(key)).map(key => [key, undefined]),
|
|
31
|
+
)
|
|
32
|
+
for (const name of ['HAI_E2E_DATA_ROOT', 'HAI_AUTH_EVIDENCE_DIR', 'HAI_CORE_NAME', 'HAI_CORE_VERSION']) {
|
|
33
|
+
if (process.env[name] !== undefined)
|
|
34
|
+
environment[name] = process.env[name]
|
|
35
|
+
}
|
|
36
|
+
Object.assign(environment, { HAI_E2E: '1', NODE_ENV: 'test', HAI_E2E_DATA_ROOT: dataRoot }, profile)
|
|
37
|
+
// Playwright 声明仅接受 string,但其 WebServer 合并 env 后直接交 Node spawn;
|
|
38
|
+
// 已用安装版真实 WebServer 验证 undefined 会删除继承项,因此仅在第三方类型边界使用窄断言。
|
|
39
|
+
return environment as Record<string, string>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 每轮生成独立可信 SM2 ring;私钥只传服务子进程,不进入公开配置或报告。
|
|
44
|
+
* @returns 符合 transport serverKeyRing 配置的 JSON 字符串。
|
|
45
|
+
* @throws 当前 Node/OpenSSL 不支持 SM2 时抛错,不降级为不可信握手。
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* // 测试内部上下文;仅交给本轮服务子进程。
|
|
49
|
+
* const env = createE2eServerEnv({
|
|
50
|
+
* HAI_E2E_DATA_ROOT: process.cwd(),
|
|
51
|
+
* HAI_CRYPTO_TRANSPORT_SERVERKEYRING: createE2eTransportRing(),
|
|
52
|
+
* })
|
|
53
|
+
* if (!env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING) throw new Error('Trusted ring is required')
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function createE2eTransportRing(): string {
|
|
57
|
+
const key = createECDH('SM2')
|
|
58
|
+
key.generateKeys()
|
|
59
|
+
return JSON.stringify({ activeKeyId: 'e2e-v1', keys: { 'e2e-v1': {
|
|
60
|
+
publicKey: key.getPublicKey('hex'),
|
|
61
|
+
privateKey: key.getPrivateKey('hex').padStart(64, '0'),
|
|
62
|
+
} } })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 只把既有的本轮 loopback 通知夹具控制地址转成明确 Reach 配置。
|
|
67
|
+
* @returns 未启用通知夹具时为空对象;启用时包含 OTP、模板及协议配置。
|
|
68
|
+
* @throws IAM_NOTIFICATION_URL 不是无凭据的 loopback HTTP 地址时抛错。
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* // 测试内部上下文;IAM_NOTIFICATION_URL 由样板门禁启动的通知夹具提供。
|
|
72
|
+
* const env = createE2eServerEnv({
|
|
73
|
+
* ...createE2eNotificationEnv(),
|
|
74
|
+
* HAI_E2E_DATA_ROOT: process.cwd(),
|
|
75
|
+
* })
|
|
76
|
+
* if (env.HAI_E2E !== '1') throw new Error('Notification profile was not isolated')
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function createE2eNotificationEnv(): Record<string, string> {
|
|
80
|
+
const address = process.env.IAM_NOTIFICATION_URL
|
|
81
|
+
if (!address)
|
|
82
|
+
return {}
|
|
83
|
+
const url = new URL(address)
|
|
84
|
+
if (url.protocol !== 'http:' || !['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname) || url.username || url.password)
|
|
85
|
+
throw new Error('E2E notification fixture must use an unauthenticated loopback HTTP address')
|
|
86
|
+
return {
|
|
87
|
+
HAI_IAM_LOGIN_OTP: 'true',
|
|
88
|
+
HAI_IAM_PASSWORDRESET_ENABLED: 'true',
|
|
89
|
+
HAI_REACH_PROVIDERS: JSON.stringify(['email', 'sms'].map(name => ({ name, type: 'api', url: new URL('/send', url).href }))),
|
|
90
|
+
HAI_REACH_TEMPLATES: JSON.stringify([
|
|
91
|
+
{ name: 'otp_email', provider: 'email', body: 'Code: {code}' },
|
|
92
|
+
{ name: 'otp_sms', provider: 'sms', body: 'Code: {code}' },
|
|
93
|
+
{ name: 'password_reset', provider: 'email', body: 'Reset: {token}' },
|
|
94
|
+
{ name: 'password_reset_sms', provider: 'sms', body: 'Reset: {token}' },
|
|
95
|
+
]),
|
|
96
|
+
HAI_IAM_AGREEMENTS: JSON.stringify({ showOnRegister: true, showOnLogin: true, userAgreementMarkdown: '# Configured registration agreement\n\n**Registration terms**\n\n- Keep your account credentials secure.', userAgreementUrl: new URL('/terms', url).href, privacyPolicyUrl: new URL('/privacy', url).href }),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** 运行浏览器验收,保留本轮证据;退出后仅回收本轮服务数据,失败和中断同样处理。 */
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
3
|
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
@@ -21,14 +21,29 @@ for (const argument of process.argv.slice(2)) {
|
|
|
21
21
|
}
|
|
22
22
|
let child
|
|
23
23
|
let interrupted = false
|
|
24
|
+
let stopping
|
|
25
|
+
/**
|
|
26
|
+
* 结束本轮 Playwright;Windows 同时结束其进程树,finally 等待该命令退出后才清理数据。
|
|
27
|
+
* @returns {void} 终止请求由 child 退出事件和 stopping Promise 确认。
|
|
28
|
+
* @example 模块内部信号处理
|
|
29
|
+
* ```js
|
|
30
|
+
* process.once('SIGTERM', stop)
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
24
33
|
function stop() {
|
|
25
34
|
if (interrupted)
|
|
26
35
|
return
|
|
27
36
|
interrupted = true
|
|
28
37
|
if (process.platform === 'win32' && child?.pid && child.exitCode === null) {
|
|
29
38
|
// Windows 的 kill 不传递 SIGTERM;同时结束本次 Playwright 创建的浏览器与服务进程。
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
stopping = new Promise((resolve) => {
|
|
40
|
+
const killer = spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' })
|
|
41
|
+
killer.once('error', () => {
|
|
42
|
+
child?.kill()
|
|
43
|
+
resolve()
|
|
44
|
+
})
|
|
45
|
+
killer.once('exit', resolve)
|
|
46
|
+
})
|
|
32
47
|
}
|
|
33
48
|
else {
|
|
34
49
|
child?.kill()
|
|
@@ -37,19 +52,37 @@ function stop() {
|
|
|
37
52
|
process.once('SIGINT', stop)
|
|
38
53
|
process.once('SIGTERM', stop)
|
|
39
54
|
try {
|
|
40
|
-
|
|
55
|
+
// 证据与临时业务数据分开;每组使用独立输出,后续 Playwright 不会清空前组证据。
|
|
56
|
+
const evidenceParent = resolve(process.env.HAI_AUTH_EVIDENCE_DIR || join(cwd, 'test-results'))
|
|
57
|
+
mkdirSync(evidenceParent, { recursive: true })
|
|
58
|
+
const runRoot = mkdtempSync(join(evidenceParent, 'run-'))
|
|
59
|
+
for (const [index, args] of groups.entries()) {
|
|
60
|
+
const groupRoot = join(runRoot, `group-${index + 1}`)
|
|
61
|
+
const groupDataRoot = join(dataRoot, `group-${index + 1}`)
|
|
62
|
+
mkdirSync(groupDataRoot)
|
|
63
|
+
const hasOutput = args.some(argument => argument === '--output' || argument.startsWith('--output='))
|
|
64
|
+
const outputArgs = hasOutput ? [] : ['--output', join(groupRoot, 'results')]
|
|
65
|
+
const html = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT
|
|
41
66
|
const code = await new Promise((resolve, reject) => {
|
|
42
|
-
child = spawn(process.execPath, [cli, 'test', ...args], {
|
|
67
|
+
child = spawn(process.execPath, [cli, 'test', ...args, ...outputArgs], {
|
|
43
68
|
cwd,
|
|
44
69
|
windowsHide: true,
|
|
45
70
|
stdio: 'inherit',
|
|
46
|
-
env: {
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
...(!html ? { PLAYWRIGHT_HTML_OUTPUT_DIR: join(groupRoot, 'html') } : {}),
|
|
74
|
+
HAI_AUTH_EVIDENCE_DIR: process.env.HAI_AUTH_EVIDENCE_DIR || join(groupRoot, 'evidence'),
|
|
75
|
+
HAI_E2E_DATA_ROOT: groupDataRoot,
|
|
76
|
+
},
|
|
47
77
|
})
|
|
48
78
|
child.once('error', reject)
|
|
49
79
|
child.once('exit', resolve)
|
|
50
80
|
})
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
// 配置组互相独立,普通失败继续收集;保留首个失败码,中断才停止后续组。
|
|
82
|
+
if (code !== 0)
|
|
83
|
+
process.exitCode ||= typeof code === 'number' && code !== 0 ? code : 1
|
|
84
|
+
if (interrupted) {
|
|
85
|
+
process.exitCode ||= 1
|
|
53
86
|
break
|
|
54
87
|
}
|
|
55
88
|
}
|
|
@@ -57,9 +90,15 @@ try {
|
|
|
57
90
|
finally {
|
|
58
91
|
process.removeListener('SIGINT', stop)
|
|
59
92
|
process.removeListener('SIGTERM', stop)
|
|
60
|
-
|
|
61
|
-
for (const name of ['test-results', 'playwright-report'])
|
|
62
|
-
rmSync(join(cwd, name), { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
93
|
+
await stopping
|
|
63
94
|
// Playwright 退出后服务进程已关闭,此时删除 SQLite / storage,避免 Windows 文件锁。
|
|
64
|
-
|
|
95
|
+
try {
|
|
96
|
+
rmSync(dataRoot, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
process.stderr.write(`E2E data cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
100
|
+
// 清理错误不能把原始失败(如 exit 7)覆盖成另一状态,也不能让成功变为假成功。
|
|
101
|
+
if (!process.exitCode)
|
|
102
|
+
process.exitCode = 1
|
|
103
|
+
}
|
|
65
104
|
}
|
|
@@ -75,3 +75,7 @@ pnpm test:e2e
|
|
|
75
75
|
- `.agents/skills/hai-app-review/SKILL.md`:应用代码审查。
|
|
76
76
|
- `.agents/skills/hai-app-tests/SKILL.md`:Vitest / Playwright 测试策略。
|
|
77
77
|
- 具体模块按需读取对应 `hai-*` Skill。
|
|
78
|
+
|
|
79
|
+
前端项目已交付对应技能时,先按 hai-ui-design 确定任务、布局、状态与组件映射,使用 hai-ui 实现,再由 hai-ui-review 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
|
|
80
|
+
|
|
81
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
- 页面组件、layout、load 只负责渲染、权限态拼装和请求编排;业务逻辑放在 `src/lib/server/services` 或模块 API。
|
|
40
40
|
- 认证授权使用 `@h-ai/iam` / `@h-ai/kit`;Web token 优先 httpOnly cookie。
|
|
41
|
-
- 所有用户可见文本走 `$lib/paraglide/messages.js`,同步 `messages/zh-CN.json
|
|
41
|
+
- 所有用户可见文本走 `$lib/paraglide/messages.js`,同步 `messages/zh-CN.json`、`messages/en-US.json`、`messages/ja-JP.json`。
|
|
42
42
|
- API 边界必须做 Zod 校验,HaiResult 错误用 kit response 转换。
|
|
43
43
|
- `+server.ts` 只能导出 handler 和 SvelteKit 允许的配置项;helper 放到 `src/lib/server/**`。
|
|
44
44
|
- 本样板默认不使用 `@h-ai/serv`、`@h-ai/api-contract`、`@h-ai/api-client`、`@h-ai/capacitor` 作为主架构。
|
|
@@ -81,3 +81,7 @@ pnpm test:e2e
|
|
|
81
81
|
- `hai-build`、`hai-app-create`、`hai-app-review`、`hai-app-tests`
|
|
82
82
|
- `hai-kit`、`hai-ui`、`hai-iam`、`hai-reldb`、`hai-cache`、`hai-crypto`
|
|
83
83
|
- 其它 `hai-*` 模块按需读取
|
|
84
|
+
|
|
85
|
+
前端页面先按 [hai-ui-design](.agents/skills/hai-ui-design/SKILL.md) 确定任务、布局、状态与组件映射,使用 [hai-ui](.agents/skills/hai-ui/SKILL.md) 实现,再由 [hai-ui-review](.agents/skills/hai-ui-review/SKILL.md) 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
|
|
86
|
+
|
|
87
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|
|
@@ -77,3 +77,5 @@ pnpm test:e2e
|
|
|
77
77
|
- `hai-build`、`hai-app-create`、`hai-app-review`、`hai-app-tests`
|
|
78
78
|
- `hai-serv`、`hai-api-contract`、`hai-api-client`
|
|
79
79
|
- `hai-core`、`hai-reldb`、`hai-cache`、`hai-iam`、`hai-crypto`
|
|
80
|
+
|
|
81
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|
|
@@ -94,3 +94,7 @@ pnpm test:e2e
|
|
|
94
94
|
## 优先 Skills
|
|
95
95
|
|
|
96
96
|
构建与验收:`hai-build`、`hai-app-review`、`hai-app-tests`。管理业务按需读取 `hai-iam`、`hai-reldb`、`hai-cache`、`hai-audit`、`hai-storage`、`hai-crypto`;配置与跨端读取 `hai-core`、`hai-ui`、`hai-serv`、`hai-api-contract`、`hai-api-client`、`hai-capacitor`。
|
|
97
|
+
|
|
98
|
+
前端页面先按 [hai-ui-design](.agents/skills/hai-ui-design/SKILL.md) 确定任务、布局、状态与组件映射,使用 [hai-ui](.agents/skills/hai-ui/SKILL.md) 实现,再由 [hai-ui-review](.agents/skills/hai-ui-review/SKILL.md) 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
|
|
99
|
+
|
|
100
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
## 架构边界
|
|
38
38
|
|
|
39
39
|
- 使用移动端友好的布局和 `@h-ai/ui` 移动组件。
|
|
40
|
-
- 页面文本走 i18n
|
|
40
|
+
- 页面文本走 i18n,同步中英日文 messages。
|
|
41
41
|
- API 输入必须校验,业务错误按 HaiResult 传递。
|
|
42
42
|
- `+server.ts` 只能导出 handler 和 SvelteKit 允许的配置项;helper 放到 `src/lib/**`。
|
|
43
43
|
- 敏感 token 不要写入不安全存储;优先复用模板已有的鉴权与请求模式。
|
|
@@ -80,3 +80,7 @@ pnpm test:e2e
|
|
|
80
80
|
- `hai-build`、`hai-app-create`、`hai-app-review`、`hai-app-tests`
|
|
81
81
|
- `hai-kit`、`hai-ui`、`hai-reldb`、`hai-cache`
|
|
82
82
|
- 其它 `hai-*` 模块按需读取
|
|
83
|
+
|
|
84
|
+
前端页面先按 [hai-ui-design](.agents/skills/hai-ui-design/SKILL.md) 确定任务、布局、状态与组件映射,使用 [hai-ui](.agents/skills/hai-ui/SKILL.md) 实现,再由 [hai-ui-review](.agents/skills/hai-ui-review/SKILL.md) 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
|
|
85
|
+
|
|
86
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
- 需求迁移到 SvelteKit 时同步运行环境、路由、初始化、构建与原生壳集成;不能仅添加 `+page.svelte` 等文件就视为完成迁移。
|
|
43
43
|
- 原生能力通过 `@h-ai/capacitor` 使用;token 原生端使用 Capacitor 安全存储,Web 预览仅使用内存存储。
|
|
44
44
|
- API 调用使用 `@h-ai/api-client`,业务错误按 HaiResult 处理。
|
|
45
|
-
- 页面文本走 i18n
|
|
45
|
+
- 页面文本走 i18n,同步中英日文 messages。
|
|
46
46
|
- 原生桥接与设备调用放在 `src/lib/capacitor.ts` 或 service,避免散落在页面模板中。
|
|
47
47
|
- 本样板默认不使用 `@h-ai/core`、`@h-ai/kit`、`@h-ai/serv`、`@h-ai/api-contract` 作为主架构。
|
|
48
48
|
|
|
@@ -92,3 +92,7 @@ pnpm test:e2e
|
|
|
92
92
|
- `hai-build`、`hai-app-review`、`hai-app-tests`
|
|
93
93
|
- `hai-ui`、`hai-api-client`、`hai-capacitor`
|
|
94
94
|
- 其它 `hai-*` 模块按需读取
|
|
95
|
+
|
|
96
|
+
前端页面先按 [hai-ui-design](.agents/skills/hai-ui-design/SKILL.md) 确定任务、布局、状态与组件映射,使用 [hai-ui](.agents/skills/hai-ui/SKILL.md) 实现,再由 [hai-ui-review](.agents/skills/hai-ui-review/SKILL.md) 检查真实页面。窄范围修改复用已有设计;按实际语言/主题/尺寸验证。
|
|
97
|
+
|
|
98
|
+
工作流技能按 hai-领域-动作 命名,能力使用类沿用领域名。需求、验收、发现与当前版本证据按 [工作交接契约](.agents/skills/hai-spec-implement/handoff.md) 传递,沿既有清单恢复任务。
|