@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.21",
|
|
5
5
|
"description": "Hai Framework CLI for project scaffolding and code generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ora": "^9.1.0",
|
|
35
35
|
"prompts": "^2.4.2",
|
|
36
36
|
"yaml": "^2.7.0",
|
|
37
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
37
|
+
"@h-ai/core": "0.1.0-alpha.21"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"lint": "eslint .",
|
|
53
53
|
"lint:fix": "eslint . --fix",
|
|
54
54
|
"test": "vitest run",
|
|
55
|
+
"test:e2e": "pnpm test:scaffold-gates",
|
|
55
56
|
"test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
|
|
56
57
|
"test:watch": "vitest",
|
|
57
58
|
"test:coverage": "vitest run --coverage",
|
package/templates/README.md
CHANGED
|
@@ -9,13 +9,13 @@ templates/
|
|
|
9
9
|
├── base/ # 所有应用类型共用的骨架文件
|
|
10
10
|
│ ├── package.json.hbs # 动态:按 features 注入依赖
|
|
11
11
|
│ ├── vite.config.ts.hbs # 动态:按 hasI18n / hasUi 注入插件
|
|
12
|
-
│ ├── tsconfig.json
|
|
13
|
-
│ ├── svelte.config.js
|
|
14
|
-
│ ├── .gitignore
|
|
12
|
+
│ ├── tsconfig.json.hbs # 动态:SvelteKit / 纯 Svelte 5 分支
|
|
13
|
+
│ ├── svelte.config.js.hbs # 动态:SvelteKit / 纯 Svelte 5 分支
|
|
14
|
+
│ ├── .gitignore.hbs # 动态:纯 Svelte 5 应用不输出 .svelte-kit
|
|
15
15
|
│ ├── playwright.config.ts
|
|
16
16
|
│ └── src/
|
|
17
|
-
│ ├── app.html.hbs # 动态:
|
|
18
|
-
│ ├── app.d.ts
|
|
17
|
+
│ ├── app.html.hbs # 动态:SvelteKit 应用输出 app shell
|
|
18
|
+
│ ├── app.d.ts.hbs # 动态:SvelteKit 应用输出 App namespace
|
|
19
19
|
│ ├── app.css
|
|
20
20
|
│ ├── hooks.server.ts.hbs # 动态:按 features / hasI18n 组装 handle 序列
|
|
21
21
|
│ └── lib/server/
|
|
@@ -33,6 +33,10 @@ templates/
|
|
|
33
33
|
│ │ └── src/routes/
|
|
34
34
|
│ ├── api/ # 纯 API 服务(无 UI / 无 i18n)
|
|
35
35
|
│ │ └── src/routes/
|
|
36
|
+
│ ├── mobile-app/ # Svelte 5 + Vite + Capacitor 移动应用
|
|
37
|
+
│ │ ├── index.html.hbs
|
|
38
|
+
│ │ ├── messages/
|
|
39
|
+
│ │ └── src/App.svelte.hbs
|
|
36
40
|
│ └── fullstack/ # 前后端分离多包工程(contract / serv / web / app / desktop / miniapp 占位)
|
|
37
41
|
│ ├── packages/ # 共享 contract 与后端 serv 包
|
|
38
42
|
│ ├── apps/ # 多前端目标,按用户选择条件生成
|
|
@@ -52,17 +56,20 @@ templates/
|
|
|
52
56
|
│ └── project.inlang/
|
|
53
57
|
│ └── settings.json # Paraglide 项目配置
|
|
54
58
|
│
|
|
55
|
-
└── skills/ # AI Skill
|
|
59
|
+
└── skills/ # AI Skill 与各 appType 桥接指引
|
|
60
|
+
└── bridges/ # admin / api / website / h5 / mobile-app / fullstack 专属 AGENTS/Copilot/Claude
|
|
56
61
|
```
|
|
57
62
|
|
|
58
63
|
## AI Skill 模板(Single Source of Truth)
|
|
59
64
|
|
|
60
65
|
`templates/skills/` 是 CLI 生成共享 AI 上下文时的单一来源,同时用于 `hai create` 与 `hai add`。
|
|
61
66
|
|
|
62
|
-
-
|
|
63
|
-
- `copilot-instructions.md` 会复制到 `.github/copilot-instructions.md
|
|
64
|
-
- `AGENTS.md`、`CLAUDE.md
|
|
65
|
-
- `opencode.json`
|
|
67
|
+
- `hai create` 会把 `templates/skills/` 下所有 `hai-*` Skill 目录复制到 `.agents/skills/<skill>/`,再按 appType 排除互斥项(例如 fullstack 不复制 `hai-kit`;admin / website / h5 不复制 `hai-serv`、`hai-api-contract`、`hai-api-client`、`hai-capacitor`;mobile-app 不复制 `hai-core`、`hai-kit`、`hai-serv`、`hai-api-contract`)
|
|
68
|
+
- `bridges/<appType>/copilot-instructions.md` 会复制到 `.github/copilot-instructions.md`;没有专属文件时回退到通用桥接文件
|
|
69
|
+
- `bridges/<appType>/AGENTS.md`、`bridges/<appType>/CLAUDE.md` 会复制到项目根目录,确保不同样板工程使用自己的 AI 指引
|
|
70
|
+
- `opencode.json` 会复制到项目根目录
|
|
71
|
+
- `templates/skills/AGENTS.md`、`CLAUDE.md`、`copilot-instructions.md` 与 `opencode.json` 不是多余副本:它们是 generic fallback,主要服务于缺少 appType 上下文时的桥接复制与 `hai add` 回填 AI 支持
|
|
72
|
+
- `opencode.json` 使用 OpenCode 的 `instructions` 配置复用 `.github/copilot-instructions.md`;`.agents/skills/` 由 OpenCode 原生发现,不再写入过期的 `skills.paths`
|
|
66
73
|
- `CLAUDE.md` 作为 Claude Code 的原生项目指引入口,并通过 `@AGENTS.md` 复用共享规范;`.agents/skills/` 仍是共享参考目录,而非 Claude Code 的原生 project skills 目录
|
|
67
74
|
- `hai add` 只补齐缺失的 AI 支持文件,不覆盖用户已自定义的桥接文件,也不会自动删除遗留的 `.github/skills/`
|
|
68
75
|
|
|
@@ -77,23 +84,27 @@ templates/
|
|
|
77
84
|
|
|
78
85
|
所有 `.hbs` 文件可使用以下变量:
|
|
79
86
|
|
|
80
|
-
| 变量 | 类型 | 说明
|
|
81
|
-
| ---------------------- | --------- |
|
|
82
|
-
| `{{projectName}}` | `string` | 项目名称(如 `my-app`)
|
|
83
|
-
| `{{appType}}` | `string` |
|
|
84
|
-
| `{{hasUi}}` | `boolean` | 是否包含 UI(非 `api` 类型为 `true`)
|
|
85
|
-
| `{{hasI18n}}` | `boolean` | 是否启用 i18n(非 `api` 类型为 `true`)
|
|
86
|
-
| `{{
|
|
87
|
-
| `{{
|
|
88
|
-
| `{{
|
|
89
|
-
| `{{
|
|
90
|
-
| `{{
|
|
91
|
-
| `{{features.
|
|
92
|
-
| `{{features.
|
|
93
|
-
| `{{features.
|
|
94
|
-
| `{{features.
|
|
95
|
-
|
|
96
|
-
`
|
|
87
|
+
| 变量 | 类型 | 说明 |
|
|
88
|
+
| ---------------------- | --------- | ----------------------------------------- |
|
|
89
|
+
| `{{projectName}}` | `string` | 项目名称(如 `my-app`) |
|
|
90
|
+
| `{{appType}}` | `string` | 应用类型 |
|
|
91
|
+
| `{{hasUi}}` | `boolean` | 是否包含 UI(非 `api` 类型为 `true`) |
|
|
92
|
+
| `{{hasI18n}}` | `boolean` | 是否启用 i18n(非 `api` 类型为 `true`) |
|
|
93
|
+
| `{{isSvelteOnlyApp}}` | `boolean` | 是否为不依赖 SvelteKit 的纯 Svelte 5 应用 |
|
|
94
|
+
| `{{isCapacitorApp}}` | `boolean` | 是否为 Capacitor 移动应用 |
|
|
95
|
+
| `{{defaultLocale}}` | `string` | 默认语言,如 `zh-CN` |
|
|
96
|
+
| `{{packageManager}}` | `string` | 包管理器:`pnpm` / `npm` / `yarn` |
|
|
97
|
+
| `{{fullstack.*}}` | `object` | fullstack 类型专用上下文 |
|
|
98
|
+
| `{{features.iam}}` | `boolean` | 是否选中 iam feature |
|
|
99
|
+
| `{{features.db}}` | `boolean` | 是否选中 db feature |
|
|
100
|
+
| `{{features.cache}}` | `boolean` | 是否选中 cache feature |
|
|
101
|
+
| `{{features.crypto}}` | `boolean` | 是否选中 crypto feature |
|
|
102
|
+
| `{{features.storage}}` | `boolean` | 是否选中 storage feature |
|
|
103
|
+
| `{{features.ai}}` | `boolean` | 是否选中 ai feature |
|
|
104
|
+
|
|
105
|
+
`appType` 支持 `admin` / `website` / `h5` / `api` / `mobile-app` / `fullstack`。
|
|
106
|
+
|
|
107
|
+
`fullstack.*` 包含包名、前端选择、依赖版本、`contractExportName` 和原生壳 `nativeAppIdSegment`,用于渲染 contract / serv / 多端 UI / Capacitor / Tauri 模板。
|
|
97
108
|
|
|
98
109
|
### 常用条件写法
|
|
99
110
|
|
|
@@ -116,8 +127,8 @@ import { iam } from '@h-ai/iam'
|
|
|
116
127
|
模板引擎执行 6 个步骤:
|
|
117
128
|
|
|
118
129
|
1. **复制 `base/` 静态文件** → 项目根
|
|
119
|
-
2. **复制 `apps/{appType}/`
|
|
120
|
-
3. **叠加 feature 静态路由** → `src/routes
|
|
130
|
+
2. **复制 `apps/{appType}/` 专属文件** → 项目根(`messages/` 仅 `hasI18n` 时复制)
|
|
131
|
+
3. **叠加 feature 静态路由** → `src/routes/`(纯 Svelte 5 应用跳过 SvelteKit 路由)
|
|
121
132
|
4. **渲染所有 `.hbs` 文件** — `base/` + `apps/{appType}/` 输出到项目根;`features/*/routes*/` 输出到 `src/routes/`
|
|
122
133
|
5. **复制 `i18n/` 脚手架** → 项目根(仅 `hasI18n` 时)
|
|
123
134
|
6. **确保 `static/` 目录存在**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# {{api.contractPackageName}}
|
|
2
|
+
|
|
3
|
+
{{projectName}} API service 的共享 contract 包。
|
|
4
|
+
|
|
5
|
+
## 包含内容
|
|
6
|
+
|
|
7
|
+
- `app-contract.ts`:应用级路由定义
|
|
8
|
+
- `app-schemas.ts`:输入输出 Zod schema
|
|
9
|
+
- `{{projectName}}-contract.ts`:工程级 contract 聚合入口
|
|
10
|
+
|
|
11
|
+
## 常用命令
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm --filter {{api.contractPackageName}} build
|
|
15
|
+
pnpm --filter {{api.contractPackageName}} test
|
|
16
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{api.contractPackageName}}",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"clean": "rimraf dist .turbo"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@h-ai/api-contract": "{{haiPackageSpecifier}}",
|
|
29
|
+
"zod": "{{api.packageSpecifiers.zod}}"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "{{api.packageSpecifiers.nodeTypes}}",
|
|
33
|
+
"rimraf": "{{api.packageSpecifiers.rimraf}}",
|
|
34
|
+
"tsup": "{{api.packageSpecifiers.tsup}}",
|
|
35
|
+
"typescript": "{{api.packageSpecifiers.typescript}}",
|
|
36
|
+
"vitest": "{{api.packageSpecifiers.vitest}}"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
2
|
+
import { AppEchoInputSchema, AppEchoOutputSchema, AppInfoOutputSchema } from './app-schemas.js'
|
|
3
|
+
|
|
4
|
+
/** 应用自有 app contract。 */
|
|
5
|
+
export const appContract = {
|
|
6
|
+
info: apiContract
|
|
7
|
+
.route({
|
|
8
|
+
method: 'POST',
|
|
9
|
+
path: '/app/info',
|
|
10
|
+
operationId: 'app.info',
|
|
11
|
+
summary: 'Get service info',
|
|
12
|
+
tags: ['app'],
|
|
13
|
+
})
|
|
14
|
+
.output(AppInfoOutputSchema),
|
|
15
|
+
echo: apiContract
|
|
16
|
+
.route({
|
|
17
|
+
method: 'POST',
|
|
18
|
+
path: '/app/echo',
|
|
19
|
+
operationId: 'app.echo',
|
|
20
|
+
summary: '{{#if features.iam}}Echo a message (auth required){{else}}Echo a message{{/if}}',
|
|
21
|
+
tags: ['app'],
|
|
22
|
+
})
|
|
23
|
+
.input(AppEchoInputSchema)
|
|
24
|
+
.output(AppEchoOutputSchema),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type AppContract = typeof appContract
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
/** 服务元信息响应数据。 */
|
|
5
|
+
export const AppInfoOutputDataSchema = z.object({
|
|
6
|
+
name: z.string(),
|
|
7
|
+
version: z.string(),
|
|
8
|
+
uptimeMs: z.number().int().nonnegative(),
|
|
9
|
+
transportEnabled: z.boolean(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
/** app.info 标准 HaiResult 输出。 */
|
|
13
|
+
export const AppInfoOutputSchema = apiContract.haiResultSchema(AppInfoOutputDataSchema)
|
|
14
|
+
|
|
15
|
+
/** 输入:单条文本消息(演示用,限制最大长度防止误用)。 */
|
|
16
|
+
export const AppEchoInputSchema = z.object({
|
|
17
|
+
message: z.string().min(1).max(2000),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
/** 输出:原样回显 + 调用者快照。 */
|
|
21
|
+
export const AppEchoOutputDataSchema = z.object({
|
|
22
|
+
message: z.string(),
|
|
23
|
+
requestId: z.string(),
|
|
24
|
+
timestamp: z.string(),
|
|
25
|
+
userId: z.string().optional(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
/** app.echo 标准 HaiResult 输出。 */
|
|
29
|
+
export const AppEchoOutputSchema = apiContract.haiResultSchema(AppEchoOutputDataSchema)
|
|
30
|
+
|
|
31
|
+
export type AppInfoOutputData = z.infer<typeof AppInfoOutputDataSchema>
|
|
32
|
+
export type AppEchoInput = z.infer<typeof AppEchoInputSchema>
|
|
33
|
+
export type AppEchoOutputData = z.infer<typeof AppEchoOutputDataSchema>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
2
|
+
import { appContract } from './app-contract.js'
|
|
3
|
+
|
|
4
|
+
/** 工程级应用 contract。 */
|
|
5
|
+
export const {{api.contractExportName}} = apiContract.create({
|
|
6
|
+
{{#if features.iam}} iam: apiContract.iam,
|
|
7
|
+
{{/if}}{{#if features.storage}} storage: apiContract.storage,
|
|
8
|
+
{{/if}}{{#if features.ai}} ai: apiContract.ai,
|
|
9
|
+
{{/if}} app: appContract,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export type ApiWorkspaceContract = typeof {{api.contractExportName}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { AppEchoInputSchema, {{api.contractExportName}} } from '../src/index.js'
|
|
3
|
+
|
|
4
|
+
describe('{{api.contractPackageName}}', () => {
|
|
5
|
+
it('includes generated contract routes', () => {
|
|
6
|
+
expect({{api.contractExportName}}.app.info).toBeDefined()
|
|
7
|
+
expect({{api.contractExportName}}.app.echo).toBeDefined()
|
|
8
|
+
{{#if features.iam}} expect({{api.contractExportName}}.iam.auth.login).toBeDefined()
|
|
9
|
+
{{/if}}{{#if features.storage}} expect({{api.contractExportName}}.storage.presignedUrls.createUpload).toBeDefined()
|
|
10
|
+
{{/if}}{{#if features.ai}} expect({{api.contractExportName}}.ai.chats.createCompletion).toBeDefined()
|
|
11
|
+
{{/if}} })
|
|
12
|
+
|
|
13
|
+
it('validates app.echo input length', () => {
|
|
14
|
+
expect(AppEchoInputSchema.safeParse({ message: 'hello' }).success).toBe(true)
|
|
15
|
+
expect(AppEchoInputSchema.safeParse({ message: '' }).success).toBe(false)
|
|
16
|
+
expect(AppEchoInputSchema.safeParse({ message: 'x'.repeat(2001) }).success).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# {{api.servicePackageName}}
|
|
2
|
+
|
|
3
|
+
{{projectName}} API service 包,负责模块初始化、procedure 装配与 HTTP 入口。
|
|
4
|
+
|
|
5
|
+
## 常用命令
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm --filter {{api.servicePackageName}} dev
|
|
9
|
+
pnpm --filter {{api.servicePackageName}} test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 关键目录
|
|
13
|
+
|
|
14
|
+
- `config/`:service 配置
|
|
15
|
+
- `src/app.ts`:HTTP App 装配
|
|
16
|
+
- `src/lib/server/init.ts`:模块初始化与关闭
|
|
17
|
+
- `src/server/procedures/`:应用级 procedures
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# {{projectName}} API Service - 应用配置
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
name: {{projectName}} API Service
|
|
6
|
+
version: 0.1.0
|
|
7
|
+
env: ${HAI_ENV:development}
|
|
8
|
+
debug: false
|
|
9
|
+
|
|
10
|
+
defaultLocale: {{defaultLocale}}
|
|
11
|
+
supportedLocales:
|
|
12
|
+
{{#if_eq defaultLocale 'en-US'}} - en-US
|
|
13
|
+
- zh-CN
|
|
14
|
+
{{else}} - zh-CN
|
|
15
|
+
- en-US
|
|
16
|
+
{{/if_eq}}
|
|
17
|
+
|
|
18
|
+
features:
|
|
19
|
+
apiAccess: true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# {{projectName}} API Service - Serv / HTTP 入口配置
|
|
3
|
+
# =============================================================================
|
|
4
|
+
|
|
5
|
+
http:
|
|
6
|
+
apiPrefix: /api/v1
|
|
7
|
+
openapi:
|
|
8
|
+
path: /openapi.json
|
|
9
|
+
docs:
|
|
10
|
+
path: /docs
|
|
11
|
+
health:
|
|
12
|
+
path: /health
|
|
13
|
+
readyPath: /ready
|
|
14
|
+
rpc: false
|
|
15
|
+
|
|
16
|
+
{{#if features.crypto}}transport:
|
|
17
|
+
keyExchangePath: /_hai/key-exchange
|
|
18
|
+
excludePaths:
|
|
19
|
+
- /health
|
|
20
|
+
- /ready
|
|
21
|
+
- /openapi.json
|
|
22
|
+
- /docs
|
|
23
|
+
- /_hai/scalar.js
|
|
24
|
+
maxClients: 10000
|
|
25
|
+
{{else}}transport: false
|
|
26
|
+
{{/if}}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{api.servicePackageName}}",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"clean": "rimraf dist .turbo",
|
|
26
|
+
"dev": "pnpm build && node dist/index.js",
|
|
27
|
+
"start": "node dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
31
|
+
"@h-ai/serv": "{{haiPackageSpecifier}}",
|
|
32
|
+
"{{api.contractPackageName}}": "workspace:*"{{#if features.ai}},
|
|
33
|
+
"@h-ai/ai": "{{haiPackageSpecifier}}"{{/if}}{{#if features.audit}},
|
|
34
|
+
"@h-ai/audit": "{{haiPackageSpecifier}}"{{/if}}{{#if features.cache}},
|
|
35
|
+
"@h-ai/cache": "{{haiPackageSpecifier}}"{{/if}}{{#if features.crypto}},
|
|
36
|
+
"@h-ai/crypto": "{{haiPackageSpecifier}}"{{/if}}{{#if features.datapipe}},
|
|
37
|
+
"@h-ai/datapipe": "{{haiPackageSpecifier}}"{{/if}}{{#if features.deploy}},
|
|
38
|
+
"@h-ai/deploy": "{{haiPackageSpecifier}}"{{/if}}{{#if features.iam}},
|
|
39
|
+
"@h-ai/iam": "{{haiPackageSpecifier}}"{{/if}}{{#if features.payment}},
|
|
40
|
+
"@h-ai/payment": "{{haiPackageSpecifier}}"{{/if}}{{#if features.reach}},
|
|
41
|
+
"@h-ai/reach": "{{haiPackageSpecifier}}"{{/if}}{{#if features.db}},
|
|
42
|
+
"@h-ai/reldb": "{{haiPackageSpecifier}}"{{/if}}{{#if features.scheduler}},
|
|
43
|
+
"@h-ai/scheduler": "{{haiPackageSpecifier}}"{{/if}}{{#if features.storage}},
|
|
44
|
+
"@h-ai/storage": "{{haiPackageSpecifier}}"{{/if}}{{#if features.vecdb}},
|
|
45
|
+
"@h-ai/vecdb": "{{haiPackageSpecifier}}"{{/if}}
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@h-ai/api-client": "{{haiPackageSpecifier}}",
|
|
49
|
+
"@types/node": "{{api.packageSpecifiers.nodeTypes}}",
|
|
50
|
+
"rimraf": "{{api.packageSpecifiers.rimraf}}",
|
|
51
|
+
"tsup": "{{api.packageSpecifiers.tsup}}",
|
|
52
|
+
"typescript": "{{api.packageSpecifiers.typescript}}",
|
|
53
|
+
"vitest": "{{api.packageSpecifiers.vitest}}"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ServConfig } from '@h-ai/serv'
|
|
2
|
+
import { core } from '@h-ai/core'
|
|
3
|
+
{{#if features.crypto}}import { crypto, TRANSPORT_PROTOCOL } from '@h-ai/crypto'
|
|
4
|
+
{{/if}}{{#if features.iam}}import { iam } from '@h-ai/iam'
|
|
5
|
+
{{/if}}import { serv } from '@h-ai/serv'
|
|
6
|
+
import { {{api.contractExportName}} } from '{{api.contractPackageName}}'
|
|
7
|
+
import pkg from '../package.json' with { type: 'json' }
|
|
8
|
+
import { {{api.serviceProceduresFactoryName}} } from './server/procedures/index.js'
|
|
9
|
+
|
|
10
|
+
const startedAt = Date.now()
|
|
11
|
+
|
|
12
|
+
interface CreateApiServiceAppOptions {
|
|
13
|
+
transport?: 'config' | 'disabled'
|
|
14
|
+
refreshCookie?: 'enabled' | 'disabled'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const LOOPBACK_ORIGIN_RE = /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/
|
|
18
|
+
const WEBVIEW_ORIGINS = new Set([
|
|
19
|
+
'capacitor://localhost',
|
|
20
|
+
'http://localhost',
|
|
21
|
+
'https://tauri.localhost',
|
|
22
|
+
'tauri://localhost',
|
|
23
|
+
])
|
|
24
|
+
const DEFAULT_CORS_ALLOWED_HEADERS = [
|
|
25
|
+
'Authorization',
|
|
26
|
+
'Content-Type',
|
|
27
|
+
{{#if features.crypto}} TRANSPORT_PROTOCOL.CLIENT_ID_HEADER,
|
|
28
|
+
TRANSPORT_PROTOCOL.ENCRYPTED_HEADER,
|
|
29
|
+
{{/if}} 'X-Hai-Locale',
|
|
30
|
+
'X-Request-Id',
|
|
31
|
+
'X-Requested-With',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
/** 创建 API Service HTTP 应用。 */
|
|
35
|
+
export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptions = {}) {
|
|
36
|
+
const servConfig = core.config.getOrThrow<ServConfig>('serv')
|
|
37
|
+
{{#if features.crypto}} const transportMode = _options.transport ?? 'config'
|
|
38
|
+
const transport = transportMode === 'disabled' || servConfig.transport === false
|
|
39
|
+
? undefined
|
|
40
|
+
: {
|
|
41
|
+
crypto,
|
|
42
|
+
keyExchangePath: servConfig.transport.keyExchangePath,
|
|
43
|
+
excludePaths: [...servConfig.transport.excludePaths],
|
|
44
|
+
maxClients: servConfig.transport.maxClients,
|
|
45
|
+
}
|
|
46
|
+
{{else}} const transport = undefined
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{#if features.iam}}
|
|
49
|
+
const refreshCookie = _options.refreshCookie === 'disabled' ? undefined : {}
|
|
50
|
+
{{/if}}
|
|
51
|
+
|
|
52
|
+
const appOptions: Parameters<typeof serv.createApp>[0] = {
|
|
53
|
+
contract: {{api.contractExportName}},
|
|
54
|
+
procedures: {{api.serviceProceduresFactoryName}}({
|
|
55
|
+
app: {
|
|
56
|
+
info: {
|
|
57
|
+
name: pkg.name,
|
|
58
|
+
version: pkg.version,
|
|
59
|
+
transportEnabled: transport !== undefined,
|
|
60
|
+
},
|
|
61
|
+
startedAt,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
http: servConfig.http,
|
|
65
|
+
{{#if features.iam}}
|
|
66
|
+
iam,
|
|
67
|
+
{{/if}}
|
|
68
|
+
middlewares: [
|
|
69
|
+
{
|
|
70
|
+
middleware: serv.cors({
|
|
71
|
+
origin: origin => resolveCorsOrigin(origin) !== null,
|
|
72
|
+
{{#if features.iam}} credentials: true,
|
|
73
|
+
{{/if}} allowedHeaders: DEFAULT_CORS_ALLOWED_HEADERS,{{#if features.crypto}}
|
|
74
|
+
exposedHeaders: [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, 'X-Request-Id'],{{/if}}
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
{{#if features.iam}}
|
|
80
|
+
|
|
81
|
+
appOptions.refreshCookie = refreshCookie
|
|
82
|
+
{{/if}}{{#if features.crypto}}
|
|
83
|
+
if (transport)
|
|
84
|
+
appOptions.transport = transport
|
|
85
|
+
{{/if}}
|
|
86
|
+
|
|
87
|
+
return serv.createApp(appOptions)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveCorsOrigin(origin: string | undefined): string | null {
|
|
91
|
+
if (!origin)
|
|
92
|
+
return null
|
|
93
|
+
if (WEBVIEW_ORIGINS.has(origin))
|
|
94
|
+
return origin
|
|
95
|
+
if (LOOPBACK_ORIGIN_RE.test(origin))
|
|
96
|
+
return origin
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { core } from '@h-ai/core'
|
|
3
|
+
import { serv } from '@h-ai/serv'
|
|
4
|
+
import { {{api.serviceAppFactoryName}} } from './app.js'
|
|
5
|
+
import { closeApp, initApp } from './lib/server/init.js'
|
|
6
|
+
|
|
7
|
+
const logger = core.logger.child({ app: '{{api.servicePackageName}}', scope: 'entrypoint' })
|
|
8
|
+
|
|
9
|
+
async function main(): Promise<void> {
|
|
10
|
+
await initApp()
|
|
11
|
+
|
|
12
|
+
serv.listen({{api.serviceAppFactoryName}}(), {
|
|
13
|
+
onListening: info => logger.info('API service listening', { address: info.address, port: info.port }),
|
|
14
|
+
onClose: closeApp,
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main().catch((error: unknown) => {
|
|
19
|
+
logger.error('API service failed to start', { error })
|
|
20
|
+
process.exitCode = 1
|
|
21
|
+
})
|