发布 v1.10 #5
@@ -76,6 +76,13 @@ pnpm dev
|
|||||||
|
|
||||||
默认测试账号:`admin / Easy@2026`
|
默认测试账号:`admin / Easy@2026`
|
||||||
|
|
||||||
|
管理端发布前可执行以下命令,验证环境契约、生产镜像和 Nginx 路由:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd easyflow-ui-admin
|
||||||
|
pnpm verify:deployment
|
||||||
|
```
|
||||||
|
|
||||||
## 后端 Jar 包构建与部署
|
## 后端 Jar 包构建与部署
|
||||||
|
|
||||||
### 构建 Jar
|
### 构建 Jar
|
||||||
|
|||||||
@@ -30,9 +30,11 @@
|
|||||||
"preview": "turbo-run preview",
|
"preview": "turbo-run preview",
|
||||||
"publint": "vsh publint",
|
"publint": "vsh publint",
|
||||||
"reinstall": "pnpm clean --del-lock && pnpm install",
|
"reinstall": "pnpm clean --del-lock && pnpm install",
|
||||||
|
"test:deployment-contract": "vitest run --dom app/vite-base-path-redirect.test.ts app/src/startup-error.test.ts app/src/router/__tests__/environment-contract.test.ts app/src/router/navigation-user-info.test.ts app/src/utils/share-route-context.test.ts app/src/utils/__tests__/login-redirect.test.ts app/src/views/ai/workflow/workflow-share-context.test.ts",
|
||||||
"test:unit": "vitest run --dom",
|
"test:unit": "vitest run --dom",
|
||||||
"test:e2e": "turbo run test:e2e",
|
"test:e2e": "turbo run test:e2e",
|
||||||
"update:deps": "npx taze -r -w",
|
"update:deps": "npx taze -r -w",
|
||||||
|
"verify:deployment": "pnpm run test:deployment-contract && pnpm run build:app && docker build --platform linux/amd64 -f scripts/deploy/Dockerfile.smoke -t easyflow-frontend-smoke:local . && cross-env EASYFLOW_FRONTEND_IMAGE=easyflow-frontend-smoke:local node scripts/deploy/smoke-test.mjs",
|
||||||
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile",
|
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile",
|
||||||
"catalog": "pnpx codemod pnpm/catalog"
|
"catalog": "pnpx codemod pnpm/catalog"
|
||||||
},
|
},
|
||||||
|
|||||||
12
easyflow-ui-admin/scripts/deploy/Dockerfile.smoke
Normal file
12
easyflow-ui-admin/scripts/deploy/Dockerfile.smoke
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:stable-alpine
|
||||||
|
|
||||||
|
RUN rm -f /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
COPY app/dist/ /usr/share/nginx/html/flow/
|
||||||
|
COPY scripts/deploy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
RUN nginx -t
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
**
|
||||||
|
!app/
|
||||||
|
!app/dist/
|
||||||
|
!app/dist/**
|
||||||
|
!scripts/
|
||||||
|
!scripts/deploy/
|
||||||
|
!scripts/deploy/nginx.conf
|
||||||
92
easyflow-ui-admin/scripts/deploy/smoke-test.mjs
Normal file
92
easyflow-ui-admin/scripts/deploy/smoke-test.mjs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { execFileSync } from 'node:child_process';
|
||||||
|
|
||||||
|
const image = process.env.EASYFLOW_FRONTEND_IMAGE || 'easyflow-frontend:0.1';
|
||||||
|
const containerName = `easyflow-frontend-smoke-${process.pid}`;
|
||||||
|
|
||||||
|
function docker(args) {
|
||||||
|
return execFileSync('docker', args, {
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
}).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForServer(origin) {
|
||||||
|
let lastError;
|
||||||
|
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${origin}/flow/`);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastError = new Error(`HTTP ${response.status}`);
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||||
|
}
|
||||||
|
throw new Error(`Nginx 未在预期时间内启动: ${String(lastError)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
docker([
|
||||||
|
'run',
|
||||||
|
'--detach',
|
||||||
|
'--rm',
|
||||||
|
'--publish',
|
||||||
|
'127.0.0.1::80',
|
||||||
|
'--name',
|
||||||
|
containerName,
|
||||||
|
image,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const portBinding = docker(['port', containerName, '80/tcp'])
|
||||||
|
.split('\n')
|
||||||
|
.find((line) => line.startsWith('127.0.0.1:'));
|
||||||
|
const port = portBinding?.match(/:(\d+)$/)?.[1];
|
||||||
|
assert(port, `无法解析容器端口: ${portBinding || 'empty'}`);
|
||||||
|
|
||||||
|
const origin = `http://127.0.0.1:${port}`;
|
||||||
|
await waitForServer(origin);
|
||||||
|
|
||||||
|
const rootResponse = await fetch(`${origin}/`, { redirect: 'manual' });
|
||||||
|
assert(rootResponse.status === 302, `根路径状态异常: ${rootResponse.status}`);
|
||||||
|
const rootLocation = rootResponse.headers.get('location');
|
||||||
|
assert(
|
||||||
|
rootLocation && new URL(rootLocation, origin).pathname === '/flow/',
|
||||||
|
`根路径重定向异常: ${rootLocation}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const flowResponse = await fetch(`${origin}/flow/`);
|
||||||
|
const indexHtml = await flowResponse.text();
|
||||||
|
assert(flowResponse.ok, `/flow/ 状态异常: ${flowResponse.status}`);
|
||||||
|
assert(indexHtml.includes('<div id="app">'), '/flow/ 未返回应用入口');
|
||||||
|
|
||||||
|
const assetPath = indexHtml.match(/(?:src|href)="(\/flow\/[^"]+)"/)?.[1];
|
||||||
|
assert(assetPath, '构建产物未使用 /flow/ 静态资源前缀');
|
||||||
|
const assetResponse = await fetch(`${origin}${assetPath}`);
|
||||||
|
assert(assetResponse.ok, `静态资源状态异常: ${assetResponse.status}`);
|
||||||
|
|
||||||
|
const invalidRootShareResponse = await fetch(
|
||||||
|
`${origin}/share/knowledge?shareKey=smoke-test`,
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
invalidRootShareResponse.status === 404,
|
||||||
|
`根路径分享地址应返回 404,实际为 ${invalidRootShareResponse.status}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'部署冒烟通过:根路径重定向、/flow/ 入口、静态资源前缀和错误分享路径均符合预期。',
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
docker(['rm', '--force', containerName]);
|
||||||
|
} catch {
|
||||||
|
// 容器可能因 --rm 已自动删除。
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user