@h-ai/cli 1.0.0-beta.5 → 1.0.0-beta.6
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 +21 -7
- package/README.ja.md +21 -7
- package/README.md +21 -7
- package/dist/{chunk-VUWM43KR.js → chunk-4XVCGFBA.js} +6123 -714
- package/dist/chunk-4XVCGFBA.js.map +1 -0
- package/dist/{chunk-YHSX7AQX.js → chunk-7DNIMG2H.js} +89 -14
- package/dist/chunk-7DNIMG2H.js.map +1 -0
- package/dist/{chunk-YVNNMC4H.js → chunk-STWZQ3IP.js} +59 -3
- package/dist/chunk-STWZQ3IP.js.map +1 -0
- package/dist/cli-config-templates-YGPVNRX5.js +5 -0
- package/dist/{cli-config-templates-LUJGDQNW.js.map → cli-config-templates-YGPVNRX5.js.map} +1 -1
- package/dist/{cli-skill-templates-COTP767H.js → cli-skill-templates-S2YH642Z.js} +3 -3
- package/dist/{cli-skill-templates-COTP767H.js.map → cli-skill-templates-S2YH642Z.js.map} +1 -1
- package/dist/index.js +362 -359
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/src-NXCCU6GO.js +5 -0
- package/dist/src-NXCCU6GO.js.map +1 -0
- package/package.json +11 -11
- package/templates/README.md +4 -0
- package/templates/apps/api/apps/{{projectName}}-serv/README.md.hbs +1 -1
- package/templates/apps/api/apps/{{projectName}}-serv/package.json.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +41 -2
- package/templates/apps/api/images/serv.Dockerfile.hbs +11 -6
- package/templates/apps/fullstack/docs/security-configuration.md.hbs +1 -1
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +11 -6
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +11 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +1 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +26 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +0 -1
- package/templates/base/images/web.Dockerfile.hbs +20 -10
- package/templates/base/package.json.hbs +3 -3
- package/templates/base/src/lib/server/init.ts.hbs +40 -2
- package/templates/shared/run-node.mjs +45 -0
- package/templates/skills/hai-ai/guide.md +1 -1
- package/templates/skills/hai-app-create/development.md +8 -7
- package/templates/skills/hai-build/SKILL.md +4 -1
- package/templates/skills/hai-build/reference.md +15 -1
- package/templates/skills/hai-core/SKILL.md +2 -2
- package/templates/skills/hai-core/reference.md +3 -3
- package/templates/skills/hai-core/request-access.md +1 -1
- package/templates/skills/hai-crypto/reference.md +1 -1
- package/templates/skills/hai-deploy/SKILL.md +8 -4
- package/templates/skills/hai-deploy/middleware.md +9 -9
- package/templates/skills/hai-iam/reference.md +2 -2
- package/templates/skills/hai-serv/reference.md +1 -1
- package/dist/chunk-VUWM43KR.js.map +0 -1
- package/dist/chunk-YHSX7AQX.js.map +0 -1
- package/dist/chunk-YVNNMC4H.js.map +0 -1
- package/dist/cli-config-templates-LUJGDQNW.js +0 -5
- package/dist/src-24PXSKE6.js +0 -5
- package/dist/src-24PXSKE6.js.map +0 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField } from './chunk-4XVCGFBA.js';
|
|
3
|
+
import './chunk-ZSDEVXZ7.js';
|
|
4
|
+
//# sourceMappingURL=src-NXCCU6GO.js.map
|
|
5
|
+
//# sourceMappingURL=src-NXCCU6GO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"src-NXCCU6GO.js"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-ai/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.6",
|
|
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.23.4 || ^24.22.1 || ^26.12.1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@h-ai/core": "1.0.0-beta.
|
|
33
|
+
"@h-ai/core": "1.0.0-beta.6",
|
|
34
34
|
"cac": "^7.0.1",
|
|
35
35
|
"chalk": "^6.0.1",
|
|
36
36
|
"fast-wrap-ansi": "^0.2.2",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"yaml": "^2.9.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@h-ai/api-client": "1.0.0-beta.
|
|
45
|
-
"@h-ai/cache": "1.0.0-beta.
|
|
46
|
-
"@h-ai/crypto": "1.0.0-beta.
|
|
47
|
-
"@h-ai/deploy": "1.0.0-beta.
|
|
48
|
-
"@h-ai/feedback": "1.0.0-beta.
|
|
49
|
-
"@h-ai/iam": "1.0.0-beta.
|
|
50
|
-
"@h-ai/reach": "1.0.0-beta.
|
|
51
|
-
"@h-ai/reldb": "1.0.0-beta.
|
|
52
|
-
"@h-ai/storage": "1.0.0-beta.
|
|
44
|
+
"@h-ai/api-client": "1.0.0-beta.6",
|
|
45
|
+
"@h-ai/cache": "1.0.0-beta.6",
|
|
46
|
+
"@h-ai/crypto": "1.0.0-beta.6",
|
|
47
|
+
"@h-ai/deploy": "1.0.0-beta.6",
|
|
48
|
+
"@h-ai/feedback": "1.0.0-beta.6",
|
|
49
|
+
"@h-ai/iam": "1.0.0-beta.6",
|
|
50
|
+
"@h-ai/reach": "1.0.0-beta.6",
|
|
51
|
+
"@h-ai/reldb": "1.0.0-beta.6",
|
|
52
|
+
"@h-ai/storage": "1.0.0-beta.6",
|
|
53
53
|
"@types/fs-extra": "^11.0.4",
|
|
54
54
|
"@types/node": "^26.6.5",
|
|
55
55
|
"@types/prompts": "^2.4.9",
|
package/templates/README.md
CHANGED
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
|
|
30
30
|
### 配置与构建
|
|
31
31
|
|
|
32
|
+
构建阶段的 pnpm fetch/install 固定追加式日志,避免 PowerShell 中动态进度刷新错位;CLI 同时规范 Windows 转发输出的换行并保留警告与错误。
|
|
33
|
+
|
|
34
|
+
Dockerfile 用全局 `HAI_NODE_IMAGE` / `HAI_NGINX_IMAGE` 接收镜像源,用构建阶段 `HAI_NPM_REGISTRY` 同时控制 Corepack 和 pnpm 下载。代理仅使用预定义构建参数,首个下载步骤检查容器内可达性;运行阶段不保存代理。CLI 的 `--image-source` / `--npm-source` 和 `--proxy-mode` 以 `hai.json.build.sources` / `build.proxy` 保存项目默认,具体契约见[下载源与代理](skills/hai-build/reference.md#下载源与代理)。
|
|
35
|
+
|
|
32
36
|
模板通过 `projectName`、`appType`、`features`、`hasUi`、`hasI18n`、`versions` 以及 `api`/`fullstack` 上下文渲染,完整字段见 [cli-types.ts](../src/cli-types.ts)。模块 YAML 使用 `moduleConfig` helper,与 [配置生成器](../src/commands/cli-config-templates.ts) 共用字段和默认值。
|
|
33
37
|
|
|
34
38
|
部署默认启用,`--no-deploy` 可关闭;SDK 是开发依赖。每个镜像使用 `images/` 中独立 Dockerfile,Web 资源放 `images/web/`。`hai.json` 的 `build.images` 选择文件,`deploy` 保存项目级部署参数;依赖输入由构建器生成到 `images/dependencies/`。所有根 `.gitignore` 均忽略 `**/images/dependencies/`、运行数据和私有环境文件。
|
|
@@ -27,7 +27,7 @@ pnpm --filter {{api.servPackageName}} test
|
|
|
27
27
|
|
|
28
28
|
### 关键目录
|
|
29
29
|
|
|
30
|
-
{{#if features.crypto}}启用 transport 时,`serverKeyRing` 与客户端 `keyStore` 都必须显式提供。CLI 在本服务 `.env.dev/.env.test/.env.prod` 中分别生成独立 ring
|
|
30
|
+
{{#if features.crypto}}启用 transport 时,`serverKeyRing` 与客户端 `keyStore` 都必须显式提供。CLI 在本服务 `.env.dev.local/.env.test.local/.env.prod.local` 中分别生成独立 ring,私有文件不提交 Git,克隆后通过受保护配置或 Secret Manager 分发,变量为 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`;运行时不生成密钥。部署直接复用所选环境文件的 ring,或由 Secret Manager 向所有节点分发相同受管 ring,配合共享数据库 registry。公钥/版本参与协商,私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发新旧版本再切 activeKeyId,过渡期后撤销旧 ID;无需会话粘性。
|
|
31
31
|
{{#unless features.db}}本模板未选择 db,registry 仅在 development/test 显式使用内存;production/staging 会拒绝启动 transport。选择 db 并配置共享数据库后再部署,或显式关闭 transport。
|
|
32
32
|
{{/unless}}{{/if}}
|
|
33
33
|
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"clean": "rimraf dist .turbo",
|
|
31
|
-
"dev": "pnpm build && node
|
|
32
|
-
"start": "node
|
|
33
|
-
"dev:test": "pnpm build && node
|
|
31
|
+
"dev": "pnpm build && node ../../scripts/run-node.mjs dev . dist/index.js",
|
|
32
|
+
"start": "node ../../scripts/run-node.mjs prod . dist/index.js",
|
|
33
|
+
"dev:test": "pnpm build && node ../../scripts/run-node.mjs test . dist/index.js",
|
|
34
34
|
"dev:prod": "pnpm build && pnpm start",
|
|
35
|
-
"start:test": "node
|
|
35
|
+
"start:test": "node ../../scripts/run-node.mjs test . dist/index.js",
|
|
36
36
|
"start:prod": "pnpm start"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
@@ -13,7 +13,7 @@ import { core } from '@h-ai/core'
|
|
|
13
13
|
import { crypto } from '@h-ai/crypto'
|
|
14
14
|
{{/if}}
|
|
15
15
|
{{#if features.feedback}}
|
|
16
|
-
import { feedback } from '@h-ai/feedback'
|
|
16
|
+
import { feedback, FeedbackConfigSchema } from '@h-ai/feedback'
|
|
17
17
|
{{/if}}
|
|
18
18
|
{{#if features.iam}}
|
|
19
19
|
import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
@@ -91,7 +91,46 @@ async function closeFeedback() {
|
|
|
91
91
|
{{/if}}
|
|
92
92
|
|
|
93
93
|
const runtime = core.module.createRuntime([
|
|
94
|
-
{ name: 'configuration', start: () => {
|
|
94
|
+
{ name: 'configuration', start: () => {
|
|
95
|
+
core.init({
|
|
96
|
+
configDir: './config',
|
|
97
|
+
configSchemas: {
|
|
98
|
+
serv: ServConfigSchema,
|
|
99
|
+
{{#if features.ai}}
|
|
100
|
+
ai: AIConfigSchema,
|
|
101
|
+
{{/if}}
|
|
102
|
+
{{#if features.audit}}
|
|
103
|
+
audit: AuditInitConfigSchema,
|
|
104
|
+
{{/if}}
|
|
105
|
+
{{#if features.cache}}
|
|
106
|
+
cache: CacheConfigSchema,
|
|
107
|
+
{{/if}}
|
|
108
|
+
{{#if features.feedback}}
|
|
109
|
+
feedback: FeedbackConfigSchema,
|
|
110
|
+
{{/if}}
|
|
111
|
+
{{#if features.iam}}
|
|
112
|
+
iam: IamConfigSchema,
|
|
113
|
+
{{/if}}
|
|
114
|
+
{{#if features.reach}}
|
|
115
|
+
reach: ReachConfigSchema,
|
|
116
|
+
{{/if}}
|
|
117
|
+
{{#if features.db}}
|
|
118
|
+
db: ReldbConfigSchema,
|
|
119
|
+
{{/if}}
|
|
120
|
+
{{#if features.audit}}
|
|
121
|
+
{{#if features.scheduler}}
|
|
122
|
+
scheduler: SchedulerConfigSchema,
|
|
123
|
+
{{/if}}
|
|
124
|
+
{{/if}}
|
|
125
|
+
{{#if features.storage}}
|
|
126
|
+
storage: StorageConfigSchema,
|
|
127
|
+
{{/if}}
|
|
128
|
+
{{#if features.vecdb}}
|
|
129
|
+
vecdb: VecdbConfigSchema,
|
|
130
|
+
{{/if}}
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
}, close: () => {} },
|
|
95
134
|
{ name: 'serv-config', start: () => core.config.validate('serv', ServConfigSchema), close: () => {} },
|
|
96
135
|
{{#if features.db}}
|
|
97
136
|
{ name: 'reldb', start: async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
ARG HAI_NODE_IMAGE=node:24.22.1-bookworm-slim
|
|
2
|
+
FROM ${HAI_NODE_IMAGE} AS build
|
|
2
3
|
ARG SQLITE_PREBUILD_MIRROR=
|
|
3
4
|
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
4
5
|
|
|
@@ -8,11 +9,15 @@ ENV CI=true
|
|
|
8
9
|
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
|
|
9
10
|
WORKDIR /workspace
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
|
|
13
|
+
ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
|
|
14
|
+
ARG npm_config_registry=$HAI_NPM_REGISTRY
|
|
15
|
+
ARG pnpm_config_registry=$HAI_NPM_REGISTRY
|
|
16
|
+
RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
|
|
12
17
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
18
|
COPY images/dependencies/ ./
|
|
14
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
19
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
|
|
20
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
|
|
16
21
|
COPY . .
|
|
17
22
|
ARG HAI_BUILD_ID=
|
|
18
23
|
ARG HAI_BUILD_COMMIT=unknown
|
|
@@ -21,7 +26,7 @@ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if
|
|
|
21
26
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{api.servPackageName}} --prod /prod/app
|
|
22
27
|
RUN rm -f /prod/app/README* && find /prod/app/dist -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' \) -delete
|
|
23
28
|
|
|
24
|
-
FROM
|
|
29
|
+
FROM ${HAI_NODE_IMAGE} AS runtime-base
|
|
25
30
|
|
|
26
31
|
ENV NODE_ENV=production
|
|
27
32
|
ENV HOST=0.0.0.0
|
|
@@ -34,5 +39,5 @@ RUN mkdir -p /app/data && chown node:node /app/data
|
|
|
34
39
|
USER node
|
|
35
40
|
EXPOSE 3000
|
|
36
41
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
37
|
-
CMD node -e "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config' }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"
|
|
42
|
+
CMD node -e "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config', configSchemas: { serv: ServConfigSchema } }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"
|
|
38
43
|
CMD ["node", "dist/index.js"]
|
|
@@ -128,7 +128,7 @@ security:
|
|
|
128
128
|
|
|
129
129
|
## 独立初始凭据和安全邀请
|
|
130
130
|
|
|
131
|
-
CLI 创建完成时会提示四个用户名、职责及密码变量位置;不在终端打印明文密码。新项目已在 `.env.dev/.env.test/.env.prod` 中为 admin、audit_admin、config_admin、business_operator 分别生成独立随机密码,各环境也不同。服务首次启动才创建缺失账号;缺少对应初始密码时跳过该账号,已有账号不覆盖。Admin Console 仓库示例由部署者在私有环境/进程中提供下表四个变量,不能把模板随机密码当成示例的固定通用密码。同名进程变量或 `.env.local` 可覆盖;修改这些初始变量不会重设已有账号的密码。需要首次强改时,在首次创建这些账号前开启上一节的 `requireInitialChange`。
|
|
131
|
+
CLI 创建完成时会提示四个用户名、职责及密码变量位置;不在终端打印明文密码。新项目已在 `.env.dev.local/.env.test.local/.env.prod.local` 中为 admin、audit_admin、config_admin、business_operator 分别生成独立随机密码,各环境也不同。服务首次启动才创建缺失账号;缺少对应初始密码时跳过该账号,已有账号不覆盖。Admin Console 仓库示例由部署者在私有环境/进程中提供下表四个变量,不能把模板随机密码当成示例的固定通用密码。同名进程变量或 `.env.local` 可覆盖;修改这些初始变量不会重设已有账号的密码。需要首次强改时,在首次创建这些账号前开启上一节的 `requireInitialChange`。
|
|
132
132
|
|
|
133
133
|
日常创建业务账号希望改为一次性邀请,先配置真实发送通道,再设置:
|
|
134
134
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
ARG HAI_NODE_IMAGE=node:24.22.1-bookworm-slim
|
|
2
|
+
FROM ${HAI_NODE_IMAGE} AS build
|
|
2
3
|
ARG SQLITE_PREBUILD_MIRROR=
|
|
3
4
|
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
4
5
|
|
|
@@ -8,11 +9,15 @@ ENV CI=true
|
|
|
8
9
|
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
|
|
9
10
|
WORKDIR /workspace
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
|
|
13
|
+
ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
|
|
14
|
+
ARG npm_config_registry=$HAI_NPM_REGISTRY
|
|
15
|
+
ARG pnpm_config_registry=$HAI_NPM_REGISTRY
|
|
16
|
+
RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
|
|
12
17
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
18
|
COPY images/dependencies/ ./
|
|
14
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
19
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
|
|
20
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
|
|
16
21
|
COPY tsconfig.base.json ./
|
|
17
22
|
COPY scripts/ ./scripts/
|
|
18
23
|
COPY packages/{{projectName}}-contract/ ./packages/{{projectName}}-contract/
|
|
@@ -25,7 +30,7 @@ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if
|
|
|
25
30
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{fullstack.servPackageName}} --prod /prod/service
|
|
26
31
|
RUN rm -f /prod/service/README* && find /prod/service/dist -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' \) -delete
|
|
27
32
|
|
|
28
|
-
FROM
|
|
33
|
+
FROM ${HAI_NODE_IMAGE} AS service
|
|
29
34
|
|
|
30
35
|
ENV NODE_ENV=production
|
|
31
36
|
ENV HOST=0.0.0.0
|
|
@@ -38,5 +43,5 @@ RUN mkdir -p /app/data && chown node:node /app/data
|
|
|
38
43
|
USER node
|
|
39
44
|
EXPOSE 3000
|
|
40
45
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
41
|
-
CMD node -e "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config' }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"
|
|
46
|
+
CMD node -e "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config', configSchemas: { serv: ServConfigSchema } }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"
|
|
42
47
|
CMD ["node", "dist/index.js"]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
ARG HAI_NODE_IMAGE=node:24.22.1-bookworm-slim
|
|
2
|
+
ARG HAI_NGINX_IMAGE=nginxinc/nginx-unprivileged:1.29.8-alpine
|
|
1
3
|
{{#if fullstack.frontends.web}}
|
|
2
|
-
FROM
|
|
4
|
+
FROM ${HAI_NODE_IMAGE} AS build
|
|
3
5
|
ARG SQLITE_PREBUILD_MIRROR=
|
|
4
6
|
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
5
7
|
|
|
@@ -8,11 +10,15 @@ ENV PATH=$PNPM_HOME:$PATH
|
|
|
8
10
|
ENV CI=true
|
|
9
11
|
WORKDIR /workspace
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
|
|
14
|
+
ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
|
|
15
|
+
ARG npm_config_registry=$HAI_NPM_REGISTRY
|
|
16
|
+
ARG pnpm_config_registry=$HAI_NPM_REGISTRY
|
|
17
|
+
RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
|
|
12
18
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
19
|
COPY images/dependencies/ ./
|
|
14
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
20
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
|
|
21
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
|
|
16
22
|
COPY tsconfig.base.json ./
|
|
17
23
|
COPY scripts/ ./scripts/
|
|
18
24
|
COPY packages/{{projectName}}-contract/ ./packages/{{projectName}}-contract/
|
|
@@ -23,7 +29,7 @@ ARG HAI_BUILD_COMMIT=unknown
|
|
|
23
29
|
ARG PUBLIC_BASE_PATH=
|
|
24
30
|
RUN pnpm --filter {{fullstack.sharedPackageName}} paraglide:compile && HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" PUBLIC_BASE_PATH="$PUBLIC_BASE_PATH" pnpm --filter {{projectName}}-web... --if-present build
|
|
25
31
|
|
|
26
|
-
FROM
|
|
32
|
+
FROM ${HAI_NGINX_IMAGE} AS web
|
|
27
33
|
|
|
28
34
|
ENV PUBLIC_API_BASE=
|
|
29
35
|
ENV NGINX_ENVSUBST_FILTER=PUBLIC_API_BASE_BASE64
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- `config/_serv.yml`:服务端运行时设置(CORS 来源等)。
|
|
21
21
|
- `config/_db.yml` / `_cache.yml` / `_iam.yml` / `_storage.yml` / `_audit.yml` / `_reach.yml`:管理及开发通知配置,初始化前使用模块公开 Schema 验证。Reach 默认控制台 Provider,生产按注释替换为 SMTP/短信/API。
|
|
22
22
|
- `config/_ai.yml` / `_vecdb.yml` / `_payment.yml` / `_scheduler.yml`:保留未启用模块的完整注释示例;配置文件存在不代表模块已接入。先在本 workspace 安装对应依赖,再在 `src/server/runtime.ts` 按公开 Schema 校验、初始化并反序关闭,接入实际业务后按需取消注释。整份注释先去掉一层外层注释,可选字段继续按需开启。
|
|
23
|
-
- `src/index.ts
|
|
23
|
+
- `src/index.ts`:进程入口,通过 `src/server/runtime.ts` 先向 Core 注册各模块公开 Schema 并加载配置,再初始化 reldb → cache → crypto → storage → reach(有配置时)→ audit → iam → scheduler(有任务时);YAML 未声明的合法可选字段同样支持细项环境变量,启动失败及退出反序释放。
|
|
24
24
|
- 选择 AI 时自动安装 reldb/vecdb/AI,并在 reldb/cache 之后初始化 vecdb、在其余依赖之后初始化 AI;关闭时先释放 AI,最后释放 vecdb/关系库,关闭失败会返回错误并保留重试义务。生产环境必须使用共享后端,根 `.env.prod` 默认连接 Compose `db/cache/storage/vecdb`,凭据和真实 Reach Provider JSON 私有注入。
|
|
25
25
|
- `src/app-server.ts`:服务装配入口,挂载健康检查、OpenAPI、docs 与 procedures;应用名称 / 版本与 CORS 来源均取自上述配置。
|
|
26
26
|
- `src/server/procedures/app-procedures.ts`:app 领域 procedures,对应 contract 中的 `app.info` / `app.echo` 路由。
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- `src/server/admin-service.ts`:角色分配、头像上传和审计编排,全部底层操作复用 hai 模块。初始使用 admin、audit_admin、config_admin、business_operator 和 user 五种角色,四类职责及基础成员权限由 security-roles.ts 配置;锁内重新鉴权,默认禁止管理员自删、自禁用、自降级,并补偿用户更新失败后的角色变更。业务规则可按需求替换,保留的不变量需验证并发与失败路径。
|
|
30
30
|
- `tests/app-server.test.ts`:真实加密 HTTP 测试,覆盖 Cookie/受信原生刷新、RBAC、旧会话权限、并发管理操作、补偿、头像安全、偏好、审计及会话失效。
|
|
31
31
|
|
|
32
|
-
CLI 已在根目录 `.env.dev/.env.test/.env.prod` 为每个环境的 admin、audit_admin、config_admin、business_operator 分别生成独立随机强密码,对应 `HAI_ADMIN_DEFAULT_PASSWORD`、`HAI_AUDIT_ADMIN_PASSWORD`、`HAI_CONFIG_ADMIN_PASSWORD`、`HAI_BUSINESS_OPERATOR_PASSWORD`。已有账号不改密或重新分配职责;未配置密码的独立职责账号不创建;`dev`/`start` 自动读取,首次登录使用用户名 `admin` 和该密码。仅在没有用户时创建管理员,不重设已有用户密码。密码不符合 `_iam.yml`
|
|
32
|
+
CLI 已在根目录 `.env.dev.local/.env.test.local/.env.prod.local` 为每个环境的 admin、audit_admin、config_admin、business_operator 分别生成独立随机强密码,对应 `HAI_ADMIN_DEFAULT_PASSWORD`、`HAI_AUDIT_ADMIN_PASSWORD`、`HAI_CONFIG_ADMIN_PASSWORD`、`HAI_BUSINESS_OPERATOR_PASSWORD`。已有账号不改密或重新分配职责;未配置密码的独立职责账号不创建;`dev`/`start` 自动读取,首次登录使用用户名 `admin` 和该密码。仅在没有用户时创建管理员,不重设已有用户密码。密码不符合 `_iam.yml` 策略时,启动日志显示具体原因及错误码。这些私有文件不提交 Git,克隆后通过受保护配置分发对应环境秘密;私有文件或进程变量可覆盖,`.env.example` 不含真实密码。所有业务 JSON 请求和响应强制加密;健康检查和随机路径的只读头像图片可直接访问。头像支持 PNG/JPEG/WebP,最大 512 KiB,校验内容特征和大小后由服务端生成 key,不能指定任意文件路径。
|
|
33
33
|
|
|
34
34
|
默认 SQLite + 本地 storage + memory cache 仅适合本地开发与显式测试,管理写入仅支持单写实例:进程内保护无过期时间,共享 cache 锁定期续租,丢失锁或释放失败会记录错误。多实例读取可使用共享数据库、Redis cache 和 S3 storage,但管理写请求必须统一路由到同一写实例;仅更换 Redis 不提供任意多写副本的最后管理员原子保证。传输在所有节点共享受管版本化密钥环与 registry,不依赖会话粘性;重启加载原 ring,轮换先分发新旧版本再切 activeKeyId。Web 使用 HttpOnly refresh Cookie + 内存 access token;仅同时满足客户端标识、body 通道头与受信原生 Origin 的 App/Desktop 请求返回 refresh token body。
|
|
35
35
|
|
|
@@ -37,9 +37,9 @@ CLI 已在根目录 `.env.dev/.env.test/.env.prod` 为每个环境的 admin、au
|
|
|
37
37
|
|
|
38
38
|
### 开发
|
|
39
39
|
|
|
40
|
-
所有配置字段以模块 Schema 为准;未使用字段和替代 Provider
|
|
40
|
+
所有配置字段以模块 Schema 为准;未使用字段和替代 Provider 用注释保留。宿主显式提供模块 Schema 后,合法可选路径可直接用细项配置;同源 `HAI_DB` / `HAI_CACHE` JSON 为基础,细项继续覆盖;`connectTimeout` 对应 `HAI_CACHE_CONNECTTIMEOUT`。凭据示例在根 `.env.example`,初始管理员密码和传输密钥环按环境生成到 `.env.dev.local/.env.test.local/.env.prod.local`;其他私有值或覆盖值保存在忽略的 `.env.local` 或部署环境。`.gitignore` 的 `**/data/` 忽略所有运行数据及 `.gitkeep`;运行时会创建目录。`crypto.init()` 无配置参数,datapipe 为纯函数,不提供虚构配置。
|
|
41
41
|
|
|
42
|
-
CLI 同时在根 `.env.dev/.env.test/.env.prod` 分别生成独立的 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,结构为 `{activeKeyId,keys:{id:{publicKey,privateKey}}}`,运行时不隐式生成。同一环境所有节点从环境文件加载相同 ring,也可由 Secret Manager 统一覆盖,客户端 registry 复用共享 reldb。私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发含新旧版本的 ring,再切 activeKeyId 并滚动重建;过渡期后删除旧 ID,旧客户端 destroy 后重协商。没有 sticky session 依赖。
|
|
42
|
+
CLI 同时在根 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成独立的 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,结构为 `{activeKeyId,keys:{id:{publicKey,privateKey}}}`,运行时不隐式生成。同一环境所有节点从环境文件加载相同 ring,也可由 Secret Manager 统一覆盖,客户端 registry 复用共享 reldb。私钥不进入普通 YAML、PUBLIC_/VITE_、日志或公开响应。轮换先分发含新旧版本的 ring,再切 activeKeyId 并滚动重建;过渡期后删除旧 ID,旧客户端 destroy 后重协商。没有 sticky session 依赖。
|
|
43
43
|
|
|
44
44
|
自动生成的 `.env` 包含已启用模块的环境参数;YAML 显式引用相同变量并保留本地回退。使用 `start:test` / `start:prod` 加载对应环境覆盖。
|
|
45
45
|
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"test:watch": "vitest",
|
|
29
29
|
"clean": "rimraf dist .turbo",
|
|
30
|
-
"dev": "pnpm build && node
|
|
31
|
-
"start": "node
|
|
32
|
-
"dev:test": "pnpm build && node
|
|
30
|
+
"dev": "pnpm build && node ../../scripts/run-node.mjs dev ../.. dist/index.js",
|
|
31
|
+
"start": "node ../../scripts/run-node.mjs prod ../.. dist/index.js",
|
|
32
|
+
"dev:test": "pnpm build && node ../../scripts/run-node.mjs test ../.. dist/index.js",
|
|
33
33
|
"dev:prod": "pnpm build && pnpm start",
|
|
34
|
-
"start:test": "node
|
|
34
|
+
"start:test": "node ../../scripts/run-node.mjs test ../.. dist/index.js",
|
|
35
35
|
"start:prod": "pnpm start"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
@@ -7,8 +7,7 @@ import { createServerApp } from './app-server.js'
|
|
|
7
7
|
import { closeModules, initializeModules } from './server/runtime.js'
|
|
8
8
|
|
|
9
9
|
async function startServer(): Promise<void> {
|
|
10
|
-
//
|
|
11
|
-
core.init({ configDir: './config' })
|
|
10
|
+
// 运行时先注册模块 Schema 并加载配置,再按依赖顺序初始化;日志设置沿用 _core.yml。
|
|
12
11
|
await initializeModules()
|
|
13
12
|
try {
|
|
14
13
|
// HOST/PORT 环境变量优先于 config/_serv.yml。
|
|
@@ -10,12 +10,12 @@ import { cache, CacheConfigSchema } from '@h-ai/cache'
|
|
|
10
10
|
import { core, HaiConfigError } from '@h-ai/core'
|
|
11
11
|
import { crypto } from '@h-ai/crypto'
|
|
12
12
|
{{#if features.feedback}}
|
|
13
|
-
import { feedback } from '@h-ai/feedback'
|
|
13
|
+
import { feedback, FeedbackConfigSchema } from '@h-ai/feedback'
|
|
14
14
|
{{/if}}
|
|
15
15
|
import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
16
16
|
import { reach, ReachConfigSchema } from '@h-ai/reach'
|
|
17
17
|
import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
18
|
-
import { scheduler } from '@h-ai/scheduler'
|
|
18
|
+
import { scheduler, SchedulerConfigSchema } from '@h-ai/scheduler'
|
|
19
19
|
import { ServConfigSchema } from '@h-ai/serv'
|
|
20
20
|
import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
21
21
|
{{#if features.vecdb}}
|
|
@@ -79,6 +79,30 @@ async function closeFeedback() {
|
|
|
79
79
|
|
|
80
80
|
{{/if}}
|
|
81
81
|
const runtime = core.module.createRuntime([
|
|
82
|
+
{ name: 'configuration', start: () => {
|
|
83
|
+
core.init({
|
|
84
|
+
configDir: './config',
|
|
85
|
+
configSchemas: {
|
|
86
|
+
{{#if features.ai}}
|
|
87
|
+
ai: AIConfigSchema,
|
|
88
|
+
{{/if}}
|
|
89
|
+
audit: AuditInitConfigSchema,
|
|
90
|
+
cache: CacheConfigSchema,
|
|
91
|
+
db: ReldbConfigSchema,
|
|
92
|
+
{{#if features.feedback}}
|
|
93
|
+
feedback: FeedbackConfigSchema,
|
|
94
|
+
{{/if}}
|
|
95
|
+
iam: IamConfigSchema,
|
|
96
|
+
reach: ReachConfigSchema,
|
|
97
|
+
scheduler: SchedulerConfigSchema,
|
|
98
|
+
serv: ServConfigSchema,
|
|
99
|
+
storage: StorageConfigSchema,
|
|
100
|
+
{{#if features.vecdb}}
|
|
101
|
+
vecdb: VecdbConfigSchema,
|
|
102
|
+
{{/if}}
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
}, close: () => {} },
|
|
82
106
|
{ name: 'serv-config', start: () => core.config.validate('serv', ServConfigSchema), close: () => {} },
|
|
83
107
|
{ name: 'reldb', start: async () => {
|
|
84
108
|
const config = ReldbConfigSchema.parse(core.config.getOrThrow('db'))
|
|
@@ -45,7 +45,6 @@ beforeAll(async () => {
|
|
|
45
45
|
vi.stubEnv('HAI_DB_DATABASE', '":memory:"')
|
|
46
46
|
vi.stubEnv('HAI_STORAGE_ROOT', storageRoot)
|
|
47
47
|
vi.stubEnv('HAI_ADMIN_DEFAULT_PASSWORD', TEST_PASSWORD)
|
|
48
|
-
core.init({ configDir: './config' })
|
|
49
48
|
await initializeModules()
|
|
50
49
|
const key = crypto.asymmetric.generateKeyPair()
|
|
51
50
|
if (!key.success)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
ARG HAI_NODE_IMAGE=node:24.22.1-bookworm-slim
|
|
2
|
+
ARG HAI_NGINX_IMAGE=nginxinc/nginx-unprivileged:1.29.8-alpine
|
|
1
3
|
{{#if isSvelteOnlyApp}}
|
|
2
|
-
FROM
|
|
4
|
+
FROM ${HAI_NODE_IMAGE} AS build
|
|
3
5
|
|
|
4
6
|
ENV PNPM_HOME=/pnpm
|
|
5
7
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -7,18 +9,22 @@ ENV CI=true
|
|
|
7
9
|
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
|
|
8
10
|
WORKDIR /workspace
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
|
|
13
|
+
ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
|
|
14
|
+
ARG npm_config_registry=$HAI_NPM_REGISTRY
|
|
15
|
+
ARG pnpm_config_registry=$HAI_NPM_REGISTRY
|
|
16
|
+
RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
|
|
11
17
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
12
18
|
COPY images/dependencies/ ./
|
|
13
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
14
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
19
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
|
|
20
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
|
|
15
21
|
COPY . .
|
|
16
22
|
ARG HAI_BUILD_ID=
|
|
17
23
|
ARG HAI_BUILD_COMMIT=unknown
|
|
18
24
|
ARG PUBLIC_BASE_PATH=
|
|
19
25
|
RUN HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" PUBLIC_BASE_PATH="$PUBLIC_BASE_PATH" pnpm compile
|
|
20
26
|
|
|
21
|
-
FROM
|
|
27
|
+
FROM ${HAI_NGINX_IMAGE} AS runtime
|
|
22
28
|
|
|
23
29
|
COPY --chmod=755 images/web/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
|
|
24
30
|
COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
|
|
@@ -29,7 +35,7 @@ EXPOSE 8080
|
|
|
29
35
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
30
36
|
CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
31
37
|
{{else}}
|
|
32
|
-
FROM
|
|
38
|
+
FROM ${HAI_NODE_IMAGE} AS build
|
|
33
39
|
ARG SQLITE_PREBUILD_MIRROR=
|
|
34
40
|
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
35
41
|
|
|
@@ -39,11 +45,15 @@ ENV CI=true
|
|
|
39
45
|
ENV ONNXRUNTIME_NODE_INSTALL_CUDA=skip
|
|
40
46
|
WORKDIR /workspace
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
ARG HAI_NPM_REGISTRY=https://registry.npmjs.org
|
|
49
|
+
ARG COREPACK_NPM_REGISTRY=$HAI_NPM_REGISTRY
|
|
50
|
+
ARG npm_config_registry=$HAI_NPM_REGISTRY
|
|
51
|
+
ARG pnpm_config_registry=$HAI_NPM_REGISTRY
|
|
52
|
+
RUN node -e "const p=process.env.HTTPS_PROXY||process.env.HTTP_PROXY;if(p){const u=new URL(p);const s=require('node:net').connect({host:u.hostname,port:Number(u.port||(u.protocol==='https:'?443:80))});s.setTimeout(3000);s.on('connect',()=>s.end());const fail=()=>{process.stderr.write('Build proxy is unreachable from the container. Check the host address and proxy listener.\\n');process.exit(1)};s.on('error',fail);s.on('timeout',fail)}" && corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config set store-dir /pnpm/store
|
|
43
53
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
44
54
|
COPY images/dependencies/ ./
|
|
45
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
46
|
-
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
55
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --reporter=append-only --ignore-scripts
|
|
56
|
+
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --reporter=append-only --offline --frozen-lockfile --ignore-scripts
|
|
47
57
|
COPY . .
|
|
48
58
|
ARG HAI_BUILD_ID=
|
|
49
59
|
ARG HAI_BUILD_COMMIT=unknown
|
|
@@ -53,7 +63,7 @@ RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if
|
|
|
53
63
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds pnpm pm deploy --filter {{projectName}} --prod /prod/app
|
|
54
64
|
RUN rm -f /prod/app/README* && find /prod/app/build -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' \) -delete
|
|
55
65
|
|
|
56
|
-
FROM
|
|
66
|
+
FROM ${HAI_NODE_IMAGE} AS runtime-base
|
|
57
67
|
|
|
58
68
|
ARG PUBLIC_BASE_PATH=
|
|
59
69
|
ENV PUBLIC_BASE_PATH=$PUBLIC_BASE_PATH
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"engines": { "node": "^22.23.4 || ^24.22.1 || ^26.12.1" },
|
|
3
3
|
"name": "{{projectName}}",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.6",
|
|
5
5
|
"private": true,
|
|
6
6
|
"packageManager": "pnpm@12.11.2",
|
|
7
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}}",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"deploy": "hai deploy",
|
|
30
30
|
{{/if}}
|
|
31
31
|
{{#unless isSvelteOnlyApp}}
|
|
32
|
-
"start": "node
|
|
33
|
-
"start:test": "node
|
|
32
|
+
"start": "node scripts/run-node.mjs prod . build/index.js",
|
|
33
|
+
"start:test": "node scripts/run-node.mjs test . build/index.js",
|
|
34
34
|
"start:prod": "pnpm start",
|
|
35
35
|
{{/unless}}
|
|
36
36
|
"preview": "vite preview",
|
|
@@ -13,7 +13,7 @@ import { core } from '@h-ai/core'
|
|
|
13
13
|
import { crypto } from '@h-ai/crypto'
|
|
14
14
|
{{/if}}
|
|
15
15
|
{{#if features.feedback}}
|
|
16
|
-
import { feedback } from '@h-ai/feedback'
|
|
16
|
+
import { feedback, FeedbackConfigSchema } from '@h-ai/feedback'
|
|
17
17
|
{{/if}}
|
|
18
18
|
{{#if features.iam}}
|
|
19
19
|
import { iam, IamConfigSchema } from '@h-ai/iam'
|
|
@@ -90,7 +90,45 @@ async function closeFeedback() {
|
|
|
90
90
|
{{/if}}
|
|
91
91
|
|
|
92
92
|
const runtime = core.module.createRuntime([
|
|
93
|
-
{ name: 'configuration', start: () => {
|
|
93
|
+
{ name: 'configuration', start: () => {
|
|
94
|
+
core.init({
|
|
95
|
+
configDir: './config',
|
|
96
|
+
configSchemas: {
|
|
97
|
+
{{#if features.ai}}
|
|
98
|
+
ai: AIConfigSchema,
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#if features.audit}}
|
|
101
|
+
audit: AuditInitConfigSchema,
|
|
102
|
+
{{/if}}
|
|
103
|
+
{{#if features.cache}}
|
|
104
|
+
cache: CacheConfigSchema,
|
|
105
|
+
{{/if}}
|
|
106
|
+
{{#if features.feedback}}
|
|
107
|
+
feedback: FeedbackConfigSchema,
|
|
108
|
+
{{/if}}
|
|
109
|
+
{{#if features.iam}}
|
|
110
|
+
iam: IamConfigSchema,
|
|
111
|
+
{{/if}}
|
|
112
|
+
{{#if features.reach}}
|
|
113
|
+
reach: ReachConfigSchema,
|
|
114
|
+
{{/if}}
|
|
115
|
+
{{#if features.db}}
|
|
116
|
+
db: ReldbConfigSchema,
|
|
117
|
+
{{/if}}
|
|
118
|
+
{{#if features.audit}}
|
|
119
|
+
{{#if features.scheduler}}
|
|
120
|
+
scheduler: SchedulerConfigSchema,
|
|
121
|
+
{{/if}}
|
|
122
|
+
{{/if}}
|
|
123
|
+
{{#if features.storage}}
|
|
124
|
+
storage: StorageConfigSchema,
|
|
125
|
+
{{/if}}
|
|
126
|
+
{{#if features.vecdb}}
|
|
127
|
+
vecdb: VecdbConfigSchema,
|
|
128
|
+
{{/if}}
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
}, close: () => {} },
|
|
94
132
|
{{#if features.db}}
|
|
95
133
|
{ name: 'reldb', start: async () => {
|
|
96
134
|
const config = ReldbConfigSchema.parse(core.config.getOrThrow('db'))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** 所有模板共用环境来源规则启动 Node,让 NODE_OPTIONS 在子进程启动前生效。 */
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import process from 'node:process'
|
|
6
|
+
import { pathToFileURL } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const [mode, environmentDirectory, entry, ...arguments_] = process.argv.slice(2)
|
|
9
|
+
if (!mode || !environmentDirectory || !entry)
|
|
10
|
+
throw new Error('Usage: node run-node.mjs <mode> <env-directory> <entry> [arguments]')
|
|
11
|
+
|
|
12
|
+
// 从实际应用解析公开 Core 入口,脚本可以放在 workspace 根目录。
|
|
13
|
+
const require = createRequire(resolve('package.json'))
|
|
14
|
+
const { core } = await import(pathToFileURL(require.resolve('@h-ai/core')).href)
|
|
15
|
+
const loaded = core.config.loadEnvFiles({ mode, envDir: resolve(environmentDirectory) })
|
|
16
|
+
if (!loaded.success)
|
|
17
|
+
throw new Error(loaded.error.message)
|
|
18
|
+
|
|
19
|
+
const child = spawn(process.execPath, [resolve(entry), ...arguments_], {
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
env: { ...process.env, ...loaded.data },
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
})
|
|
25
|
+
function interrupt() {
|
|
26
|
+
child.kill('SIGINT')
|
|
27
|
+
}
|
|
28
|
+
function terminate() {
|
|
29
|
+
child.kill('SIGTERM')
|
|
30
|
+
}
|
|
31
|
+
process.on('SIGINT', interrupt)
|
|
32
|
+
process.on('SIGTERM', terminate)
|
|
33
|
+
try {
|
|
34
|
+
const result = await new Promise((resolve, reject) => {
|
|
35
|
+
child.once('error', reject)
|
|
36
|
+
child.once('close', (code, signal) => resolve({ code, signal }))
|
|
37
|
+
})
|
|
38
|
+
process.exitCode = result.code ?? (result.signal === 'SIGINT' ? 130 : 143)
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
process.off('SIGINT', interrupt)
|
|
42
|
+
process.off('SIGTERM', terminate)
|
|
43
|
+
if (child.exitCode === null && child.signalCode === null)
|
|
44
|
+
child.kill('SIGTERM')
|
|
45
|
+
}
|
|
@@ -427,7 +427,7 @@ await saveImage(image.data.images[0].data, image.data.images[0].mimeType)
|
|
|
427
427
|
```
|
|
428
428
|
|
|
429
429
|
- `Pollinations` 提供可免费起步的开发额度,但仍需 API key;不要把“免费额度”描述为匿名或无限调用。
|
|
430
|
-
- 应用应通过 `core.init({ configDir: './config' })` 加载 `_core.yml` / `_ai.yml
|
|
430
|
+
- 应用应通过 `core.init({ configDir: './config', configSchemas: { ai: AIConfigSchema } })` 加载 `_core.yml` / `_ai.yml`,并登记其他所用模块的公开 Schema,再以 `core.config.validate('ai', AIConfigSchema)` 校验并传给 `ai.init`;不要在启动代码中重复拼装模型、端点和超时,也不要传入 `logging` 覆盖 `_core.yml`。
|
|
431
431
|
- Pollinations 的 HTTP 402 表示账户余额或该 API Key 的预算耗尽;401 是密钥无效,403 是权限或模型访问被拒。诊断日志记录厂商、模型、状态、上游错误码和请求 ID,不记录密钥、提示词或图片内容。
|
|
432
432
|
- Qwen 等返回的临时图片 URL 会在 Provider 内立即下载,公共结果不泄漏易过期 URL。
|
|
433
433
|
- 参考图统一使用 `{ data: Uint8Array, mimeType: 'image/...' }`;不要把厂商 URL、multipart 或 Base64 结构泄漏到业务层。
|