@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +20 -8
- package/README.ja.md +20 -8
- package/README.md +20 -8
- package/dist/{chunk-MUHRBJSW.js → chunk-K6RWTTHD.js} +546 -97
- package/dist/chunk-K6RWTTHD.js.map +1 -0
- package/dist/{chunk-AKV2CQZV.js → chunk-SWEU7KUE.js} +71 -59
- package/dist/chunk-SWEU7KUE.js.map +1 -0
- package/dist/{chunk-EF7WOFWZ.js → chunk-YVNNMC4H.js} +364 -136
- package/dist/chunk-YVNNMC4H.js.map +1 -0
- package/dist/cli-config-templates-LUJGDQNW.js +5 -0
- package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LUJGDQNW.js.map} +1 -1
- package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-VV2XWYON.js} +3 -3
- package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-VV2XWYON.js.map} +1 -1
- package/dist/index.js +117 -208
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/{src-TEBHR5I5.js → src-IR5AU43K.js} +3 -3
- package/dist/src-IR5AU43K.js.map +1 -0
- package/package.json +11 -11
- package/templates/apps/api/.dockerignore.hbs +10 -0
- package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +20 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +6 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +1 -1
- package/templates/apps/api/images/serv.Dockerfile.hbs +3 -2
- package/templates/apps/fullstack/.dockerignore.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +16 -2
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +6 -2
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +6 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +10 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +14 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +7 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +1 -1
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +1 -0
- package/templates/apps/website/scripts/static-website.mjs.hbs +3 -3
- package/templates/base/.dockerignore.hbs +10 -0
- package/templates/base/images/web.Dockerfile.hbs +4 -2
- package/templates/base/package.json.hbs +1 -1
- package/templates/base/src/hooks.server.ts.hbs +9 -7
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +3 -1
- package/templates/skills/hai-ai/guide.md +5 -3
- package/templates/skills/hai-app-create/development.md +5 -3
- package/templates/skills/hai-app-create/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +5 -5
- package/templates/skills/hai-build/paths.md +1 -1
- package/templates/skills/hai-build/reference.md +1 -1
- package/templates/skills/hai-core/request-access.md +1 -1
- package/templates/skills/hai-deploy/SKILL.md +15 -3
- package/templates/skills/hai-deploy/middleware.md +4 -2
- package/templates/skills/hai-serv/reference.md +5 -8
- package/dist/chunk-AKV2CQZV.js.map +0 -1
- package/dist/chunk-EF7WOFWZ.js.map +0 -1
- package/dist/chunk-MUHRBJSW.js.map +0 -1
- package/dist/cli-config-templates-HHFHNXBN.js +0 -5
- package/dist/src-TEBHR5I5.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, readDeploymentArtifact, saveDeploymentEnvironment } from './chunk-
|
|
2
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, readDeploymentArtifact, saveDeploymentEnvironment } from './chunk-K6RWTTHD.js';
|
|
3
3
|
import './chunk-ZSDEVXZ7.js';
|
|
4
|
-
//# sourceMappingURL=src-
|
|
5
|
-
//# sourceMappingURL=src-
|
|
4
|
+
//# sourceMappingURL=src-IR5AU43K.js.map
|
|
5
|
+
//# sourceMappingURL=src-IR5AU43K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"src-IR5AU43K.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.4",
|
|
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.4",
|
|
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.4",
|
|
45
|
+
"@h-ai/cache": "1.0.0-beta.4",
|
|
46
|
+
"@h-ai/crypto": "1.0.0-beta.4",
|
|
47
|
+
"@h-ai/deploy": "1.0.0-beta.4",
|
|
48
|
+
"@h-ai/feedback": "1.0.0-beta.4",
|
|
49
|
+
"@h-ai/iam": "1.0.0-beta.4",
|
|
50
|
+
"@h-ai/reach": "1.0.0-beta.4",
|
|
51
|
+
"@h-ai/reldb": "1.0.0-beta.4",
|
|
52
|
+
"@h-ai/storage": "1.0.0-beta.4",
|
|
53
53
|
"@types/fs-extra": "^11.0.4",
|
|
54
54
|
"@types/node": "^26.6.5",
|
|
55
55
|
"@types/prompts": "^2.4.9",
|
|
@@ -7,15 +7,29 @@ server:
|
|
|
7
7
|
port: 3000
|
|
8
8
|
|
|
9
9
|
http:
|
|
10
|
+
# auto 仅在受管私有 Compose 代理中采用容器网段;普通宿主忽略转发头。
|
|
11
|
+
trustedProxies: auto
|
|
12
|
+
access: false
|
|
13
|
+
# 独立文件与下方内联规则二选一,用所选配置块替换 access: false。
|
|
14
|
+
# access:
|
|
15
|
+
# file: ./config/security/request-access.yml
|
|
16
|
+
# access:
|
|
17
|
+
# global:
|
|
18
|
+
# timezone: UTC # 可用 HAI_SERV_HTTP_ACCESS_GLOBAL_TIMEZONE=Asia/Shanghai
|
|
19
|
+
# allowIps: []
|
|
20
|
+
# denyIps: []
|
|
21
|
+
# windows: []
|
|
22
|
+
# rules: []
|
|
10
23
|
apiPrefix: /api/v1
|
|
11
24
|
openapi:
|
|
12
25
|
path: /openapi.json
|
|
26
|
+
# HAI_SERV_HTTP_DOCS=false 关闭文档;开启时再配置 path/requireAuth。
|
|
13
27
|
docs:
|
|
14
28
|
path: /docs
|
|
29
|
+
# requireAuth: true
|
|
15
30
|
health:
|
|
16
31
|
path: /health
|
|
17
32
|
readyPath: /ready
|
|
18
|
-
rpc: false
|
|
19
33
|
|
|
20
34
|
cors:
|
|
21
35
|
origin: ${HAI_SERV_CORS_ORIGIN:*}
|
|
@@ -43,6 +57,11 @@ cors:
|
|
|
43
57
|
{{else}}
|
|
44
58
|
maxPlaintextBytes: ${HAI_SERV_TRANSPORT_MAXPLAINTEXTBYTES:1048576}
|
|
45
59
|
{{/if}}
|
|
60
|
+
# HAI_SERV_TRANSPORT_ROUTEPLAINTEXTBYTES 可填 JSON 路径映射。
|
|
61
|
+
routePlaintextBytes: {}
|
|
62
|
+
# 单条路径需要更大请求体时填写字节数,* 只匹配一段路径。
|
|
63
|
+
# routePlaintextBytes:
|
|
64
|
+
# /api/v1/uploads/*: 8388608
|
|
46
65
|
keyExchangePath: /_hai/key-exchange
|
|
47
66
|
excludePaths:
|
|
48
67
|
- /health
|
|
@@ -57,7 +57,12 @@ export function {{api.serviceAppFactoryName}}(_options: CreateApiServiceAppOptio
|
|
|
57
57
|
},
|
|
58
58
|
{{else}} routePlaintextBytes: servConfig.transport.routePlaintextBytes,
|
|
59
59
|
{{/if}}
|
|
60
|
-
excludePaths: [...
|
|
60
|
+
excludePaths: [...new Set([
|
|
61
|
+
...servConfig.transport.excludePaths,
|
|
62
|
+
...(servConfig.http.health ? [servConfig.http.health.path, servConfig.http.health.readyPath].filter((path): path is `/${string}` => Boolean(path)) : []),
|
|
63
|
+
...(servConfig.http.openapi && security.exposeApiDocs ? [servConfig.http.openapi.path] : []),
|
|
64
|
+
...(servConfig.http.docs && security.exposeApiDocs ? [servConfig.http.docs.path, `${servConfig.http.docs.path.slice(0, servConfig.http.docs.path.lastIndexOf('/') + 1)}_hai/scalar.js`] : []),
|
|
65
|
+
])],
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
{{else}} const transport = undefined
|
|
@@ -24,12 +24,12 @@ import { reach, ReachConfigSchema } from '@h-ai/reach'
|
|
|
24
24
|
{{#if features.db}}
|
|
25
25
|
import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
26
26
|
{{/if}}
|
|
27
|
-
import { ServConfigSchema } from '@h-ai/serv'
|
|
28
27
|
{{#if features.audit}}
|
|
29
28
|
{{#if features.scheduler}}
|
|
30
29
|
import { scheduler, SchedulerConfigSchema } from '@h-ai/scheduler'
|
|
31
30
|
{{/if}}
|
|
32
31
|
{{/if}}
|
|
32
|
+
import { ServConfigSchema } from '@h-ai/serv'
|
|
33
33
|
{{#if features.storage}}
|
|
34
34
|
import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
35
35
|
{{/if}}
|
|
@@ -12,13 +12,14 @@ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config s
|
|
|
12
12
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
13
|
COPY images/dependencies/ ./
|
|
14
14
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
COPY . .
|
|
16
15
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
16
|
+
COPY . .
|
|
17
17
|
ARG HAI_BUILD_ID=
|
|
18
18
|
ARG HAI_BUILD_COMMIT=unknown
|
|
19
19
|
RUN HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" pnpm compile
|
|
20
20
|
RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
21
21
|
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
|
+
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
|
|
22
23
|
|
|
23
24
|
FROM node:24.22.1-bookworm-slim AS runtime-base
|
|
24
25
|
|
|
@@ -33,5 +34,5 @@ RUN mkdir -p /app/data && chown node:node /app/data
|
|
|
33
34
|
USER node
|
|
34
35
|
EXPOSE 3000
|
|
35
36
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
36
|
-
CMD node -e "fetch('http://127.0.0.1:'+
|
|
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); } })()"
|
|
37
38
|
CMD ["node", "dist/index.js"]
|
|
@@ -32,7 +32,7 @@ pnpm --filter {{projectName}}-web dev
|
|
|
32
32
|
pnpm --filter {{projectName}}-web build
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
从工程根运行 `pnpm build --target
|
|
35
|
+
从工程根运行 `pnpm build --target image` 导出镜像。默认带部署工具,可运行 `pnpm run deploy --env test --build` 一次完成构建发布,或 `pnpm run deploy --env prod --bundle-only` 交付已有镜像及跨平台脚本。完整包包含自建中间件,人工部署与按服务更新见工程根 README。
|
|
36
36
|
|
|
37
37
|
### IAM 认证配置
|
|
38
38
|
|
|
@@ -805,6 +805,8 @@ test('administrator and user complete account, avatar, preference, password and
|
|
|
805
805
|
{{#if fullstack.e2eFrontend}}
|
|
806
806
|
|
|
807
807
|
test('feedback submits an empty description and business operator can process and reopen it', async ({ page }, testInfo) => {
|
|
808
|
+
// 附件脱敏、桌面/手机截图和导出校验共用完整流程预算,目录代理也执行相同断言。
|
|
809
|
+
test.setTimeout(120_000)
|
|
808
810
|
await page.goto('./')
|
|
809
811
|
await login(page, 'business_operator', businessOperatorPassword)
|
|
810
812
|
const feedbackImage = Buffer.from(await page.evaluate(() => {
|
|
@@ -887,6 +889,11 @@ test('feedback submits an empty description and business operator can process an
|
|
|
887
889
|
await expectUncovered(uploaded)
|
|
888
890
|
await capture(page, 'feedback-preview.png', testInfo.outputPath('feedback-preview.png'))
|
|
889
891
|
await uploaded.screenshot({ path: testInfo.outputPath('feedback-image-after-mask-detail.png'), animations: 'disabled' })
|
|
892
|
+
// 回执只表示记录已受理;等这份大附件真正上传成功,再开始后台处理操作。
|
|
893
|
+
const largeUpload = page.waitForResponse(response => response.request().method() === 'POST'
|
|
894
|
+
&& /\/feedback\/reports\/[^/]+\/attachments\/[^/]+$/.test(new URL(response.url()).pathname)
|
|
895
|
+
&& (response.request().postDataBuffer()?.byteLength ?? 0) > 2 * 1024 * 1024, { timeout: 60_000 })
|
|
896
|
+
.then(response => ({ response }), (error: unknown) => ({ error }))
|
|
890
897
|
await dialog.getByRole('button', { name: /提交反馈|Submit Feedback|フィードバックを送信/ }).click()
|
|
891
898
|
await expect(dialog.getByTestId('feedback-receipt')).toContainText(/FB-/)
|
|
892
899
|
const number = (await dialog.getByTestId('feedback-receipt').textContent())?.match(/FB-[A-Za-z0-9-]+/)?.[0]
|
|
@@ -895,13 +902,20 @@ test('feedback submits an empty description and business operator can process an
|
|
|
895
902
|
throw new Error('Feedback receipt number is missing')
|
|
896
903
|
await expectDialogViewport()
|
|
897
904
|
await capture(page, 'feedback-accepted.png', testInfo.outputPath('feedback-accepted.png'))
|
|
905
|
+
const completedUpload = await largeUpload
|
|
906
|
+
if ('error' in completedUpload)
|
|
907
|
+
throw completedUpload.error
|
|
908
|
+
expect(completedUpload.response.status()).toBe(200)
|
|
898
909
|
await dialog.getByRole('button', { name: /关闭|Close|閉じる/ }).filter({ hasText: /关闭|Close|閉じる/ }).click()
|
|
899
910
|
await page.getByTestId('nav-feedback').click()
|
|
900
911
|
await expect(page.getByTestId('feedback-page')).toBeVisible()
|
|
901
912
|
const row = page.getByRole('row').filter({ hasText: number })
|
|
902
913
|
await expect(row).toBeVisible()
|
|
903
|
-
|
|
904
|
-
await
|
|
914
|
+
const handled = page.waitForResponse(response => response.request().method() === 'PATCH' && new URL(response.url()).pathname.endsWith('/status'))
|
|
915
|
+
await row.getByRole('button', { name: /标记已处理|Mark(?: as)? handled|処理済み/ }).click()
|
|
916
|
+
expect((await handled).status()).toBe(200)
|
|
917
|
+
// 仅核对状态徽标;“标记已处理”按钮本身也包含“已处理”,不能据此放行。
|
|
918
|
+
await expect(row.getByText(/^(已处理|Handled|処理済み)$/)).toBeVisible()
|
|
905
919
|
await row.getByRole('button', { name: /撤销处理|Reopen|処理を取り消す/ }).scrollIntoViewIfNeeded()
|
|
906
920
|
await capture(page, 'feedback-handled.png', testInfo.outputPath('feedback-handled.png'))
|
|
907
921
|
await row.getByRole('button', { name: /撤销处理|Reopen|処理を取り消す/ }).click()
|
|
@@ -12,14 +12,18 @@ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config s
|
|
|
12
12
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
13
|
COPY images/dependencies/ ./
|
|
14
14
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
COPY . .
|
|
16
15
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
16
|
+
COPY tsconfig.base.json ./
|
|
17
|
+
COPY scripts/ ./scripts/
|
|
18
|
+
COPY packages/{{projectName}}-contract/ ./packages/{{projectName}}-contract/
|
|
19
|
+
COPY packages/{{projectName}}-serv/ ./packages/{{projectName}}-serv/
|
|
17
20
|
ARG HAI_BUILD_ID=
|
|
18
21
|
ARG HAI_BUILD_COMMIT=unknown
|
|
19
22
|
RUN HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" pnpm --filter {{fullstack.servPackageName}}... --if-present build
|
|
20
23
|
|
|
21
24
|
RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
22
25
|
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
|
+
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
|
|
23
27
|
|
|
24
28
|
FROM node:24.22.1-bookworm-slim AS service
|
|
25
29
|
|
|
@@ -34,5 +38,5 @@ RUN mkdir -p /app/data && chown node:node /app/data
|
|
|
34
38
|
USER node
|
|
35
39
|
EXPOSE 3000
|
|
36
40
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
37
|
-
CMD node -e "fetch('http://127.0.0.1:'+
|
|
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); } })()"
|
|
38
42
|
CMD ["node", "dist/index.js"]
|
|
@@ -12,8 +12,12 @@ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config s
|
|
|
12
12
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
13
13
|
COPY images/dependencies/ ./
|
|
14
14
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
15
|
-
COPY . .
|
|
16
15
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
16
|
+
COPY tsconfig.base.json ./
|
|
17
|
+
COPY scripts/ ./scripts/
|
|
18
|
+
COPY packages/{{projectName}}-contract/ ./packages/{{projectName}}-contract/
|
|
19
|
+
COPY packages/{{projectName}}-shared/ ./packages/{{projectName}}-shared/
|
|
20
|
+
COPY apps/{{projectName}}-web/ ./apps/{{projectName}}-web/
|
|
17
21
|
ARG HAI_BUILD_ID=
|
|
18
22
|
ARG HAI_BUILD_COMMIT=unknown
|
|
19
23
|
ARG PUBLIC_BASE_PATH=
|
|
@@ -30,6 +34,7 @@ COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
|
|
|
30
34
|
COPY --chmod=755 images/web/19-runtime-config.envsh /docker-entrypoint.d/19-runtime-config.envsh
|
|
31
35
|
COPY images/web/runtime-config.js.template /etc/nginx/templates/runtime-config.js.template
|
|
32
36
|
COPY --from=build /workspace/apps/{{projectName}}-web/dist /usr/share/nginx/html
|
|
37
|
+
RUN find /usr/share/nginx/html -type f -name '*.map' -delete
|
|
33
38
|
|
|
34
39
|
EXPOSE 8080
|
|
35
40
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
@@ -48,7 +48,7 @@ CLI 同时在根 `.env.dev/.env.test/.env.prod` 分别生成独立的 `HAI_CRYPT
|
|
|
48
48
|
|
|
49
49
|
### 容器运行
|
|
50
50
|
|
|
51
|
-
从工程根运行 `pnpm build --target
|
|
51
|
+
从工程根运行 `pnpm build --target image` 导出镜像。默认带部署工具,可运行 `pnpm run deploy --env test --build` 一次完成构建发布,或 `pnpm run deploy --env prod --bundle-only` 交付已有镜像及跨平台脚本。完整包包含自建中间件,人工部署与按服务更新见工程根 README。
|
|
52
52
|
|
|
53
53
|
- 服务镜像直接由 `@h-ai/serv` 对外监听,配置挂载点为 `/app/config`,数据目录为 `/app/data`,不包含额外转发进程。
|
|
54
54
|
- Web 镜像不烘焙 API 地址;启动时使用 `-e PUBLIC_API_BASE=https://api.example.com` 注入,同一镜像可部署到不同环境。未设置时 Web 默认请求自身 origin。
|
|
@@ -4,7 +4,10 @@ providers:
|
|
|
4
4
|
type: console
|
|
5
5
|
- name: sms
|
|
6
6
|
type: console
|
|
7
|
-
|
|
7
|
+
# 生产示例:用下面 providers 整段替换上方控制台列表;保留需要的项。
|
|
8
|
+
# 数组索引从 0 开始,删掉某项后同步修改其余环境变量中的索引。
|
|
9
|
+
# providers:
|
|
10
|
+
# SMTP 邮件;名称 email 与下方通知模板对应。
|
|
8
11
|
# - name: email
|
|
9
12
|
# type: smtp
|
|
10
13
|
# host: ${HAI_REACH_PROVIDERS_0_HOST:smtp.example.com}
|
|
@@ -16,8 +19,8 @@ providers:
|
|
|
16
19
|
# 阿里云短信替换 sms 控制台 Provider。
|
|
17
20
|
# - name: sms
|
|
18
21
|
# type: aliyun-sms
|
|
19
|
-
# accessKeyId: ${
|
|
20
|
-
# accessKeySecret: ${
|
|
22
|
+
# accessKeyId: ${HAI_REACH_PROVIDERS_1_ACCESSKEYID}
|
|
23
|
+
# accessKeySecret: ${HAI_REACH_PROVIDERS_1_ACCESSKEYSECRET}
|
|
21
24
|
# signName: MyApp
|
|
22
25
|
# endpoint: dysmsapi.aliyuncs.com
|
|
23
26
|
# 自有发送服务:按需将 template.provider 改为 custom。
|
|
@@ -26,9 +29,9 @@ providers:
|
|
|
26
29
|
# url: https://notify.example.com/send
|
|
27
30
|
# method: POST # POST | PUT
|
|
28
31
|
# headers:
|
|
29
|
-
# Authorization: '${
|
|
32
|
+
# Authorization: '${HAI_REACH_PROVIDERS_2_HEADERS_AUTHORIZATION}'
|
|
30
33
|
# timeout: 10000
|
|
31
|
-
# idempotent: ${
|
|
34
|
+
# idempotent: ${HAI_REACH_PROVIDERS_2_IDEMPOTENT:false} # 仅接收端具备原子持久去重时启用;默认在途不明结果转 unknown
|
|
32
35
|
templates:
|
|
33
36
|
- name: otp_email
|
|
34
37
|
provider: email
|
|
@@ -52,8 +55,8 @@ templates:
|
|
|
52
55
|
# start: '22:00'
|
|
53
56
|
# end: '08:00'
|
|
54
57
|
|
|
55
|
-
#
|
|
56
|
-
#
|
|
58
|
+
# 消息发送失败后最多尝试 5 次。首次重试等待 1 分钟,以后每次加倍,最长等待 1 小时。
|
|
59
|
+
# 尝试次数用完后停止,交给管理员处理;若无法确认对方是否已收到,先核对结果,避免重复发送。
|
|
57
60
|
queue:
|
|
58
61
|
maxAttempts: ${HAI_REACH_QUEUE_MAXATTEMPTS:5}
|
|
59
62
|
retryDelayMs: ${HAI_REACH_QUEUE_RETRYDELAYMS:60000}
|
|
@@ -16,22 +16,24 @@ http:
|
|
|
16
16
|
access: false
|
|
17
17
|
# access:
|
|
18
18
|
# file: ./config/security/request-access.yml
|
|
19
|
+
# 或将 access: false 替换为下列内联配置;与 file 二选一。
|
|
20
|
+
# access:
|
|
21
|
+
# global:
|
|
22
|
+
# timezone: UTC # 可用 HAI_SERV_HTTP_ACCESS_GLOBAL_TIMEZONE=Asia/Shanghai
|
|
23
|
+
# allowIps: []
|
|
24
|
+
# denyIps: []
|
|
25
|
+
# windows: []
|
|
26
|
+
# rules: []
|
|
19
27
|
apiPrefix: /api/v1
|
|
20
28
|
openapi:
|
|
21
29
|
path: /openapi.json
|
|
30
|
+
# HAI_SERV_HTTP_DOCS=false 关闭文档;开启时再配置 path/requireAuth。
|
|
22
31
|
docs:
|
|
23
32
|
path: /docs
|
|
24
33
|
# requireAuth: true
|
|
25
34
|
health:
|
|
26
35
|
path: /health
|
|
27
36
|
readyPath: /ready
|
|
28
|
-
rpc: false
|
|
29
|
-
# 内部 RPC 可选;启用时用对象替换 false,gateway-only 必须配置网关密钥。
|
|
30
|
-
# rpc:
|
|
31
|
-
# prefix: /api/rpc
|
|
32
|
-
# access: gateway-only # loopback | private-network | gateway-only
|
|
33
|
-
# gatewayHeader: X-Gateway-Key
|
|
34
|
-
# gatewaySecret: ${HAI_SERV_HTTP_RPC_GATEWAYSECRET}
|
|
35
37
|
|
|
36
38
|
cors:
|
|
37
39
|
origin: ${HAI_SERV_CORS_ORIGIN:*}
|
|
@@ -60,5 +62,10 @@ transport:
|
|
|
60
62
|
{{else}}
|
|
61
63
|
maxPlaintextBytes: ${HAI_SERV_TRANSPORT_MAXPLAINTEXTBYTES:1048576}
|
|
62
64
|
{{/if}}
|
|
65
|
+
# HAI_SERV_TRANSPORT_ROUTEPLAINTEXTBYTES 可填 JSON,例如 {"/api/v1/uploads/*":8388608}。
|
|
66
|
+
routePlaintextBytes: {}
|
|
63
67
|
keyExchangePath: /_hai/key-exchange
|
|
64
68
|
# excludePaths: [/health, /ready]
|
|
69
|
+
# 单条路径需要更大请求体时填写字节数,* 只匹配一段路径。
|
|
70
|
+
# routePlaintextBytes: # HAI_SERV_TRANSPORT_ROUTEPLAINTEXTBYTES 可填 JSON 路径映射
|
|
71
|
+
# /api/v1/uploads/*: 8388608
|
|
@@ -43,11 +43,17 @@
|
|
|
43
43
|
"@h-ai/cache": "{{haiPackageSpecifier}}",
|
|
44
44
|
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
45
45
|
"@h-ai/crypto": "{{haiPackageSpecifier}}",
|
|
46
|
+
{{#if features.datapipe}}
|
|
47
|
+
"@h-ai/datapipe": "{{haiPackageSpecifier}}",
|
|
48
|
+
{{/if}}
|
|
46
49
|
{{#if features.feedback}}
|
|
47
50
|
"@h-ai/api-contract": "{{haiPackageSpecifier}}",
|
|
48
51
|
"@h-ai/feedback": "{{haiPackageSpecifier}}",
|
|
49
52
|
{{/if}}
|
|
50
53
|
"@h-ai/iam": "{{haiPackageSpecifier}}",
|
|
54
|
+
{{#if features.payment}}
|
|
55
|
+
"@h-ai/payment": "{{haiPackageSpecifier}}",
|
|
56
|
+
{{/if}}
|
|
51
57
|
"@h-ai/reach": "{{haiPackageSpecifier}}",
|
|
52
58
|
"@h-ai/reldb": "{{haiPackageSpecifier}}",
|
|
53
59
|
"@h-ai/scheduler": "{{haiPackageSpecifier}}",
|
|
@@ -37,7 +37,7 @@ export function createServerApp(): ServHttpApp {
|
|
|
37
37
|
`${servConfig.http.apiPrefix}/assets`,
|
|
38
38
|
...(servConfig.http.health ? [servConfig.http.health.path, servConfig.http.health.readyPath].filter((path): path is `/${string}` => Boolean(path)) : []),
|
|
39
39
|
...(servConfig.http.openapi && security.exposeApiDocs ? [servConfig.http.openapi.path] : []),
|
|
40
|
-
...(servConfig.http.docs && security.exposeApiDocs ? [servConfig.http.docs.path] : []),
|
|
40
|
+
...(servConfig.http.docs && security.exposeApiDocs ? [servConfig.http.docs.path, `${servConfig.http.docs.path.slice(0, servConfig.http.docs.path.lastIndexOf('/') + 1)}_hai/scalar.js`] : []),
|
|
41
41
|
]
|
|
42
42
|
{{#if features.feedback}}
|
|
43
43
|
// 普通业务日志与反馈使用同一可信应用范围;身份仍由 IAM 验证。
|
|
@@ -231,7 +231,7 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
231
231
|
}
|
|
232
232
|
})
|
|
233
233
|
|
|
234
|
-
it('updates personal preferences
|
|
234
|
+
it('updates personal preferences and clears the email address', async () => {
|
|
235
235
|
expect((await call('/profile/update', { displayName: 'Updated', locale: 'en-US', theme: '{{fullstack.theme.defaultTheme}}' }, userToken)).success).toBe(true)
|
|
236
236
|
const current = await call<{ metadata: { locale: string, theme: string } }>('/auth/me', undefined, userToken, 'GET')
|
|
237
237
|
expect(current.success).toBe(true)
|
|
@@ -242,6 +242,9 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
242
242
|
expect(cleared.success).toBe(true)
|
|
243
243
|
if (cleared.success)
|
|
244
244
|
expect(cleared.data.email).toBe('')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('rejects invalid images, oversized payloads and anonymous avatar uploads', async () => {
|
|
245
248
|
const invalid = await call('/profile/avatar', { contentType: 'image/png', base64: 'PHN2Zz48L3N2Zz4=' }, userToken)
|
|
246
249
|
expect(invalid.success).toBe(false)
|
|
247
250
|
if (!invalid.success)
|
|
@@ -252,6 +255,9 @@ describe('{{fullstack.servPackageName}} mini administration', () => {
|
|
|
252
255
|
expect(anonymous.success).toBe(false)
|
|
253
256
|
if (!anonymous.success)
|
|
254
257
|
expect(anonymous.error.httpStatus).toBe(401)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('serves only uploaded avatar resources and restricts profile fields', async () => {
|
|
255
261
|
const injected = await transport.encryptedFetch(`http://localhost${PREFIX}/profile/update`, {
|
|
256
262
|
method: 'POST',
|
|
257
263
|
headers: { 'content-type': 'application/json', 'authorization': `Bearer ${userToken}` },
|
|
@@ -58,7 +58,7 @@ beforeAll(async () => {
|
|
|
58
58
|
contract: {{fullstack.contractExportName}},
|
|
59
59
|
procedures: createServerProcedures({ app: { name: 'Security fixture', version: 'test', env: 'test', defaultLocale: 'en-US', supportedLocales: ['en-US'], startedAt: new Date().toISOString(), frontends: ['web'] } }),
|
|
60
60
|
iam,
|
|
61
|
-
http: { openapi: false, docs: false
|
|
61
|
+
http: { openapi: false, docs: false },
|
|
62
62
|
})
|
|
63
63
|
server = serv.listen(app, { host: '127.0.0.1', port: 0 })
|
|
64
64
|
if (!server.server.listening)
|
|
@@ -9,21 +9,21 @@ const message = core.i18n.createMessageGetter({
|
|
|
9
9
|
'zh-CN': {
|
|
10
10
|
flag: 'HAI_BUILD_STATIC仅支持0或1;省略或0保持SSR,1启用website静态构建。',
|
|
11
11
|
base: 'PUBLIC_BASE_PATH须为空或以/开头的站内目录;不允许外部地址、末尾/、查询、片段或路径逃逸。',
|
|
12
|
-
modules: '静态website
|
|
12
|
+
modules: '静态website不能启用服务端业务模块。请移除服务端模块及其初始化后再构建,或继续使用默认SSR。',
|
|
13
13
|
routes: '静态website不能包含自定义API、表单actions或未预渲染页面;请移除动态入口或使用SSR。',
|
|
14
14
|
assets: 'static是公开资产目录,不能放入.env私有环境文件或符号链接;请移动私有内容后重新构建。',
|
|
15
15
|
},
|
|
16
16
|
'en-US': {
|
|
17
17
|
flag: 'HAI_BUILD_STATIC accepts only 0 or 1: omitted/0 keeps SSR, and 1 enables a static website build.',
|
|
18
18
|
base: 'PUBLIC_BASE_PATH must be empty or a site-local directory beginning with /. External URLs, trailing /, queries, fragments and path traversal are not allowed.',
|
|
19
|
-
modules: 'A static website cannot enable server-side business modules.
|
|
19
|
+
modules: 'A static website cannot enable server-side business modules. Remove server modules and their initialization before building, or keep the default SSR mode.',
|
|
20
20
|
routes: 'A static website cannot contain custom APIs, form actions or pages that were not prerendered. Remove dynamic routes or use SSR.',
|
|
21
21
|
assets: 'static is a public asset directory and cannot contain private .env files or symbolic links. Move private content before building.',
|
|
22
22
|
},
|
|
23
23
|
'ja-JP': {
|
|
24
24
|
flag: 'HAI_BUILD_STATICは0または1です。省略/0はSSR、1はwebsiteの静的構築を有効にします。',
|
|
25
25
|
base: 'PUBLIC_BASE_PATHは空、または/で始まるサイト内ディレクトリーです。外部URL、末尾の/、クエリー、フラグメント、パス逸脱は使えません。',
|
|
26
|
-
modules: '静的website
|
|
26
|
+
modules: '静的websiteではサーバー業務モジュールを有効にできません。サーバーモジュールと初期化処理を除去するか、既定のSSRを使用してください。',
|
|
27
27
|
routes: '静的websiteに独自API、form actions、事前描画されないページを含められません。動的ルートを削除するかSSRを使用してください。',
|
|
28
28
|
assets: 'staticは公開資産ディレクトリーです。非公開.envファイルやシンボリックリンクを置かず、非公開内容を移動してから構築してください。',
|
|
29
29
|
},
|
|
@@ -11,8 +11,8 @@ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config s
|
|
|
11
11
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
12
12
|
COPY images/dependencies/ ./
|
|
13
13
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
14
|
-
COPY . .
|
|
15
14
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
15
|
+
COPY . .
|
|
16
16
|
ARG HAI_BUILD_ID=
|
|
17
17
|
ARG HAI_BUILD_COMMIT=unknown
|
|
18
18
|
ARG PUBLIC_BASE_PATH=
|
|
@@ -23,6 +23,7 @@ FROM nginxinc/nginx-unprivileged:1.29.8-alpine AS runtime
|
|
|
23
23
|
COPY --chmod=755 images/web/19-runtime-config.sh /docker-entrypoint.d/19-hai-runtime-config.sh
|
|
24
24
|
COPY images/web/nginx.conf /etc/nginx/conf.d/default.conf
|
|
25
25
|
COPY --from=build /workspace/dist /usr/share/nginx/html
|
|
26
|
+
RUN find /usr/share/nginx/html -type f -name '*.map' -delete
|
|
26
27
|
|
|
27
28
|
EXPOSE 8080
|
|
28
29
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
@@ -42,14 +43,15 @@ RUN corepack enable && corepack prepare pnpm@12.11.2 --activate && pnpm config s
|
|
|
42
43
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
43
44
|
COPY images/dependencies/ ./
|
|
44
45
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch --ignore-scripts
|
|
45
|
-
COPY . .
|
|
46
46
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
47
|
+
COPY . .
|
|
47
48
|
ARG HAI_BUILD_ID=
|
|
48
49
|
ARG HAI_BUILD_COMMIT=unknown
|
|
49
50
|
ARG PUBLIC_BASE_PATH=
|
|
50
51
|
RUN HAI_BUILD_ID="$HAI_BUILD_ID" HAI_BUILD_COMMIT="$HAI_BUILD_COMMIT" PUBLIC_BASE_PATH="$PUBLIC_BASE_PATH" pnpm compile
|
|
51
52
|
RUN --mount=type=cache,id={{projectName}}-sqlite,target=/root/.npm/_prebuilds if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
52
53
|
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
|
+
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
|
|
53
55
|
|
|
54
56
|
FROM node:24.22.1-bookworm-slim AS runtime-base
|
|
55
57
|
|
|
@@ -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.4",
|
|
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}}",
|
|
@@ -8,13 +8,7 @@
|
|
|
8
8
|
import type { TransportEncryptionOptions } from '@h-ai/kit'
|
|
9
9
|
{{/if}}
|
|
10
10
|
import type { Handle } from '@sveltejs/kit'
|
|
11
|
-
{{#if features.cache}}
|
|
12
|
-
import process from 'node:process'
|
|
13
|
-
{{else}}
|
|
14
|
-
{{#if features.crypto}}
|
|
15
11
|
import process from 'node:process'
|
|
16
|
-
{{/if}}
|
|
17
|
-
{{/if}}
|
|
18
12
|
{{#if hasI18n}}
|
|
19
13
|
import { base } from '$app/paths'
|
|
20
14
|
{{else}}
|
|
@@ -29,7 +23,7 @@ import { base } from '$app/paths'
|
|
|
29
23
|
{{#if hasI18n}}
|
|
30
24
|
import { paraglideMiddleware } from '$lib/paraglide/server.js'
|
|
31
25
|
{{/if}}
|
|
32
|
-
import { initApp } from '$lib/server/init.js'
|
|
26
|
+
import { closeApp, initApp } from '$lib/server/init.js'
|
|
33
27
|
import { getKitConfig } from '$lib/server/kit-config.js'
|
|
34
28
|
{{#if features.cache}}
|
|
35
29
|
import { cache } from '@h-ai/cache'
|
|
@@ -54,6 +48,14 @@ import { reldb } from '@h-ai/reldb'
|
|
|
54
48
|
|
|
55
49
|
let appInitPromise: Promise<void> | null = null
|
|
56
50
|
|
|
51
|
+
// Node 适配器停止接收请求后释放模块,避免数据库或后台任务阻止进程退出。
|
|
52
|
+
process.once('sveltekit:shutdown', () => {
|
|
53
|
+
void closeApp().catch((error: unknown) => {
|
|
54
|
+
core.logger.error('Application shutdown failed', { error })
|
|
55
|
+
process.exitCode = 1
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
57
59
|
async function ensureAppInitialized() {
|
|
58
60
|
if (!appInitPromise) {
|
|
59
61
|
appInitPromise = initApp().catch((err: unknown) => {
|
|
@@ -28,5 +28,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|
|
28
28
|
metadata,
|
|
29
29
|
notifyUrl: notifyUrl ?? `${new URL(request.url).origin}${base}/api/v1/payment/notify/${provider}`,
|
|
30
30
|
})
|
|
31
|
-
|
|
31
|
+
if (!result.success)
|
|
32
|
+
return kit.response.fromError(result.error, locals.requestId)
|
|
33
|
+
return kit.response.ok(result.data, locals.requestId)
|
|
32
34
|
}
|