@h-ai/cli 0.1.0-alpha.50 → 0.1.0-alpha.53
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/dist/index.js +155 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/apps/api/.dockerignore.hbs +14 -0
- package/templates/apps/api/Dockerfile.hbs +37 -0
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +6 -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/data/.gitkeep.hbs +1 -0
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +2 -0
- package/templates/apps/api/package.json.hbs +4 -0
- package/templates/apps/fullstack/.dockerignore.hbs +16 -0
- package/templates/apps/fullstack/Dockerfile.hbs +58 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +2 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/public/runtime-config.js.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/tests/api.test.ts.hbs +27 -0
- package/templates/apps/fullstack/docker/19-runtime-config.envsh.hbs +5 -0
- package/templates/apps/fullstack/docker/nginx.conf.hbs +24 -0
- package/templates/apps/fullstack/docker/runtime-config.js.template.hbs +3 -0
- package/templates/apps/fullstack/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/data/.gitkeep.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +10 -1
- package/templates/base/.dockerignore.hbs +17 -0
- package/templates/base/.gitignore.hbs +2 -1
- package/templates/base/Dockerfile.hbs +62 -0
- package/templates/base/docker/nginx.conf.hbs +18 -0
- package/templates/base/package.json.hbs +16 -6
- package/templates/base/svelte.config.js.hbs +0 -8
- package/templates/skills/hai-ai/SKILL.md +4 -0
- package/templates/skills/hai-app-create/SKILL.md +15 -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.53",
|
|
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.53"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
FROM node:22-bookworm-slim AS build
|
|
3
|
+
|
|
4
|
+
ENV PNPM_HOME=/pnpm
|
|
5
|
+
ENV PATH=$PNPM_HOME:$PATH
|
|
6
|
+
ENV CI=true
|
|
7
|
+
WORKDIR /workspace
|
|
8
|
+
|
|
9
|
+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
10
|
+
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
11
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
|
12
|
+
COPY . .
|
|
13
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
14
|
+
RUN pnpm build
|
|
15
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm --filter {{api.servicePackageName}} --prod deploy /prod/app
|
|
16
|
+
|
|
17
|
+
FROM node:22-bookworm-slim AS runtime-base
|
|
18
|
+
|
|
19
|
+
ENV NODE_ENV=production
|
|
20
|
+
ENV HOST=0.0.0.0
|
|
21
|
+
ENV PORT=3000
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
COPY --from=build --chown=node:node /prod/app ./
|
|
25
|
+
RUN mkdir -p /app/data && chown node:node /app/data
|
|
26
|
+
|
|
27
|
+
USER node
|
|
28
|
+
EXPOSE 3000
|
|
29
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
30
|
+
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))"
|
|
31
|
+
CMD ["node", "dist/index.js"]
|
|
32
|
+
|
|
33
|
+
FROM runtime-base AS runtime-with-data
|
|
34
|
+
|
|
35
|
+
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
36
|
+
|
|
37
|
+
FROM runtime-base AS runtime
|
|
@@ -9,6 +9,12 @@ pnpm --filter {{api.servicePackageName}} dev
|
|
|
9
9
|
pnpm --filter {{api.servicePackageName}} test
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
## 容器运行
|
|
13
|
+
|
|
14
|
+
- 根目录运行 `pnpm docker:build` / `pnpm podman:build`;镜像监听 `0.0.0.0:3000`。
|
|
15
|
+
- 配置默认打包自本目录 `config/`,可在运行时只读挂载到 `/app/config`,敏感值通过环境变量注入。
|
|
16
|
+
- `data/` 可作为 SQLite/本地存储目录;`pnpm docker:build:data` / `pnpm podman:build:data` 会将其固化为镜像初始数据,生产写入应挂载 `/app/data` volume。
|
|
17
|
+
|
|
12
18
|
## 关键目录
|
|
13
19
|
|
|
14
20
|
- `config/`:service 配置
|
|
@@ -18,10 +18,10 @@ http:
|
|
|
18
18
|
rpc: false
|
|
19
19
|
|
|
20
20
|
cors:
|
|
21
|
-
origin:
|
|
22
|
-
nativeOrigins: http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost
|
|
23
|
-
{{#if features.iam}} credentials: true
|
|
24
|
-
{{else}} credentials: false
|
|
21
|
+
origin: ${HAI_CORS_ORIGIN:*}
|
|
22
|
+
nativeOrigins: ${HAI_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
|
|
23
|
+
{{#if features.iam}} credentials: ${HAI_CORS_CREDENTIALS:true}
|
|
24
|
+
{{else}} credentials: ${HAI_CORS_CREDENTIALS:false}
|
|
25
25
|
{{/if}} allowedHeaders:
|
|
26
26
|
- Authorization
|
|
27
27
|
- Content-Type
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"dev": "pnpm --filter {{api.servicePackageName}} dev",
|
|
10
10
|
"compile": "pnpm typecheck && pnpm build",
|
|
11
11
|
"build": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present build",
|
|
12
|
+
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
13
|
+
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
|
|
14
|
+
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
15
|
+
"podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./apps/{{projectName}}-service/data -t {{projectName}}:latest .",
|
|
12
16
|
"typecheck": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present typecheck",
|
|
13
17
|
"lint": "pnpm -r --if-present lint",
|
|
14
18
|
"test": "pnpm --filter {{api.contractPackageName}} build && pnpm -r --if-present test",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
FROM node:22-bookworm-slim AS build
|
|
3
|
+
|
|
4
|
+
ENV PNPM_HOME=/pnpm
|
|
5
|
+
ENV PATH=$PNPM_HOME:$PATH
|
|
6
|
+
ENV CI=true
|
|
7
|
+
WORKDIR /workspace
|
|
8
|
+
|
|
9
|
+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
10
|
+
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
11
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
|
12
|
+
COPY . .
|
|
13
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
14
|
+
RUN pnpm build
|
|
15
|
+
|
|
16
|
+
FROM build AS service-build
|
|
17
|
+
|
|
18
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm --filter {{fullstack.servPackageName}} --prod deploy /prod/service
|
|
19
|
+
|
|
20
|
+
FROM node:22-bookworm-slim AS service
|
|
21
|
+
|
|
22
|
+
ENV NODE_ENV=production
|
|
23
|
+
ENV HOST=0.0.0.0
|
|
24
|
+
ENV PORT=3000
|
|
25
|
+
WORKDIR /app
|
|
26
|
+
|
|
27
|
+
COPY --from=service-build --chown=node:node /prod/service ./
|
|
28
|
+
RUN mkdir -p /app/data && chown node:node /app/data
|
|
29
|
+
|
|
30
|
+
USER node
|
|
31
|
+
EXPOSE 3000
|
|
32
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
33
|
+
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))"
|
|
34
|
+
CMD ["node", "dist/index.js"]
|
|
35
|
+
|
|
36
|
+
FROM service AS runtime-with-data
|
|
37
|
+
|
|
38
|
+
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
39
|
+
|
|
40
|
+
FROM service AS runtime
|
|
41
|
+
|
|
42
|
+
{{#if fullstack.frontends.web}}
|
|
43
|
+
FROM nginxinc/nginx-unprivileged:1.29-alpine AS web
|
|
44
|
+
|
|
45
|
+
ENV PUBLIC_API_BASE=
|
|
46
|
+
ENV NGINX_ENVSUBST_FILTER=PUBLIC_API_BASE_BASE64
|
|
47
|
+
ENV NGINX_ENVSUBST_OUTPUT_DIR=/tmp/hai-runtime-config
|
|
48
|
+
|
|
49
|
+
RUN mkdir -p /tmp/hai-runtime-config
|
|
50
|
+
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
|
51
|
+
COPY --chmod=755 docker/19-runtime-config.envsh /docker-entrypoint.d/19-runtime-config.envsh
|
|
52
|
+
COPY docker/runtime-config.js.template /etc/nginx/templates/runtime-config.js.template
|
|
53
|
+
COPY --from=build /workspace/apps/{{projectName}}-web/dist /usr/share/nginx/html
|
|
54
|
+
|
|
55
|
+
EXPOSE 8080
|
|
56
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
57
|
+
CMD wget -Y off -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
58
|
+
{{/if}}
|
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
8
|
<title>{{projectName}} Web</title>
|
|
9
|
+
<script src="/runtime-config.js"></script>
|
|
9
10
|
</head>
|
|
10
11
|
<body>
|
|
11
12
|
<div id="app"></div>
|
|
12
13
|
<script type="module" src="/src/main.ts"></script>
|
|
13
14
|
</body>
|
|
14
15
|
</html>
|
|
15
|
-
{{/if}}
|
|
16
|
+
{{/if}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
globalThis.__HAI_PUBLIC_CONFIG__ = {}
|
|
@@ -24,5 +24,32 @@ describe('fetchAppInfo', () => {
|
|
|
24
24
|
|
|
25
25
|
expect(info.name).toBe('{{fullstack.servPackageName}}')
|
|
26
26
|
})
|
|
27
|
+
|
|
28
|
+
it('uses API origin injected at container startup', async () => {
|
|
29
|
+
Object.assign(globalThis, {
|
|
30
|
+
__HAI_PUBLIC_CONFIG__: { apiBase: 'https://api.example.com' },
|
|
31
|
+
})
|
|
32
|
+
let requestedUrl = ''
|
|
33
|
+
const fetchFn: typeof fetch = async (input) => {
|
|
34
|
+
requestedUrl = input instanceof Request ? input.url : String(input)
|
|
35
|
+
return new Response(JSON.stringify({
|
|
36
|
+
success: true,
|
|
37
|
+
data: {
|
|
38
|
+
name: '{{fullstack.servPackageName}}',
|
|
39
|
+
version: '0.1.0',
|
|
40
|
+
startedAt: '2026-01-01T00:00:00.000Z',
|
|
41
|
+
frontends: ['web'],
|
|
42
|
+
},
|
|
43
|
+
}), { headers: { 'content-type': 'application/json' } })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await fetchAppInfo({ fetchFn })
|
|
48
|
+
expect(requestedUrl).toContain('https://api.example.com/api/v1')
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
Reflect.deleteProperty(globalThis, '__HAI_PUBLIC_CONFIG__')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
27
54
|
})
|
|
28
55
|
{{/if}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{{! @skipIfEmpty }}
|
|
2
|
+
{{#if fullstack.frontends.web}}
|
|
3
|
+
server {
|
|
4
|
+
listen 8080;
|
|
5
|
+
server_name _;
|
|
6
|
+
root /usr/share/nginx/html;
|
|
7
|
+
|
|
8
|
+
location / {
|
|
9
|
+
try_files $uri $uri/ /index.html;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
location = /runtime-config.js {
|
|
13
|
+
alias /tmp/hai-runtime-config/runtime-config.js;
|
|
14
|
+
add_header Cache-Control "no-store";
|
|
15
|
+
default_type application/javascript;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
location = /health {
|
|
19
|
+
access_log off;
|
|
20
|
+
add_header Content-Type text/plain;
|
|
21
|
+
return 200 'ok';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
{{/if}}
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"dev": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm --parallel --filter {{fullstack.servPackageName}}{{#each fullstack.frontendApps}} --filter {{packageName}}{{/each}} dev",
|
|
12
12
|
"compile": "pnpm typecheck && pnpm build",
|
|
13
13
|
"build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
|
|
14
|
+
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
15
|
+
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
|
|
16
|
+
"docker:build:web": "docker build --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
17
|
+
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
18
|
+
"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}},
|
|
19
|
+
"podman:build:web": "podman build --format docker --jobs=1 --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
14
20
|
"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}}",
|
|
15
21
|
"deploy": "pnpm package",
|
|
16
22
|
"typecheck": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present typecheck",
|
|
@@ -15,3 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
- `pnpm --filter {{fullstack.servPackageName}} dev`
|
|
17
17
|
- `pnpm --filter {{fullstack.servPackageName}} test`
|
|
18
|
+
|
|
19
|
+
## 容器运行
|
|
20
|
+
|
|
21
|
+
- 根目录 `pnpm docker:build` / `pnpm podman:build` 直接生成本服务镜像;Web 使用独立的 `docker:build:web` / `podman:build:web` 镜像。
|
|
22
|
+
- 服务镜像直接由 `@h-ai/serv` 对外监听,配置挂载点为 `/app/config`,数据目录为 `/app/data`,不包含额外转发进程。
|
|
23
|
+
- Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
|
|
24
|
+
- YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
|
|
25
|
+
- 将 SQLite/本地存储放入本目录 `data/` 后运行 `pnpm docker:build:data` / `pnpm podman:build:data`,可生成带初始数据的镜像,持续写入仍应使用 volume。
|
|
@@ -21,9 +21,9 @@ http:
|
|
|
21
21
|
rpc: false
|
|
22
22
|
|
|
23
23
|
cors:
|
|
24
|
-
origin:
|
|
25
|
-
nativeOrigins: http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost
|
|
26
|
-
credentials: false
|
|
24
|
+
origin: ${HAI_CORS_ORIGIN:*}
|
|
25
|
+
nativeOrigins: ${HAI_CORS_NATIVEORIGINS:http://localhost,https://tauri.localhost,tauri://localhost,capacitor://localhost}
|
|
26
|
+
credentials: ${HAI_CORS_CREDENTIALS:false}
|
|
27
27
|
allowedHeaders:
|
|
28
28
|
- Authorization
|
|
29
29
|
- Content-Type
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs
CHANGED
|
@@ -38,7 +38,16 @@ export async function fetchAppInfo(options: FetchAppInfoOptions = {}): Promise<A
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function resolveDefaultApiOrigin(): string {
|
|
41
|
-
return
|
|
41
|
+
return resolveRuntimeApiOrigin()
|
|
42
|
+
|| import.meta.env.PUBLIC_API_BASE
|
|
43
|
+
|| (typeof globalThis.location === 'undefined' ? DEFAULT_API_ORIGIN : globalThis.location.origin)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveRuntimeApiOrigin(): string | undefined {
|
|
47
|
+
const runtimeConfig = (globalThis as typeof globalThis & {
|
|
48
|
+
__HAI_PUBLIC_CONFIG__?: { apiBase?: string }
|
|
49
|
+
}).__HAI_PUBLIC_CONFIG__
|
|
50
|
+
return runtimeConfig?.apiBase
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
function normalizeApiBaseUrl(value: string): string {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# syntax=docker/dockerfile:1.7
|
|
2
|
+
{{#if isSvelteOnlyApp}}
|
|
3
|
+
FROM node:22-bookworm-slim AS build
|
|
4
|
+
|
|
5
|
+
ENV PNPM_HOME=/pnpm
|
|
6
|
+
ENV PATH=$PNPM_HOME:$PATH
|
|
7
|
+
ENV CI=true
|
|
8
|
+
WORKDIR /workspace
|
|
9
|
+
|
|
10
|
+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
11
|
+
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
12
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
|
13
|
+
COPY . .
|
|
14
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
15
|
+
RUN pnpm build
|
|
16
|
+
|
|
17
|
+
FROM nginxinc/nginx-unprivileged:1.29-alpine AS runtime
|
|
18
|
+
|
|
19
|
+
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
|
20
|
+
COPY --from=build /workspace/dist /usr/share/nginx/html
|
|
21
|
+
|
|
22
|
+
EXPOSE 8080
|
|
23
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
24
|
+
CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
25
|
+
{{else}}
|
|
26
|
+
FROM node:22-bookworm-slim AS build
|
|
27
|
+
|
|
28
|
+
ENV PNPM_HOME=/pnpm
|
|
29
|
+
ENV PATH=$PNPM_HOME:$PATH
|
|
30
|
+
ENV CI=true
|
|
31
|
+
WORKDIR /workspace
|
|
32
|
+
|
|
33
|
+
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
|
34
|
+
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
35
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
|
36
|
+
COPY . .
|
|
37
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
38
|
+
RUN pnpm build
|
|
39
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm --filter {{projectName}} --prod deploy /prod/app
|
|
40
|
+
|
|
41
|
+
FROM node:22-bookworm-slim AS runtime-base
|
|
42
|
+
|
|
43
|
+
ENV NODE_ENV=production
|
|
44
|
+
ENV HOST=0.0.0.0
|
|
45
|
+
ENV PORT=3000
|
|
46
|
+
WORKDIR /app
|
|
47
|
+
|
|
48
|
+
COPY --from=build --chown=node:node /prod/app ./
|
|
49
|
+
RUN mkdir -p /app/data && chown node:node /app/data
|
|
50
|
+
|
|
51
|
+
USER node
|
|
52
|
+
EXPOSE 3000
|
|
53
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
54
|
+
CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
55
|
+
CMD ["node", "build"]
|
|
56
|
+
|
|
57
|
+
FROM runtime-base AS runtime-with-data
|
|
58
|
+
|
|
59
|
+
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
60
|
+
|
|
61
|
+
FROM runtime-base AS runtime
|
|
62
|
+
{{/if}}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{{! @skipIfEmpty }}
|
|
2
|
+
{{#if isSvelteOnlyApp}}
|
|
3
|
+
server {
|
|
4
|
+
listen 8080;
|
|
5
|
+
server_name _;
|
|
6
|
+
root /usr/share/nginx/html;
|
|
7
|
+
|
|
8
|
+
location / {
|
|
9
|
+
try_files $uri $uri/ /index.html;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
location = /health {
|
|
13
|
+
access_log off;
|
|
14
|
+
add_header Content-Type text/plain;
|
|
15
|
+
return 200 'ok';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
{{/if}}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.53",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
|
|
6
6
|
"keywords": ["hai", {{#unless isSvelteOnlyApp}}"sveltekit", {{/unless}}"svelte5", "{{appType}}"],
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"type": "module",
|
|
9
|
+
{{#unless isSvelteOnlyApp}}
|
|
10
|
+
"files": [
|
|
11
|
+
"build",
|
|
12
|
+
"config",
|
|
13
|
+
"data",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
{{/unless}}
|
|
9
17
|
"scripts": {
|
|
10
18
|
{{#if hasI18n}}
|
|
11
19
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
@@ -19,7 +27,13 @@
|
|
|
19
27
|
"test": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vitest run",
|
|
20
28
|
"test:watch": "vitest",
|
|
21
29
|
"test:e2e": "playwright test",
|
|
22
|
-
"clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}"
|
|
30
|
+
"clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}",
|
|
31
|
+
"docker:build": "docker build --target runtime -t {{projectName}}:latest .",
|
|
32
|
+
{{#unless isSvelteOnlyApp}}
|
|
33
|
+
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest .",
|
|
34
|
+
{{/unless}}
|
|
35
|
+
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest ."{{#unless isSvelteOnlyApp}},
|
|
36
|
+
"podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest ."{{/unless}}{{#if isCapacitorApp}},
|
|
23
37
|
"cap:add:android": "cap add android",
|
|
24
38
|
"cap:add:ios": "cap add ios",
|
|
25
39
|
"cap:sync": "pnpm build && cap sync",
|
|
@@ -78,11 +92,7 @@
|
|
|
78
92
|
"@capacitor/push-notifications": "^8.0.1",
|
|
79
93
|
"@capacitor/status-bar": "^8.0.1",
|
|
80
94
|
{{else}}
|
|
81
|
-
{{#if_eq appType 'api'}}
|
|
82
95
|
"@sveltejs/adapter-node": "^5.5.4",
|
|
83
|
-
{{else}}
|
|
84
|
-
"@sveltejs/adapter-auto": "^7.0.1",
|
|
85
|
-
{{/if_eq}}
|
|
86
96
|
{{/if}}
|
|
87
97
|
{{#unless isSvelteOnlyApp}}
|
|
88
98
|
"@sveltejs/kit": "^2.61.1",
|
|
@@ -8,15 +8,7 @@ import process from 'node:process'
|
|
|
8
8
|
import { autoImportHaiUi } from '@h-ai/ui/auto-import'
|
|
9
9
|
{{/if}}
|
|
10
10
|
{{#unless isSvelteOnlyApp}}
|
|
11
|
-
{{#if isCapacitorApp}}
|
|
12
|
-
import adapter from '@sveltejs/adapter-static'
|
|
13
|
-
{{else}}
|
|
14
|
-
{{#if_eq appType 'api'}}
|
|
15
11
|
import adapter from '@sveltejs/adapter-node'
|
|
16
|
-
{{else}}
|
|
17
|
-
import adapter from '@sveltejs/adapter-auto'
|
|
18
|
-
{{/if_eq}}
|
|
19
|
-
{{/if}}
|
|
20
12
|
{{/unless}}
|
|
21
13
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
22
14
|
|
|
@@ -337,6 +337,10 @@ if (!playable.success)
|
|
|
337
337
|
- 平台不支持的输入方式(如 OpenAI/MiMo 的持续音频输入)会抛 `AUDIO_UNSUPPORTED_INPUT`,不伪装成实时。
|
|
338
338
|
- 浏览器/移动端经 `@h-ai/serv` 统一语音 WebSocket 入口访问;客户端用已登录 HTTP 请求获取短期一次性 ticket,`@h-ai/ai/client` 通过 `audio.getTicket` 建连,不把 IAM access token 放入 URL。浏览器客户端区分正常结束、取消(`AUDIO_CANCELLED`)与 `end` 前异常断连(`AUDIO_CONNECTION_FAILED`);服务端 error 帧保留领域错误码,`synthesize` 不返回未完成的部分音频。
|
|
339
339
|
|
|
340
|
+
### 本地 CPU 模型服务
|
|
341
|
+
|
|
342
|
+
hai-framework 源码仓库提供 `pnpm model <name> --device cpu` 与 `pnpm model:run <name>`,默认优先使用 Podman,可构建/启动 `faster-whisper-large-v3`、`indextts-2.5`、`qwen3-4b`。三套权重均优先从清单锁定的 ModelScope revision 下载并复用容器 volume,Hugging Face 只作回退。Whisper/IndexTTS 仍配置为 `audio.models[].baseUrl`,Qwen3-4B 仍配置为 OpenAI-compatible `llm.models[].baseUrl`;部署位置不进入 Provider 名称。离线部署使用准备权重后的 bundled 镜像。
|
|
343
|
+
|
|
340
344
|
## 文生图(Image)
|
|
341
345
|
|
|
342
346
|
`ai.image` 只暴露稳定的提示词、模型别名、像素尺寸、可选参考图和图片字节;OpenAI、Google、Qwen、Seedream 与 Pollinations 的请求结构、鉴权、响应解析和临时 URL 下载均封装在内部 Provider。
|
|
@@ -354,6 +354,20 @@ core.init() → reldb.init() → cache.init() → iam.init() → createBusinessT
|
|
|
354
354
|
|
|
355
355
|
---
|
|
356
356
|
|
|
357
|
+
## 容器交付
|
|
358
|
+
|
|
359
|
+
- 保持脚手架根目录 `Dockerfile`、`.dockerignore` 与 `package.json` 中 `docker:build*` / `podman:build*` 命令同步。
|
|
360
|
+
- Podman 多阶段构建保持 `--format docker --jobs=1`;前者保留 `HEALTHCHECK`,后者避免部分 Podman 5.8/Buildah 组合在并行 stage 构建时触发内部 semaphore panic。
|
|
361
|
+
- 先复制 lockfile 与 workspace 配置执行 `pnpm fetch`,再复制源码并离线安装、构建;避免源码变化重复下载依赖,同时兼容新增 workspace。
|
|
362
|
+
- SvelteKit 服务使用 Node adapter;纯 Vite 前端使用带 SPA fallback 的静态镜像;fullstack 的 Service 与 Web 分别交付,Service 直接监听且不增加转发进程。
|
|
363
|
+
- Web 的 `PUBLIC_*` 部署配置在容器启动时生成静态运行时配置,不作为 build arg 烘焙,确保同一镜像可跨环境部署。
|
|
364
|
+
- 配置文件随镜像提供,并允许只读挂载覆盖;密钥只通过环境变量或 `--env-file` 注入。
|
|
365
|
+
- 普通构建不携带 `data/`;只有用户明确运行 `docker:build:data` / `podman:build:data` 时才通过 named build context 嵌入 SQLite/本地存储初始快照。
|
|
366
|
+
- 数据库和本地存储的持续写入必须落到 volume,不能依赖镜像可写层。
|
|
367
|
+
- 完成前实际启动镜像,验证健康端点、非 root 用户、环境变量覆盖、配置挂载和预置数据读取。
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
357
371
|
## 检查清单
|
|
358
372
|
|
|
359
373
|
- [ ] **TDD 流程已执行**:先写测试 → 确认失败 → 再实现 → 确认通过
|
|
@@ -365,6 +379,7 @@ core.init() → reldb.init() → cache.init() → iam.init() → createBusinessT
|
|
|
365
379
|
- [ ] 日志级别正确(读 debug / 写 debug+info / 失败 warn|error)
|
|
366
380
|
- [ ] 禁止 `console.log`、`any`、硬编码密钥
|
|
367
381
|
- [ ] 用户可见文本走 i18n
|
|
382
|
+
- [ ] Docker / Podman 镜像可启动,配置/环境变量/数据目录行为已验证
|
|
368
383
|
- [ ] 新增表有索引定义
|
|
369
384
|
- [ ] 组件优先使用 `@h-ai/ui`
|
|
370
385
|
- [ ] `pnpm typecheck && pnpm lint && pnpm test` 通过
|