207 lines
9.3 KiB
JavaScript
207 lines
9.3 KiB
JavaScript
#!/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")}`);
|