@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.66
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 +13 -7
- package/README.ja.md +13 -7
- package/README.md +14 -8
- package/dist/{chunk-IPG3QNYK.js → chunk-FWN5B54L.js} +36 -4
- package/dist/chunk-FWN5B54L.js.map +1 -0
- package/dist/{chunk-47V5OT22.js → chunk-QYX6V7H2.js} +17 -43
- package/dist/chunk-QYX6V7H2.js.map +1 -0
- package/dist/cli-config-templates-3XSFIXBP.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-TSYEQTCY.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
- package/dist/index.js +273 -208
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-RDQ2EIBT.js} +506 -488
- package/dist/src-RDQ2EIBT.js.map +1 -0
- package/package.json +8 -8
- package/templates/apps/api/.dockerignore.hbs +4 -5
- package/templates/apps/api/.gitignore.hbs +2 -1
- package/templates/apps/api/Dockerfile.hbs +2 -15
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +0 -1
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/package.json.hbs +5 -8
- package/templates/apps/fullstack/.dockerignore.hbs +4 -5
- package/templates/apps/fullstack/.gitignore.hbs +2 -1
- package/templates/apps/fullstack/Dockerfile.hbs +3 -15
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +1 -0
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/package.json.hbs +5 -12
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +1 -1
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/base/.dockerignore.hbs +4 -5
- package/templates/base/.gitignore.hbs +2 -1
- package/templates/base/Dockerfile.hbs +5 -22
- package/templates/base/docker/nginx.conf.hbs +6 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/package.json.hbs +13 -17
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +6 -2
- package/templates/shared/.gitattributes +5 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/hai-app-create/development.md +3 -1
- package/templates/skills/hai-app-create/reference.md +2 -2
- package/templates/skills/hai-build/SKILL.md +16 -1
- package/templates/skills/hai-deploy/SKILL.md +52 -260
- package/templates/skills/hai-deploy/middleware.md +8 -14
- package/templates/skills/hai-ui/reference.md +2 -0
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
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.66",
|
|
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.66",
|
|
34
34
|
"cac": "^7.0.1",
|
|
35
35
|
"chalk": "^6.0.1",
|
|
36
36
|
"fast-wrap-ansi": "^0.2.2",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"yaml": "^2.9.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@h-ai/cache": "0.1.0-alpha.
|
|
45
|
-
"@h-ai/crypto": "0.1.0-alpha.
|
|
46
|
-
"@h-ai/deploy": "0.1.0-alpha.
|
|
47
|
-
"@h-ai/reach": "0.1.0-alpha.
|
|
48
|
-
"@h-ai/reldb": "0.1.0-alpha.
|
|
49
|
-
"@h-ai/storage": "0.1.0-alpha.
|
|
44
|
+
"@h-ai/cache": "0.1.0-alpha.66",
|
|
45
|
+
"@h-ai/crypto": "0.1.0-alpha.66",
|
|
46
|
+
"@h-ai/deploy": "0.1.0-alpha.66",
|
|
47
|
+
"@h-ai/reach": "0.1.0-alpha.66",
|
|
48
|
+
"@h-ai/reldb": "0.1.0-alpha.66",
|
|
49
|
+
"@h-ai/storage": "0.1.0-alpha.66",
|
|
50
50
|
"@types/fs-extra": "^11.0.4",
|
|
51
51
|
"@types/node": "^26.6.5",
|
|
52
52
|
"@types/prompts": "^2.4.9",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.git
|
|
2
|
+
**/*.tar
|
|
2
3
|
.github
|
|
3
4
|
.agents
|
|
4
5
|
.claude
|
|
@@ -12,8 +13,6 @@ playwright-report
|
|
|
12
13
|
test-results
|
|
13
14
|
**/data
|
|
14
15
|
*.log
|
|
15
|
-
#
|
|
16
|
-
**/.env
|
|
17
|
-
**/.
|
|
18
|
-
**/.env.runtime
|
|
19
|
-
**/.hai/deploy
|
|
16
|
+
# 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
|
|
17
|
+
**/.env.*
|
|
18
|
+
**/.hai
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
FROM node:24-bookworm-slim AS build
|
|
3
3
|
ARG SQLITE_PREBUILD_MIRROR
|
|
4
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
|
|
9
5
|
|
|
10
6
|
ENV PNPM_HOME=/pnpm
|
|
11
7
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -13,21 +9,16 @@ ENV CI=true
|
|
|
13
9
|
WORKDIR /workspace
|
|
14
10
|
|
|
15
11
|
RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
|
|
16
|
-
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
17
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
18
12
|
COPY . .
|
|
13
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
19
14
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
20
|
-
RUN pnpm
|
|
15
|
+
RUN pnpm compile
|
|
21
16
|
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
|
|
22
17
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{api.servicePackageName}} --prod /prod/app
|
|
23
18
|
|
|
24
19
|
FROM node:24-bookworm-slim AS runtime-base
|
|
25
|
-
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
26
|
-
ARG HAI_CORE_ENV=production
|
|
27
20
|
|
|
28
21
|
ENV NODE_ENV=production
|
|
29
|
-
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
30
|
-
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
31
22
|
ENV HOST=0.0.0.0
|
|
32
23
|
ENV PORT=3000
|
|
33
24
|
WORKDIR /app
|
|
@@ -41,8 +32,4 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
|
41
32
|
CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
42
33
|
CMD ["node", "dist/index.js"]
|
|
43
34
|
|
|
44
|
-
FROM runtime-base AS runtime-with-data
|
|
45
|
-
|
|
46
|
-
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
47
|
-
|
|
48
35
|
FROM runtime-base AS runtime
|
|
@@ -11,9 +11,9 @@ pnpm --filter {{api.servicePackageName}} test
|
|
|
11
11
|
|
|
12
12
|
## 容器运行
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
从工程根运行 `pnpm build --target docker` 构建并导出镜像,再运行 `pnpm deploy --env test`。deploy 只消费已构建镜像;含 Web 时一并发布 API/Web。网络不通用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。
|
|
15
15
|
- 配置默认打包自本目录 `config/`,可在运行时只读挂载到 `/app/config`,敏感值通过环境变量注入。
|
|
16
|
-
|
|
16
|
+
从工程根运行 `pnpm build --target docker` 构建并导出镜像,再运行 `pnpm deploy --env test`。deploy 只消费已构建镜像;含 Web 时一并发布 API/Web。网络不通用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。
|
|
17
17
|
|
|
18
18
|
## 关键目录
|
|
19
19
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "images": [{ "name": "app", "target": "runtime", "containerPort": 3000 }] }
|
|
@@ -13,20 +13,16 @@
|
|
|
13
13
|
"start:prod": "pnpm --filter {{api.servicePackageName}} start:prod",
|
|
14
14
|
"dev:test": "pnpm --filter {{api.servicePackageName}} dev:test",
|
|
15
15
|
"dev:prod": "pnpm --filter {{api.servicePackageName}} dev:prod",
|
|
16
|
-
"compile": "pnpm
|
|
17
|
-
"build": "
|
|
18
|
-
"
|
|
19
|
-
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
|
|
20
|
-
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
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 .",
|
|
16
|
+
"compile": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
|
|
17
|
+
"build": "hai build",
|
|
18
|
+
"deploy": "hai deploy apps/{{projectName}}-service",
|
|
22
19
|
"check": "pnpm typecheck",
|
|
23
20
|
"typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
|
|
24
21
|
"lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
|
|
25
22
|
"lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
|
|
26
23
|
"test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
|
|
27
24
|
"test:watch": "pnpm -r --parallel --if-present test:watch",
|
|
28
|
-
"
|
|
29
|
-
"test:e2e": "pnpm build && playwright test",
|
|
25
|
+
"test:e2e": "pnpm compile && node scripts/run-e2e.mjs",
|
|
30
26
|
"clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
|
|
31
27
|
},
|
|
32
28
|
"dependencies": {
|
|
@@ -34,6 +30,7 @@
|
|
|
34
30
|
"{{api.contractPackageName}}": "workspace:*"
|
|
35
31
|
},
|
|
36
32
|
"devDependencies": {
|
|
33
|
+
"@h-ai/cli": "{{haiPackageSpecifier}}",
|
|
37
34
|
"@h-ai/api-client": "{{haiPackageSpecifier}}",
|
|
38
35
|
{{#if features.crypto}}
|
|
39
36
|
"@h-ai/crypto": "{{haiPackageSpecifier}}",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.git
|
|
2
|
+
**/*.tar
|
|
2
3
|
Dockerfile
|
|
3
4
|
.github
|
|
4
5
|
.agents
|
|
@@ -14,8 +15,6 @@ playwright-report
|
|
|
14
15
|
test-results
|
|
15
16
|
**/data
|
|
16
17
|
*.log
|
|
17
|
-
#
|
|
18
|
-
**/.env
|
|
19
|
-
**/.
|
|
20
|
-
**/.env.runtime
|
|
21
|
-
**/.hai/deploy
|
|
18
|
+
# 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
|
|
19
|
+
**/.env.*
|
|
20
|
+
**/.hai
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
FROM node:24-bookworm-slim AS build
|
|
3
3
|
ARG SQLITE_PREBUILD_MIRROR
|
|
4
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
|
|
9
5
|
|
|
10
6
|
ENV PNPM_HOME=/pnpm
|
|
11
7
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -13,11 +9,11 @@ ENV CI=true
|
|
|
13
9
|
WORKDIR /workspace
|
|
14
10
|
|
|
15
11
|
RUN corepack enable && corepack prepare pnpm@12.11.2 --activate
|
|
16
|
-
|
|
17
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
12
|
+
# 本地 file: 归档和 workspace 文件必须先进入上下文,离线材料不能只复制 lockfile。
|
|
18
13
|
COPY . .
|
|
14
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
19
15
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
20
|
-
RUN pnpm
|
|
16
|
+
RUN pnpm compile
|
|
21
17
|
|
|
22
18
|
FROM build AS service-build
|
|
23
19
|
|
|
@@ -25,12 +21,8 @@ RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3;
|
|
|
25
21
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{fullstack.servPackageName}} --prod /prod/service
|
|
26
22
|
|
|
27
23
|
FROM node:24-bookworm-slim AS service
|
|
28
|
-
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
29
|
-
ARG HAI_CORE_ENV=production
|
|
30
24
|
|
|
31
25
|
ENV NODE_ENV=production
|
|
32
|
-
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
33
|
-
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
34
26
|
ENV HOST=0.0.0.0
|
|
35
27
|
ENV PORT=3000
|
|
36
28
|
WORKDIR /app
|
|
@@ -44,10 +36,6 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
|
44
36
|
CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
45
37
|
CMD ["node", "dist/index.js"]
|
|
46
38
|
|
|
47
|
-
FROM service AS runtime-with-data
|
|
48
|
-
|
|
49
|
-
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
50
|
-
|
|
51
39
|
FROM service AS runtime
|
|
52
40
|
|
|
53
41
|
{{#if fullstack.frontends.web}}
|
|
@@ -29,9 +29,6 @@ pnpm --filter {{projectName}}-app package
|
|
|
29
29
|
pnpm --filter {{projectName}}-app cap:build:android:debug
|
|
30
30
|
pnpm --filter {{projectName}}-app cap:build:android:release
|
|
31
31
|
pnpm --filter {{projectName}}-app cap:build:ios
|
|
32
|
-
|
|
33
|
-
# 当前模板的 deploy 入口默认执行 package,可按目标商店或 CI 平台替换
|
|
34
|
-
pnpm --filter {{projectName}}-app deploy
|
|
35
32
|
```
|
|
36
33
|
|
|
37
34
|
部署前确认 `PUBLIC_API_BASE` 在 `.env` 中指向可访问的后端服务。Android 模拟器访问宿主机通常使用 `http://10.0.2.2:3000`。
|
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
"dev:prod": "vite --mode prod",
|
|
14
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.app}}",
|
|
15
15
|
"build": "pnpm paraglide:compile && vite build",
|
|
16
|
-
"build:prod": "pnpm paraglide:compile && vite build --mode prod",
|
|
17
16
|
"package": "pnpm cap:sync",
|
|
18
|
-
"deploy": "pnpm package",
|
|
19
17
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.app}} --strictPort",
|
|
20
18
|
"check": "pnpm typecheck",
|
|
21
19
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
@@ -11,8 +11,8 @@ import { defineConfig } from 'vitest/config'
|
|
|
11
11
|
|
|
12
12
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
13
13
|
|
|
14
|
-
export default defineConfig(({ mode }) => {
|
|
15
|
-
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
14
|
+
export default defineConfig(({ mode, command }) => {
|
|
15
|
+
const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
|
|
16
16
|
if (!envResult.success)
|
|
17
17
|
throw new Error(envResult.error.message)
|
|
18
18
|
const env = envResult.data
|
|
@@ -26,14 +26,11 @@ pnpm --filter {{projectName}}-desktop tauri:dev
|
|
|
26
26
|
# 编译 WebView 前端产物到 dist/
|
|
27
27
|
pnpm --filter {{projectName}}-desktop build
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
pnpm --
|
|
31
|
-
|
|
32
|
-
# 当前模板的 deploy 入口默认执行 package,可按发布平台替换
|
|
33
|
-
pnpm --filter {{projectName}}-desktop deploy
|
|
29
|
+
# 在工程根打包桌面安装包;在对应系统构建 windows / linux / macos
|
|
30
|
+
pnpm build --target desktop --platform windows --arch x64
|
|
34
31
|
```
|
|
35
32
|
|
|
36
|
-
打包前需要安装 Rust 工具链和 Tauri
|
|
33
|
+
打包前需要安装 Rust 工具链和 Tauri 平台依赖。构建不读取 .env.prod/.env.local;启动已安装程序前设置进程 PUBLIC_API_BASE(HTTP(S),不含凭据、查询和片段)。Rust 会在页面加载前注入公开地址,仅将该 Origin 加入 CSP;不需要为 test/prod 重打安装包。缺省连接 http://localhost:3000/api/v1。
|
|
37
34
|
|
|
38
35
|
## IAM 认证配置
|
|
39
36
|
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
"dev:test": "vite --mode test",
|
|
13
13
|
"dev:prod": "vite --mode prod",
|
|
14
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.desktop}}",
|
|
15
|
-
"
|
|
16
|
-
"build
|
|
15
|
+
"compile": "pnpm paraglide:compile && vite build",
|
|
16
|
+
"build": "pnpm compile",
|
|
17
17
|
"package": "pnpm tauri:build",
|
|
18
|
-
"deploy": "pnpm package",
|
|
19
18
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.desktop}} --strictPort",
|
|
20
19
|
"check": "pnpm typecheck",
|
|
21
20
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
@@ -1,16 +1,87 @@
|
|
|
1
1
|
{{! @skipIfEmpty }}
|
|
2
2
|
{{#if fullstack.frontends.desktop}}
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use tauri::utils::config::{Csp, CspDirectiveSources};
|
|
5
|
+
|
|
6
|
+
/// 校验运行期 API 地址;例如 `https://api.example.test/api/v1`,不接收认证信息或查询参数。
|
|
7
|
+
fn parse_runtime_api(value: &str) -> Result<tauri::Url, &'static str> {
|
|
8
|
+
let url = tauri::Url::parse(value).map_err(|_| "Invalid PUBLIC_API_BASE")?;
|
|
9
|
+
if !["http", "https"].contains(&url.scheme())
|
|
10
|
+
|| url.host_str().is_none()
|
|
11
|
+
|| !url.username().is_empty()
|
|
12
|
+
|| url.password().is_some()
|
|
13
|
+
|| url.query().is_some()
|
|
14
|
+
|| url.fragment().is_some()
|
|
15
|
+
{
|
|
16
|
+
return Err(
|
|
17
|
+
"PUBLIC_API_BASE must be an HTTP(S) URL without credentials, query or fragment",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
Ok(url)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// 仅将当前运行期 API 的 origin 加入 CSP;不放宽到所有 HTTP/HTTPS 站点。
|
|
24
|
+
fn runtime_csp(policy: Csp, origin: &str) -> Csp {
|
|
25
|
+
let mut directives: HashMap<String, CspDirectiveSources> = policy.into();
|
|
26
|
+
for name in ["connect-src", "img-src"] {
|
|
27
|
+
directives.entry(name.to_string()).or_default().push(origin);
|
|
28
|
+
}
|
|
29
|
+
directives.into()
|
|
30
|
+
}
|
|
31
|
+
|
|
3
32
|
#[tauri::command]
|
|
4
33
|
fn greet(name: &str) -> String {
|
|
5
|
-
format!("Hello, {}! Welcome to
|
|
34
|
+
format!("Hello, {}! Welcome to Desktop.", name)
|
|
6
35
|
}
|
|
7
36
|
|
|
8
37
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
9
38
|
pub fn run() {
|
|
39
|
+
// 构建不读取部署环境;启动时从进程 PUBLIC_API_BASE 获取公开地址。
|
|
40
|
+
let api = parse_runtime_api(
|
|
41
|
+
&std::env::var("PUBLIC_API_BASE")
|
|
42
|
+
.unwrap_or_else(|_| "http://localhost:3000/api/v1".to_string()),
|
|
43
|
+
)
|
|
44
|
+
.expect("Invalid runtime API configuration");
|
|
45
|
+
let mut context = tauri::generate_context!();
|
|
46
|
+
if let Some(policy) = context.config_mut().app.security.csp.take() {
|
|
47
|
+
context.config_mut().app.security.csp =
|
|
48
|
+
Some(runtime_csp(policy, &api.origin().ascii_serialization()));
|
|
49
|
+
}
|
|
50
|
+
let script = format!(
|
|
51
|
+
"globalThis.__HAI_PUBLIC_CONFIG__={};",
|
|
52
|
+
serde_json::json!({ "apiBase": api.as_str() })
|
|
53
|
+
);
|
|
10
54
|
tauri::Builder::default()
|
|
11
55
|
.plugin(tauri_plugin_shell::init())
|
|
56
|
+
.append_invoke_initialization_script(script)
|
|
12
57
|
.invoke_handler(tauri::generate_handler![greet])
|
|
13
|
-
.run(
|
|
58
|
+
.run(context)
|
|
14
59
|
.expect("error while running tauri application");
|
|
15
60
|
}
|
|
61
|
+
|
|
62
|
+
#[cfg(test)]
|
|
63
|
+
mod tests {
|
|
64
|
+
use super::*;
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn validates_runtime_address_and_scopes_csp() {
|
|
68
|
+
let api = parse_runtime_api("https://api.example.test:8443/custom/v2").unwrap();
|
|
69
|
+
let policy = runtime_csp(
|
|
70
|
+
Csp::Policy("default-src 'self'; connect-src 'self'".into()),
|
|
71
|
+
&api.origin().ascii_serialization(),
|
|
72
|
+
);
|
|
73
|
+
let directives: HashMap<String, CspDirectiveSources> = policy.into();
|
|
74
|
+
let sources: Vec<String> = directives["connect-src"].clone().into();
|
|
75
|
+
assert!(sources.contains(&"https://api.example.test:8443".to_string()));
|
|
76
|
+
assert!(!sources.contains(&"https:".to_string()));
|
|
77
|
+
for value in [
|
|
78
|
+
"file:///tmp/config",
|
|
79
|
+
"https://user:secret@host",
|
|
80
|
+
"https://host/?token=secret",
|
|
81
|
+
"relative",
|
|
82
|
+
] {
|
|
83
|
+
assert!(parse_runtime_api(value).is_err());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
16
87
|
{{/if}}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
{
|
|
4
4
|
"$schema": "https://schema.tauri.app/config/2",
|
|
5
5
|
"productName": {{json fullstack.appName}},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "../../../package.json",
|
|
7
7
|
"identifier": "com.hai.{{fullstack.nativeAppIdSegment}}.desktop",
|
|
8
8
|
"build": {
|
|
9
9
|
"beforeDevCommand": "pnpm dev",
|
|
10
|
-
"beforeBuildCommand": "pnpm
|
|
10
|
+
"beforeBuildCommand": "pnpm compile",
|
|
11
11
|
"devUrl": "http://localhost:{{fullstack.ports.desktop}}",
|
|
12
12
|
"frontendDist": "../dist"
|
|
13
13
|
},
|
|
@@ -12,8 +12,8 @@ import { defineConfig } from 'vitest/config'
|
|
|
12
12
|
const host = process.env.TAURI_DEV_HOST
|
|
13
13
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
14
14
|
|
|
15
|
-
export default defineConfig(({ mode }) => {
|
|
16
|
-
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
15
|
+
export default defineConfig(({ mode, command }) => {
|
|
16
|
+
const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
|
|
17
17
|
if (!envResult.success)
|
|
18
18
|
throw new Error(envResult.error.message)
|
|
19
19
|
const env = envResult.data
|
|
@@ -20,14 +20,10 @@ pnpm --filter {{projectName}}-web dev
|
|
|
20
20
|
|
|
21
21
|
# 编译 Web 前端
|
|
22
22
|
pnpm --filter {{projectName}}-web build
|
|
23
|
-
|
|
24
|
-
# 打包部署产物;默认等同 build,可按目标平台替换
|
|
25
|
-
pnpm --filter {{projectName}}-web package
|
|
26
|
-
|
|
27
|
-
# 当前模板的 deploy 入口默认执行 package,可替换为 CDN / 容器发布脚本
|
|
28
|
-
pnpm --filter {{projectName}}-web deploy
|
|
29
23
|
```
|
|
30
24
|
|
|
25
|
+
从工程根运行 `pnpm build --target docker` 构建并导出镜像。启用 deploy 功能后,运行根命令 `pnpm deploy --env test`,一次发布 API/Web;网络不通时用 `pnpm deploy --env prod --bundle-only`,按输出说明手工搬运 tar。部署不重新构建。
|
|
26
|
+
|
|
31
27
|
## IAM 认证配置
|
|
32
28
|
|
|
33
29
|
服务端 `config/_iam.yml` 统一控制登录、注册、重置、协议与 `ui` 品牌。邮件/短信可用 `_reach.yml` 中的 Provider/标准模板或自定义发送回调。`AuthPanel` 使用安全公开配置及框架认证动作;无需在页面适配 Token、验证码倒计时和错误提示。所有节点共享同一 DB 与 Redis;Memory 仅用于单进程。
|
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
"dev:prod": "vite --mode prod",
|
|
14
14
|
"dev": "vite dev --host 127.0.0.1 --port {{fullstack.ports.web}}",
|
|
15
15
|
"build": "pnpm paraglide:compile && vite build",
|
|
16
|
-
"build:prod": "pnpm paraglide:compile && vite build --mode prod",
|
|
17
16
|
"package": "pnpm build",
|
|
18
|
-
"deploy": "pnpm package",
|
|
19
17
|
"preview": "vite preview --host 127.0.0.1 --port {{fullstack.ports.web}} --strictPort",
|
|
20
18
|
"check": "pnpm typecheck",
|
|
21
19
|
"typecheck": "pnpm paraglide:compile && svelte-check --tsconfig ./tsconfig.json",
|
|
@@ -11,8 +11,8 @@ import { defineConfig } from 'vitest/config'
|
|
|
11
11
|
|
|
12
12
|
const envDir = fileURLToPath(new URL('../../', import.meta.url))
|
|
13
13
|
|
|
14
|
-
export default defineConfig(({ mode }) => {
|
|
15
|
-
const envResult = core.config.loadEnvFiles({ mode, envDir })
|
|
14
|
+
export default defineConfig(({ mode, command }) => {
|
|
15
|
+
const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir })
|
|
16
16
|
if (!envResult.success)
|
|
17
17
|
throw new Error(envResult.error.message)
|
|
18
18
|
const env = envResult.data
|
|
@@ -96,6 +96,7 @@ test('branded authentication, profile layout and blur validation work across scr
|
|
|
96
96
|
await capture(page, '{{projectName}}-register-mobile.png', testInfo.outputPath('register-mobile.png'))
|
|
97
97
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
|
98
98
|
await page.reload()
|
|
99
|
+
await expect(page.getByTestId('login-form')).toBeVisible()
|
|
99
100
|
await capture(page, '{{projectName}}-login-mobile.png', testInfo.outputPath('login-mobile.png'))
|
|
100
101
|
await page.setViewportSize({ width: 1440, height: 960 })
|
|
101
102
|
await login(page, 'admin', adminPassword)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"images": [
|
|
3
|
+
{ "name": "app", "target": "runtime", "containerPort": 3000 }{{#if fullstack.frontends.web}},
|
|
4
|
+
{ "name": "web", "target": "web", "containerPort": 8080 }{{/if}}
|
|
5
|
+
]{{#if fullstack.frontends.desktop}},
|
|
6
|
+
"desktop": ["apps/{{projectName}}-desktop"]{{/if}}
|
|
7
|
+
}
|
|
@@ -15,24 +15,16 @@
|
|
|
15
15
|
"start:prod": "pnpm --filter {{fullstack.servPackageName}} start:prod",
|
|
16
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
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
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
22
|
-
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
|
|
23
|
-
"docker:build:web": "docker build --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
24
|
-
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
25
|
-
"podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
|
|
26
|
-
"podman:build:web": "podman build --format docker --jobs=1 --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
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}}",
|
|
28
|
-
"deploy": "pnpm package",
|
|
18
|
+
"compile": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
|
|
19
|
+
"build": "hai build",
|
|
20
|
+
"deploy": "hai deploy packages/{{projectName}}-serv",
|
|
29
21
|
"check": "pnpm typecheck",
|
|
30
22
|
"typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
|
|
31
23
|
"lint": "eslint *.js *.ts e2e && pnpm -r --if-present lint",
|
|
32
24
|
"lint:fix": "eslint *.js *.ts e2e --fix && pnpm -r --if-present lint:fix",
|
|
33
25
|
"test": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present test",
|
|
34
26
|
"test:watch": "pnpm -r --parallel --if-present test:watch",
|
|
35
|
-
"test:e2e": "pnpm
|
|
27
|
+
"test:e2e": "pnpm compile && node scripts/run-e2e.mjs",
|
|
36
28
|
"clean": "pnpm -r --if-present clean && rimraf node_modules .turbo"
|
|
37
29
|
},
|
|
38
30
|
"dependencies": {
|
|
@@ -41,6 +33,7 @@
|
|
|
41
33
|
"{{fullstack.sharedPackageName}}": "workspace:*"
|
|
42
34
|
},
|
|
43
35
|
"devDependencies": {
|
|
36
|
+
"@h-ai/cli": "{{haiPackageSpecifier}}",
|
|
44
37
|
{{#if features.deploy}}
|
|
45
38
|
"@h-ai/deploy": "{{haiPackageSpecifier}}",
|
|
46
39
|
{{/if}}
|
|
@@ -35,11 +35,11 @@ CLI 已生成根目录 `.env.local`,`HAI_ADMIN_DEFAULT_PASSWORD` 为每个项
|
|
|
35
35
|
|
|
36
36
|
## 容器运行
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
从工程根运行 `pnpm build --target docker` 构建并导出镜像。启用 deploy 功能后,运行根命令 `pnpm deploy --env test`;含 Web 镜像时一并发布 API/Web,部署不重新构建。网络不通时用 `pnpm deploy --env prod --bundle-only`,按说明手工搬运 tar。
|
|
39
|
+
|
|
39
40
|
- 服务镜像直接由 `@h-ai/serv` 对外监听,配置挂载点为 `/app/config`,数据目录为 `/app/data`,不包含额外转发进程。
|
|
40
41
|
- Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
|
|
41
42
|
- YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
|
|
42
|
-
- 将 SQLite/本地存储放入本目录 `data/` 后运行 `pnpm docker:build:data` / `pnpm podman:build:data`,可生成带初始数据的镜像,持续写入仍应使用 volume。
|
|
43
43
|
|
|
44
44
|
## IAM 认证配置
|
|
45
45
|
|
|
@@ -39,7 +39,7 @@ The scaffold theme configuration determines the themes compiled into frontend CS
|
|
|
39
39
|
|
|
40
40
|
## Validation
|
|
41
41
|
|
|
42
|
-
Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation.
|
|
42
|
+
Run root `pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` and `pnpm test:e2e`. Shared API tests exercise real transport key exchange, encrypted request/response handling, configured core metadata and credential forwarding. Root browser tests cover administrator and ordinary-user workflows, avatar rendering, persisted preferences, password/session invalidation and narrow-screen layout. All selected Web/App/Desktop previews also exercise login, tours, help and profile navigation. Clean screenshots, reports, temporary projects, containers and helper processes in finally/teardown, including failure paths. These browser checks do not replace native-shell or miniapp device acceptance.
|
|
43
43
|
|
|
44
44
|
## IAM configuration
|
|
45
45
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
globalThis.__HAI_PUBLIC_CONFIG__ ??= {}
|
|
@@ -10,7 +10,8 @@ const DEFAULT_API_BASE = 'http://localhost:3000/api/v1'
|
|
|
10
10
|
let initialized = false
|
|
11
11
|
|
|
12
12
|
export function resolveApiBase(): string {
|
|
13
|
-
|
|
13
|
+
const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { apiBase?: string } }).__HAI_PUBLIC_CONFIG__
|
|
14
|
+
return runtime?.apiBase || import.meta.env.PUBLIC_API_BASE || DEFAULT_API_BASE
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export async function usesNativeTokenStorage(): Promise<boolean> {
|
|
@@ -5,8 +5,9 @@ declare const __APP_CORE_CONFIG__: Pick<CoreConfig, 'name' | 'version' | 'env' |
|
|
|
5
5
|
logging?: Pick<NonNullable<CoreConfig['logging']>, 'level' | 'format'>
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/** 构建包含公开默认值;预览或容器启动时的公开配置覆盖默认值,不重建静态资源。 */
|
|
9
|
+
const runtime = (globalThis as typeof globalThis & { __HAI_PUBLIC_CONFIG__?: { core?: Partial<typeof __APP_CORE_CONFIG__> } }).__HAI_PUBLIC_CONFIG__?.core
|
|
10
|
+
export const appConfig = { ...__APP_CORE_CONFIG__, ...runtime, logging: { ...__APP_CORE_CONFIG__.logging, ...runtime?.logging } }
|
|
10
11
|
|
|
11
12
|
export function initClientCore(): void {
|
|
12
13
|
core.init({ logging: appConfig.logging })
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.git
|
|
2
|
+
**/*.tar
|
|
2
3
|
.github
|
|
3
4
|
.agents
|
|
4
5
|
.claude
|
|
@@ -15,8 +16,6 @@ playwright-report
|
|
|
15
16
|
test-results
|
|
16
17
|
**/data
|
|
17
18
|
*.log
|
|
18
|
-
#
|
|
19
|
-
**/.env
|
|
20
|
-
**/.
|
|
21
|
-
**/.env.runtime
|
|
22
|
-
**/.hai/deploy
|
|
19
|
+
# 仅保留公共基础 .env;分环境配置、私有覆盖及已有交付包不进入构建上下文。
|
|
20
|
+
**/.env.*
|
|
21
|
+
**/.hai
|