#!/usr/bin/env node /** * 本地 CI:在无 Gitea Actions runner 时,按 .gitea/workflows/ci.yml 的门禁顺序执行。 * 用法:pnpm ci:local(推荐通过 ./.tools/pnpm ci:local 锁定 pnpm 10) */ import { spawnSync } from 'node:child_process'; const pnpm = process.env.npm_execpath ? [process.execPath, process.env.npm_execpath] : ['pnpm']; /** 每一步可包含多条 pnpm 命令,任一失败立即退出。 */ const steps = [ { label: '启动本地依赖(PG / Redis / S3)', commands: [['compose:up']] }, { label: '格式化检查', commands: [['format:check']] }, { label: 'Lint', commands: [['lint']] }, { label: '类型检查', commands: [['typecheck']] }, { label: '单元测试', commands: [['test']] }, { label: '构建', commands: [['build']] }, { label: '数据库迁移与种子', commands: [['exec', 'prisma', 'migrate', 'deploy'], ['db:seed']], }, { label: '集成测试', commands: [['test:int']] }, { label: 'OpenAPI 导出与契约检查', commands: [['openapi:export'], ['openapi:diff']] }, { label: '端到端测试', commands: [['test:e2e']] }, ]; function runPnpm(args) { const result = spawnSync(pnpm[0], [...pnpm.slice(1), ...args], { env: process.env, stdio: 'inherit', }); if (result.error) { console.error(`\n执行 pnpm ${args.join(' ')} 失败:${result.error.message}`); process.exit(1); } if (result.status !== 0) { console.error(`\n门禁失败:pnpm ${args.join(' ')}(exit ${result.status ?? 1})`); process.exit(result.status ?? 1); } } const startedAt = Date.now(); for (const [index, step] of steps.entries()) { console.log(`\n[${index + 1}/${steps.length}] ${step.label}`); for (const args of step.commands) { runPnpm(args); } } const seconds = ((Date.now() - startedAt) / 1000).toFixed(1); console.log(`\n本地 CI 全部门禁通过,用时 ${seconds}s。`);