@h-ai/cli 0.1.0-alpha.20 → 0.1.0-alpha.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.md +10 -7
- package/dist/{chunk-GSMXGUYF.js → chunk-5IGMJVV5.js} +56 -38
- package/dist/chunk-5IGMJVV5.js.map +1 -0
- package/dist/{chunk-3BBXDQ4Y.js → chunk-CFCXOKGV.js} +24 -2
- package/dist/chunk-CFCXOKGV.js.map +1 -0
- package/dist/cli-config-templates-5J5VKWWD.js +5 -0
- package/dist/{cli-config-templates-IGGO7P45.js.map → cli-config-templates-5J5VKWWD.js.map} +1 -1
- package/dist/cli-skill-templates-JMSV2SVW.js +5 -0
- package/dist/{cli-skill-templates-IMYZPQRG.js.map → cli-skill-templates-JMSV2SVW.js.map} +1 -1
- package/dist/index.js +567 -75
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/templates/README.md +40 -29
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +16 -0
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +38 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-contract.ts.hbs +27 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +33 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +3 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +12 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +18 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tsconfig.json.hbs +12 -0
- package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +11 -0
- package/templates/apps/api/apps/{{projectName}}-contract/vitest.config.ts.hbs +9 -0
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +17 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +19 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +26 -0
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +55 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +98 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +21 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +184 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs +49 -0
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +18 -0
- package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +168 -0
- package/templates/apps/api/apps/{{projectName}}-service/tsconfig.json.hbs +16 -0
- package/templates/apps/api/apps/{{projectName}}-service/tsup.config.ts.hbs +11 -0
- package/templates/apps/api/apps/{{projectName}}-service/vitest.config.ts.hbs +9 -0
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +18 -0
- package/templates/apps/api/eslint.config.js +32 -0
- package/templates/apps/api/package.json.hbs +29 -0
- package/templates/apps/api/playwright.config.ts.hbs +58 -0
- package/templates/apps/api/pnpm-workspace.yaml.hbs +14 -0
- package/templates/apps/api/tsconfig.base.json +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/.env.example.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -7
- package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +18 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +16 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/en-US.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/zh-CN.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +51 -29
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +186 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.css.hbs +2 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.d.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +18 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/main.ts.hbs +14 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +1 -5
- package/templates/apps/fullstack/apps/{{projectName}}-app/tsconfig.json.hbs +10 -4
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +4 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/.env.example.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +20 -6
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/index.html.hbs +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +33 -30
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +152 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.d.ts.hbs +1 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/main.ts.hbs +13 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/Cargo.toml.hbs +22 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/build.rs.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/capabilities/default.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/128x128@2x.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/32x32.png +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.icns +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/icons/icon.ico +0 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +16 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/main.rs.hbs +8 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +41 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +2 -8
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/tsconfig.json.hbs +9 -4
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +44 -21
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +15 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +15 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +25 -27
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/App.svelte.hbs +153 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.d.ts.hbs +0 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +14 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +1 -5
- package/templates/apps/fullstack/apps/{{projectName}}-web/tsconfig.json.hbs +10 -4
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +2 -1
- package/templates/apps/fullstack/eslint.config.js +0 -1
- package/templates/apps/fullstack/package.json.hbs +18 -17
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/{server-app.test.ts.hbs → app-server.test.ts.hbs} +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +9 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/index.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +5 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ThemeSwitcher.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tsconfig.json.hbs +1 -1
- package/templates/apps/fullstack/playwright.config.ts.hbs +41 -4
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +15 -0
- package/templates/apps/mobile-app/.env.hbs +3 -0
- package/templates/apps/{android-app → mobile-app}/capacitor.config.ts.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +13 -0
- package/templates/apps/mobile-app/messages/en-US.json +36 -0
- package/templates/apps/mobile-app/messages/zh-CN.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +162 -0
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +54 -0
- package/templates/apps/{android-app → mobile-app}/src/lib/capacitor.ts +5 -6
- package/templates/apps/mobile-app/src/main.ts.hbs +11 -0
- package/templates/base/.gitignore.hbs +16 -0
- package/templates/base/{eslint.config.js → eslint.config.js.hbs} +4 -1
- package/templates/base/package.json.hbs +52 -33
- package/templates/base/playwright.config.ts +30 -3
- package/templates/base/pnpm-workspace.yaml.hbs +14 -0
- package/templates/base/src/app.d.ts.hbs +3 -0
- package/templates/base/src/app.html.hbs +3 -0
- package/templates/base/svelte.config.js.hbs +43 -3
- package/templates/base/tests/scaffold.test.ts.hbs +7 -0
- package/templates/base/tsconfig.json.hbs +50 -0
- package/templates/base/vite.config.ts.hbs +10 -0
- package/templates/skills/AGENTS.md +57 -24
- package/templates/skills/CLAUDE.md +18 -7
- package/templates/skills/bridges/admin/AGENTS.md +66 -0
- package/templates/skills/bridges/admin/CLAUDE.md +19 -0
- package/templates/skills/bridges/admin/copilot-instructions.md +46 -0
- package/templates/skills/bridges/api/AGENTS.md +65 -0
- package/templates/skills/bridges/api/CLAUDE.md +19 -0
- package/templates/skills/bridges/api/copilot-instructions.md +46 -0
- package/templates/skills/bridges/fullstack/AGENTS.md +84 -0
- package/templates/skills/bridges/fullstack/CLAUDE.md +19 -0
- package/templates/skills/bridges/fullstack/copilot-instructions.md +54 -0
- package/templates/skills/bridges/h5/AGENTS.md +65 -0
- package/templates/skills/bridges/h5/CLAUDE.md +19 -0
- package/templates/skills/bridges/h5/copilot-instructions.md +44 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +77 -0
- package/templates/skills/bridges/mobile-app/CLAUDE.md +19 -0
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +45 -0
- package/templates/skills/bridges/website/AGENTS.md +65 -0
- package/templates/skills/bridges/website/CLAUDE.md +19 -0
- package/templates/skills/bridges/website/copilot-instructions.md +44 -0
- package/templates/skills/copilot-instructions.md +27 -64
- package/templates/skills/hai-app-create/SKILL.md +1 -0
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-app-tests/SKILL.md +28 -3
- package/templates/skills/hai-capacitor/SKILL.md +14 -22
- package/templates/skills/opencode.json +1 -6
- package/dist/chunk-3BBXDQ4Y.js.map +0 -1
- package/dist/chunk-GSMXGUYF.js.map +0 -1
- package/dist/cli-config-templates-IGGO7P45.js +0 -5
- package/dist/cli-skill-templates-IMYZPQRG.js +0 -5
- package/templates/apps/android-app/.env.hbs +0 -3
- package/templates/apps/android-app/messages/en-US.json +0 -24
- package/templates/apps/android-app/messages/zh-CN.json +0 -24
- package/templates/apps/android-app/src/lib/api.ts.hbs +0 -32
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +0 -43
- package/templates/apps/android-app/src/routes/+layout.ts +0 -7
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +0 -38
- package/templates/apps/api/src/routes/+layout.svelte +0 -14
- package/templates/apps/api/src/routes/+page.svelte +0 -11
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +0 -14
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +0 -37
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +0 -30
- package/templates/apps/api/tests/health.test.ts.hbs +0 -26
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/app.html.hbs +0 -14
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+layout.svelte.hbs +0 -18
- package/templates/apps/fullstack/apps/{{projectName}}-web/src/routes/+page.svelte.hbs +0 -162
- package/templates/apps/fullstack/pnpm-workspace.yaml +0 -7
- package/templates/base/tsconfig.json +0 -20
- /package/templates/apps/fullstack/packages/{{projectName}}-serv/src/{server-app.ts.hbs → app-server.ts.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/{theme.svelte.ts.hbs → theme-store.svelte.ts.hbs} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
{{#if features.ai}}import type { AIConfigInput } from '@h-ai/ai'
|
|
2
|
+
{{/if}}{{#if features.cache}}import type { CacheConfigInput } from '@h-ai/cache'
|
|
3
|
+
{{/if}}{{#if features.iam}}import type { IamConfigInput } from '@h-ai/iam'
|
|
4
|
+
{{/if}}{{#if features.crypto}}import type { ServConfig } from '@h-ai/serv'
|
|
5
|
+
{{/if}}{{#if features.storage}}import type { StorageConfigInput } from '@h-ai/storage'
|
|
6
|
+
{{/if}}{{#if features.ai}}import { ai, AIConfigSchema } from '@h-ai/ai'
|
|
7
|
+
{{/if}}{{#if features.cache}}import { cache, CacheConfigSchema } from '@h-ai/cache'
|
|
8
|
+
{{/if}}import { core } from '@h-ai/core'
|
|
9
|
+
{{#if features.crypto}}import { crypto } from '@h-ai/crypto'
|
|
10
|
+
{{/if}}{{#if features.iam}}import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
11
|
+
{{/if}}{{#if features.db}}import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
12
|
+
{{/if}}import { ServConfigSchema } from '@h-ai/serv'
|
|
13
|
+
{{#if features.storage}}import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
14
|
+
{{/if}}{{#if features.vecdb}}import { vecdb, VecdbConfigSchema } from '@h-ai/vecdb'
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
{{#if features.db}}type DbConfigInput = Parameters<typeof reldb.init>[0]
|
|
18
|
+
{{/if}}{{#if features.vecdb}}type VecdbConfigInput = Parameters<typeof vecdb.init>[0]
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
let initialized = false
|
|
22
|
+
let initPromise: Promise<void> | null = null
|
|
23
|
+
{{#if features.crypto}}let cryptoInitialized = false
|
|
24
|
+
{{/if}}
|
|
25
|
+
|
|
26
|
+
export async function initApp(): Promise<void> {
|
|
27
|
+
if (initialized)
|
|
28
|
+
return
|
|
29
|
+
if (initPromise)
|
|
30
|
+
return initPromise
|
|
31
|
+
|
|
32
|
+
initPromise = doInit()
|
|
33
|
+
try {
|
|
34
|
+
await initPromise
|
|
35
|
+
initialized = true
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
initPromise = null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function doInit(): Promise<void> {
|
|
43
|
+
core.init({
|
|
44
|
+
configDir: './config',
|
|
45
|
+
logging: { level: 'info' },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const servValidation = core.config.validate('serv', ServConfigSchema)
|
|
49
|
+
if (!servValidation.success) {
|
|
50
|
+
throw new Error(`Serv config invalid: ${servValidation.error.message}`)
|
|
51
|
+
}
|
|
52
|
+
{{#if features.crypto}}
|
|
53
|
+
const servConfig = core.config.getOrThrow<ServConfig>('serv')
|
|
54
|
+
if (servConfig.transport !== false) {
|
|
55
|
+
const cryptoResult = await crypto.init()
|
|
56
|
+
if (!cryptoResult.success) {
|
|
57
|
+
throw new Error(`Crypto initialization failed: ${cryptoResult.error.message}`)
|
|
58
|
+
}
|
|
59
|
+
cryptoInitialized = true
|
|
60
|
+
}
|
|
61
|
+
{{/if}}{{#if features.db}}
|
|
62
|
+
|
|
63
|
+
const dbValidation = core.config.validate('db', ReldbConfigSchema)
|
|
64
|
+
if (!dbValidation.success) {
|
|
65
|
+
throw new Error(`DB config invalid: ${dbValidation.error.message}`)
|
|
66
|
+
}
|
|
67
|
+
{{/if}}{{#if features.cache}}
|
|
68
|
+
|
|
69
|
+
const cacheValidation = core.config.validate('cache', CacheConfigSchema)
|
|
70
|
+
if (!cacheValidation.success) {
|
|
71
|
+
throw new Error(`Cache config invalid: ${cacheValidation.error.message}`)
|
|
72
|
+
}
|
|
73
|
+
{{/if}}{{#if features.vecdb}}
|
|
74
|
+
|
|
75
|
+
const vecdbValidation = core.config.validate('vecdb', VecdbConfigSchema)
|
|
76
|
+
if (!vecdbValidation.success) {
|
|
77
|
+
throw new Error(`VecDB config invalid: ${vecdbValidation.error.message}`)
|
|
78
|
+
}
|
|
79
|
+
{{/if}}{{#if features.iam}}
|
|
80
|
+
|
|
81
|
+
const iamValidation = core.config.validate('iam', IamConfigSchema)
|
|
82
|
+
if (!iamValidation.success) {
|
|
83
|
+
throw new Error(`IAM config invalid: ${iamValidation.error.message}`)
|
|
84
|
+
}
|
|
85
|
+
{{/if}}{{#if features.storage}}
|
|
86
|
+
|
|
87
|
+
const storageValidation = core.config.validate('storage', StorageConfigSchema)
|
|
88
|
+
if (!storageValidation.success) {
|
|
89
|
+
throw new Error(`Storage config invalid: ${storageValidation.error.message}`)
|
|
90
|
+
}
|
|
91
|
+
{{/if}}{{#if features.ai}}
|
|
92
|
+
|
|
93
|
+
const aiValidation = core.config.validate('ai', AIConfigSchema)
|
|
94
|
+
if (!aiValidation.success) {
|
|
95
|
+
throw new Error(`AI config invalid: ${aiValidation.error.message}`)
|
|
96
|
+
}
|
|
97
|
+
{{/if}}{{#if features.db}}
|
|
98
|
+
|
|
99
|
+
const dbConfig = core.config.getOrThrow<DbConfigInput>('db')
|
|
100
|
+
{{/if}}{{#if features.cache}} const cacheConfig = core.config.getOrThrow<CacheConfigInput>('cache')
|
|
101
|
+
{{/if}}{{#if features.vecdb}} const vecdbConfig = core.config.getOrThrow<VecdbConfigInput>('vecdb')
|
|
102
|
+
{{/if}}{{#if features.iam}} const iamConfig = core.config.getOrThrow<IamConfigInput>('iam')
|
|
103
|
+
{{/if}}{{#if features.storage}} const storageConfig = core.config.getOrThrow<StorageConfigInput>('storage')
|
|
104
|
+
{{/if}}{{#if features.ai}} const aiConfig = core.config.getOrThrow<AIConfigInput>('ai')
|
|
105
|
+
{{/if}}{{#if features.db}}
|
|
106
|
+
|
|
107
|
+
if (dbConfig.type === 'sqlite') {
|
|
108
|
+
const fs = await import('node:fs')
|
|
109
|
+
const path = await import('node:path')
|
|
110
|
+
const dbDir = path.dirname(dbConfig.database)
|
|
111
|
+
if (!fs.existsSync(dbDir)) {
|
|
112
|
+
fs.mkdirSync(dbDir, { recursive: true })
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const dbResult = await reldb.init(dbConfig)
|
|
117
|
+
if (!dbResult.success) {
|
|
118
|
+
throw new Error(`Database initialization failed: ${dbResult.error.message}`)
|
|
119
|
+
}
|
|
120
|
+
{{/if}}{{#if features.cache}}
|
|
121
|
+
|
|
122
|
+
const cacheResult = await cache.init(cacheConfig)
|
|
123
|
+
if (!cacheResult.success) {
|
|
124
|
+
throw new Error(`Cache initialization failed: ${cacheResult.error.message}`)
|
|
125
|
+
}
|
|
126
|
+
{{/if}}{{#if features.vecdb}}
|
|
127
|
+
|
|
128
|
+
const vecdbResult = await vecdb.init(vecdbConfig)
|
|
129
|
+
if (!vecdbResult.success) {
|
|
130
|
+
throw new Error(`VecDB initialization failed: ${vecdbResult.error.message}`)
|
|
131
|
+
}
|
|
132
|
+
{{/if}}{{#if features.iam}}
|
|
133
|
+
|
|
134
|
+
const iamResult = await iam.init(iamConfig)
|
|
135
|
+
if (!iamResult.success) {
|
|
136
|
+
throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
|
|
137
|
+
}
|
|
138
|
+
{{/if}}{{#if features.storage}}
|
|
139
|
+
|
|
140
|
+
const storageResult = await storage.init(storageConfig)
|
|
141
|
+
if (!storageResult.success) {
|
|
142
|
+
throw new Error(`Storage initialization failed: ${storageResult.error.message}`)
|
|
143
|
+
}
|
|
144
|
+
{{/if}}{{#if features.ai}}
|
|
145
|
+
|
|
146
|
+
const aiResult = await ai.init(aiConfig)
|
|
147
|
+
if (!aiResult.success) {
|
|
148
|
+
throw new Error(`AI initialization failed: ${aiResult.error.message}`)
|
|
149
|
+
}
|
|
150
|
+
{{/if}}
|
|
151
|
+
|
|
152
|
+
core.logger.info('API service initialized')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function closeApp(): Promise<void> {
|
|
156
|
+
if (!initialized && !initPromise)
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
const closers: Array<readonly [string, () => unknown]> = [
|
|
160
|
+
{{#if features.ai}} ['ai', () => ai.close()],
|
|
161
|
+
{{/if}}{{#if features.storage}} ['storage', () => storage.close()],
|
|
162
|
+
{{/if}}{{#if features.iam}} ['iam', () => iam.close()],
|
|
163
|
+
{{/if}}{{#if features.vecdb}} ['vecdb', () => vecdb.close()],
|
|
164
|
+
{{/if}}{{#if features.cache}} ['cache', () => cache.close()],
|
|
165
|
+
{{/if}}{{#if features.db}} ['reldb', () => reldb.close()],
|
|
166
|
+
{{/if}} ]
|
|
167
|
+
{{#if features.crypto}} if (cryptoInitialized) {
|
|
168
|
+
closers.push(['crypto', () => crypto.close()])
|
|
169
|
+
}
|
|
170
|
+
{{/if}}
|
|
171
|
+
|
|
172
|
+
for (const [name, close] of closers) {
|
|
173
|
+
try {
|
|
174
|
+
await close()
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
core.logger.warn(`Module close failed: ${name}`, { error })
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
initialized = false
|
|
182
|
+
{{#if features.crypto}} cryptoInitialized = false
|
|
183
|
+
{{/if}} core.logger.info('API service closed')
|
|
184
|
+
}
|
package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/app-procedures.ts.hbs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ServContext } from '@h-ai/serv'
|
|
2
|
+
{{#if features.iam}}import type { AppEchoInput, AppEchoOutputData, AppInfoOutputData } from '{{api.contractPackageName}}'
|
|
3
|
+
{{else}}import type { AppEchoOutputData, AppInfoOutputData } from '{{api.contractPackageName}}'
|
|
4
|
+
{{/if}}
|
|
5
|
+
import { ok } from '@h-ai/core'
|
|
6
|
+
import { serv } from '@h-ai/serv'
|
|
7
|
+
import { appContract } from '{{api.contractPackageName}}'
|
|
8
|
+
|
|
9
|
+
export interface AppProcedureDeps {
|
|
10
|
+
readonly info: {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly version: string
|
|
13
|
+
readonly transportEnabled: boolean
|
|
14
|
+
}
|
|
15
|
+
readonly startedAt: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const p = serv.implement(appContract).$context<ServContext>()
|
|
19
|
+
|
|
20
|
+
/** 创建应用自有 procedures。 */
|
|
21
|
+
export function createAppProcedures(deps: AppProcedureDeps) {
|
|
22
|
+
return p.router({
|
|
23
|
+
info: p.info.handler(() => {
|
|
24
|
+
const data: AppInfoOutputData = {
|
|
25
|
+
name: deps.info.name,
|
|
26
|
+
version: deps.info.version,
|
|
27
|
+
transportEnabled: deps.info.transportEnabled,
|
|
28
|
+
uptimeMs: Math.max(0, Date.now() - deps.startedAt),
|
|
29
|
+
}
|
|
30
|
+
return ok(data)
|
|
31
|
+
}),
|
|
32
|
+
echo: {{#if features.iam}}p.echo.handler(serv.requireAuth<AppEchoInput, AppEchoOutputData>(({ input, context }) => {
|
|
33
|
+
const data: AppEchoOutputData = {
|
|
34
|
+
message: input.message,
|
|
35
|
+
userId: context.session!.userId,
|
|
36
|
+
requestId: context.requestId,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
}
|
|
39
|
+
return Promise.resolve(ok(data))
|
|
40
|
+
})){{else}}p.echo.handler(({ input, context }) => {
|
|
41
|
+
const data: AppEchoOutputData = {
|
|
42
|
+
message: input.message,
|
|
43
|
+
requestId: context.requestId,
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
}
|
|
46
|
+
return ok(data)
|
|
47
|
+
}){{/if}},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AppProcedureDeps } from './app-procedures.js'
|
|
2
|
+
{{#if features.ai}}import { ai } from '@h-ai/ai'
|
|
3
|
+
{{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
|
|
4
|
+
{{/if}}{{#if features.ai}}import { createAiProcedures } from '@h-ai/serv/features/ai'
|
|
5
|
+
{{/if}}{{#if features.iam}}import { createIamProcedures } from '@h-ai/serv/features/iam'
|
|
6
|
+
{{/if}}{{#if features.storage}}import { createStorageProcedures } from '@h-ai/serv/features/storage'
|
|
7
|
+
import { storage } from '@h-ai/storage'
|
|
8
|
+
{{/if}}import { createAppProcedures } from './app-procedures.js'
|
|
9
|
+
|
|
10
|
+
/** 创建应用级 procedures 聚合。 */
|
|
11
|
+
export function {{api.serviceProceduresFactoryName}}(deps: { app: AppProcedureDeps }) {
|
|
12
|
+
return {
|
|
13
|
+
{{#if features.iam}} iam: createIamProcedures({ iam }),
|
|
14
|
+
{{/if}}{{#if features.storage}} storage: createStorageProcedures({ storage }),
|
|
15
|
+
{{/if}}{{#if features.ai}} ai: createAiProcedures({ ai }),
|
|
16
|
+
{{/if}} app: createAppProcedures(deps.app),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { AddressInfo } from 'node:net'
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
{{#if features.ai}}import { ai } from '@h-ai/ai'
|
|
6
|
+
{{/if}}import { apiClient } from '@h-ai/api-client'
|
|
7
|
+
{{#if features.cache}}import { cache } from '@h-ai/cache'
|
|
8
|
+
{{/if}}import { core } from '@h-ai/core'
|
|
9
|
+
{{#if features.iam}}import { iam } from '@h-ai/iam'
|
|
10
|
+
{{/if}}{{#if features.db}}import { reldb } from '@h-ai/reldb'
|
|
11
|
+
{{/if}}import { serv } from '@h-ai/serv'
|
|
12
|
+
{{#if features.storage}}import { storage } from '@h-ai/storage'
|
|
13
|
+
{{/if}}{{#if features.vecdb}}import { vecdb } from '@h-ai/vecdb'
|
|
14
|
+
{{/if}}import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
16
|
+
|
|
17
|
+
import { {{api.serviceAppFactoryName}} } from '../src/app.js'
|
|
18
|
+
|
|
19
|
+
let tempRoot = ''
|
|
20
|
+
let baseUrl = ''
|
|
21
|
+
let client: ReturnType<typeof apiClient.create<typeof {{api.contractExportName}}>>
|
|
22
|
+
let server: ReturnType<typeof serv.listen>
|
|
23
|
+
{{#if features.storage}}let storageRoot = ''
|
|
24
|
+
{{/if}}{{#if features.vecdb}}let vecdbRoot = ''
|
|
25
|
+
{{/if}}
|
|
26
|
+
|
|
27
|
+
{{#if features.iam}}
|
|
28
|
+
function uniqueUser(prefix = 'api') {
|
|
29
|
+
const suffix = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`.slice(-8)
|
|
30
|
+
return {
|
|
31
|
+
username: `${prefix}_${suffix}`,
|
|
32
|
+
password: 'TestPass123',
|
|
33
|
+
email: `${prefix}_${suffix}@test.local`,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
{{/if}}
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
tempRoot = mkdtempSync(join(tmpdir(), '{{projectName}}-service-test-'))
|
|
40
|
+
{{#if features.storage}} storageRoot = join(tempRoot, 'storage')
|
|
41
|
+
{{/if}}{{#if features.vecdb}} vecdbRoot = join(tempRoot, 'vecdb')
|
|
42
|
+
{{/if}}
|
|
43
|
+
|
|
44
|
+
core.init({
|
|
45
|
+
configDir: './config',
|
|
46
|
+
logging: { level: 'warn' },
|
|
47
|
+
})
|
|
48
|
+
{{#if features.db}}
|
|
49
|
+
|
|
50
|
+
const dbResult = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
51
|
+
if (!dbResult.success)
|
|
52
|
+
throw new Error(`reldb: ${dbResult.error.message}`)
|
|
53
|
+
{{/if}}{{#if features.cache}}
|
|
54
|
+
|
|
55
|
+
const cacheResult = await cache.init({ type: 'memory' })
|
|
56
|
+
if (!cacheResult.success)
|
|
57
|
+
throw new Error(`cache: ${cacheResult.error.message}`)
|
|
58
|
+
{{/if}}{{#if features.vecdb}}
|
|
59
|
+
|
|
60
|
+
const vecdbResult = await vecdb.init({ type: 'lancedb', path: vecdbRoot })
|
|
61
|
+
if (!vecdbResult.success)
|
|
62
|
+
throw new Error(`vecdb: ${vecdbResult.error.message}`)
|
|
63
|
+
{{/if}}{{#if features.iam}}
|
|
64
|
+
|
|
65
|
+
const iamResult = await iam.init({})
|
|
66
|
+
if (!iamResult.success)
|
|
67
|
+
throw new Error(`iam: ${iamResult.error.message}`)
|
|
68
|
+
{{/if}}{{#if features.storage}}
|
|
69
|
+
|
|
70
|
+
const storageResult = await storage.init({ type: 'local', root: storageRoot })
|
|
71
|
+
if (!storageResult.success)
|
|
72
|
+
throw new Error(`storage: ${storageResult.error.message}`)
|
|
73
|
+
{{/if}}{{#if features.ai}}
|
|
74
|
+
|
|
75
|
+
const aiResult = await ai.init({})
|
|
76
|
+
if (!aiResult.success)
|
|
77
|
+
throw new Error(`ai: ${aiResult.error.message}`)
|
|
78
|
+
{{/if}}
|
|
79
|
+
|
|
80
|
+
const app = {{api.serviceAppFactoryName}}({
|
|
81
|
+
transport: 'disabled',{{#if features.iam}}
|
|
82
|
+
refreshCookie: 'disabled',{{/if}}
|
|
83
|
+
})
|
|
84
|
+
const listening = new Promise<AddressInfo>((resolve) => {
|
|
85
|
+
server = serv.listen(app, {
|
|
86
|
+
host: '127.0.0.1',
|
|
87
|
+
port: 0,
|
|
88
|
+
onListening: info => resolve(info as AddressInfo),
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
const address = await listening
|
|
92
|
+
baseUrl = `http://127.0.0.1:${address.port}`
|
|
93
|
+
|
|
94
|
+
client = apiClient.create({{api.contractExportName}})
|
|
95
|
+
const initResult = await client.init({
|
|
96
|
+
baseUrl: `${baseUrl}/api/v1`,
|
|
97
|
+
auth: { storage: apiClient.tokenStorage.memory() },
|
|
98
|
+
})
|
|
99
|
+
if (!initResult.success)
|
|
100
|
+
throw new Error(`client.init: ${initResult.error.message}`)
|
|
101
|
+
}, 30_000)
|
|
102
|
+
|
|
103
|
+
afterAll(async () => {
|
|
104
|
+
await client?.close()
|
|
105
|
+
await server?.close()
|
|
106
|
+
{{#if features.ai}} await ai.close()
|
|
107
|
+
{{/if}}{{#if features.storage}} await storage.close()
|
|
108
|
+
{{/if}}{{#if features.iam}} await iam.close()
|
|
109
|
+
{{/if}}{{#if features.vecdb}} await vecdb.close()
|
|
110
|
+
{{/if}}{{#if features.cache}} await cache.close()
|
|
111
|
+
{{/if}}{{#if features.db}} await reldb.close()
|
|
112
|
+
{{/if}} rmSync(tempRoot, { recursive: true, force: true })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('{{api.servicePackageName}}', () => {
|
|
116
|
+
it('returns health status', async () => {
|
|
117
|
+
const response = await fetch(`${baseUrl}/health`)
|
|
118
|
+
|
|
119
|
+
expect(response.status).toBe(200)
|
|
120
|
+
await expect(response.json()).resolves.toEqual({ status: 'ok' })
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('returns app info as HaiResult', async () => {
|
|
124
|
+
const result = await client.app.info()
|
|
125
|
+
|
|
126
|
+
expect(result.success).toBe(true)
|
|
127
|
+
if (result.success) {
|
|
128
|
+
expect(result.data.name).toContain('{{projectName}}')
|
|
129
|
+
expect(typeof result.data.transportEnabled).toBe('boolean')
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('validates echo input', async () => {
|
|
134
|
+
const result = await client.app.echo({ message: '' })
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(false)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
{{#if features.iam}}
|
|
140
|
+
it('registers a user and returns authenticated echo context', async () => {
|
|
141
|
+
const user = uniqueUser('svc')
|
|
142
|
+
const register = await client.iam.auth.register({
|
|
143
|
+
username: user.username,
|
|
144
|
+
password: user.password,
|
|
145
|
+
email: user.email,
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
expect(register.success).toBe(true)
|
|
149
|
+
|
|
150
|
+
const result = await client.app.echo({ message: 'hello unit' })
|
|
151
|
+
expect(result.success).toBe(true)
|
|
152
|
+
if (result.success) {
|
|
153
|
+
expect(result.data.message).toBe('hello unit')
|
|
154
|
+
expect(result.data.userId).toBeTruthy()
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
{{else}}
|
|
158
|
+
it('returns echo result as HaiResult', async () => {
|
|
159
|
+
const result = await client.app.echo({ message: 'hello unit' })
|
|
160
|
+
|
|
161
|
+
expect(result.success).toBe(true)
|
|
162
|
+
if (result.success) {
|
|
163
|
+
expect(result.data.message).toBe('hello unit')
|
|
164
|
+
expect(result.data.userId).toBeUndefined()
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
{{/if}}
|
|
168
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"types": ["node"],
|
|
6
|
+
"verbatimModuleSyntax": true,
|
|
7
|
+
"skipLibCheck": true
|
|
8
|
+
},
|
|
9
|
+
"include": [
|
|
10
|
+
"src/**/*.ts"
|
|
11
|
+
],
|
|
12
|
+
"exclude": [
|
|
13
|
+
"node_modules",
|
|
14
|
+
"dist"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
test('service health endpoint returns ok', async ({ request }) => {
|
|
4
|
+
const response = await request.get('/health')
|
|
5
|
+
|
|
6
|
+
expect(response.status()).toBe(200)
|
|
7
|
+
await expect(response.json()).resolves.toEqual({ status: 'ok' })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('service app.info endpoint returns HaiResult', async ({ request }) => {
|
|
11
|
+
const response = await request.post('/api/v1/app/info')
|
|
12
|
+
const body = await response.json()
|
|
13
|
+
|
|
14
|
+
expect(response.status()).toBe(200)
|
|
15
|
+
expect(body.success).toBe(true)
|
|
16
|
+
expect(body.data.name).toContain('{{projectName}}')
|
|
17
|
+
expect(typeof body.data.transportEnabled).toBe('boolean')
|
|
18
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import antfu from '@antfu/eslint-config'
|
|
2
|
+
|
|
3
|
+
export default antfu({
|
|
4
|
+
formatters: true,
|
|
5
|
+
ignores: [
|
|
6
|
+
'**/dist/**',
|
|
7
|
+
'**/node_modules/**',
|
|
8
|
+
'**/.agents/**',
|
|
9
|
+
'**/.github/**',
|
|
10
|
+
'**/AGENTS.md',
|
|
11
|
+
'**/CLAUDE.md',
|
|
12
|
+
'**/README.md',
|
|
13
|
+
'**/opencode.json',
|
|
14
|
+
'**/package.json',
|
|
15
|
+
],
|
|
16
|
+
}, {
|
|
17
|
+
files: ['**/*.js', '**/*.mjs', '**/*.ts'],
|
|
18
|
+
rules: {
|
|
19
|
+
'no-console': 'error',
|
|
20
|
+
'ts/no-explicit-any': 'error',
|
|
21
|
+
},
|
|
22
|
+
}, {
|
|
23
|
+
files: ['**/tsconfig.json', '**/tsconfig.*.json'],
|
|
24
|
+
rules: {
|
|
25
|
+
'jsonc/sort-keys': 'off',
|
|
26
|
+
},
|
|
27
|
+
}, {
|
|
28
|
+
files: ['**/index.ts'],
|
|
29
|
+
rules: {
|
|
30
|
+
'perfectionist/sort-exports': 'off',
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"packageManager": "pnpm@9.15.0",
|
|
7
|
+
"description": "Typed hai API workspace with separated contract and service packages.",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "pnpm --filter {{api.servicePackageName}} dev",
|
|
10
|
+
"compile": "pnpm typecheck && pnpm build",
|
|
11
|
+
"build": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
|
|
12
|
+
"typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
|
|
13
|
+
"lint": "pnpm -r --if-present lint",
|
|
14
|
+
"test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
|
|
15
|
+
"test:e2e": "pnpm build && playwright test",
|
|
16
|
+
"clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@antfu/eslint-config": "{{api.packageSpecifiers.antfuEslintConfig}}",
|
|
20
|
+
"@playwright/test": "{{api.packageSpecifiers.playwright}}",
|
|
21
|
+
"@types/node": "{{api.packageSpecifiers.nodeTypes}}",
|
|
22
|
+
"eslint": "{{api.packageSpecifiers.eslint}}",
|
|
23
|
+
"eslint-plugin-format": "{{api.packageSpecifiers.eslintPluginFormat}}",
|
|
24
|
+
"rimraf": "{{api.packageSpecifiers.rimraf}}",
|
|
25
|
+
"tsup": "{{api.packageSpecifiers.tsup}}",
|
|
26
|
+
"typescript": "{{api.packageSpecifiers.typescript}}",
|
|
27
|
+
"vitest": "{{api.packageSpecifiers.vitest}}"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { defineConfig } from '@playwright/test'
|
|
3
|
+
|
|
4
|
+
const defaultBaseURL = 'http://127.0.0.1:3000'
|
|
5
|
+
const baseURL = process.env.BASE_URL || defaultBaseURL
|
|
6
|
+
const serviceHost = resolveHostFromBaseUrl(baseURL, '127.0.0.1')
|
|
7
|
+
const servicePort = resolvePortFromBaseUrl(baseURL, '3000')
|
|
8
|
+
|
|
9
|
+
function resolveHostFromBaseUrl(urlString: string, fallbackHost: string): string {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(urlString)
|
|
12
|
+
return url.hostname || fallbackHost
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return fallbackHost
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string {
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(urlString)
|
|
22
|
+
if (url.port) {
|
|
23
|
+
return url.port
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return url.protocol === 'https:' ? '443' : '80'
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return fallbackPort
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default defineConfig({
|
|
34
|
+
testDir: './e2e',
|
|
35
|
+
fullyParallel: false,
|
|
36
|
+
forbidOnly: !!process.env.CI,
|
|
37
|
+
retries: process.env.CI ? 2 : 0,
|
|
38
|
+
workers: 1,
|
|
39
|
+
reporter: 'list',
|
|
40
|
+
timeout: 30_000,
|
|
41
|
+
use: {
|
|
42
|
+
baseURL,
|
|
43
|
+
channel: 'chrome',
|
|
44
|
+
trace: 'on-first-retry',
|
|
45
|
+
screenshot: 'only-on-failure',
|
|
46
|
+
},
|
|
47
|
+
webServer: [{
|
|
48
|
+
name: '{{api.servicePackageName}}',
|
|
49
|
+
command: 'pnpm --filter {{api.servicePackageName}} start',
|
|
50
|
+
env: {
|
|
51
|
+
HOST: serviceHost,
|
|
52
|
+
PORT: servicePort,
|
|
53
|
+
},
|
|
54
|
+
url: `${baseURL}/health`,
|
|
55
|
+
reuseExistingServer: false,
|
|
56
|
+
timeout: 120_000,
|
|
57
|
+
}],
|
|
58
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"ignoreDeprecations": "6.0",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"verbatimModuleSyntax": true,
|
|
13
|
+
"skipLibCheck": true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{{! @skipIfEmpty }}
|
|
2
|
+
{{#if fullstack.frontends.app}}
|
|
3
|
+
# {{projectName}}-app — 环境变量
|
|
4
|
+
# Android 模拟器访问宿主机后端时通常使用 http://10.0.2.2:3000。
|
|
5
|
+
|
|
6
|
+
PUBLIC_API_BASE=http://localhost:3000
|
|
7
|
+
|
|
8
|
+
# 仅用于 Capacitor live reload 前端页面。
|
|
9
|
+
# CAPACITOR_SERVER_URL=http://localhost:{{fullstack.ports.app}}
|
|
10
|
+
{{/if}}
|