#!/usr/bin/env node /** * 合同管理工具 — 完整 E2E 生成 + 验证 * 使用 Certified Software Generator v1.1 的标准 Pipeline * 不修改任何 Generator 源码 */ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "node:fs"; import { resolve, dirname, join } from "node:path"; import { execSync } from "node:child_process"; import { fileURLToPath } from "node:url"; const __dirname = dirname(resolve(fileURLToPath(import.meta.url))); const WORKSPACE = resolve(__dirname, ".."); const SCRIPTS = resolve(WORKSPACE, "scripts"); const B = resolve(WORKSPACE, ".benchmark", "contract-mgmt"); const INPUT = "开发一个合同管理桌面工具,支持合同创建、合同审批、合同到期提醒、客户管理、合同金额统计、操作日志"; mkdirSync(B, { recursive: true }); function cmd(c, opts = {}) { try { return execSync(c, { cwd: WORKSPACE, encoding: "utf8", ...opts, stdio: opts.stdio ?? "pipe" }); } catch (e) { return { error: e.message, stderr: e.stderr?.toString() || "", stdout: e.stdout?.toString() || "" }; } } function runAgent(script, args) { const t0 = Date.now(); const result = cmd(`node ${join(SCRIPTS, script)} ${args}`); const ms = Date.now() - t0; if (result.error) { try { return { ms, ...JSON.parse(result.stdout?.trim() || "{}") }; } catch { return { ms, error: result.error }; } } try { return { ms, ...JSON.parse(result.trim()) }; } catch { return { ms, raw: result }; } } function countFiles(dir) { try { return parseInt(execSync(`find ${dir} -type f 2>/dev/null | wc -l`, { encoding: "utf8", cwd: WORKSPACE }).trim(), 10); } catch { return 0; } } // Validation functions (same as e2e-benchmark-v1.mjs) function validateFrontend(dir) { if (!existsSync(dir)) return { pass: false, reason: "dir missing" }; const checks = { packageJson: existsSync(join(dir, "package.json")), appDir: existsSync(join(dir, "app")), services: existsSync(join(dir, "services")) || existsSync(join(dir, "src", "services")), types: existsSync(join(dir, "types")) || existsSync(join(dir, "src", "types")), }; const fileCount = countFiles(dir); return { pass: checks.packageJson && checks.appDir && fileCount > 10, fileCount, ...checks }; } function validateBackend(dir) { if (!existsSync(dir)) return { pass: false, reason: "dir missing" }; const checks = { packageJson: existsSync(join(dir, "package.json")), routes: existsSync(join(dir, "src", "routes")), services: existsSync(join(dir, "src", "services")), auth: existsSync(join(dir, "src", "routes", "auth.ts")) || existsSync(join(dir, "src", "routes", "auth-route.ts")), db: existsSync(join(dir, "src", "db")) || existsSync(join(dir, "src", "schema")), }; const fileCount = countFiles(dir); return { pass: checks.packageJson && checks.routes && checks.services && fileCount > 10, fileCount, ...checks }; } function validateFullstack(dir) { if (!existsSync(dir)) return { pass: false, reason: "dir missing" }; const checks = { packageJson: existsSync(join(dir, "package.json")), web: existsSync(join(dir, "apps", "web")), api: existsSync(join(dir, "apps", "api")), shared: existsSync(join(dir, "packages")), }; const fileCount = countFiles(dir); return { pass: checks.packageJson && checks.web && checks.api && fileCount > 30, fileCount, ...checks }; } function validateElectron(dir) { if (!existsSync(dir)) return { pass: false, reason: "dir missing" }; const checks = { packageJson: existsSync(join(dir, "package.json")), main: existsSync(join(dir, "electron", "main.ts")), preload: existsSync(join(dir, "electron", "preload.ts")), ipc: existsSync(join(dir, "electron", "ipc.ts")), yml: existsSync(join(dir, "electron-builder.yml")), }; const fileCount = countFiles(dir); return { pass: checks.packageJson && checks.main && checks.preload && fileCount >= 8, fileCount, ...checks }; } function validateRelease(dir) { if (!existsSync(dir)) return { pass: false, reason: "dir missing" }; const rDir = join(dir, "release"); const checks = { versionJson: existsSync(join(rDir, "version.json")), manifest: existsSync(join(rDir, "manifests", "manifest.json")), checksums: existsSync(join(rDir, "checksums", "checksums.txt")), releaseNotes: existsSync(join(rDir, "release-notes", "release-notes.md")), buildInfo: existsSync(join(rDir, "build-info.json")), }; if (checks.checksums) { const lines = readFileSync(join(rDir, "checksums", "checksums.txt"), "utf8").trim().split("\n").filter(l => l.length > 0); checks.checksumCount = lines.length; checks.checksumsValid = lines.every(l => l.split(/\s+/)[0]?.length === 64); } if (checks.buildInfo) { try { const bi = JSON.parse(readFileSync(join(rDir, "build-info.json"), "utf8")); checks.buildInfoValid = !!(bi.nodeVersion && bi.generatorVersion); } catch { checks.buildInfoValid = false; } } const fileCount = countFiles(dir); return { pass: checks.versionJson && checks.manifest && checks.checksums && checks.releaseNotes && checks.buildInfo, fileCount, ...checks }; } // ═══════════════════════════════════════════════════════ // MAIN // ═══════════════════════════════════════════════════════ console.log("═".repeat(60)); console.log(" 合同管理工具 — Certified Generator v1.1 E2E 验证"); console.log("═".repeat(60)); console.log(`\n需求: ${INPUT}\n`); // SF-01: PRD console.log("▸ SF-01 Project Intake..."); const s1 = runAgent("project-intake-agent.mjs", `--input "${INPUT}" --output "${join(B, "prd.json")}"`); console.log(` ✓ PRD: ${s1.projectName || "?"} (${s1.ms}ms) ${s1.error ? "❌ " + s1.error : "✅"}`); // SF-02: Architecture console.log("▸ SF-02 Architecture..."); const s2 = runAgent("architecture-agent.mjs", `--input "${join(B, "prd.json")}" --output "${join(B, "arch.json")}"`); console.log(` ✓ Architecture: ${s2.moduleCount || "?"} modules (${s2.ms}ms) ${s2.error ? "❌ " + s2.error : "✅"}`); // SF-03: Frontend console.log("▸ SF-03 Frontend Builder..."); const feOut = join(B, "frontend"); const s3 = runAgent("frontend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${feOut}"`); const feVal = validateFrontend(feOut); console.log(` ✓ Frontend: ${feVal.fileCount} files (${s3.ms}ms) ${feVal.pass ? "✅" : "❌"}`); // SF-04: Backend console.log("▸ SF-04 Backend Builder..."); const beOut = join(B, "backend"); const s4 = runAgent("backend-builder-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${beOut}"`); const beVal = validateBackend(beOut); console.log(` ✓ Backend: ${beVal.fileCount} files (${s4.ms}ms) ${beVal.pass ? "✅" : "❌"}`); // SF-05: Fullstack console.log("▸ SF-05 Fullstack Composer..."); const fsOut = join(B, "fullstack"); const s5 = runAgent("fullstack-composer-agent.mjs", `--prd "${join(B, "prd.json")}" --arch "${join(B, "arch.json")}" --output "${fsOut}"`); const fsVal = validateFullstack(fsOut); console.log(` ✓ Fullstack: ${fsVal.fileCount} files (${s5.ms}ms) ${fsVal.pass ? "✅" : "❌"}`); // SF-06: Electron console.log("▸ SF-06 Electron Builder..."); const elOut = join(B, "electron"); const s6 = runAgent("electron-builder-agent.mjs", `--input "${fsOut}" --output "${elOut}" --prd "${join(B, "prd.json")}"`); const elVal = validateElectron(elOut); console.log(` ✓ Electron: ${elVal.fileCount} files (${s6.ms}ms) ${elVal.pass ? "✅" : "❌"}`); // SF-07: Release console.log("▸ SF-07 Release Builder..."); const rlOut = join(B, "release"); const s7 = runAgent("release-builder-agent.mjs", `--input "${fsOut}" --output "${rlOut}"`); const rlVal = validateRelease(rlOut); console.log(` ✓ Release: ${rlVal.fileCount} files (${s7.ms}ms) ${rlVal.pass ? "✅" : "❌"}`); // Summary const totalMs = s1.ms + s2.ms + s3.ms + s4.ms + s5.ms + s6.ms + s7.ms; const totalFiles = (s3.stats?.totalFiles || feVal.fileCount) + (s4.stats?.totalFiles || beVal.fileCount) + (s5.stats?.totalFiles || fsVal.fileCount) + (s6.stats?.totalFiles || elVal.fileCount) + (s7.stats?.totalFiles || rlVal.fileCount); const allPass = feVal.pass && beVal.pass && fsVal.pass && elVal.pass && rlVal.pass; console.log("\n" + "═".repeat(60)); console.log(` 总计: ${totalFiles} files | ${totalMs}ms | ${allPass ? "✅ ALL PASS" : "❌ HAS FAILURES"}`); console.log("═".repeat(60)); // Output summary as JSON const summary = { input: INPUT, projectName: s1.projectName, domain: s1.domain, stages: { prd: { ms: s1.ms, error: s1.error }, arch: { ms: s2.ms, modules: s2.moduleCount, error: s2.error }, frontend: { ms: s3.ms, files: feVal.fileCount, pass: feVal.pass, error: s3.error }, backend: { ms: s4.ms, files: beVal.fileCount, pass: beVal.pass, error: s4.error }, fullstack: { ms: s5.ms, files: fsVal.fileCount, pass: fsVal.pass, error: s5.error }, electron: { ms: s6.ms, files: elVal.fileCount, pass: elVal.pass, error: s6.error }, release: { ms: s7.ms, files: rlVal.fileCount, pass: rlVal.pass, error: s7.error }, }, totalMs, totalFiles, allPass, outputDir: B, }; writeFileSync(join(B, "summary.json"), JSON.stringify(summary, null, 2)); console.log(`\nSummary: ${join(B, "summary.json")}`);