#!/usr/bin/env npx tsx /** * Standalone org chart comparison generator — pure SVG (no Playwright). * * Generates SVG files for all 5 styles × 3 org sizes, plus a comparison HTML page. * Uses the server-side SVG renderer directly — same code that powers the routes. * * Usage: * npx tsx scripts/generate-org-chart-satori-comparison.ts * * Output: tmp/org-chart-svg-comparison/ */ import * as fs from "fs"; import * as path from "path"; import { renderOrgChartSvg, renderOrgChartPng, type OrgNode, type OrgChartStyle, ORG_CHART_STYLES, } from "../server/src/routes/org-chart-svg.js"; // ── Sample org data ────────────────────────────────────────────── const ORGS: Record = { sm: { id: "ceo", name: "CEO", role: "Chief Executive", status: "active", reports: [ { id: "eng1", name: "Engineer", role: "Engineering", status: "active", reports: [] }, { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, ], }, med: { id: "ceo", name: "CEO", role: "Chief Executive", status: "active", reports: [ { id: "cto", name: "CTO", role: "Technology", status: "active", reports: [ { id: "eng1", name: "ClaudeCoder", role: "Engineering", status: "active", reports: [] }, { id: "eng2", name: "CodexCoder", role: "Engineering", status: "active", reports: [] }, { id: "eng3", name: "SparkCoder", role: "Engineering", status: "active", reports: [] }, { id: "eng4", name: "CursorCoder", role: "Engineering", status: "active", reports: [] }, { id: "qa1", name: "QA", role: "Quality", status: "active", reports: [] }, ], }, { id: "cmo", name: "CMO", role: "Marketing", status: "active", reports: [ { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, ], }, ], }, lg: { id: "ceo", name: "CEO", role: "Chief Executive", status: "active", reports: [ { id: "cto", name: "CTO", role: "Technology", status: "active", reports: [ { id: "eng1", name: "Eng 1", role: "Engineering", status: "active", reports: [] }, { id: "eng2", name: "Eng 2", role: "Engineering", status: "active", reports: [] }, { id: "eng3", name: "Eng 3", role: "Engineering", status: "active", reports: [] }, { id: "qa1", name: "QA", role: "Quality", status: "active", reports: [] }, ], }, { id: "cmo", name: "CMO", role: "Marketing", status: "active", reports: [ { id: "des1", name: "Designer", role: "Design", status: "active", reports: [] }, { id: "wrt1", name: "Content", role: "Engineering", status: "active", reports: [] }, ], }, { id: "cfo", name: "CFO", role: "Finance", status: "active", reports: [ { id: "fin1", name: "Analyst", role: "Finance", status: "active", reports: [] }, ], }, { id: "coo", name: "COO", role: "Operations", status: "active", reports: [ { id: "ops1", name: "Ops 1", role: "Operations", status: "active", reports: [] }, { id: "ops2", name: "Ops 2", role: "Operations", status: "active", reports: [] }, { id: "devops1", name: "DevOps", role: "Operations", status: "active", reports: [] }, ], }, ], }, }; const STYLE_META: Record = { monochrome: { name: "Monochrome", vibe: "Vercel — zero color noise, dark", bestFor: "GitHub READMEs, developer docs" }, nebula: { name: "Nebula", vibe: "Glassmorphism — cosmic gradient", bestFor: "Hero sections, marketing" }, circuit: { name: "Circuit", vibe: "Linear/Raycast — indigo traces", bestFor: "Product pages, dev tools" }, warmth: { name: "Warmth", vibe: "Airbnb — light, colored avatars", bestFor: "Light-mode READMEs, presentations" }, schematic: { name: "Schematic", vibe: "Blueprint — grid bg, monospace", bestFor: "Technical docs, infra diagrams" }, }; // ── Main ───────────────────────────────────────────────────────── async function main() { const outDir = path.resolve("tmp/org-chart-svg-comparison"); fs.mkdirSync(outDir, { recursive: true }); const sizes = ["sm", "med", "lg"] as const; const results: string[] = []; for (const style of ORG_CHART_STYLES) { for (const size of sizes) { const svg = renderOrgChartSvg([ORGS[size]], style); const svgFile = `${style}-${size}.svg`; fs.writeFileSync(path.join(outDir, svgFile), svg); results.push(svgFile); console.log(` ✓ ${svgFile}`); // Also generate PNG try { const png = await renderOrgChartPng([ORGS[size]], style); const pngFile = `${style}-${size}.png`; fs.writeFileSync(path.join(outDir, pngFile), png); results.push(pngFile); console.log(` ✓ ${pngFile}`); } catch (e) { console.log(` ⚠ PNG failed for ${style}-${size}: ${(e as Error).message}`); } } } // Build comparison HTML let html = ` Org Chart Style Comparison — Pure SVG (No Playwright)

Org Chart Export — Style Comparison

5 styles × 3 org sizes. Pure SVG — no Playwright, no Satori, no browser needed.

Server-side compatible — works on any route
`; for (const style of ORG_CHART_STYLES) { const meta = STYLE_META[style]; html += `

${meta.name}

${meta.vibe} — Best for: ${meta.bestFor}
Small / Medium / Large
3 agents
8 agents
14 agents
`; } html += `

Why Pure SVG instead of Satori?

Satori converts JSX → SVG using Yoga (flexbox). It's great for OG cards but has limitations for org charts: no ::before/::after pseudo-elements, no CSS grid, limited gradient support, and connector lines between nodes would need post-processing.

Pure SVG rendering (what we're using here) gives us full control over layout, connectors, gradients, filters, and patterns — with zero runtime dependencies beyond sharp for PNG. It runs on any Node.js route, generates in <10ms, and produces identical output every time.

Routes: GET /api/companies/:id/org.svg?style=monochrome and GET /api/companies/:id/org.png?style=circuit

`; fs.writeFileSync(path.join(outDir, "comparison.html"), html); console.log(`\n✓ All done! ${results.length} files generated.`); console.log(` Open: tmp/org-chart-svg-comparison/comparison.html`); } main().catch((e) => { console.error(e); process.exit(1); });