chore: add release verification gate
This commit is contained in:
parent
58f6f5f249
commit
4463bf1fc7
@ -81,6 +81,7 @@ pnpm check:artifacts
|
||||
pnpm check:quality
|
||||
pnpm clean:artifacts
|
||||
pnpm audit:security
|
||||
pnpm verify:release
|
||||
pnpm exec tsc --noEmit --pretty false
|
||||
pnpm test
|
||||
pnpm db:migrate
|
||||
@ -91,10 +92,17 @@ pnpm db:generate
|
||||
|
||||
```bash
|
||||
pnpm check:quality
|
||||
pnpm audit:security
|
||||
AUTH_TRUST_HOST=true PORT=3001 pnpm dev
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm smoke:routes
|
||||
```
|
||||
|
||||
发布或创建 PR 前可使用一条命令串起质量、安全和路由烟测:
|
||||
|
||||
```bash
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm verify:release
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
|
||||
@ -19,6 +19,7 @@ git diff --check
|
||||
pnpm check:artifacts
|
||||
pnpm clean:artifacts
|
||||
pnpm audit:security
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm verify:release
|
||||
```
|
||||
|
||||
等价的一键质量门禁:
|
||||
@ -27,6 +28,14 @@ pnpm audit:security
|
||||
pnpm check:quality
|
||||
```
|
||||
|
||||
发布或创建 PR 前的一键验收:
|
||||
|
||||
```bash
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm verify:release
|
||||
```
|
||||
|
||||
`pnpm verify:release` 会串起 `pnpm check:quality`、`pnpm audit:security` 和 `pnpm smoke:routes`。它要求显式设置 `SMOKE_BASE_URL`,避免误把路由烟测跑到错误环境。
|
||||
|
||||
其中 `pnpm check:artifacts` 会检查 `.DS_Store`、swap 文件、`.env.local`、`.claude/`、migration backup 和 `tsconfig.tsbuildinfo` 等本地污染物是否被 Git 追踪。
|
||||
|
||||
`pnpm clean:artifacts` 会清理当前工作区常见本地垃圾文件,例如 `.DS_Store`、swap 文件和 `tsconfig.tsbuildinfo`。它会跳过 `.git`、`node_modules`、`.next`、`.pnpm-store`、`.venv` 等重目录,适合在提交或打包前手动执行。
|
||||
|
||||
@ -6,10 +6,8 @@
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm check:quality
|
||||
pnpm audit:security
|
||||
AUTH_TRUST_HOST=true PORT=3001 pnpm dev
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm smoke:routes
|
||||
SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm verify:release
|
||||
```
|
||||
|
||||
预期:
|
||||
@ -20,6 +18,8 @@ SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm smoke:routes
|
||||
- `/classic` 和 `/chat/test-id` 重定向到 `/`。
|
||||
- `/settings` 未登录重定向到 `/login?callbackUrl=%2Fsettings`。
|
||||
|
||||
`pnpm verify:release` 会依次运行 `pnpm check:quality`、`pnpm audit:security` 和 `pnpm smoke:routes`。必须显式提供 `SMOKE_BASE_URL`,确保路由烟测指向本地、预发或生产中的明确目标。
|
||||
|
||||
## 发布边界
|
||||
|
||||
- 旧 Chat UI 已移除;`/api/chat` 保留为 Canvas/Agent 内部生成 API。
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
"audit:security:json": "pnpm audit --registry=https://registry.npmjs.org --audit-level high --json",
|
||||
"clean:artifacts": "node scripts/clean-local-artifacts.mjs",
|
||||
"smoke:routes": "node scripts/smoke-routes.mjs",
|
||||
"verify:release": "node scripts/verify-release.mjs",
|
||||
"format": "pnpm exec ultracite fix",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "npx tsx lib/db/migrate.ts",
|
||||
|
||||
67
scripts/verify-release.mjs
Normal file
67
scripts/verify-release.mjs
Normal file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const baseUrl = process.env.SMOKE_BASE_URL || "";
|
||||
const skipSmoke = process.argv.includes("--skip-smoke");
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: "quality gate",
|
||||
command: "pnpm",
|
||||
args: ["check:quality"],
|
||||
},
|
||||
{
|
||||
name: "dependency audit",
|
||||
command: "pnpm",
|
||||
args: ["audit:security"],
|
||||
},
|
||||
];
|
||||
|
||||
if (baseUrl) {
|
||||
steps.push({
|
||||
name: "route smoke",
|
||||
command: "pnpm",
|
||||
args: ["smoke:routes"],
|
||||
});
|
||||
} else if (!skipSmoke) {
|
||||
console.error(
|
||||
[
|
||||
"Release verification requires SMOKE_BASE_URL so route smoke tests run against an explicit target.",
|
||||
"Example:",
|
||||
" SMOKE_BASE_URL=http://127.0.0.1:3001 pnpm verify:release",
|
||||
"Use `pnpm verify:release -- --skip-smoke` only for documentation or CI dry-runs where no app is running.",
|
||||
].join("\n")
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const [index, step] of steps.entries()) {
|
||||
console.log(`\n[release:${index + 1}/${steps.length}] ${step.name}`);
|
||||
await run(step.command, step.args);
|
||||
}
|
||||
|
||||
if (skipSmoke && !baseUrl) {
|
||||
console.warn("\n[release] route smoke skipped by explicit --skip-smoke flag.");
|
||||
}
|
||||
|
||||
console.log("\nRelease verification passed.");
|
||||
|
||||
function run(command, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
env: process.env,
|
||||
shell: false,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
child.on("error", reject);
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user