Files
16gagent/test/benchmark/domain-benchmark-suite.mjs
T
2026-06-06 10:40:48 +08:00

452 lines
28 KiB
JavaScript

#!/usr/bin/env node
/**
* Domain Benchmark Suite — runs 10 domains through full pipeline.
*
* For each domain:
* 1. Generate fullstack project
* 2. npm install + tsc build
* 3. Start server, test Health, Auth, CRUD
*
* Output: benchmark-report.md
*/
import { execSync, spawn } from "node:child_process";
import { existsSync, rmSync, readFileSync, writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import * as http from "node:http";
const __dirname = dirname(resolve(fileURLToPath(import.meta.url)));
const WORKSPACE = resolve(__dirname, "..", "..");
const BENCH_DIR = resolve(WORKSPACE, ".tmp-benchmark");
const DOMAINS = [
{
name: "PetCare", label: "宠物管理",
prd: { projectName: "PetCare", domain: "pet", summary: "宠物健康管理应用", features: [{ name: "宠物档案", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "pets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "species", type: "TEXT" }, { name: "breed", type: "TEXT" }, { name: "weight_kg", type: "REAL" }, { name: "created_at", type: "TEXT" }] }, { table: "schedules", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" }, { name: "completed", type: "INTEGER" }, { name: "created_at", type: "TEXT" }] }, { table: "daily_logs", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "pet_id", type: "TEXT", constraints: "NOT NULL, FK → pets.id" }, { name: "category", type: "TEXT", constraints: "NOT NULL" }, { name: "value", type: "TEXT", constraints: "NOT NULL" }, { name: "logged_at", type: "TEXT" }] }], apiDesign: [{ resource: "pets", basePath: "/api/pets", endpoints: [{ method: "GET", path: "/api/pets" }, { method: "POST", path: "/api/pets" }, { method: "GET", path: "/api/pets/:id" }, { method: "PUT", path: "/api/pets/:id" }, { method: "DELETE", path: "/api/pets/:id" }] }] },
},
{
name: "CRM", label: "客户关系管理",
prd: { projectName: "CRM", domain: "enterprise", summary: "CRM系统", features: [{ name: "客户管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "customers", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "company", type: "TEXT" }, { name: "email", type: "TEXT" }, { name: "phone", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "deals", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "customer_id", type: "TEXT", constraints: "NOT NULL, FK → customers.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "amount", type: "REAL" }, { name: "stage", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "activities", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "customer_id", type: "TEXT", constraints: "NOT NULL, FK → customers.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "note", type: "TEXT", constraints: "NOT NULL" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "customers", basePath: "/api/customers", endpoints: [{ method: "GET", path: "/api/customers" }, { method: "POST", path: "/api/customers" }, { method: "GET", path: "/api/customers/:id" }, { method: "PUT", path: "/api/customers/:id" }, { method: "DELETE", path: "/api/customers/:id" }] }] },
},
{
name: "Inventory", label: "库存管理",
prd: { projectName: "Inventory", domain: "ecommerce", summary: "库存管理", features: [{ name: "商品管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "products", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "sku", type: "TEXT" }, { name: "quantity", type: "INTEGER" }, { name: "price", type: "REAL" }, { name: "created_at", type: "TEXT" }] }, { table: "transactions", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "product_id", type: "TEXT", constraints: "NOT NULL, FK → products.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "quantity", type: "INTEGER", constraints: "NOT NULL" }, { name: "note", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "products", basePath: "/api/products", endpoints: [{ method: "GET", path: "/api/products" }, { method: "POST", path: "/api/products" }, { method: "GET", path: "/api/products/:id" }, { method: "PUT", path: "/api/products/:id" }, { method: "DELETE", path: "/api/products/:id" }] }] },
},
{
name: "TicketSystem", label: "工单系统",
prd: { projectName: "TicketSystem", domain: "enterprise", summary: "工单管理", features: [{ name: "工单管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "tickets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT", constraints: "NOT NULL" }, { name: "priority", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "assignee_id", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "comments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "ticket_id", type: "TEXT", constraints: "NOT NULL, FK → tickets.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT", constraints: "NOT NULL" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "tickets", basePath: "/api/tickets", endpoints: [{ method: "GET", path: "/api/tickets" }, { method: "POST", path: "/api/tickets" }, { method: "GET", path: "/api/tickets/:id" }, { method: "PUT", path: "/api/tickets/:id" }, { method: "DELETE", path: "/api/tickets/:id" }] }] },
},
{
name: "BlogCMS", label: "博客CMS",
prd: { projectName: "BlogCMS", domain: "note", summary: "博客系统", features: [{ name: "文章管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "posts", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "author_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT", constraints: "NOT NULL" }, { name: "slug", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }, { name: "updated_at", type: "TEXT" }] }, { table: "tags", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "name", type: "TEXT", constraints: "UNIQUE NOT NULL" }, { name: "created_at", type: "TEXT" }] }, { table: "post_tags", fields: [{ name: "post_id", type: "TEXT", constraints: "NOT NULL, FK → posts.id" }, { name: "tag_id", type: "TEXT", constraints: "NOT NULL, FK → tags.id" }] }], apiDesign: [{ resource: "posts", basePath: "/api/posts", endpoints: [{ method: "GET", path: "/api/posts" }, { method: "POST", path: "/api/posts" }, { method: "GET", path: "/api/posts/:id" }, { method: "PUT", path: "/api/posts/:id" }, { method: "DELETE", path: "/api/posts/:id" }] }] },
},
{
name: "ProjectMgmt", label: "项目管理",
prd: { projectName: "ProjectMgmt", domain: "enterprise", summary: "项目管理", features: [{ name: "项目管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "projects", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "tasks", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "project_id", type: "TEXT", constraints: "NOT NULL, FK → projects.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "assignee_id", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "priority", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "projects", basePath: "/api/projects", endpoints: [{ method: "GET", path: "/api/projects" }, { method: "POST", path: "/api/projects" }, { method: "GET", path: "/api/projects/:id" }, { method: "PUT", path: "/api/projects/:id" }, { method: "DELETE", path: "/api/projects/:id" }] }] },
},
{
name: "HRSystem", label: "人力资源",
prd: { projectName: "HRSystem", domain: "enterprise", summary: "人力资源管理", features: [{ name: "员工管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "employees", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "department", type: "TEXT", constraints: "NOT NULL" }, { name: "position", type: "TEXT", constraints: "NOT NULL" }, { name: "hire_date", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "attendances", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "employee_id", type: "TEXT", constraints: "NOT NULL, FK → employees.id" }, { name: "check_in", type: "TEXT" }, { name: "check_out", type: "TEXT" }, { name: "date", type: "TEXT", constraints: "NOT NULL" }, { name: "status", type: "TEXT" }] }, { table: "leaves", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "employee_id", type: "TEXT", constraints: "NOT NULL, FK → employees.id" }, { name: "type", type: "TEXT", constraints: "NOT NULL" }, { name: "start_date", type: "TEXT", constraints: "NOT NULL" }, { name: "end_date", type: "TEXT", constraints: "NOT NULL" }, { name: "status", type: "TEXT" }, { name: "reason", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }], apiDesign: [{ resource: "employees", basePath: "/api/employees", endpoints: [{ method: "GET", path: "/api/employees" }, { method: "POST", path: "/api/employees" }, { method: "GET", path: "/api/employees/:id" }, { method: "PUT", path: "/api/employees/:id" }, { method: "DELETE", path: "/api/employees/:id" }] }] },
},
{
name: "AssetMgmt", label: "资产管理",
prd: { projectName: "AssetMgmt", domain: "enterprise", summary: "资产管理", features: [{ name: "资产管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "assets", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "category", type: "TEXT", constraints: "NOT NULL" }, { name: "serial_number", type: "TEXT" }, { name: "status", type: "TEXT" }, { name: "assigned_to", type: "TEXT" }, { name: "purchase_date", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "assignments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "asset_id", type: "TEXT", constraints: "NOT NULL, FK → assets.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "assigned_at", type: "TEXT" }, { name: "returned_at", type: "TEXT" }, { name: "note", type: "TEXT" }] }], apiDesign: [{ resource: "assets", basePath: "/api/assets", endpoints: [{ method: "GET", path: "/api/assets" }, { method: "POST", path: "/api/assets" }, { method: "GET", path: "/api/assets/:id" }, { method: "PUT", path: "/api/assets/:id" }, { method: "DELETE", path: "/api/assets/:id" }] }] },
},
{
name: "CourseMgmt", label: "课程管理",
prd: { projectName: "CourseMgmt", domain: "education", summary: "课程管理", features: [{ name: "课程管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "courses", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "instructor_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "description", type: "TEXT" }, { name: "price", type: "REAL" }, { name: "status", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "lessons", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "course_id", type: "TEXT", constraints: "NOT NULL, FK → courses.id" }, { name: "title", type: "TEXT", constraints: "NOT NULL" }, { name: "content", type: "TEXT" }, { name: "sort_order", type: "INTEGER" }, { name: "created_at", type: "TEXT" }] }, { table: "enrollments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "course_id", type: "TEXT", constraints: "NOT NULL, FK → courses.id" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL" }, { name: "enrolled_at", type: "TEXT" }, { name: "progress", type: "INTEGER" }] }], apiDesign: [{ resource: "courses", basePath: "/api/courses", endpoints: [{ method: "GET", path: "/api/courses" }, { method: "POST", path: "/api/courses" }, { method: "GET", path: "/api/courses/:id" }, { method: "PUT", path: "/api/courses/:id" }, { method: "DELETE", path: "/api/courses/:id" }] }] },
},
{
name: "AppointmentScheduling", label: "预约排程",
prd: { projectName: "Appointment", domain: "enterprise", summary: "预约管理", features: [{ name: "预约管理", priority: "P0" }], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] },
arch: { databaseSchema: [{ table: "appointments", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "user_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "provider_id", type: "TEXT" }, { name: "service", type: "TEXT", constraints: "NOT NULL" }, { name: "scheduled_at", type: "TEXT", constraints: "NOT NULL" }, { name: "duration_min", type: "INTEGER" }, { name: "status", type: "TEXT" }, { name: "notes", type: "TEXT" }, { name: "created_at", type: "TEXT" }] }, { table: "availability", fields: [{ name: "id", type: "TEXT", constraints: "PK" }, { name: "provider_id", type: "TEXT", constraints: "NOT NULL" }, { name: "day_of_week", type: "INTEGER", constraints: "NOT NULL" }, { name: "start_time", type: "TEXT", constraints: "NOT NULL" }, { name: "end_time", type: "TEXT", constraints: "NOT NULL" }] }], apiDesign: [{ resource: "appointments", basePath: "/api/appointments", endpoints: [{ method: "GET", path: "/api/appointments" }, { method: "POST", path: "/api/appointments" }, { method: "GET", path: "/api/appointments/:id" }, { method: "PUT", path: "/api/appointments/:id" }, { method: "DELETE", path: "/api/appointments/:id" }] }] },
},
];
// --- HTTP helpers ---
function httpPost(url, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const u = new URL(url);
const headers = { "Content-Type": "application/json", "Content-Length": payload.length.toString() };
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: "POST", headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
});
req.on("error", reject);
req.write(payload);
req.end();
});
}
function httpGet(url, token) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const headers = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
http.get({ hostname: u.hostname, port: u.port, path: u.pathname, headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
}).on("error", reject);
});
}
function httpPut(url, body, token) {
return new Promise((resolve, reject) => {
const payload = JSON.stringify(body);
const u = new URL(url);
const headers = { "Content-Type": "application/json", "Content-Length": payload.length.toString() };
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: "PUT", headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve({ raw: d, status: res.statusCode }); } });
});
req.on("error", reject);
req.write(payload);
req.end();
});
}
function waitForServer(url, timeoutMs = 20000) {
return new Promise((resolve, reject) => {
const start = Date.now();
function check() {
http.get(url, (res) => { res.resume(); res.on("end", resolve); }).on("error", () => {
if (Date.now() - start > timeoutMs) reject(new Error("Server never started"));
else setTimeout(check, 500);
});
}
check();
});
}
async function runCustomCommand(domainLabel, tmpDir, installOnly = false) {
try {
execSync("npm install --no-audit --no-fund", { cwd: tmpDir, encoding: "utf-8", timeout: 180000, stdio: "pipe" });
return true;
} catch {
return false;
}
}
async function runDomain(i, domain) {
console.log(`\n${"─".repeat(60)}`);
console.log(`[${i + 1}/${DOMAINS.length}] ${domain.label} (${domain.name})`);
console.log(`${"─".repeat(60)}`);
const result = {
name: domain.name,
label: domain.label,
files: 0,
build: false,
health: false,
register: false,
login: false,
create: false,
read: false,
update: false,
delete: false,
errors: [],
timeMs: 0,
};
const t0 = Date.now();
const tmpDir = `${BENCH_DIR}/${domain.name}`;
rmSync(tmpDir, { recursive: true, force: true });
// 1. Generate
try {
const { composeFullstack, writeFullstack } = await import("../../scripts/fullstack-composer-agent.mjs");
const genResult = await composeFullstack(domain.prd, domain.arch);
if (genResult.error) {
result.errors.push(`Generation: ${genResult.error} ${genResult.message}`);
result.timeMs = Date.now() - t0;
return result;
}
writeFullstack(genResult, tmpDir);
result.files = genResult.stats.totalFiles;
console.log(` Generated ${result.files} files`);
} catch (e) {
result.errors.push(`Generation: ${e.message}`);
result.timeMs = Date.now() - t0;
return result;
}
// Clean stale db
try { rmSync(tmpDir + "/apps/api/data", { recursive: true, force: true }); } catch {}
// 2. Install
console.log(" npm install...");
try {
execSync("npm install --no-audit --no-fund", { cwd: tmpDir, encoding: "utf-8", timeout: 180000, stdio: "pipe" });
console.log(" ✅ npm install");
} catch (e) {
result.errors.push(`npm install: ${(e.stderr || "").slice(0, 200)}`);
console.log(" ❌ npm install failed");
result.timeMs = Date.now() - t0;
return result;
}
// 3. Build (tsc)
console.log(" tsc...");
try {
execSync("npx tsc --noEmit", { cwd: tmpDir + "/apps/api", encoding: "utf-8", timeout: 60000, stdio: "pipe" });
result.build = true;
console.log(" ✅ tsc");
} catch (e) {
result.errors.push(`tsc: ${(e.stdout || e.stderr || "").slice(0, 200)}`);
console.log(" ❌ tsc failed");
result.timeMs = Date.now() - t0;
return result;
}
// 4. Server + API
const port = 4100 + i;
const proc = spawn("npx", ["tsx", "src/index.ts"], {
cwd: tmpDir + "/apps/api",
env: { ...process.env, JWT_SECRET: `bench-${i}`, DATABASE_URL: ":memory:", PORT: String(port) },
stdio: "pipe",
});
try {
await waitForServer(`http://localhost:${port}/api/health`);
console.log(" ✅ Server started");
} catch (e) {
result.errors.push(`Server start: ${e.message}`);
console.log(" ❌ Server failed to start");
proc.kill("SIGTERM");
result.timeMs = Date.now() - t0;
return result;
}
result.health = true;
// Auth
const username = `u${Date.now()}`;
try {
const reg = await httpPost(`http://localhost:${port}/api/auth/register`, { username, password: "123456" });
if (!reg.token || !reg.user?.id) throw new Error(`malformed: ${JSON.stringify(reg).slice(0, 100)}`);
const token = reg.token;
const userId = reg.user.id;
result.register = true;
console.log(" ✅ Register");
// Login
try {
const login = await httpPost(`http://localhost:${port}/api/auth/login`, { username, password: "123456" });
if (login.token) result.login = true;
console.log(" ✅ Login");
} catch (e) {
result.errors.push(`Login: ${e.message}`);
console.log(" ❌ Login");
}
// CRUD — find resource endpoint with POST
const resource = (domain.arch.apiDesign || [])[0];
const crudPath = resource?.basePath || "";
const base = `http://localhost:${port}${crudPath}`;
// Build create payload: FK→userId, required fields→sample
const table = (domain.arch.databaseSchema || [])[0];
const payload = {};
for (const f of (table?.fields || [])) {
if (f.name === "id") continue;
const fname = f.name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
if (f.constraints?.includes("FK")) {
payload[fname] = userId;
} else if (f.constraints?.includes("NOT NULL")) {
if (f.type?.toUpperCase().includes("INT") || f.type?.toUpperCase().includes("REAL")) payload[fname] = 42;
else if (f.type?.toUpperCase().includes("TEXT")) payload[fname] = `bench-${domain.name}`;
}
}
// Create
let createdId;
try {
const create = await httpPost(base, payload, token);
createdId = create.data?.id;
if (createdId) {
result.create = true;
console.log(" ✅ Create");
} else {
result.errors.push(`Create: no id in ${JSON.stringify(create).slice(0, 200)}`);
console.log(" ❌ Create: no id");
}
} catch (e) {
result.errors.push(`Create: ${e.message}`);
console.log(" ❌ Create");
}
// Read
if (createdId) {
try {
const list = await httpGet(base, token);
if (list.data?.length > 0) result.read = true;
console.log(` ${result.read ? "✅" : "❌"} Read`);
} catch (e) {
result.errors.push(`Read: ${e.message}`);
console.log(" ❌ Read");
}
// Update
try {
const upd = await httpPut(`${base}/${createdId}`, { ...payload }, token);
if (upd.data?.id) result.update = true;
console.log(` ${result.update ? "✅" : "❌"} Update`);
// Delete
try {
const del = await httpRequest("DELETE", `${base}/${createdId}`, null, token);
if (del >= 200 && del < 300) result.delete = true;
console.log(` ${result.delete ? "✅" : "❌"} Delete (HTTP ${del})`);
} catch (e) {
result.errors.push(`Delete: ${e.message}`);
console.log(" ❌ Delete");
}
} catch (e) {
// PUT failed but try DELETE anyway with POST (some frameworks lack PUT)
try {
const del = await httpRequest("DELETE", `${base}/${createdId}`, null, token);
if (del >= 200 && del < 300) result.delete = true;
console.log(` ${result.delete ? "✅" : "❌"} Delete (HTTP ${del})`);
} catch (e2) {
result.errors.push(`Update/Delete: ${e.message}; ${e2.message}`);
}
}
}
} catch (e) {
result.errors.push(`Auth: ${e.message}`);
console.log(" ❌ Auth failed");
}
proc.kill("SIGTERM");
result.timeMs = Date.now() - t0;
return result;
}
function httpRequest(method, url, body, token) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const headers = {};
if (body) {
const p = JSON.stringify(body);
headers["Content-Type"] = "application/json";
headers["Content-Length"] = p.length.toString();
}
if (token) headers["Authorization"] = `Bearer ${token}`;
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method, headers, timeout: 10000 }, (res) => {
let d = ""; res.on("data", c => d += c); res.on("end", () => resolve(res.statusCode));
});
req.on("error", reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
// --- Report ---
function generateReport(results, totalMs) {
const pass = r => r.build && r.create && r.read && r.update && r.delete;
const passed = results.filter(pass);
const lines = [];
lines.push("# SF-05 Domain Benchmark Report");
lines.push("");
lines.push(`> **Generated:** ${new Date().toISOString()}`);
lines.push(`> **Domains Tested:** ${results.length}`);
lines.push(`> **Total Time:** ${(totalMs / 1000).toFixed(1)}s`);
lines.push("");
lines.push("## Summary");
lines.push("");
lines.push(`| Metric | Value |`);
lines.push(`|--------|-------|`);
lines.push(`| Total Domains | ${results.length} |`);
lines.push(`| Build Pass | ${results.filter(r => r.build).length}/${results.length} |`);
lines.push(`| CRUD Pass | ${passed.length}/${results.length} |`);
lines.push(`| PASS RATE | **${((passed.length / results.length) * 100).toFixed(0)}%** |`);
lines.push(`| Avg Files | ${(results.reduce((s, r) => s + r.files, 0) / results.length).toFixed(0)} |`);
lines.push("");
lines.push("## Domain Results");
lines.push("");
lines.push(`| # | Domain | Files | Build | Health | Register | Login | Create | Read | Update | Delete | Result |`);
lines.push(`|---|--------|-------|-------|--------|----------|-------|--------|------|--------|--------|--------|`);
for (let i = 0; i < results.length; i++) {
const r = results[i];
const ik = v => v ? "✅" : "❌";
const status = pass(r) ? "✅ PASS" : r.build ? "⚠️ PARTIAL" : "❌ FAIL";
lines.push(`| ${i + 1} | ${r.name} | ${r.files} | ${ik(r.build)} | ${ik(r.health)} | ${ik(r.register)} | ${ik(r.login)} | ${ik(r.create)} | ${ik(r.read)} | ${ik(r.update)} | ${ik(r.delete)} | ${status} |`);
}
lines.push("");
// Failures
const failed = results.filter(r => !pass(r));
if (failed.length > 0) {
lines.push("## Failure Analysis");
lines.push("");
for (const r of failed) {
lines.push(`### ${r.name} (${r.label})`);
for (const e of r.errors) lines.push(`- ${e}`);
lines.push("");
}
}
lines.push("## ✅ Passed");
if (passed.length > 0) {
for (const r of passed) lines.push(`- **${r.name}** (${r.label}) — ${r.files} files, ${(r.timeMs / 1000).toFixed(1)}s`);
} else {
lines.push("None — all domains have issues.");
}
lines.push("");
lines.push("## Required Fixes");
const errorCats = {};
for (const r of failed) for (const e of r.errors) {
const cat = e.split(":")[0];
errorCats[cat] = (errorCats[cat] || 0) + 1;
}
for (const [cat, count] of Object.entries(errorCats).sort((a, b) => b[1] - a[1])) {
lines.push(`- **${cat}**: ${count} occurrence(s)`);
}
return lines.join("\n");
}
// --- Main ---
async function main() {
console.log("=".repeat(60));
console.log("SF-05 Domain Benchmark Suite");
console.log("=".repeat(60));
const t0 = Date.now();
const results = [];
// Clear temp
rmSync(BENCH_DIR, { recursive: true, force: true });
for (let i = 0; i < DOMAINS.length; i++) {
const r = await runDomain(i, DOMAINS[i]);
results.push(r);
}
const elapsed = Date.now() - t0;
const report = generateReport(results, elapsed);
writeFileSync(resolve(WORKSPACE, "benchmark-report.md"), report);
console.log(`\n${"=".repeat(60)}`);
console.log(`📊 Report: benchmark-report.md`);
console.log(`${"=".repeat(60)}`);
console.log(report);
}
main().catch(err => { console.error("FAIL:", err); process.exit(1); });