diff --git a/packages/diagrams/src/architecture.ts b/packages/diagrams/src/architecture.ts new file mode 100644 index 0000000..408c073 --- /dev/null +++ b/packages/diagrams/src/architecture.ts @@ -0,0 +1,49 @@ +import type { ModuleNode, DependencyEdge } from "@codeboard/shared"; + +function sanitizeId(name: string): string { + return name.replace(/[^a-zA-Z0-9]/g, "_"); +} + +function truncateLabel(name: string, max = 20): string { + return name.length > max ? name.slice(0, max - 1) + "\u2026" : name; +} + +export function generateArchitectureDiagram( + modules: ModuleNode[], + deps: DependencyEdge[] +): string { + if (modules.length === 0) { + return "flowchart TD\n empty[No modules detected]"; + } + + const lines: string[] = ["flowchart TD"]; + + const moduleIds = new Map(); + for (const mod of modules) { + const id = sanitizeId(mod.name); + moduleIds.set(mod.path, id); + const fileCount = mod.files.length; + lines.push(` ${id}["${truncateLabel(mod.name)}\\n${fileCount} files"]`); + } + + const edgeSet = new Set(); + for (const dep of deps) { + const sourceModule = modules.find((m) => m.files.includes(dep.source)); + const targetModule = modules.find((m) => m.files.includes(dep.target)); + + if (!sourceModule || !targetModule) continue; + if (sourceModule.path === targetModule.path) continue; + + const sourceId = moduleIds.get(sourceModule.path); + const targetId = moduleIds.get(targetModule.path); + if (!sourceId || !targetId) continue; + + const edgeKey = `${sourceId}-${targetId}`; + if (edgeSet.has(edgeKey)) continue; + edgeSet.add(edgeKey); + + lines.push(` ${sourceId} --> ${targetId}`); + } + + return lines.join("\n"); +}