@h-ai/cli 0.1.0-alpha.51 → 0.1.0-alpha.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/{chunk-U4PA632G.js → chunk-FVDXJYMO.js} +34 -67
- package/dist/chunk-FVDXJYMO.js.map +1 -0
- package/dist/cli-config-templates-WGN2I23J.js +5 -0
- package/dist/{cli-config-templates-GSXKLGU5.js.map → cli-config-templates-WGN2I23J.js.map} +1 -1
- package/dist/index.js +299 -127
- package/dist/index.js.map +1 -1
- package/dist/{src-QNEJJ3BV.js → src-6ZBWDBUB.js} +127 -87
- package/dist/{src-QNEJJ3BV.js.map → src-6ZBWDBUB.js.map} +1 -1
- package/package.json +13 -4
- package/templates/apps/api/Dockerfile.hbs +6 -3
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +1 -0
- package/templates/apps/api/apps/{{projectName}}-service/data/.gitkeep.hbs +1 -0
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -0
- package/templates/apps/api/package.json.hbs +1 -0
- package/templates/apps/fullstack/.dockerignore.hbs +2 -0
- package/templates/apps/fullstack/Dockerfile.hbs +23 -36
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/index.html.hbs +2 -1
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +1 -0
- 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 +6 -0
- package/templates/apps/fullstack/docker/runtime-config.js.template.hbs +3 -0
- package/templates/apps/fullstack/package.json.hbs +3 -4
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/data/.gitkeep.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -1
- package/templates/base/.gitignore.hbs +2 -1
- package/templates/base/Dockerfile.hbs +10 -4
- package/templates/base/eslint.config.js.hbs +1 -0
- package/templates/base/package.json.hbs +3 -1
- package/templates/skills/hai-ai/SKILL.md +4 -0
- package/templates/skills/hai-ai/reference.md +5 -1
- package/templates/skills/hai-app-create/SKILL.md +3 -1
- package/templates/skills/hai-app-review/SKILL.md +6 -0
- package/templates/skills/hai-build/SKILL.md +8 -0
- package/templates/skills/hai-cache/SKILL.md +2 -0
- package/templates/skills/hai-capacitor/SKILL.md +2 -0
- package/templates/skills/hai-ci/SKILL.md +12 -0
- package/templates/skills/hai-core/SKILL.md +2 -0
- package/templates/skills/hai-crypto/SKILL.md +2 -0
- package/templates/skills/hai-deploy/SKILL.md +12 -0
- package/templates/skills/hai-iam/SKILL.md +6 -0
- package/templates/skills/hai-scheduler/SKILL.md +12 -0
- package/templates/skills/hai-ui/SKILL.md +4 -0
- package/dist/chunk-U4PA632G.js.map +0 -1
- package/dist/cli-config-templates-GSXKLGU5.js +0 -5
- package/templates/apps/fullstack/docker/fullstack-server.mjs.hbs +0 -194
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.54",
|
|
5
5
|
"description": "Hai Framework CLI for project scaffolding and code generation.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.12.0"
|
|
31
|
+
},
|
|
29
32
|
"dependencies": {
|
|
30
33
|
"cac": "^7.0.0",
|
|
31
34
|
"chalk": "^5.4.1",
|
|
@@ -34,7 +37,7 @@
|
|
|
34
37
|
"ora": "^9.1.0",
|
|
35
38
|
"prompts": "^2.4.2",
|
|
36
39
|
"yaml": "^2.7.0",
|
|
37
|
-
"@h-ai/core": "0.1.0-alpha.
|
|
40
|
+
"@h-ai/core": "0.1.0-alpha.54"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -43,7 +46,12 @@
|
|
|
43
46
|
"rimraf": "^6.1.3",
|
|
44
47
|
"tsup": "^8.5.1",
|
|
45
48
|
"typescript": "^6.0.3",
|
|
46
|
-
"vitest": "^4.1.7"
|
|
49
|
+
"vitest": "^4.1.7",
|
|
50
|
+
"@h-ai/deploy": "0.1.0-alpha.54",
|
|
51
|
+
"@h-ai/reach": "0.1.0-alpha.54",
|
|
52
|
+
"@h-ai/reldb": "0.1.0-alpha.54",
|
|
53
|
+
"@h-ai/cache": "0.1.0-alpha.54",
|
|
54
|
+
"@h-ai/storage": "0.1.0-alpha.54"
|
|
47
55
|
},
|
|
48
56
|
"scripts": {
|
|
49
57
|
"build": "tsup",
|
|
@@ -56,6 +64,7 @@
|
|
|
56
64
|
"test:scaffold-gates": "pnpm build && node tests/run-scaffold-gates.mjs",
|
|
57
65
|
"test:watch": "vitest",
|
|
58
66
|
"test:coverage": "vitest run --coverage",
|
|
59
|
-
"clean": "rimraf dist .turbo"
|
|
67
|
+
"clean": "rimraf dist .turbo",
|
|
68
|
+
"build:clean": "rimraf dist"
|
|
60
69
|
}
|
|
61
70
|
}
|
|
@@ -3,13 +3,16 @@ FROM node:22-bookworm-slim AS build
|
|
|
3
3
|
|
|
4
4
|
ENV PNPM_HOME=/pnpm
|
|
5
5
|
ENV PATH=$PNPM_HOME:$PATH
|
|
6
|
+
ENV CI=true
|
|
6
7
|
WORKDIR /workspace
|
|
7
8
|
|
|
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
|
|
9
12
|
COPY . .
|
|
10
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|
13
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
11
14
|
RUN pnpm build
|
|
12
|
-
RUN pnpm --filter {{api.servicePackageName}} --prod deploy /prod/app
|
|
15
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm --filter {{api.servicePackageName}} --prod deploy /prod/app
|
|
13
16
|
|
|
14
17
|
FROM node:22-bookworm-slim AS runtime-base
|
|
15
18
|
|
|
@@ -29,6 +32,6 @@ CMD ["node", "dist/index.js"]
|
|
|
29
32
|
|
|
30
33
|
FROM runtime-base AS runtime-with-data
|
|
31
34
|
|
|
32
|
-
COPY --from=embedded-data --chown=node:node
|
|
35
|
+
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
33
36
|
|
|
34
37
|
FROM runtime-base AS runtime
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
# syntax=docker/dockerfile:1.7
|
|
2
2
|
FROM node:22-bookworm-slim AS build
|
|
3
3
|
|
|
4
|
-
ARG PUBLIC_API_BASE
|
|
5
4
|
ENV PNPM_HOME=/pnpm
|
|
6
5
|
ENV PATH=$PNPM_HOME:$PATH
|
|
7
|
-
ENV
|
|
6
|
+
ENV CI=true
|
|
8
7
|
WORKDIR /workspace
|
|
9
8
|
|
|
10
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
|
|
11
12
|
COPY . .
|
|
12
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|
13
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
13
14
|
RUN pnpm build
|
|
14
|
-
|
|
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
|
|
15
19
|
|
|
16
20
|
FROM node:22-bookworm-slim AS service
|
|
17
21
|
|
|
@@ -20,7 +24,7 @@ ENV HOST=0.0.0.0
|
|
|
20
24
|
ENV PORT=3000
|
|
21
25
|
WORKDIR /app
|
|
22
26
|
|
|
23
|
-
COPY --from=build --chown=node:node /prod/service ./
|
|
27
|
+
COPY --from=service-build --chown=node:node /prod/service ./
|
|
24
28
|
RUN mkdir -p /app/data && chown node:node /app/data
|
|
25
29
|
|
|
26
30
|
USER node
|
|
@@ -29,43 +33,26 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
|
29
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))"
|
|
30
34
|
CMD ["node", "dist/index.js"]
|
|
31
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
|
+
|
|
32
42
|
{{#if fullstack.frontends.web}}
|
|
33
43
|
FROM nginxinc/nginx-unprivileged:1.29-alpine AS web
|
|
34
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
|
|
35
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
|
|
36
53
|
COPY --from=build /workspace/apps/{{projectName}}-web/dist /usr/share/nginx/html
|
|
37
54
|
|
|
38
55
|
EXPOSE 8080
|
|
39
56
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
40
|
-
CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
41
|
-
|
|
42
|
-
FROM node:22-bookworm-slim AS runtime-base
|
|
43
|
-
|
|
44
|
-
ENV NODE_ENV=production
|
|
45
|
-
ENV HAI_WEB_PORT=8080
|
|
46
|
-
ENV HAI_SERV_PORT=3000
|
|
47
|
-
WORKDIR /app
|
|
48
|
-
|
|
49
|
-
COPY --from=build --chown=node:node /prod/service ./service
|
|
50
|
-
COPY --from=build --chown=node:node /workspace/apps/{{projectName}}-web/dist ./web
|
|
51
|
-
COPY --chown=node:node docker/fullstack-server.mjs ./fullstack-server.mjs
|
|
52
|
-
RUN mkdir -p /app/service/data && chown node:node /app/service/data
|
|
53
|
-
|
|
54
|
-
USER node
|
|
55
|
-
EXPOSE 8080
|
|
56
|
-
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
57
|
-
CMD node -e "fetch('http://127.0.0.1:'+process.env.HAI_WEB_PORT+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
58
|
-
CMD ["node", "fullstack-server.mjs"]
|
|
59
|
-
|
|
60
|
-
FROM runtime-base AS runtime-with-data
|
|
61
|
-
|
|
62
|
-
COPY --from=embedded-data --chown=node:node . /app/service/data
|
|
63
|
-
|
|
64
|
-
FROM runtime-base AS runtime
|
|
65
|
-
{{else}}
|
|
66
|
-
FROM service AS runtime-with-data
|
|
67
|
-
|
|
68
|
-
COPY --from=embedded-data --chown=node:node . /app/data
|
|
69
|
-
|
|
70
|
-
FROM service AS runtime
|
|
57
|
+
CMD wget -Y off -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
71
58
|
{{/if}}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"version": "0.1.0",
|
|
7
7
|
"private": true,
|
|
8
|
+
"engines": { "node": ">=22.12.0" },
|
|
8
9
|
"scripts": {
|
|
9
10
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
10
11
|
"paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
|
|
@@ -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}}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"version": "0.1.0",
|
|
7
7
|
"private": true,
|
|
8
|
+
"engines": { "node": ">=22.12.0" },
|
|
8
9
|
"scripts": {
|
|
9
10
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
10
11
|
"paraglide:watch": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --watch",
|
|
@@ -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}}
|
|
@@ -9,6 +9,12 @@ server {
|
|
|
9
9
|
try_files $uri $uri/ /index.html;
|
|
10
10
|
}
|
|
11
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
|
+
|
|
12
18
|
location = /health {
|
|
13
19
|
access_log off;
|
|
14
20
|
add_header Content-Type text/plain;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"engines": { "node": ">=22.12.0" },
|
|
2
3
|
"name": "{{projectName}}",
|
|
3
4
|
"type": "module",
|
|
4
5
|
"version": "0.1.0",
|
|
@@ -12,12 +13,10 @@
|
|
|
12
13
|
"compile": "pnpm typecheck && pnpm build",
|
|
13
14
|
"build": "pnpm --filter {{fullstack.contractPackageName}} build && pnpm i18n:compile && pnpm -r --if-present build",
|
|
14
15
|
"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 .",
|
|
16
|
-
"docker:build:service": "docker build --target service -t {{projectName}}-serv:latest ."{{#if fullstack.frontends.web}},
|
|
16
|
+
"docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest ."{{#if fullstack.frontends.web}},
|
|
17
17
|
"docker:build:web": "docker build --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
18
18
|
"podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest .",
|
|
19
|
-
"podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./packages/{{projectName}}-serv/data -t {{projectName}}:latest .",
|
|
20
|
-
"podman:build:service": "podman build --format docker --jobs=1 --target service -t {{projectName}}-serv:latest ."{{#if fullstack.frontends.web}},
|
|
19
|
+
"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}},
|
|
21
20
|
"podman:build:web": "podman build --format docker --jobs=1 --target web -t {{projectName}}-web:latest ."{{/if}},
|
|
22
21
|
"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}}",
|
|
23
22
|
"deploy": "pnpm package",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
## 容器运行
|
|
20
20
|
|
|
21
|
-
- 根目录 `pnpm docker:build` / `pnpm podman:build`
|
|
22
|
-
-
|
|
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。
|
|
23
24
|
- YAML 配置使用 `${ENV_NAME:default}` 接收运行时环境变量;不要把密钥写入镜像。
|
|
24
25
|
- 将 SQLite/本地存储放入本目录 `data/` 后运行 `pnpm docker:build:data` / `pnpm podman:build:data`,可生成带初始数据的镜像,持续写入仍应使用 volume。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs
CHANGED
|
@@ -38,10 +38,18 @@ 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
|
|
42
43
|
|| (typeof globalThis.location === 'undefined' ? DEFAULT_API_ORIGIN : globalThis.location.origin)
|
|
43
44
|
}
|
|
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
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
function normalizeApiBaseUrl(value: string): string {
|
|
46
54
|
const trimmed = trimTrailingSlash(value)
|
|
47
55
|
return trimmed.endsWith('/api/v1')
|
|
@@ -4,11 +4,14 @@ FROM node:22-bookworm-slim AS build
|
|
|
4
4
|
|
|
5
5
|
ENV PNPM_HOME=/pnpm
|
|
6
6
|
ENV PATH=$PNPM_HOME:$PATH
|
|
7
|
+
ENV CI=true
|
|
7
8
|
WORKDIR /workspace
|
|
8
9
|
|
|
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
|
|
10
13
|
COPY . .
|
|
11
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|
14
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
12
15
|
RUN pnpm build
|
|
13
16
|
|
|
14
17
|
FROM nginxinc/nginx-unprivileged:1.29-alpine AS runtime
|
|
@@ -24,13 +27,16 @@ FROM node:22-bookworm-slim AS build
|
|
|
24
27
|
|
|
25
28
|
ENV PNPM_HOME=/pnpm
|
|
26
29
|
ENV PATH=$PNPM_HOME:$PATH
|
|
30
|
+
ENV CI=true
|
|
27
31
|
WORKDIR /workspace
|
|
28
32
|
|
|
29
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
|
|
30
36
|
COPY . .
|
|
31
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
|
37
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
32
38
|
RUN pnpm build
|
|
33
|
-
RUN pnpm --filter {{projectName}} --prod deploy /prod/app
|
|
39
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm --filter {{projectName}} --prod deploy /prod/app
|
|
34
40
|
|
|
35
41
|
FROM node:22-bookworm-slim AS runtime-base
|
|
36
42
|
|
|
@@ -50,7 +56,7 @@ CMD ["node", "build"]
|
|
|
50
56
|
|
|
51
57
|
FROM runtime-base AS runtime-with-data
|
|
52
58
|
|
|
53
|
-
COPY --from=embedded-data --chown=node:node
|
|
59
|
+
COPY --from=embedded-data --chown=node:node / /app/data/
|
|
54
60
|
|
|
55
61
|
FROM runtime-base AS runtime
|
|
56
62
|
{{/if}}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
+
"engines": { "node": ">=22.12.0" },
|
|
2
3
|
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.54",
|
|
4
5
|
"private": true,
|
|
6
|
+
"packageManager": "pnpm@9.15.0",
|
|
5
7
|
"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
8
|
"keywords": ["hai", {{#unless isSvelteOnlyApp}}"sveltekit", {{/unless}}"svelte5", "{{appType}}"],
|
|
7
9
|
"license": "Apache-2.0",
|
|
@@ -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。
|
|
@@ -71,6 +71,8 @@ await mcp.connect(new StdioServerTransport())
|
|
|
71
71
|
|
|
72
72
|
### `ai.mcp`
|
|
73
73
|
|
|
74
|
+
这是进程内注册表,不会自动绑定独立 MCP Server 的 transport。工具注册时编译 JSON Schema、调用时校验输入;非法输入不执行 handler。资源必须恰好含 text/blob 之一;提示词必填参数必须是自身字符串属性,输出使用 SDK 消息类型(含 image/audio)。同名注册覆盖,缺失 requestId 自动补 UUID。已移除未使用的 MCPProvider 与 AIMCPFunctionsDeps 类型;使用 MCPOperations 即可。
|
|
75
|
+
|
|
74
76
|
| 方法 | 说明 |
|
|
75
77
|
| --- | --- |
|
|
76
78
|
| `registerTool(definition, handler)` | 注册内置工具 |
|
|
@@ -212,7 +214,9 @@ const result = await ai.rag.query('核心架构是什么?', {
|
|
|
212
214
|
| `listMessages(filter)` | 查询 A2A 消息记录 |
|
|
213
215
|
| `callRemoteAgent(remoteUrl, message, options?)` | 调用远端 Agent;只依赖 `ai.init()`,不要求本地 executor |
|
|
214
216
|
|
|
215
|
-
A2A 服务端 SDK handler 在注册 executor 时延迟创建;远端客户端调用不依赖服务端配置。
|
|
217
|
+
A2A 服务端 SDK handler 在注册 executor 时延迟创建;远端客户端调用不依赖服务端配置。getAgentCard 返回完整 A2A 0.3 Card(MIME、streaming、安全方案);Kit 默认发现路径为 `/.well-known/agent-card.json`。handleRequest 在未初始化时也返回 JSON-RPC 错误;可信 context.agentId 映射 SDK user.userName。Kit 配置 authenticate 后空身份返回 401;不声明 push notifications、gRPC、REST 支持。
|
|
218
|
+
|
|
219
|
+
A2A 的任务表和消息表在 `ai.init()` 阶段初始化。任务按可信 `agentId` 隔离读取(含获取、取消和继续消息);匿名请求共享匿名作用域,需隔离的部署必须开启认证。`listMessages` 是服务端管理接口,支持 `callerId`、`contextId`、`since` 过滤,不能直接向未授权客户端暴露。旧版任务记录没有可靠主体信息,升级后不会自动推断归属或删除数据,需由应用审核后迁移 `object_id` 为 `user:<agentId>`(匿名为 `anonymous`)。旧消息缺少主体/上下文字段时不会匹配相应过滤条件。
|
|
216
220
|
|
|
217
221
|
`callRemoteAgent` 只接受 HTTP(S) 且拒绝 URL 内嵌凭据。该校验不是完整 SSRF 防护:remote URL 若来自外部输入,应用必须先按 origin 白名单过滤,并通过出口代理限制 DNS 重绑定、重定向到私网与云元数据地址。
|
|
218
222
|
|
|
@@ -358,7 +358,9 @@ core.init() → reldb.init() → cache.init() → iam.init() → createBusinessT
|
|
|
358
358
|
|
|
359
359
|
- 保持脚手架根目录 `Dockerfile`、`.dockerignore` 与 `package.json` 中 `docker:build*` / `podman:build*` 命令同步。
|
|
360
360
|
- Podman 多阶段构建保持 `--format docker --jobs=1`;前者保留 `HEALTHCHECK`,后者避免部分 Podman 5.8/Buildah 组合在并行 stage 构建时触发内部 semaphore panic。
|
|
361
|
-
-
|
|
361
|
+
- 先复制 lockfile 与 workspace 配置执行 `pnpm fetch`,再复制源码并离线安装、构建;避免源码变化重复下载依赖,同时兼容新增 workspace。
|
|
362
|
+
- SvelteKit 服务使用 Node adapter;纯 Vite 前端使用带 SPA fallback 的静态镜像;fullstack 的 Service 与 Web 分别交付,Service 直接监听且不增加转发进程。
|
|
363
|
+
- Web 的 `PUBLIC_*` 部署配置在容器启动时生成静态运行时配置,不作为 build arg 烘焙,确保同一镜像可跨环境部署。
|
|
362
364
|
- 配置文件随镜像提供,并允许只读挂载覆盖;密钥只通过环境变量或 `--env-file` 注入。
|
|
363
365
|
- 普通构建不携带 `data/`;只有用户明确运行 `docker:build:data` / `podman:build:data` 时才通过 named build context 嵌入 SQLite/本地存储初始快照。
|
|
364
366
|
- 数据库和本地存储的持续写入必须落到 volume,不能依赖镜像可写层。
|
|
@@ -63,6 +63,8 @@ description: 对应用代码进行审查:路由安全 → 认证授权 → i18
|
|
|
63
63
|
|
|
64
64
|
### HaiResult 处理
|
|
65
65
|
|
|
66
|
+
- [ ] 投递等业务状态按 `sent` 等字段判断,失败保留用户输入;HTTP/HaiResult 成功不代替业务完成
|
|
67
|
+
|
|
66
68
|
- [ ] 所有框架模块调用都检查 `result.success`
|
|
67
69
|
- [ ] 上游 HaiResult 错误直接透传,不重新包装
|
|
68
70
|
- [ ] 不使用 `try/catch` 来处理框架模块返回的错误(模块 API 不抛异常,统一返回 `HaiResult<T>`)
|
|
@@ -179,3 +181,7 @@ if (!result.success)
|
|
|
179
181
|
- `hai-build`:项目架构总览与 TDD 工作流导航
|
|
180
182
|
- `hai-app-create`:TDD 驱动的应用功能创建规范
|
|
181
183
|
- `hai-app-tests`:TDD 测试规范(Red 阶段详细指引)
|
|
184
|
+
|
|
185
|
+
管理端数据读取在 HTTP 应用边界保留失败:查询异常返回 503 和可刷新错误页,分页任一页或权限关联失败均中止加载,禁止回退为空列表、零统计或空权限。未知权限代码返回 400,权限读取失败时不得继续写入。底层模块仍使用 HaiResult。
|
|
186
|
+
|
|
187
|
+
失败与降级提示也必须国际化:官网 API 按请求 locale 生成 AI 降级/邮件送达状态;未送达不能称为已接收。CLI 部署状态使用 cliM,Gallery 加密能力限制使用双语消息。中文与英文分别验证未配置、失败和成功路径,无需调用付费服务。
|
|
@@ -7,6 +7,12 @@ description: 应用开发入口技能。提供项目架构总览(SSR/SPA/原
|
|
|
7
7
|
|
|
8
8
|
## 能力契约
|
|
9
9
|
|
|
10
|
+
框架开发、CLI、服务端最低 Node.js 版本统一为 22.12.0;包与脚手架 engines、共享 node22 编译目标和 CI 最低版本必须同步。不再按 Node 20 编写兼容分支。
|
|
11
|
+
|
|
12
|
+
tsup 包的 Turbo `build` 依赖不缓存的 `build:clean`(仅清本包 dist),再执行构建或恢复缓存。不要移除此步骤:Turbo 缓存恢复不会自动移除另一模式的多余文件。
|
|
13
|
+
|
|
14
|
+
使用 Turbo 时,影响构建的环境变量必须进入任务 `env`,共享构建配置进入 `globalDependencies`。框架的 `HAI_DOCKER_PROD_BUILD=true` 关闭声明和 sourcemap,应与普通构建使用不同缓存;PUBLIC/VITE/TAURI 变量同样参与构建缓存。切换后验证实际产物并恢复普通开发构建。
|
|
15
|
+
|
|
10
16
|
| 项目 | 契约 |
|
|
11
17
|
| --- | --- |
|
|
12
18
|
| 能力 | 应用开发入口技能。提供项目架构总览(SSR/SPA/原生 App 多端)、TDD 开发工作流、模块初始化顺序与技能导航。当用户需要了解项目结构、查找正确的技能、或开始新任务时触发。 |
|
|
@@ -342,3 +348,5 @@ pnpm test # Vitest 单元测试
|
|
|
342
348
|
3. `pnpm test` 通过(单元测试)
|
|
343
349
|
4. `pnpm --filter <app-name> test:e2e` 通过(E2E 测试)
|
|
344
350
|
5. 搜索确认所有引用点已更新
|
|
351
|
+
|
|
352
|
+
原生交付必须单独执行 Tauri/Gradle/Xcode 打包,并在目标 OS/设备验证安装启动、登录/退出、重启会话、安全 TokenStore、真实 API 域名与覆盖升级。Web 构建/E2E 不代替原生验收。保存提交、包 hash、平台/工具链及逐项结果;缺平台写未验证,禁止静默 skip。hai-framework 仓库参考 `.github/workflows/native.yml` 与 desktop-app / mobile-app README 的「原生交付与验收」章节。
|
|
@@ -17,6 +17,8 @@ description: 使用 @h-ai/cache 进行内存或 Redis 缓存操作(kv/hash/lis
|
|
|
17
17
|
|
|
18
18
|
> `@h-ai/cache` 提供统一缓存接口,支持 Memory 与 Redis 后端,包含 KV / Hash / List / Set / ZSet / 分布式锁 六类操作。
|
|
19
19
|
|
|
20
|
+
内存 KV 在到期时间点即失效。整数计数器拒绝 null/boolean/array、非整数增量和安全整数溢出,失败不修改原值;Hash 的 `__proto__` / `constructor` 名称按普通字段保留。
|
|
21
|
+
|
|
20
22
|
---
|
|
21
23
|
|
|
22
24
|
## 运行环境
|
|
@@ -377,3 +377,5 @@ async function captureAndUpload() {
|
|
|
377
377
|
- `hai-api-client`:HTTP 客户端(Token 管理依赖 capacitor 存储)
|
|
378
378
|
- `hai-iam`:认证流程(登录获取 Token → 存储到 Capacitor)
|
|
379
379
|
- `hai-ui`:移动端 UI 组件(SafeArea、BottomNav 等)
|
|
380
|
+
|
|
381
|
+
原生交付必须单独执行 Tauri/Gradle/Xcode 打包,并在目标 OS/设备验证安装启动、登录/退出、重启会话、安全 TokenStore、真实 API 域名与覆盖升级。Web 构建/E2E 不代替原生验收。保存提交、包 hash、平台/工具链及逐项结果;缺平台写未验证,禁止静默 skip。hai-framework 仓库参考 `.github/workflows/native.yml` 与 desktop-app / mobile-app README 的「原生交付与验收」章节。
|
|
@@ -7,6 +7,15 @@ description: 设计和维护 hai-framework 应用仓库的 CI/CD、GitHub Action
|
|
|
7
7
|
|
|
8
8
|
## 能力契约
|
|
9
9
|
|
|
10
|
+
框架完整质量门禁在 Node.js 22.12.0(声明的最低版本)执行;升级 engines 时同步 CI、README 和脚手架,不能只在更高版本通过后宣称最低版本通过。
|
|
11
|
+
|
|
12
|
+
框架发布通过实际 npm 精确版本和 GitHub Release 判断完成,不能只看 tag。部分失败重跑原提交,只补发缺失包;npm 全部成功后再建 tag/Release。查询异常与 404 必须区分,tag 指向其他提交时停止补发,禁止将新源码混入旧版本。
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Turbo 的 `build.env` 必须列出影响产物的变量;框架包含容器构建开关、E2E 构建模式及 PUBLIC/VITE/TAURI 前缀,共享 tsup 配置也纳入依赖。Docker/Testcontainers 连接变量通过测试任务透传。验证缓存须切换变量检查哈希和真实产物,不能只检查配置文本。
|
|
16
|
+
|
|
17
|
+
框架仓库发布验收使用 `pnpm test:release-local`;其 E2E 与 CI 共用根 `pnpm e2e`,覆盖 CLI 和全部 Web 应用,不能缩减为仅 Admin Console。应用仓库应在自己的根 E2E 命令集中维护所有端到端入口。容器和原生验收须独立记录实际结果。
|
|
18
|
+
|
|
10
19
|
| 项目 | 契约 |
|
|
11
20
|
| --- | --- |
|
|
12
21
|
| 能力 | 设计和维护 hai-framework 应用仓库的 CI/CD、GitHub Actions、质量门禁、secret scan 与 workflow 安全;当需求涉及 CI、GitHub Actions、required checks、泄漏扫描或 release workflow 编排时使用。 |
|
|
@@ -120,3 +129,6 @@ pnpm build
|
|
|
120
129
|
- `hai-pr-review` — PR 审查与 AI Review 策略
|
|
121
130
|
- `hai-deploy` — 应用部署与云服务开通
|
|
122
131
|
- `hai-framework-sync` — hai-framework 与应用仓库同步策略
|
|
132
|
+
|
|
133
|
+
发布完成判定始终查询根版本的 npm 包;发布 job 同步包版本但不提交,标签固定指向触发质量验收的源码提交。发布成功后由独立 job 提交版本同步;完整重跑即使无需补发也会执行同步,远端已存在相同内容时成功退出,分支有新源码时拒绝覆盖并要求从最新 main 重跑。
|
|
134
|
+
原生交付必须单独执行 Tauri/Gradle/Xcode 打包,并在目标 OS/设备验证安装启动、登录/退出、重启会话、安全 TokenStore、真实 API 域名与覆盖升级。Web 构建/E2E 不代替原生验收。保存提交、包 hash、平台/工具链及逐项结果;缺平台写未验证,禁止静默 skip。hai-framework 仓库参考 `.github/workflows/native.yml` 与 desktop-app / mobile-app README 的「原生交付与验收」章节。
|
|
@@ -572,3 +572,5 @@ logger.debug('Data fetched', { count: items.length })
|
|
|
572
572
|
| `redact` 字段脱敏 | 不生效(需手动脱敏) |
|
|
573
573
|
| `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
|
|
574
574
|
| context 输出 | JSON 字符串追加到消息末尾(非结构化对象) |
|
|
575
|
+
|
|
576
|
+
云资源配置消费:Neon 输出 HAI_DB(PostgreSQL JSON),Upstash 输出 HAI_CACHE(Redis rediss URL JSON),R2 输出 HAI_STORAGE(扁平 S3 JSON)。邮件/短信输出 HAI_REACH_PROVIDERS 数组,deployApp 合并两个渠道并保留 YAML 中的消息模板;此覆盖替换原 providers 数组。Core 支持整体对象/数组覆盖,父节点优先于叶子变量,仍执行 Schema 校验;构建子进程与目标运行环境使用相同开通结果。R2 必须提供已有 S3 accessKeyId/secretAccessKey;Resend 必须提供已验证域名的 from;阿里云必须提供已审核 signName/模板。应用须已有相应 _db/_cache/_storage/_reach.yml 并在启动时加载,网络客户端需要 Node 运行时。
|
|
@@ -281,3 +281,5 @@ if (verifyResult.success && verifyResult.data) {
|
|
|
281
281
|
- `hai-iam`:密码哈希(内部自动调用 crypto.password)
|
|
282
282
|
- `hai-kit`:SvelteKit 集成(传输加密)
|
|
283
283
|
- `hai-serv` / `hai-api-client`:跨域 HTTP API 的透明传输加密
|
|
284
|
+
|
|
285
|
+
管理端在线加密演示直接调用 Crypto SM3/SM4 API,SM4 使用随机临时密钥和 IV,并在显示成功前解密校验;密钥不保存。输入改变即清空旧结果,禁止用随机摘要或 Base64 伪装算法结果。
|