@h-ai/cli 0.1.0-alpha.64 → 0.1.0-alpha.65
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 +20 -4
- package/README.ja.md +20 -4
- package/README.md +28 -12
- package/dist/chunk-47V5OT22.js +5639 -0
- package/dist/chunk-47V5OT22.js.map +1 -0
- package/dist/{chunk-X3RSYNBG.js → chunk-IPG3QNYK.js} +62 -4
- package/dist/chunk-IPG3QNYK.js.map +1 -0
- package/dist/cli-config-templates-SJ2MBIR7.js +5 -0
- package/dist/{cli-config-templates-QRF6B5T4.js.map → cli-config-templates-SJ2MBIR7.js.map} +1 -1
- package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-A5VIR5J6.js} +3 -3
- package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-A5VIR5J6.js.map} +1 -1
- package/dist/index.js +524 -66
- package/dist/index.js.map +1 -1
- package/dist/{src-DUGLX4YB.js → src-6PPBKQ6Q.js} +666 -290
- package/dist/src-6PPBKQ6Q.js.map +1 -0
- package/package.json +9 -7
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
- package/templates/apps/api/.dockerignore.hbs +5 -0
- package/templates/apps/api/.gitignore.hbs +4 -4
- package/templates/apps/api/Dockerfile.hbs +11 -1
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +9 -2
- package/templates/apps/api/package.json.hbs +9 -0
- package/templates/apps/fullstack/.dockerignore.hbs +5 -3
- package/templates/apps/fullstack/.gitignore.hbs +22 -9
- package/templates/apps/fullstack/Dockerfile.hbs +11 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +5 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +23 -1
- package/templates/apps/fullstack/package.json.hbs +12 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +6 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +9 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +14 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
- package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
- package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
- package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
- package/templates/base/.dockerignore.hbs +5 -0
- package/templates/base/.gitignore.hbs +18 -4
- package/templates/base/Dockerfile.hbs +17 -1
- package/templates/base/e2e/app.spec.ts.hbs +14 -0
- package/templates/base/package.json.hbs +14 -2
- package/templates/base/tests/scaffold.test.ts.hbs +9 -3
- package/templates/base/vite.config.ts.hbs +6 -4
- package/templates/shared/hash-router.svelte.ts +83 -0
- package/templates/skills/AGENTS.md +15 -1
- package/templates/skills/bridges/admin/AGENTS.md +17 -1
- package/templates/skills/bridges/api/AGENTS.md +17 -3
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
- package/templates/skills/bridges/h5/AGENTS.md +17 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
- package/templates/skills/bridges/website/AGENTS.md +17 -1
- package/templates/skills/hai-ai/reference.md +2 -2
- package/templates/skills/hai-app-create/SKILL.md +5 -3
- package/templates/skills/hai-app-create/development.md +126 -2
- package/templates/skills/hai-app-create/reference.md +2 -2
- package/templates/skills/hai-app-review/SKILL.md +2 -2
- package/templates/skills/hai-app-tests/SKILL.md +2 -1
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-core/SKILL.md +2 -1
- package/templates/skills/hai-deploy/SKILL.md +42 -34
- package/templates/skills/hai-deploy/middleware.md +94 -0
- package/templates/skills/hai-framework-sync/SKILL.md +1 -1
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-reach/reference.md +8 -8
- package/templates/skills/hai-spec-implement/SKILL.md +12 -9
- package/templates/skills/hai-spec-implement/changes.md +67 -0
- package/templates/skills/hai-spec-implement/templates.md +25 -3
- package/templates/skills/hai-spec-implement/workflow.md +7 -6
- package/templates/skills/hai-spec-review/SKILL.md +14 -5
- package/templates/skills/hai-spec-review/checklist.md +4 -3
- package/templates/skills/hai-storage/SKILL.md +2 -0
- package/dist/chunk-WW2MIO6T.js +0 -992
- package/dist/chunk-WW2MIO6T.js.map +0 -1
- package/dist/chunk-X3RSYNBG.js.map +0 -1
- package/dist/cli-config-templates-QRF6B5T4.js +0 -5
- package/dist/src-DUGLX4YB.js.map +0 -1
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/ProfileSettings.svelte.hbs → pages/settings.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.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.65",
|
|
5
5
|
"description": "Hai Framework CLI for project scaffolding and code generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"node": "^22.22.2 || ^24.15.0 || >=26.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
33
|
+
"@h-ai/core": "0.1.0-alpha.65",
|
|
34
34
|
"cac": "^7.0.1",
|
|
35
35
|
"chalk": "^6.0.1",
|
|
36
36
|
"fast-wrap-ansi": "^0.2.2",
|
|
@@ -41,11 +41,12 @@
|
|
|
41
41
|
"yaml": "^2.9.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@h-ai/cache": "0.1.0-alpha.
|
|
45
|
-
"@h-ai/
|
|
46
|
-
"@h-ai/
|
|
47
|
-
"@h-ai/
|
|
48
|
-
"@h-ai/
|
|
44
|
+
"@h-ai/cache": "0.1.0-alpha.65",
|
|
45
|
+
"@h-ai/crypto": "0.1.0-alpha.65",
|
|
46
|
+
"@h-ai/deploy": "0.1.0-alpha.65",
|
|
47
|
+
"@h-ai/reach": "0.1.0-alpha.65",
|
|
48
|
+
"@h-ai/reldb": "0.1.0-alpha.65",
|
|
49
|
+
"@h-ai/storage": "0.1.0-alpha.65",
|
|
49
50
|
"@types/fs-extra": "^11.0.4",
|
|
50
51
|
"@types/node": "^26.6.5",
|
|
51
52
|
"@types/prompts": "^2.4.9",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"lint": "eslint .",
|
|
63
64
|
"lint:fix": "eslint . --fix",
|
|
64
65
|
"test": "vitest run",
|
|
66
|
+
"test:deploy-gates": "node tests/run-scaffold-gates.mjs --deploy",
|
|
65
67
|
"test:e2e": "pnpm test:scaffold-gates",
|
|
66
68
|
"test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
|
|
67
69
|
"test:watch": "vitest",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
<title>{page.data.appConfig.name}</title>
|
|
33
33
|
</svelte:head>
|
|
34
34
|
|
|
35
|
-
<div class='min-h-screen bg-base-200'>
|
|
36
|
-
{
|
|
35
|
+
<div class='min-h-screen flex flex-col bg-base-200'>
|
|
36
|
+
<header class='navbar border-b border-base-300 bg-base-100 px-6'><span class='text-lg font-semibold'>{page.data.appConfig.name}</span></header>
|
|
37
|
+
<div class='flex-1'>{@render children()}</div>
|
|
38
|
+
<footer class='border-t border-base-300 bg-base-100 p-3 text-center text-xs text-base-content/60'>{page.data.appConfig.name} · {page.data.appConfig.version} · {page.data.appConfig.env}</footer>
|
|
37
39
|
</div>
|
|
@@ -5,10 +5,10 @@ dist
|
|
|
5
5
|
build
|
|
6
6
|
playwright-report
|
|
7
7
|
test-results
|
|
8
|
-
.env
|
|
9
|
-
.env.*
|
|
10
|
-
!.env.example
|
|
11
|
-
!.env.example.hbs
|
|
12
8
|
|
|
13
9
|
# hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
|
|
14
10
|
.hai/
|
|
11
|
+
|
|
12
|
+
# 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
|
|
13
|
+
.env.local
|
|
14
|
+
.env.*.local
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# syntax=docker/dockerfile:1.7
|
|
2
2
|
FROM node:24-bookworm-slim AS build
|
|
3
|
+
ARG SQLITE_PREBUILD_MIRROR
|
|
4
|
+
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
5
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
6
|
+
ARG HAI_CORE_ENV=production
|
|
7
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
8
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
3
9
|
|
|
4
10
|
ENV PNPM_HOME=/pnpm
|
|
5
11
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -12,12 +18,16 @@ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
|
12
18
|
COPY . .
|
|
13
19
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
14
20
|
RUN pnpm build
|
|
21
|
+
RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
15
22
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{api.servicePackageName}} --prod /prod/app
|
|
16
23
|
|
|
17
24
|
FROM node:24-bookworm-slim AS runtime-base
|
|
25
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
26
|
+
ARG HAI_CORE_ENV=production
|
|
18
27
|
|
|
19
28
|
ENV NODE_ENV=production
|
|
20
|
-
ENV
|
|
29
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
30
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
21
31
|
ENV HOST=0.0.0.0
|
|
22
32
|
ENV PORT=3000
|
|
23
33
|
WORKDIR /app
|
|
@@ -18,10 +18,10 @@ http:
|
|
|
18
18
|
rpc: false
|
|
19
19
|
|
|
20
20
|
cors:
|
|
21
|
-
origin: ${
|
|
22
|
-
nativeOrigins: ${
|
|
23
|
-
{{#if features.iam}} credentials: ${
|
|
24
|
-
{{else}} credentials: ${
|
|
21
|
+
origin: ${HAI_SERV_CORS_ORIGIN:*}
|
|
22
|
+
nativeOrigins: ${HAI_SERV_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
|
|
23
|
+
{{#if features.iam}} credentials: ${HAI_SERV_CORS_CREDENTIALS:true}
|
|
24
|
+
{{else}} credentials: ${HAI_SERV_CORS_CREDENTIALS:false}
|
|
25
25
|
{{/if}} allowedHeaders:
|
|
26
26
|
- Authorization
|
|
27
27
|
- Content-Type
|
|
@@ -21,13 +21,20 @@
|
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup",
|
|
24
|
+
"build:prod": "pnpm build",
|
|
25
|
+
"check": "pnpm typecheck",
|
|
24
26
|
"typecheck": "tsc --noEmit",
|
|
25
27
|
"lint": "eslint .",
|
|
28
|
+
"lint:fix": "eslint . --fix",
|
|
26
29
|
"test": "vitest run",
|
|
27
30
|
"test:watch": "vitest",
|
|
28
31
|
"clean": "rimraf dist .turbo",
|
|
29
|
-
"dev": "pnpm build && node dist/index.js",
|
|
30
|
-
"start": "node dist/index.js"
|
|
32
|
+
"dev": "pnpm build && node --env-file=.env --env-file=.env.dev --env-file-if-exists=.env.local dist/index.js",
|
|
33
|
+
"start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local dist/index.js",
|
|
34
|
+
"dev:test": "pnpm build && node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local dist/index.js",
|
|
35
|
+
"dev:prod": "pnpm build && pnpm start",
|
|
36
|
+
"start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local dist/index.js",
|
|
37
|
+
"start:prod": "pnpm start"
|
|
31
38
|
},
|
|
32
39
|
"dependencies": {
|
|
33
40
|
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
@@ -8,15 +8,24 @@
|
|
|
8
8
|
"description": "Typed hai API workspace with separated contract and service packages.",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "pnpm --filter {{api.servicePackageName}} dev",
|
|
11
|
+
"start": "pnpm --filter {{api.servicePackageName}} start",
|
|
12
|
+
"start:test": "pnpm --filter {{api.servicePackageName}} start:test",
|
|
13
|
+
"start:prod": "pnpm --filter {{api.servicePackageName}} start:prod",
|
|
14
|
+
"dev:test": "pnpm --filter {{api.servicePackageName}} dev:test",
|
|
15
|
+
"dev:prod": "pnpm --filter {{api.servicePackageName}} dev:prod",
|
|
11
16
|
"compile": "pnpm typecheck && pnpm build",
|
|
12
17
|
"build": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
|
|
13
18
|
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
14
19
|
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
|
|
15
20
|
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
16
21
|
"podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
|
|
22
|
+
"check": "pnpm typecheck",
|
|
17
23
|
"typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
|
|
18
24
|
"lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
|
|
25
|
+
"lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
|
|
19
26
|
"test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
|
|
27
|
+
"test:watch": "pnpm -r --parallel --if-present test:watch",
|
|
28
|
+
"build:prod": "pnpm build",
|
|
20
29
|
"test:e2e": "pnpm build && playwright test",
|
|
21
30
|
"clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
|
|
22
31
|
},
|
|
@@ -6,9 +6,6 @@ Dockerfile
|
|
|
6
6
|
.codex
|
|
7
7
|
.opencode
|
|
8
8
|
.turbo
|
|
9
|
-
.env
|
|
10
|
-
.env.*
|
|
11
|
-
!.env.example
|
|
12
9
|
**/build
|
|
13
10
|
**/dist
|
|
14
11
|
**/node_modules
|
|
@@ -17,3 +14,8 @@ playwright-report
|
|
|
17
14
|
test-results
|
|
18
15
|
**/data
|
|
19
16
|
*.log
|
|
17
|
+
# 仅排除本机私有配置,与 Git 处理一致;公共 .env 与 dev/test/prod 随上下文构建。
|
|
18
|
+
**/.env.local
|
|
19
|
+
**/.env.*.local
|
|
20
|
+
**/.env.runtime
|
|
21
|
+
**/.hai/deploy
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
+
# 构建产物目录
|
|
1
2
|
node_modules
|
|
2
|
-
**/data/
|
|
3
|
-
.turbo
|
|
4
|
-
dist
|
|
5
3
|
build
|
|
4
|
+
dist
|
|
5
|
+
.turbo
|
|
6
|
+
android
|
|
7
|
+
ios
|
|
6
8
|
**/src/lib/paraglide
|
|
7
|
-
playwright-report
|
|
8
|
-
test-results
|
|
9
|
-
.env
|
|
10
|
-
.env.*
|
|
11
|
-
!.env.example
|
|
12
|
-
!.env.example.hbs
|
|
13
9
|
**/src-tauri/gen/
|
|
14
10
|
**/src-tauri/target/
|
|
11
|
+
**/hash-core/target/
|
|
12
|
+
|
|
13
|
+
# 日志测试目录
|
|
14
|
+
*.log
|
|
15
|
+
test-results/
|
|
16
|
+
playwright-report/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# 系统文件
|
|
20
|
+
.DS_Store
|
|
21
|
+
|
|
22
|
+
# 本地数据目录
|
|
23
|
+
**/data/
|
|
15
24
|
|
|
16
25
|
# hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
|
|
17
26
|
.hai/
|
|
27
|
+
|
|
28
|
+
# 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
|
|
29
|
+
.env.local
|
|
30
|
+
.env.*.local
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# syntax=docker/dockerfile:1.7
|
|
2
2
|
FROM node:24-bookworm-slim AS build
|
|
3
|
+
ARG SQLITE_PREBUILD_MIRROR
|
|
4
|
+
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
5
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
6
|
+
ARG HAI_CORE_ENV=production
|
|
7
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
8
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
3
9
|
|
|
4
10
|
ENV PNPM_HOME=/pnpm
|
|
5
11
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -15,12 +21,16 @@ RUN pnpm build
|
|
|
15
21
|
|
|
16
22
|
FROM build AS service-build
|
|
17
23
|
|
|
24
|
+
RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
18
25
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{fullstack.servPackageName}} --prod /prod/service
|
|
19
26
|
|
|
20
27
|
FROM node:24-bookworm-slim AS service
|
|
28
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
29
|
+
ARG HAI_CORE_ENV=production
|
|
21
30
|
|
|
22
31
|
ENV NODE_ENV=production
|
|
23
|
-
ENV
|
|
32
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
33
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
24
34
|
ENV HOST=0.0.0.0
|
|
25
35
|
ENV PORT=3000
|
|
26
36
|
WORKDIR /app
|
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
11
11
|
"paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
|
|
12
|
+
"dev:test": "vite --mode test",
|
|
13
|
+
"dev:prod": "vite --mode prod",
|
|
12
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.app}}",
|
|
13
15
|
"build": "pnpm paraglide:compile && vite build",
|
|
16
|
+
"build:prod": "pnpm paraglide:compile && vite build --mode prod",
|
|
14
17
|
"package": "pnpm cap:sync",
|
|
15
18
|
"deploy": "pnpm package",
|
|
16
19
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.app}} --strictPort",
|
|
20
|
+
"check": "pnpm typecheck",
|
|
17
21
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
18
22
|
"lint": "eslint .",
|
|
23
|
+
"lint:fix": "eslint . --fix",
|
|
19
24
|
"test": "pnpm paraglide:compile && vitest run --passWithNoTests",
|
|
20
25
|
"test:watch": "pnpm paraglide:compile && vitest",
|
|
21
26
|
"clean": "rimraf dist build android ios .turbo src/lib/paraglide",
|
|
@@ -50,6 +55,7 @@
|
|
|
50
55
|
"@capacitor/preferences": "{{fullstack.packageSpecifiers.capacitorPreferences}}",
|
|
51
56
|
"@capacitor/push-notifications": "{{fullstack.packageSpecifiers.capacitorPushNotifications}}",
|
|
52
57
|
"@capacitor/status-bar": "{{fullstack.packageSpecifiers.capacitorStatusBar}}",
|
|
58
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
53
59
|
"@iconify-json/tabler": "{{fullstack.packageSpecifiers.iconifyJsonTabler}}",
|
|
54
60
|
"@iconify/tailwind4": "{{fullstack.packageSpecifiers.iconifyTailwind}}",
|
|
55
61
|
"@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
{{#if fullstack.frontends.app}}
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { core } from '@h-ai/core'
|
|
5
6
|
import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
|
|
6
7
|
import { paraglideVitePlugin } from '@inlang/paraglide-js'
|
|
7
8
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
8
9
|
import tailwindcss from '@tailwindcss/vite'
|
|
9
|
-
import { loadEnv } from 'vite'
|
|
10
10
|
import { defineConfig } from 'vitest/config'
|
|
11
11
|
|
|
12
12
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
13
13
|
|
|
14
14
|
export default defineConfig(({ mode }) => {
|
|
15
|
-
const
|
|
15
|
+
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
16
|
+
if (!envResult.success)
|
|
17
|
+
throw new Error(envResult.error.message)
|
|
18
|
+
const env = envResult.data
|
|
19
|
+
Object.assign(process.env, env)
|
|
16
20
|
const target = (env.PUBLIC_API_BASE || process.env.SERVICE_BASE_URL || 'http://127.0.0.1:3000').replace(/\/api\/v1\/?$/, '')
|
|
17
21
|
const proxy = { '/api': { target, changeOrigin: true } }
|
|
18
22
|
return {
|
|
19
|
-
envDir,
|
|
23
|
+
envDir: false,
|
|
20
24
|
server: { proxy },
|
|
21
25
|
preview: { proxy },
|
|
22
26
|
build: {
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
11
11
|
"paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
|
|
12
|
+
"dev:test": "vite --mode test",
|
|
13
|
+
"dev:prod": "vite --mode prod",
|
|
12
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.desktop}}",
|
|
13
15
|
"build": "pnpm paraglide:compile && vite build",
|
|
16
|
+
"build:prod": "pnpm paraglide:compile && vite build --mode prod",
|
|
14
17
|
"package": "pnpm tauri:build",
|
|
15
18
|
"deploy": "pnpm package",
|
|
16
19
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.desktop}} --strictPort",
|
|
20
|
+
"check": "pnpm typecheck",
|
|
17
21
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
18
22
|
"lint": "eslint src vite.config.ts svelte.config.js",
|
|
19
23
|
"lint:fix": "eslint src vite.config.ts svelte.config.js --fix",
|
|
@@ -31,6 +35,7 @@
|
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@antfu/eslint-config": "{{fullstack.packageSpecifiers.antfuEslintConfig}}",
|
|
38
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
34
39
|
"@iconify-json/tabler": "{{fullstack.packageSpecifiers.iconifyJsonTabler}}",
|
|
35
40
|
"@iconify/tailwind4": "{{fullstack.packageSpecifiers.iconifyTailwind}}",
|
|
36
41
|
"@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
|
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
{{#if fullstack.frontends.desktop}}
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { core } from '@h-ai/core'
|
|
5
6
|
import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
|
|
6
7
|
import { paraglideVitePlugin } from '@inlang/paraglide-js'
|
|
7
8
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
8
9
|
import tailwindcss from '@tailwindcss/vite'
|
|
9
|
-
import { loadEnv } from 'vite'
|
|
10
10
|
import { defineConfig } from 'vitest/config'
|
|
11
11
|
|
|
12
12
|
const host = process.env.TAURI_DEV_HOST
|
|
13
13
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
14
14
|
|
|
15
15
|
export default defineConfig(({ mode }) => {
|
|
16
|
-
const
|
|
16
|
+
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
17
|
+
if (!envResult.success)
|
|
18
|
+
throw new Error(envResult.error.message)
|
|
19
|
+
const env = envResult.data
|
|
20
|
+
Object.assign(process.env, env)
|
|
17
21
|
const target = (env.PUBLIC_API_BASE || process.env.SERVICE_BASE_URL || 'http://127.0.0.1:3000').replace(/\/api\/v1\/?$/, '')
|
|
18
22
|
const proxy = { '/api': { target, changeOrigin: true } }
|
|
19
23
|
|
|
20
24
|
return {
|
|
21
|
-
envDir,
|
|
25
|
+
envDir: false,
|
|
22
26
|
preview: { proxy },
|
|
23
27
|
build: {
|
|
24
28
|
rolldownOptions: {
|
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
11
11
|
"paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
|
|
12
|
+
"dev:test": "vite --mode test",
|
|
13
|
+
"dev:prod": "vite --mode prod",
|
|
12
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.web}}",
|
|
13
15
|
"build": "pnpm paraglide:compile && vite build",
|
|
16
|
+
"build:prod": "pnpm paraglide:compile && vite build --mode prod",
|
|
14
17
|
"package": "pnpm build",
|
|
15
18
|
"deploy": "pnpm package",
|
|
16
19
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.web}} --strictPort",
|
|
20
|
+
"check": "pnpm typecheck",
|
|
17
21
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
18
22
|
"lint": "eslint .",
|
|
23
|
+
"lint:fix": "eslint . --fix",
|
|
19
24
|
"test": "vitest run --passWithNoTests",
|
|
20
25
|
"test:watch": "vitest",
|
|
21
26
|
"clean": "rimraf dist build .turbo src/lib/paraglide"
|
|
@@ -26,6 +31,7 @@
|
|
|
26
31
|
},
|
|
27
32
|
"devDependencies": {
|
|
28
33
|
"@antfu/eslint-config": "{{fullstack.packageSpecifiers.antfuEslintConfig}}",
|
|
34
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
29
35
|
"@iconify-json/tabler": "{{fullstack.packageSpecifiers.iconifyJsonTabler}}",
|
|
30
36
|
"@iconify/tailwind4": "{{fullstack.packageSpecifiers.iconifyTailwind}}",
|
|
31
37
|
"@inlang/paraglide-js": "{{fullstack.packageSpecifiers.inlangParaglide}}",
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
{{#if fullstack.frontends.web}}
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { core } from '@h-ai/core'
|
|
5
6
|
import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
|
|
6
7
|
import { paraglideVitePlugin } from '@inlang/paraglide-js'
|
|
7
8
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
8
9
|
import tailwindcss from '@tailwindcss/vite'
|
|
9
|
-
import { loadEnv } from 'vite'
|
|
10
10
|
import { defineConfig } from 'vitest/config'
|
|
11
11
|
|
|
12
12
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
13
13
|
|
|
14
14
|
export default defineConfig(({ mode }) => {
|
|
15
|
-
const
|
|
15
|
+
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
16
|
+
if (!envResult.success)
|
|
17
|
+
throw new Error(envResult.error.message)
|
|
18
|
+
const env = envResult.data
|
|
19
|
+
Object.assign(process.env, env)
|
|
16
20
|
const target = (env.PUBLIC_API_BASE || process.env.SERVICE_BASE_URL || 'http://127.0.0.1:3000').replace(/\/api\/v1\/?$/, '')
|
|
17
21
|
const proxy = { '/api': { target, changeOrigin: true } }
|
|
18
22
|
return {
|
|
19
|
-
envDir,
|
|
23
|
+
envDir: false,
|
|
20
24
|
server: { proxy },
|
|
21
25
|
preview: { proxy },
|
|
22
26
|
build: {
|
|
@@ -61,11 +61,33 @@ test('service rejects plaintext business requests', async ({ request }) => {
|
|
|
61
61
|
})
|
|
62
62
|
|
|
63
63
|
{{#if fullstack.e2eFrontend}}
|
|
64
|
+
test('hash routing restores direct links, refresh and browser history', async ({ page }) => {
|
|
65
|
+
await page.goto('/#/roles?source=direct')
|
|
66
|
+
await login(page, 'admin', adminPassword)
|
|
67
|
+
await expect(page.getByTestId('roles-page')).toBeVisible()
|
|
68
|
+
await expect(page).toHaveURL(/#\/roles\?source=direct$/)
|
|
69
|
+
await page.reload()
|
|
70
|
+
await expect(page.getByTestId('roles-page')).toBeVisible()
|
|
71
|
+
await expect(page.locator('header')).toBeVisible()
|
|
72
|
+
await expect(page.locator('footer')).toBeVisible()
|
|
73
|
+
await page.getByTestId('nav-settings').click()
|
|
74
|
+
await expect(page).toHaveURL(/#\/settings$/)
|
|
75
|
+
await page.goBack()
|
|
76
|
+
await expect(page.getByTestId('roles-page')).toBeVisible()
|
|
77
|
+
await page.goForward()
|
|
78
|
+
await expect(page.getByTestId('profile-form')).toBeVisible()
|
|
79
|
+
await page.goto('/#/unknown-page')
|
|
80
|
+
await expect(page).toHaveURL(/#\/users$/)
|
|
81
|
+
await expect(page.getByTestId('users-page')).toBeVisible()
|
|
82
|
+
})
|
|
83
|
+
|
|
64
84
|
test('branded authentication, profile layout and blur validation work across screen sizes', async ({ page }, testInfo) => {
|
|
65
85
|
test.setTimeout(60_000)
|
|
66
86
|
await page.setViewportSize({ width: 1440, height: 960 })
|
|
67
87
|
await page.goto('/')
|
|
68
88
|
await expect(page.getByRole('heading', { name: configuredName, exact: true })).toBeVisible()
|
|
89
|
+
await expect(page.locator('header')).toBeVisible()
|
|
90
|
+
await expect(page.locator('footer')).toBeVisible()
|
|
69
91
|
await capture(page, '{{projectName}}-login-desktop.png', testInfo.outputPath('login-desktop.png'))
|
|
70
92
|
await page.getByRole('button', { name: /立即注册|Register now/, exact: true }).click()
|
|
71
93
|
await expect(page.locator('.register-form')).toBeVisible()
|
|
@@ -179,7 +201,7 @@ const additionalFrontends = [
|
|
|
179
201
|
{{#each fullstack.frontendApps}}
|
|
180
202
|
{
|
|
181
203
|
target: '{{target}}',
|
|
182
|
-
url: process.env
|
|
204
|
+
url: process.env['HAI_E2E_{{target}}_URL'.toUpperCase()] || 'http://127.0.0.1:{{port}}',
|
|
183
205
|
},
|
|
184
206
|
{{/each}}
|
|
185
207
|
].filter(frontend => frontend.target !== '{{fullstack.e2eFrontend.target}}')
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
"i18n:compile": "pnpm --filter {{fullstack.sharedPackageName}} paraglide:compile{{#each fullstack.frontendApps}} && pnpm --filter {{packageName}} paraglide:compile{{/each}}",
|
|
11
11
|
"postinstall": "pnpm i18n:compile",
|
|
12
12
|
"dev": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev",
|
|
13
|
+
"start": "pnpm --filter {{fullstack.servPackageName}} start",
|
|
14
|
+
"start:test": "pnpm --filter {{fullstack.servPackageName}} start:test",
|
|
15
|
+
"start:prod": "pnpm --filter {{fullstack.servPackageName}} start:prod",
|
|
16
|
+
"dev:test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev:test",
|
|
17
|
+
"dev:prod": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev:prod",
|
|
18
|
+
"build:prod": "pnpm build && pnpm -r --if-present build:prod",
|
|
13
19
|
"compile": "pnpm typecheck && pnpm build",
|
|
14
20
|
"build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
|
|
15
21
|
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
@@ -20,9 +26,12 @@
|
|
|
20
26
|
"podman:build:web": "podman build --format docker --jobs=1 --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
21
27
|
"package": "pnpm build{{#if fullstack.frontends.web}} && pnpm --filter {{projectName}}-web package{{/if}}{{#if fullstack.frontends.app}} && pnpm --filter {{projectName}}-app package{{/if}}{{#if fullstack.frontends.desktop}} && pnpm --filter {{projectName}}-desktop package{{/if}}",
|
|
22
28
|
"deploy": "pnpm package",
|
|
29
|
+
"check": "pnpm typecheck",
|
|
23
30
|
"typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
|
|
24
31
|
"lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
|
|
32
|
+
"lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
|
|
25
33
|
"test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present test",
|
|
34
|
+
"test:watch": "pnpm -r --parallel --if-present test:watch",
|
|
26
35
|
"test:e2e": "pnpm build && playwright test",
|
|
27
36
|
"clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
|
|
28
37
|
},
|
|
@@ -32,6 +41,9 @@
|
|
|
32
41
|
"{{fullstack.sharedPackageName}}": "workspace:*"
|
|
33
42
|
},
|
|
34
43
|
"devDependencies": {
|
|
44
|
+
{{#if features.deploy}}
|
|
45
|
+
"@h-ai/deploy": "{{haiPackageSpecifier}}",
|
|
46
|
+
{{/if}}
|
|
35
47
|
"@antfu/eslint-config": "{{fullstack.packageSpecifiers.antfuEslintConfig}}",
|
|
36
48
|
"@playwright/test": "{{fullstack.packageSpecifiers.playwright}}",
|
|
37
49
|
"@sveltejs/vite-plugin-svelte": "{{fullstack.packageSpecifiers.sveltePlugin}}",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# {{fullstack.contractPackageName}}
|
|
2
2
|
|
|
3
|
-
共享 API 契约包,包含 Zod Schema、`apiContract.route()` 业务接口定义与工程级 contract
|
|
3
|
+
共享 API 契约包,包含 Zod Schema、`apiContract.route()` 业务接口定义与工程级 contract。业务 schema、角色、流程及限额是模板初始方案,按用户需求修改,并同步服务端授权、调用方、测试与说明;不要求需求沿用默认业务。
|
|
4
4
|
|
|
5
5
|
## 骨架说明
|
|
6
6
|
|
|
7
7
|
- `src/app-schemas.ts`:跨接口、跨层复用的输入/数据 Schema 与类型。
|
|
8
8
|
- `src/app-contract.ts`:app 领域 contract;每个业务 path 直接写在对应接口定义中。
|
|
9
|
-
- `src/admin-schemas.ts` / `src/admin-contract.ts
|
|
9
|
+
- `src/admin-schemas.ts` / `src/admin-contract.ts`:示例管理员用户、初始 admin/user 角色、分页审计、个人设置、加密 JSON 头像上传 Schema/类型/路由;认证复用框架的 IAM auth contract,通过服务端配置启用注册、密码/LDAP/验证码登录及密码重置。
|
|
10
10
|
- `src/{{projectName}}-contract.ts`:工程级 contract,供 `@h-ai/serv` 与 `@h-ai/api-client` 共用。
|
|
11
11
|
|
|
12
12
|
只使用一次的输出 Schema 直接内联到 `.output(...)`;同一 contract 文件内重复使用时才保留私有常量。
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsup",
|
|
17
|
+
"check": "pnpm typecheck",
|
|
17
18
|
"typecheck": "tsc --noEmit",
|
|
18
19
|
"lint": "eslint .",
|
|
20
|
+
"lint:fix": "eslint . --fix",
|
|
19
21
|
"test": "vitest run",
|
|
20
22
|
"test:watch": "vitest",
|
|
21
23
|
"clean": "rimraf dist .turbo"
|
package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
ProfileUpdateSchema,
|
|
15
15
|
} from './admin-schemas.js'
|
|
16
16
|
|
|
17
|
-
/** mini
|
|
17
|
+
/** mini 管理示例的业务路径;初始 admin/user 角色可按实际需求调整。 */
|
|
18
18
|
export const adminContract = {
|
|
19
19
|
users: {
|
|
20
20
|
list: apiContract.route({ method: 'POST', path: '/admin/users/list', tags: ['admin'] })
|
package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
import { apiContract } from '@h-ai/api-contract'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import
|
|
4
|
-
appContract,
|
|
5
|
-
AppEchoInputSchema,
|
|
6
|
-
AppInfoOutputDataSchema,
|
|
7
|
-
ENABLED_FRONTENDS,
|
|
8
|
-
{{fullstack.contractExportName}},
|
|
9
|
-
} from '../src/index.js'
|
|
3
|
+
import * as contracts from '../src/index.js'
|
|
10
4
|
|
|
11
5
|
describe('{{fullstack.contractPackageName}}', () => {
|
|
12
6
|
it('defines app contract routes', () => {
|
|
13
|
-
expect({{fullstack.contractExportName}}.app).toBeDefined()
|
|
14
|
-
expect(appContract.info).toBeDefined()
|
|
15
|
-
expect(apiContract.pathOf(appContract.info)).toBe('/app/info')
|
|
7
|
+
expect(contracts.{{fullstack.contractExportName}}.app).toBeDefined()
|
|
8
|
+
expect(contracts.appContract.info).toBeDefined()
|
|
9
|
+
expect(apiContract.pathOf(contracts.appContract.info)).toBe('/app/info')
|
|
16
10
|
})
|
|
17
11
|
|
|
18
12
|
it('validates app info payload', () => {
|
|
19
|
-
const result = AppInfoOutputDataSchema.safeParse({
|
|
13
|
+
const result = contracts.AppInfoOutputDataSchema.safeParse({
|
|
20
14
|
name: '{{fullstack.servPackageName}}',
|
|
21
15
|
version: '0.1.0',
|
|
22
16
|
env: 'test',
|
|
23
17
|
defaultLocale: 'zh-CN',
|
|
24
18
|
supportedLocales: ['zh-CN', 'en-US'],
|
|
25
19
|
startedAt: '2026-01-01T00:00:00.000Z',
|
|
26
|
-
frontends: [...ENABLED_FRONTENDS],
|
|
20
|
+
frontends: [...contracts.ENABLED_FRONTENDS],
|
|
27
21
|
})
|
|
28
22
|
|
|
29
23
|
expect(result.success).toBe(true)
|
|
30
24
|
})
|
|
31
25
|
|
|
32
26
|
it('validates echo input boundaries', () => {
|
|
33
|
-
expect(AppEchoInputSchema.safeParse({ message: 'hello' }).success).toBe(true)
|
|
34
|
-
expect(AppEchoInputSchema.safeParse({ message: '' }).success).toBe(false)
|
|
27
|
+
expect(contracts.AppEchoInputSchema.safeParse({ message: 'hello' }).success).toBe(true)
|
|
28
|
+
expect(contracts.AppEchoInputSchema.safeParse({ message: '' }).success).toBe(false)
|
|
35
29
|
})
|
|
36
30
|
})
|