// 静态检查:语法 + 类型 + 构建 + 关键标记 tripwire(CI 与 pre-push 共用) import { spawnSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import url from 'node:url'; const root = path.resolve(path.dirname(url.fileURLToPath(import.meta.url)), '..'); let failures = 0; const step = (label, fn) => { process.stdout.write('==> ' + label + ' ... '); try { fn(); console.log('OK'); } catch (e) { console.log('FAIL\n' + String(e.message || e).slice(-2000)); failures++; } }; // 1) 所有服务端 .mjs 语法检查(新增文件自动纳入) const mjsDirs = ['sync-server/src', 'app/server', 'scripts']; const mjsFiles = mjsDirs.flatMap((d) => fs.readdirSync(path.join(root, d)).filter((f) => f.endsWith('.mjs')).map((f) => path.join(d, f))); step('node --check × ' + mjsFiles.length + ' 个文件', () => { for (const f of mjsFiles) { const r = spawnSync(process.execPath, ['--check', path.join(root, f)], { encoding: 'utf8' }); if (r.status !== 0) throw new Error(f + '\n' + r.stderr); } }); // 2) TypeScript 类型检查 const tsc = path.join(root, 'app/node_modules/typescript/bin/tsc'); if (fs.existsSync(tsc)) { step('tsc --noEmit(前端类型)', () => { const r = spawnSync(process.execPath, [tsc, '--noEmit'], { cwd: path.join(root, 'app'), encoding: 'utf8' }); if (r.status !== 0) throw new Error(r.stdout || r.stderr); }); } else console.log('==> tsc 跳过(app/node_modules 不存在,先安装依赖)'); // 3) 前端构建(确保源码可构建;产物不提交) const vite = path.join(root, 'app/node_modules/vite/bin/vite.js'); if (fs.existsSync(vite)) { step('vite build', () => { const r = spawnSync(process.execPath, [vite, 'build'], { cwd: path.join(root, 'app'), encoding: 'utf8' }); if (r.status !== 0) throw new Error(r.stdout || r.stderr); }); } else console.log('==> vite 跳过(app/node_modules 不存在)'); // 4) 关键文件 tripwire:防止「改了没提交」类事故再次发生 step('关键标记 tripwire', () => { const musts = [ ['app/server/db.mjs', "case 'demo_seed'"], ['app/server/db.mjs', 'DEMO_ROWS'], ['app/public/sw.js', 'fp-static-v3'], ['sync-server/src/db.mjs', "name='remark'"], ['sync-server/src/index.mjs', 'demo_seed'], ]; for (const [f, needle] of musts) { const p = path.join(root, f); if (!fs.existsSync(p) || !fs.readFileSync(p, 'utf8').includes(needle)) { throw new Error(f + ' 缺少关键标记: ' + needle); } } }); if (failures) { console.error('\n✗ check 失败:' + failures + ' 项'); process.exit(1); } console.log('\n✓ check 全部通过');