@wtdlee/repomap 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Kyuhwan Lee
3
+ Copyright (c) 2025 wtdlee
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">@wtdlee/repomap</h1>
6
6
 
7
7
  <p align="center">
8
- <a href="https://www.npmjs.com/package/@wtdlee/repomap"><img src="https://badge.fury.io/js/@wtdlee%2Frepomap.svg" alt="npm version"></a>
8
+ <a href="https://www.npmjs.com/package/@wtdlee/repomap"><img src="https://img.shields.io/npm/v/@wtdlee/repomap.svg" alt="npm version"></a>
9
9
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
10
10
  </p>
11
11
 
@@ -1 +1 @@
1
- export{f as ALL_GRAPHQL_HOOKS,a as BaseAnalyzer,B as DataFlowAnalyzer,h as GRAPHQL_INDICATORS,d as GRAPHQL_MUTATION_HOOKS,e as GRAPHQL_OTHER_HOOKS,c as GRAPHQL_QUERY_HOOKS,A as GraphQLAnalyzer,g as HOOK_TYPE_MAP,z as PagesAnalyzer,C as RestApiAnalyzer,o as cleanOperationName,s as extractGraphQLContext,x as extractGraphQLOperationsFromFile,t as extractOperationNameFromGqlCall,u as extractOperationNameFromTemplate,q as getCalleeName,y as getHookInfoString,m as getHookType,v as hasGraphQLArgument,n as hasGraphQLIndicators,l as isGraphQLHook,j as isMutationHook,i as isQueryHook,k as isSubscriptionHook,p as parseToAst,w as resolveOperationName,r as traverseAst}from'../chunk-IGRCLYVZ.js';import'../chunk-ZWRDP37E.js';
1
+ export{f as ALL_GRAPHQL_HOOKS,a as BaseAnalyzer,B as DataFlowAnalyzer,h as GRAPHQL_INDICATORS,d as GRAPHQL_MUTATION_HOOKS,e as GRAPHQL_OTHER_HOOKS,c as GRAPHQL_QUERY_HOOKS,A as GraphQLAnalyzer,g as HOOK_TYPE_MAP,z as PagesAnalyzer,C as RestApiAnalyzer,o as cleanOperationName,s as extractGraphQLContext,x as extractGraphQLOperationsFromFile,t as extractOperationNameFromGqlCall,u as extractOperationNameFromTemplate,q as getCalleeName,y as getHookInfoString,m as getHookType,v as hasGraphQLArgument,n as hasGraphQLIndicators,l as isGraphQLHook,j as isMutationHook,i as isQueryHook,k as isSubscriptionHook,p as parseToAst,w as resolveOperationName,r as traverseAst}from'../chunk-O2SKKQVN.js';
@@ -1,4 +1,4 @@
1
- import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE3.js';import {C,B,A,z,b as b$1}from'./chunk-IGRCLYVZ.js';import {a,b}from'./chunk-QBSB6BIU.js';import {a as a$1}from'./chunk-ATRSGO6O.js';import {k}from'./chunk-H7VVRHQZ.js';import {simpleGit}from'simple-git';import*as L from'fs/promises';import*as M from'path';import {parseSync}from'@swc/core';import fe from'express';import {Server}from'socket.io';import*as ge from'http';import {marked}from'marked';import*as me from'net';var W=class{config;mermaidGenerator;markdownGenerator;constructor(s){this.config=s,this.mermaidGenerator=new a,this.markdownGenerator=new b;}async generate(){let s=[];for(let k of this.config.repositories)try{let x=await this.analyzeRepository(k);s.push(x);}catch(x){console.error(`\u274C ${k.name}: ${x.message}`);}let r=this.analyzeCrossRepo(s),e=s.map(k=>k.analysis),a=this.extractCrossRepoLinks(e),o=this.mermaidGenerator.generateAll(e,a),i={generatedAt:new Date().toISOString(),repositories:s,crossRepoAnalysis:r,diagrams:o};return await this.writeDocumentation(i),i}async analyzeRepository(s){let{version:r,commitHash:e}=await this.getRepoInfo(s),a=s.analyzers.map(b=>this.createAnalyzer(b,s)).filter(b=>b!==null),o=Date.now(),i=await Promise.all(a.map(b=>b.analyze())),k=((Date.now()-o)/1e3).toFixed(1);console.log(` Analyzed ${s.displayName} in ${k}s`);let x=this.mergeAnalysisResults(i,s.name,r,e);await this.enrichPagesWithHookGraphQL(x,s.path);let $={totalPages:x.pages.length,totalComponents:x.components.length,totalGraphQLOperations:x.graphqlOperations.length,totalDataFlows:x.dataFlows.length,authRequiredPages:x.pages.filter(b=>b.authentication.required).length,publicPages:x.pages.filter(b=>!b.authentication.required).length};return {name:s.name,displayName:s.displayName,version:r,commitHash:e,analysis:x,summary:$}}async getRepoInfo(s){try{let a=(await simpleGit(s.path).log({n:1})).latest?.hash||"unknown",o="unknown";try{let i=M.join(s.path,"package.json");o=JSON.parse(await L.readFile(i,"utf-8")).version||"unknown";}catch{}return {version:o,commitHash:a}}catch{return {version:"unknown",commitHash:"unknown"}}}createAnalyzer(s,r){switch(s){case "pages":if(r.type==="nextjs"||r.type==="rails"||r.type==="generic")return new z(r);break;case "graphql":return new A(r);case "dataflow":case "components":return new B(r);case "rest-api":case "api":return new C(r)}return null}mergeAnalysisResults(s,r,e,a){let o={repository:r,timestamp:new Date().toISOString(),version:e,commitHash:a,coverage:{tsFilesScanned:0,tsParseFailures:0,graphqlParseFailures:0,codegenFilesDetected:0,codegenFilesParsed:0,codegenExportsFound:0},pages:[],graphqlOperations:[],apiCalls:[],components:[],dataFlows:[],apiEndpoints:[],models:[],crossRepoLinks:[]};for(let i of s)i.coverage&&o.coverage&&(o.coverage.tsFilesScanned+=i.coverage.tsFilesScanned||0,o.coverage.tsParseFailures+=i.coverage.tsParseFailures||0,o.coverage.graphqlParseFailures+=i.coverage.graphqlParseFailures||0,o.coverage.codegenFilesDetected+=i.coverage.codegenFilesDetected||0,o.coverage.codegenFilesParsed+=i.coverage.codegenFilesParsed||0,o.coverage.codegenExportsFound+=i.coverage.codegenExportsFound||0),i.pages&&o.pages.push(...i.pages),i.graphqlOperations&&o.graphqlOperations.push(...i.graphqlOperations),i.apiCalls&&o.apiCalls.push(...i.apiCalls),i.components&&o.components.push(...i.components),i.dataFlows&&o.dataFlows.push(...i.dataFlows),i.apiEndpoints&&o.apiEndpoints.push(...i.apiEndpoints),i.models&&o.models.push(...i.models),i.crossRepoLinks&&o.crossRepoLinks.push(...i.crossRepoLinks);return o}analyzeCrossRepo(s){let r=[],e=[],a=[],o=[],i=new Map;for(let $ of s)for(let b of $.analysis.graphqlOperations){let E=i.get(b.name)||[];E.push($.name),i.set(b.name,E);}for(let[$,b]of i)b.length>1&&r.push($);let k=s.filter($=>$.analysis.pages.length>0),x=s.filter($=>$.analysis.apiEndpoints.length>0);for(let $ of k)for(let b of x)for(let E of b.analysis.apiEndpoints)e.push({frontend:$.name,backend:b.name,endpoint:E.path,operations:$.analysis.graphqlOperations.filter(N=>N.usedIn.length>0).map(N=>N.name)});return {sharedTypes:r,apiConnections:e,navigationFlows:a,dataFlowAcrossRepos:o}}extractCrossRepoLinks(s){let r=[],e=new Map;for(let a of s)for(let o of a.graphqlOperations){let i=e.get(o.name)||[];i.push(a),e.set(o.name,i);}for(let[a,o]of e)o.length>1&&r.push({sourceRepo:o[0].repository,sourcePath:`graphql/${a}`,targetRepo:o[1].repository,targetPath:`graphql/${a}`,linkType:"graphql-operation",description:`Shared GraphQL operation: ${a}`});return r}async enrichPagesWithHookGraphQL(s,r){let e=s.graphqlOperations.filter(n=>n.type==="query"||n.type==="mutation"||n.type==="subscription"),a=/\.(ts|tsx|js|jsx)$/,o=n=>M.normalize(n).replace(/\\/g,"/"),i=(this.config.analysis?.include||["**/*.ts","**/*.tsx"]).map(String),k=(this.config.analysis?.exclude||[]).map(String),x=(await import('fast-glob')).default,$=await x(i,{cwd:r,ignore:["**/node_modules/**","**/.next/**","**/dist/**","**/build/**","**/coverage/**",...k],onlyFiles:true,unique:true,dot:false}),b=new Set($.map(o)),E=new Map;for(let n of b){let t=n.replace(a,"");E.has(t)||E.set(t,n);}let N=new b$1(r,b),Z=new Set(["src/"]);for(let n of b){let t=n.indexOf("/src/");t!==-1&&Z.add(n.slice(0,t+5));}let O=n=>{let t=o(n).replace(a,""),p=E.get(t);if(p)return p;let c=E.get(t+"/index");return c||null},ye=async()=>{let n=["tsconfig.json","jsconfig.json"];for(let t of n)try{let p=await L.readFile(M.join(r,t),"utf-8"),l=JSON.parse(p)?.compilerOptions||{},d=typeof l.baseUrl=="string"?l.baseUrl:void 0,R=typeof l.paths=="object"&&l.paths?l.paths:void 0;return {baseUrl:d,paths:R}}catch{}return {}},{baseUrl:F,paths:V}=await ye(),ve=n=>{if(!V)return [];let t=[];for(let[p,c]of Object.entries(V)){if(!p.includes("*")){n===p&&t.push(...c);continue}let[l,d]=p.split("*");if(!n.startsWith(l)||!n.endsWith(d))continue;let R=n.slice(l.length,n.length-d.length);for(let w of c)w.includes("*")?t.push(w.replace("*",R)):t.push(w);}return t},B=(n,t)=>{if(!t)return null;let p=N.resolve(n,t);if(p)return p.file;if(t.startsWith(".")){let l=M.dirname(n);return O(M.join(l,t))}if(t.startsWith("@/")){let l=t.replace("@/","");if(F){let d=O(M.join(F,l));if(d)return d}for(let d of Z){let R=O(d+l);if(R)return R}return null}let c=ve(t);if(c.length>0)for(let l of c){let d=O(F?M.join(F,l):l);if(d)return d}if(F){let l=O(M.join(F,t));if(l)return l}return null},j=new Map,Y=new Map,Q=async n=>{let t=o(n),p=Y.get(t);if(p!==void 0)return p;try{let c=M.join(r,t),l=await L.readFile(c,"utf-8");return Y.set(t,l),l}catch{return Y.set(t,""),null}},we=async n=>{let t=o(n),p=j.get(t);if(p)return p.map(h=>({spec:h,names:null}));let c=await Q(t);if(!c)return j.set(t,[]),[];let l;try{let h=t.endsWith(".ts")||t.endsWith(".tsx"),g=t.endsWith(".tsx")||t.endsWith(".jsx");l=parseSync(c,{syntax:h?"typescript":"ecmascript",tsx:g,jsx:g,comments:!1});}catch{return j.set(t,[]),[]}let d=new Set,R=[],w=(h,g,m)=>{typeof h!="string"||h.length===0||(d.add(h),R.push({spec:h,names:g,pos:m}));},f=h=>{if(!h||typeof h!="object")return;let g=h,m=g;if(m.type==="ImportDeclaration"){if(!m.typeOnly){let v=m.source?.value,u=[],y=m.specifiers||[];for(let C of y){let q=C,D=q.type;if(D==="ImportDefaultSpecifier"||D==="ImportNamespaceSpecifier"){u=null;break}if(D==="ImportSpecifier"){let G=q.imported?.value,P=q.local?.value,z=G||P;z&&u.push(z);}}Array.isArray(u)&&u.length===0&&(u=null),w(v,u,m.span?.start);}}else if(m.type==="ExportAllDeclaration")w(m.source?.value,null,m.span?.start);else if(m.type==="ExportNamedDeclaration"){let v=m.source?.value,u=m.specifiers||[],y=[];for(let C of u){let q=C;if(q.type==="ExportSpecifier"){let G=q.exported?.value,P=q.orig?.value,z=G||P;z&&y.push(z);}}w(v,y.length>0?y:null,m.span?.start);}else if(m.type==="CallExpression"){let v=m.callee||null;if(v?.type==="Identifier"&&v.value==="require"){let u=m.arguments?.[0]?.expression;u?.type==="StringLiteral"&&w(u.value,null,m.span?.start);}if(v?.type==="Import"){let u=m.arguments?.[0]?.expression;u?.type==="StringLiteral"&&w(u.value,null,m.span?.start);}}for(let v of Object.keys(g)){let u=g[v];if(Array.isArray(u))for(let y of u)f(y);else u&&typeof u=="object"&&f(u);}};f(l);let A=Array.from(d);return j.set(t,A),R.filter(h=>typeof h.spec=="string"&&h.spec.length>0)},xe=(n,t)=>{if(t===void 0||t<0)return;let p=1;for(let c=0;c<n.length&&c<t;c++)n.charCodeAt(c)===10&&p++;return p},be=(n,t)=>{if(t<0)return;let p=1;for(let c=0;c<n.length&&c<t;c++)n.charCodeAt(c)===10&&p++;return p},H=new Map,_=async n=>{let t=o(n),p=H.get(t);if(p)return p;let c=await Q(t);if(!c){let h={named:new Map,stars:[],isBarrel:false};return H.set(t,h),h}let l;try{let h=t.endsWith(".ts")||t.endsWith(".tsx"),g=t.endsWith(".tsx")||t.endsWith(".jsx");l=parseSync(c,{syntax:h?"typescript":"ecmascript",tsx:g,jsx:g,comments:!1});}catch{let h={named:new Map,stars:[],isBarrel:false};return H.set(t,h),h}let d=new Map,R=[],w=true,f=l?.body;if(Array.isArray(f))for(let h of f){let g=h,m=g.type;if(m&&m!=="ImportDeclaration"){if(m==="ExportAllDeclaration"){let v=g.source?.value;typeof v=="string"&&R.push(v);continue}if(m==="ExportNamedDeclaration"){let v=g.source?.value,u=g.specifiers||[];if(typeof v=="string"&&Array.isArray(u))for(let y of u){let C=y;if(C.type!=="ExportSpecifier")continue;let D=C.exported?.value,G=C.orig?.value,P=D||G;P&&d.set(P,v);}continue}w=false;}}else w=false;let A={named:d,stars:R,isBarrel:w};return H.set(t,A),A},ee=async(n,t,p)=>{let c=o(n);if(p.has(c))return null;p.add(c);let l=await _(c),d=l.named.get(t);if(d)return B(c,d);for(let R of l.stars){let w=B(c,R);if(!w)continue;let f=await ee(w,t,p);if(f)return f}return null},K=new Map;for(let n of e){let t=new Set;n.filePath&&t.add(n.filePath);for(let p of n.usedIn||[])t.add(p);for(let p of t){let c=K.get(p)||[];c.push({opName:n.name,opType:n.type}),K.set(p,c);}}let ke=n=>{if(!n)return null;let t=[`src/pages/${n}`,`pages/${n}`,`src/app/${n}`,`app/${n}`,`frontend/src/pages/${n}`,`frontend/src/app/${n}`,`app/javascript/pages/${n}`,`app/javascript/app/${n}`];for(let l of t){if(b.has(l))return l;let d=O(l);if(d)return d}let p=[],c=[`/pages/${n}`,`/app/${n}`,`/${n}`];for(let l of b)for(let d of c)if(l.endsWith(d)&&!(d.startsWith("/pages/")&&!l.includes("/pages/"))&&!(d.startsWith("/app/")&&!l.includes("/app/"))){p.push(l);break}return p.length===0?null:(p.sort((l,d)=>l.length-d.length),p[0])},J=new Map,U=new Map;for(let n of s.pages){let t=ke(n.filePath);if(!t)continue;let p=new Map,c=new Set,l=[{f:t,depth:0}],d=new Map,R=30,w=2e4;for(;l.length>0;){let f=l.shift();if(!f)break;if(c.has(f.f))continue;if(c.add(f.f),c.size>=w)break;let A=K.get(f.f);if(A)for(let g of A){let m=p.get(g.opName);(!m||f.depth<m.distance)&&p.set(g.opName,{opName:g.opName,opType:g.opType,sourceFile:f.f,distance:f.depth});}if(f.depth>=R)continue;let h=await we(f.f);for(let g of h){let m=B(f.f,g.spec);if(m){if(!c.has(m)&&(l.push({f:m,depth:f.depth+1}),!d.has(m))){let v=await Q(f.f),u=v?xe(v,g.pos):void 0,y=Array.isArray(g.names)&&g.names.length>0?`names:${g.names.join(",")}`:void 0;d.set(m,{from:f.f,spec:g.spec,line:u,detail:y});}if(Array.isArray(g.names)&&g.names.length>0){if((await _(m)).isBarrel)for(let u of g.names){let y=await ee(m,u,new Set);y&&!c.has(y)&&(l.push({f:y,depth:f.depth+2}),d.has(y)||d.set(y,{from:m,spec:`re-export:${u}`,line:void 0,detail:"barrel"}));}}else if(g.names===null){let v=await _(m);if(v.isBarrel){for(let u of v.stars){let y=B(m,u);y&&!c.has(y)&&l.push({f:y,depth:f.depth+2});}for(let u of v.named.values()){let y=B(m,u);y&&!c.has(y)&&l.push({f:y,depth:f.depth+2});}}}}}}J.set(n.path,{page:n,entryFile:t,parent:d,bestByOp:p});for(let f of c)U.set(f,(U.get(f)||0)+1);}let qe=J.size,Re=Array.from(U.values()).sort((n,t)=>n-t),$e=((n,t)=>{if(n.length===0)return 0;let p=Math.min(n.length-1,Math.max(0,Math.floor(n.length*t)));return n[p]})(Re,.9),Me=Math.max(10,$e),Te=Math.max(2,Math.floor(qe*.05));for(let{page:n,entryFile:t,parent:p,bestByOp:c}of J.values()){let l=new Set((n.dataFetching||[]).map(d=>d.operationName?.replace(/^[→\->\s]+/,"")||""));for(let{opName:d,opType:R,sourceFile:w,distance:f}of c.values()){if(l.has(d))continue;l.add(d);let A=U.get(w)||0,h;f===0?h=void 0:A>=Me?h=`common:${w}`:f<=2||A<=Te?h=`close:${w}`:h=`indirect:${w}`;let g=f===0||f<=2?"certain":h?.startsWith("common:")?"unknown":"likely",m=[];if(f>0&&w!==t){let u=[],y=w,C=new Set;for(;y!==t&&!C.has(y);){C.add(y);let q=p.get(y);if(!q)break;u.push({from:q.from,to:y,spec:q.spec,line:q.line,detail:q.detail}),y=q.from;}u.reverse();for(let q of u)m.push({kind:"import-edge",file:q.from,line:q.line,detail:`${q.spec} -> ${q.to}${q.detail?` (${q.detail})`:""}`});}let v=await Q(w);if(v){let u=v.indexOf(`${d}Document`)>=0?v.indexOf(`${d}Document`):v.indexOf(d),y=u>=0?be(v,u):void 0;m.push({kind:"operation-reference",file:w,line:y,detail:`ref:${d}`});}else m.push({kind:"operation-reference",file:w,detail:`ref:${d}`});n.dataFetching.push({type:R==="mutation"?"useMutation":R==="subscription"?"useSubscription":"useQuery",operationName:d,source:h,confidence:g,evidence:m.length>0?m:void 0});}}}async writeDocumentation(s){let r=this.config.outputDir;await L.mkdir(r,{recursive:true});let e=this.markdownGenerator.generateDocumentation(s);for(let[o,i]of e){let k=M.join(r,o),x=M.dirname(k);await L.mkdir(x,{recursive:true}),await L.writeFile(k,i,"utf-8");}let a=M.join(r,"report.json");await L.writeFile(a,JSON.stringify(s,null,2),"utf-8");}};function Ae(S){return new Promise(s=>{let r=me.createServer();r.once("error",e=>{e.code,s(false);}),r.once("listening",()=>{r.close(),s(true);}),r.listen(S);})}async function ue(S,s=10){for(let r=0;r<s;r++){let e=S+r;if(await Ae(e))return e}throw new Error(`No available port found between ${S} and ${S+s-1}`)}var he=class{config;port;app;server;io;engine;currentReport=null;envResult=null;railsAnalysis=null;constructor(s,r=3030){this.config=s,this.port=r,this.app=fe(),this.server=ge.createServer(this.app),this.io=new Server(this.server),this.engine=new W(s),this.setupRoutes(),this.setupSocketIO();}setupRoutes(){this.app.use("/assets",fe.static(M.join(this.config.outputDir,"assets"))),["common.css","page-map.css","docs.css","rails-map.css"].forEach(e=>{this.app.get(`/${e}`,async(a,o)=>{let i=[M.join(M.dirname(new URL(import.meta.url).pathname),"generators","assets",e),M.join(M.dirname(new URL(import.meta.url).pathname),"..","generators","assets",e),M.join(process.cwd(),"dist","generators","assets",e),M.join(process.cwd(),"src","generators","assets",e)];for(let k of i)try{let x=await L.readFile(k,"utf-8");o.type("text/css").send(x);return}catch{}o.status(404).send("CSS not found");});}),["favicon.ico","favicon.svg","favicon-96x96.png","apple-touch-icon.png","site.webmanifest","web-app-manifest-192x192.png","web-app-manifest-512x512.png"].forEach(e=>{(e==="favicon.ico"?[`/${e}`,`/favicon/${e}`]:[`/favicon/${e}`]).forEach(o=>{this.app.get(o,async(i,k)=>{let x=[M.join(M.dirname(new URL(import.meta.url).pathname),"generators","assets","favicon",e),M.join(M.dirname(new URL(import.meta.url).pathname),"..","generators","assets","favicon",e),M.join(process.cwd(),"dist","generators","assets","favicon",e),M.join(process.cwd(),"src","generators","assets","favicon",e)];for(let $ of x)try{let b=await L.readFile($),E=e.split(".").pop(),N={ico:"image/x-icon",svg:"image/svg+xml",png:"image/png",webmanifest:"application/manifest+json"};k.type(N[E||""]||"application/octet-stream").send(b);return}catch{}k.status(404).send("File not found");});});}),this.app.get("/",(e,a)=>{a.redirect("/page-map");}),this.app.get("/page-map",(e,a)=>{if(!this.currentReport){a.status(503).send("Documentation not ready yet");return}let o=new a$1;a.send(o.generatePageMapHtml(this.currentReport,{envResult:this.envResult,railsAnalysis:this.railsAnalysis}));}),this.app.get("/rails-map",(e,a)=>{if(!this.railsAnalysis){a.status(404).send("No Rails environment detected");return}let o=new a$2;a.send(o.generateFromResult(this.railsAnalysis));}),this.app.get("/docs",async(e,a)=>{a.send(await this.renderPage("index"));}),this.app.get("/docs/*path",async(e,a)=>{let o=e.params.path,i=Array.isArray(o)?o.join("/"):o||"index";a.send(await this.renderPage(i));}),this.app.get("/api/report",(e,a)=>{a.json(this.currentReport);}),this.app.get("/api/env",(e,a)=>{a.json(this.envResult);}),this.app.get("/api/rails",(e,a)=>{this.railsAnalysis?a.json(this.railsAnalysis):a.status(404).json({error:"No Rails analysis available"});}),this.app.get("/api/diagram/:name",(e,a)=>{let o=this.currentReport?.diagrams.find(i=>i.title.toLowerCase().replace(/\s+/g,"-")===e.params.name);o?a.json(o):a.status(404).json({error:"Diagram not found"});}),this.app.post("/api/regenerate",async(e,a)=>{try{await this.regenerate(),a.json({success:!0});}catch(o){a.status(500).json({error:o.message});}});}setupSocketIO(){this.io.on("connection",s=>{s.on("disconnect",()=>{});});}async renderPage(s){let r=s.replace(/\.md$/,""),e=M.join(this.config.outputDir,`${r}.md`),a="";try{let o=await L.readFile(e,"utf-8"),i=await marked.parse(o);i=i.replace(/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g,'<div class="mermaid">$1</div>'),i=i.replace(/<table>/g,'<div class="table-wrapper"><table>'),i=i.replace(/<\/table>/g,"</table></div>"),a=i;}catch(o){let i=o;if(i.code==="ENOENT"){let k=this.currentReport?.repositories.map(x=>x.name)||[];a=`
1
+ import {b as b$2,a as a$2}from'./chunk-DSYVU23K.js';import {a as a$3}from'./chunk-VV3A3UE3.js';import {C,B,A as A$1,z,b as b$1}from'./chunk-O2SKKQVN.js';import {a,b}from'./chunk-QBSB6BIU.js';import {a as a$1}from'./chunk-JBPSEUZS.js';import {k}from'./chunk-H7VVRHQZ.js';import {simpleGit}from'simple-git';import*as A from'fs/promises';import*as R from'path';import {parseSync}from'@swc/core';import he from'express';import {Server}from'socket.io';import*as ye from'http';import {marked}from'marked';import*as ue from'net';var W=class{config;mermaidGenerator;markdownGenerator;constructor(o){this.config=o,this.mermaidGenerator=new a,this.markdownGenerator=new b;}async generate(){let o=[];for(let k of this.config.repositories)try{let x=await this.analyzeRepository(k);o.push(x);}catch(x){console.error(`\u274C ${k.name}: ${x.message}`);}let r=this.analyzeCrossRepo(o),e=o.map(k=>k.analysis),a=this.extractCrossRepoLinks(e),s=this.mermaidGenerator.generateAll(e,a),i={generatedAt:new Date().toISOString(),repositories:o,crossRepoAnalysis:r,diagrams:s};return await this.writeDocumentation(i),i}async analyzeRepository(o){let{version:r,commitHash:e}=await this.getRepoInfo(o),a=o.analyzers.map(b=>this.createAnalyzer(b,o)).filter(b=>b!==null),s=Date.now(),i=await Promise.all(a.map(b=>b.analyze())),k=((Date.now()-s)/1e3).toFixed(1);console.log(` Analyzed ${o.displayName} in ${k}s`);let x=this.mergeAnalysisResults(i,o.name,r,e);await this.enrichPagesWithHookGraphQL(x,o.path);let T={totalPages:x.pages.length,totalComponents:x.components.length,totalGraphQLOperations:x.graphqlOperations.length,totalDataFlows:x.dataFlows.length,authRequiredPages:x.pages.filter(b=>b.authentication.required).length,publicPages:x.pages.filter(b=>!b.authentication.required).length};return {name:o.name,displayName:o.displayName,version:r,commitHash:e,analysis:x,summary:T}}async getRepoInfo(o){try{let a=(await simpleGit(o.path).log({n:1})).latest?.hash||"unknown",s="unknown";try{let i=R.join(o.path,"package.json");s=JSON.parse(await A.readFile(i,"utf-8")).version||"unknown";}catch{}return {version:s,commitHash:a}}catch{return {version:"unknown",commitHash:"unknown"}}}createAnalyzer(o,r){switch(o){case "pages":if(r.type==="nextjs"||r.type==="rails"||r.type==="generic")return new z(r);break;case "graphql":return new A$1(r);case "dataflow":case "components":return new B(r);case "rest-api":case "api":return new C(r)}return null}mergeAnalysisResults(o,r,e,a){let s={repository:r,timestamp:new Date().toISOString(),version:e,commitHash:a,coverage:{tsFilesScanned:0,tsParseFailures:0,graphqlParseFailures:0,codegenFilesDetected:0,codegenFilesParsed:0,codegenExportsFound:0},pages:[],graphqlOperations:[],apiCalls:[],components:[],dataFlows:[],apiEndpoints:[],models:[],crossRepoLinks:[]};for(let i of o)i.coverage&&s.coverage&&(s.coverage.tsFilesScanned+=i.coverage.tsFilesScanned||0,s.coverage.tsParseFailures+=i.coverage.tsParseFailures||0,s.coverage.graphqlParseFailures+=i.coverage.graphqlParseFailures||0,s.coverage.codegenFilesDetected+=i.coverage.codegenFilesDetected||0,s.coverage.codegenFilesParsed+=i.coverage.codegenFilesParsed||0,s.coverage.codegenExportsFound+=i.coverage.codegenExportsFound||0),i.pages&&s.pages.push(...i.pages),i.graphqlOperations&&s.graphqlOperations.push(...i.graphqlOperations),i.apiCalls&&s.apiCalls.push(...i.apiCalls),i.components&&s.components.push(...i.components),i.dataFlows&&s.dataFlows.push(...i.dataFlows),i.apiEndpoints&&s.apiEndpoints.push(...i.apiEndpoints),i.models&&s.models.push(...i.models),i.crossRepoLinks&&s.crossRepoLinks.push(...i.crossRepoLinks);return s}analyzeCrossRepo(o){let r=[],e=[],a=[],s=[],i=new Map;for(let T of o)for(let b of T.analysis.graphqlOperations){let $=i.get(b.name)||[];$.push(T.name),i.set(b.name,$);}for(let[T,b]of i)b.length>1&&r.push(T);let k=o.filter(T=>T.analysis.pages.length>0),x=o.filter(T=>T.analysis.apiEndpoints.length>0);for(let T of k)for(let b of x)for(let $ of b.analysis.apiEndpoints)e.push({frontend:T.name,backend:b.name,endpoint:$.path,operations:T.analysis.graphqlOperations.filter(N=>N.usedIn.length>0).map(N=>N.name)});return {sharedTypes:r,apiConnections:e,navigationFlows:a,dataFlowAcrossRepos:s}}extractCrossRepoLinks(o){let r=[],e=new Map;for(let a of o)for(let s of a.graphqlOperations){let i=e.get(s.name)||[];i.push(a),e.set(s.name,i);}for(let[a,s]of e)s.length>1&&r.push({sourceRepo:s[0].repository,sourcePath:`graphql/${a}`,targetRepo:s[1].repository,targetPath:`graphql/${a}`,linkType:"graphql-operation",description:`Shared GraphQL operation: ${a}`});return r}async enrichPagesWithHookGraphQL(o,r){let e=o.graphqlOperations.filter(n=>n.type==="query"||n.type==="mutation"||n.type==="subscription"),a=/\.(ts|tsx|js|jsx)$/,s=n=>R.normalize(n).replace(/\\/g,"/"),i=(this.config.analysis?.include||["**/*.ts","**/*.tsx"]).map(String),k=(this.config.analysis?.exclude||[]).map(String),x=(await import('fast-glob')).default,T=await x(i,{cwd:r,ignore:["**/node_modules/**","**/.next/**","**/dist/**","**/build/**","**/coverage/**",...k],onlyFiles:true,unique:true,dot:false}),b=new Set(T.map(s)),$=new Map;for(let n of b){let t=n.replace(a,"");$.has(t)||$.set(t,n);}let N=new b$1(r,b),Z=new Set(["src/"]);for(let n of b){let t=n.indexOf("/src/");t!==-1&&Z.add(n.slice(0,t+5));}let F=n=>{let t=s(n).replace(a,""),p=$.get(t);if(p)return p;let c=$.get(t+"/index");return c||null},ve=async()=>{let n=["tsconfig.json","jsconfig.json"];for(let t of n)try{let p=await A.readFile(R.join(r,t),"utf-8"),l=JSON.parse(p)?.compilerOptions||{},d=typeof l.baseUrl=="string"?l.baseUrl:void 0,q=typeof l.paths=="object"&&l.paths?l.paths:void 0;return {baseUrl:d,paths:q}}catch{}return {}},{baseUrl:D,paths:V}=await ve(),we=n=>{if(!V)return [];let t=[];for(let[p,c]of Object.entries(V)){if(!p.includes("*")){n===p&&t.push(...c);continue}let[l,d]=p.split("*");if(!n.startsWith(l)||!n.endsWith(d))continue;let q=n.slice(l.length,n.length-d.length);for(let w of c)w.includes("*")?t.push(w.replace("*",q)):t.push(w);}return t},I=(n,t)=>{if(!t)return null;let p=N.resolve(n,t);if(p)return p.file;if(t.startsWith(".")){let l=R.dirname(n);return F(R.join(l,t))}if(t.startsWith("@/")){let l=t.replace("@/","");if(D){let d=F(R.join(D,l));if(d)return d}for(let d of Z){let q=F(d+l);if(q)return q}return null}let c=we(t);if(c.length>0)for(let l of c){let d=F(D?R.join(D,l):l);if(d)return d}if(D){let l=F(R.join(D,t));if(l)return l}return null},j=new Map,_=new Map,Q=async n=>{let t=s(n),p=_.get(t);if(p!==void 0)return p;try{let c=R.join(r,t),l=await A.readFile(c,"utf-8");return _.set(t,l),l}catch{return _.set(t,""),null}},xe=async n=>{let t=s(n),p=j.get(t);if(p)return p.map(h=>({spec:h,names:null}));let c=await Q(t);if(!c)return j.set(t,[]),[];let l;try{let h=t.endsWith(".ts")||t.endsWith(".tsx"),g=t.endsWith(".tsx")||t.endsWith(".jsx");l=parseSync(c,{syntax:h?"typescript":"ecmascript",tsx:g,jsx:g,comments:!1});}catch{return j.set(t,[]),[]}let d=new Set,q=[],w=(h,g,m)=>{typeof h!="string"||h.length===0||(d.add(h),q.push({spec:h,names:g,pos:m}));},f=h=>{if(!h||typeof h!="object")return;let g=h,m=g;if(m.type==="ImportDeclaration"){if(!m.typeOnly){let v=m.source?.value,u=[],y=m.specifiers||[];for(let E of y){let M=E,B=M.type;if(B==="ImportDefaultSpecifier"||B==="ImportNamespaceSpecifier"){u=null;break}if(B==="ImportSpecifier"){let G=M.imported?.value,P=M.local?.value,z=G||P;z&&u.push(z);}}Array.isArray(u)&&u.length===0&&(u=null),w(v,u,m.span?.start);}}else if(m.type==="ExportAllDeclaration")w(m.source?.value,null,m.span?.start);else if(m.type==="ExportNamedDeclaration"){let v=m.source?.value,u=m.specifiers||[],y=[];for(let E of u){let M=E;if(M.type==="ExportSpecifier"){let G=M.exported?.value,P=M.orig?.value,z=G||P;z&&y.push(z);}}w(v,y.length>0?y:null,m.span?.start);}else if(m.type==="CallExpression"){let v=m.callee||null;if(v?.type==="Identifier"&&v.value==="require"){let u=m.arguments?.[0]?.expression;u?.type==="StringLiteral"&&w(u.value,null,m.span?.start);}if(v?.type==="Import"){let u=m.arguments?.[0]?.expression;u?.type==="StringLiteral"&&w(u.value,null,m.span?.start);}}for(let v of Object.keys(g)){let u=g[v];if(Array.isArray(u))for(let y of u)f(y);else u&&typeof u=="object"&&f(u);}};f(l);let C=Array.from(d);return j.set(t,C),q.filter(h=>typeof h.spec=="string"&&h.spec.length>0)},be=(n,t)=>{if(t===void 0||t<0)return;let p=1;for(let c=0;c<n.length&&c<t;c++)n.charCodeAt(c)===10&&p++;return p},ke=(n,t)=>{if(t<0)return;let p=1;for(let c=0;c<n.length&&c<t;c++)n.charCodeAt(c)===10&&p++;return p},H=new Map,Y=async n=>{let t=s(n),p=H.get(t);if(p)return p;let c=await Q(t);if(!c){let h={named:new Map,stars:[],isBarrel:false};return H.set(t,h),h}let l;try{let h=t.endsWith(".ts")||t.endsWith(".tsx"),g=t.endsWith(".tsx")||t.endsWith(".jsx");l=parseSync(c,{syntax:h?"typescript":"ecmascript",tsx:g,jsx:g,comments:!1});}catch{let h={named:new Map,stars:[],isBarrel:false};return H.set(t,h),h}let d=new Map,q=[],w=true,f=l?.body;if(Array.isArray(f))for(let h of f){let g=h,m=g.type;if(m&&m!=="ImportDeclaration"){if(m==="ExportAllDeclaration"){let v=g.source?.value;typeof v=="string"&&q.push(v);continue}if(m==="ExportNamedDeclaration"){let v=g.source?.value,u=g.specifiers||[];if(typeof v=="string"&&Array.isArray(u))for(let y of u){let E=y;if(E.type!=="ExportSpecifier")continue;let B=E.exported?.value,G=E.orig?.value,P=B||G;P&&d.set(P,v);}continue}w=false;}}else w=false;let C={named:d,stars:q,isBarrel:w};return H.set(t,C),C},ee=async(n,t,p)=>{let c=s(n);if(p.has(c))return null;p.add(c);let l=await Y(c),d=l.named.get(t);if(d)return I(c,d);for(let q of l.stars){let w=I(c,q);if(!w)continue;let f=await ee(w,t,p);if(f)return f}return null},X=new Map;for(let n of e){let t=new Set;n.filePath&&t.add(n.filePath);for(let p of n.usedIn||[])t.add(p);for(let p of t){let c=X.get(p)||[];c.push({opName:n.name,opType:n.type}),X.set(p,c);}}let Me=n=>{if(!n)return null;let t=[`src/pages/${n}`,`pages/${n}`,`src/app/${n}`,`app/${n}`,`frontend/src/pages/${n}`,`frontend/src/app/${n}`,`app/javascript/pages/${n}`,`app/javascript/app/${n}`];for(let l of t){if(b.has(l))return l;let d=F(l);if(d)return d}let p=[],c=[`/pages/${n}`,`/app/${n}`,`/${n}`];for(let l of b)for(let d of c)if(l.endsWith(d)&&!(d.startsWith("/pages/")&&!l.includes("/pages/"))&&!(d.startsWith("/app/")&&!l.includes("/app/"))){p.push(l);break}return p.length===0?null:(p.sort((l,d)=>l.length-d.length),p[0])},K=new Map,U=new Map;for(let n of o.pages){let t=Me(n.filePath);if(!t)continue;let p=new Map,c=new Set,l=[{f:t,depth:0}],d=new Map,q=30,w=2e4;for(;l.length>0;){let f=l.shift();if(!f)break;if(c.has(f.f))continue;if(c.add(f.f),c.size>=w)break;let C=X.get(f.f);if(C)for(let g of C){let m=p.get(g.opName);(!m||f.depth<m.distance)&&p.set(g.opName,{opName:g.opName,opType:g.opType,sourceFile:f.f,distance:f.depth});}if(f.depth>=q)continue;let h=await xe(f.f);for(let g of h){let m=I(f.f,g.spec);if(m){if(!c.has(m)&&(l.push({f:m,depth:f.depth+1}),!d.has(m))){let v=await Q(f.f),u=v?be(v,g.pos):void 0,y=Array.isArray(g.names)&&g.names.length>0?`names:${g.names.join(",")}`:void 0;d.set(m,{from:f.f,spec:g.spec,line:u,detail:y});}if(Array.isArray(g.names)&&g.names.length>0){if((await Y(m)).isBarrel)for(let u of g.names){let y=await ee(m,u,new Set);y&&!c.has(y)&&(l.push({f:y,depth:f.depth+2}),d.has(y)||d.set(y,{from:m,spec:`re-export:${u}`,line:void 0,detail:"barrel"}));}}else if(g.names===null){let v=await Y(m);if(v.isBarrel){for(let u of v.stars){let y=I(m,u);y&&!c.has(y)&&l.push({f:y,depth:f.depth+2});}for(let u of v.named.values()){let y=I(m,u);y&&!c.has(y)&&l.push({f:y,depth:f.depth+2});}}}}}}K.set(n.path,{page:n,entryFile:t,parent:d,bestByOp:p});for(let f of c)U.set(f,(U.get(f)||0)+1);}let Re=K.size,qe=Array.from(U.values()).sort((n,t)=>n-t),Te=((n,t)=>{if(n.length===0)return 0;let p=Math.min(n.length-1,Math.max(0,Math.floor(n.length*t)));return n[p]})(qe,.9),Se=Math.max(10,Te),$e=Math.max(2,Math.floor(Re*.05));for(let{page:n,entryFile:t,parent:p,bestByOp:c}of K.values()){let l=new Set((n.dataFetching||[]).map(d=>d.operationName?.replace(/^[→\->\s]+/,"")||""));for(let{opName:d,opType:q,sourceFile:w,distance:f}of c.values()){if(l.has(d))continue;l.add(d);let C=U.get(w)||0,h;f===0?h=void 0:C>=Se?h=`common:${w}`:f<=2||C<=$e?h=`close:${w}`:h=`indirect:${w}`;let g=f===0||f<=2?"certain":h?.startsWith("common:")?"unknown":"likely",m=[];if(f>0&&w!==t){let u=[],y=w,E=new Set;for(;y!==t&&!E.has(y);){E.add(y);let M=p.get(y);if(!M)break;u.push({from:M.from,to:y,spec:M.spec,line:M.line,detail:M.detail}),y=M.from;}u.reverse();for(let M of u)m.push({kind:"import-edge",file:M.from,line:M.line,detail:`${M.spec} -> ${M.to}${M.detail?` (${M.detail})`:""}`});}let v=await Q(w);if(v){let u=v.indexOf(`${d}Document`)>=0?v.indexOf(`${d}Document`):v.indexOf(d),y=u>=0?ke(v,u):void 0;m.push({kind:"operation-reference",file:w,line:y,detail:`ref:${d}`});}else m.push({kind:"operation-reference",file:w,detail:`ref:${d}`});n.dataFetching.push({type:q==="mutation"?"useMutation":q==="subscription"?"useSubscription":"useQuery",operationName:d,source:h,confidence:g,evidence:m.length>0?m:void 0});}}}async writeDocumentation(o){let r=this.config.outputDir;await A.mkdir(r,{recursive:true});let e=this.markdownGenerator.generateDocumentation(o);for(let[s,i]of e){let k=R.join(r,s),x=R.dirname(k);await A.mkdir(x,{recursive:true}),await A.writeFile(k,i,"utf-8");}let a=R.join(r,"report.json");await A.writeFile(a,JSON.stringify(o,null,2),"utf-8");}};function Ee(L){return new Promise(o=>{let r=ue.createServer();r.once("error",e=>{e.code,o(false);}),r.once("listening",()=>{r.close(),o(true);}),r.listen(L);})}async function fe(L,o=10){for(let r=0;r<o;r++){let e=L+r;if(await Ee(e))return e}throw new Error(`No available port found between ${L} and ${L+o-1}`)}var ge=class{config;port;app;server;io;engine;currentReport=null;envResult=null;railsAnalysis=null;constructor(o,r=3030){this.config=o,this.port=r,this.app=he(),this.server=ye.createServer(this.app),this.io=new Server(this.server),this.engine=new W(o),this.setupRoutes(),this.setupSocketIO();}setupRoutes(){this.app.use("/assets",he.static(R.join(this.config.outputDir,"assets"))),this.app.get("/icons.svg",async(e,a)=>{let s=[R.join(R.dirname(new URL(import.meta.url).pathname),"generators","assets","icons.svg"),R.join(R.dirname(new URL(import.meta.url).pathname),"..","generators","assets","icons.svg"),R.join(process.cwd(),"dist","generators","assets","icons.svg"),R.join(process.cwd(),"src","generators","assets","icons.svg")];for(let i of s)try{let k=await A.readFile(i);a.type("image/svg+xml").send(k);return}catch{}a.status(404).send("icons.svg not found");}),["common.css","page-map.css","docs.css","rails-map.css"].forEach(e=>{this.app.get(`/${e}`,async(a,s)=>{let i=[R.join(R.dirname(new URL(import.meta.url).pathname),"generators","assets",e),R.join(R.dirname(new URL(import.meta.url).pathname),"..","generators","assets",e),R.join(process.cwd(),"dist","generators","assets",e),R.join(process.cwd(),"src","generators","assets",e)];for(let k of i)try{let x=await A.readFile(k,"utf-8");s.type("text/css").send(x);return}catch{}s.status(404).send("CSS not found");});}),["favicon.ico","favicon.svg","favicon-96x96.png","apple-touch-icon.png","site.webmanifest","web-app-manifest-192x192.png","web-app-manifest-512x512.png"].forEach(e=>{(e==="favicon.ico"?[`/${e}`,`/favicon/${e}`]:[`/favicon/${e}`]).forEach(s=>{this.app.get(s,async(i,k)=>{let x=[R.join(R.dirname(new URL(import.meta.url).pathname),"generators","assets","favicon",e),R.join(R.dirname(new URL(import.meta.url).pathname),"..","generators","assets","favicon",e),R.join(process.cwd(),"dist","generators","assets","favicon",e),R.join(process.cwd(),"src","generators","assets","favicon",e)];for(let T of x)try{let b=await A.readFile(T),$=e.split(".").pop(),N={ico:"image/x-icon",svg:"image/svg+xml",png:"image/png",webmanifest:"application/manifest+json"};k.type(N[$||""]||"application/octet-stream").send(b);return}catch{}k.status(404).send("File not found");});});}),this.app.get("/",(e,a)=>{a.redirect("/page-map");}),this.app.get("/page-map",(e,a)=>{if(!this.currentReport){a.status(503).send("Documentation not ready yet");return}let s=new a$1;a.send(s.generatePageMapHtml(this.currentReport,{envResult:this.envResult,railsAnalysis:this.railsAnalysis}));}),this.app.get("/rails-map",(e,a)=>{if(!this.railsAnalysis){a.status(404).send("No Rails environment detected");return}let s=new b$2;a.send(s.generateFromResult(this.railsAnalysis));}),this.app.get("/docs",async(e,a)=>{a.send(await this.renderPage("index"));}),this.app.get("/docs/*path",async(e,a)=>{let s=e.params.path,i=Array.isArray(s)?s.join("/"):s||"index";a.send(await this.renderPage(i));}),this.app.get("/api/report",(e,a)=>{a.json(this.currentReport);}),this.app.get("/api/env",(e,a)=>{a.json(this.envResult);}),this.app.get("/api/rails",(e,a)=>{this.railsAnalysis?a.json(this.railsAnalysis):a.status(404).json({error:"No Rails analysis available"});}),this.app.get("/api/diagram/:name",(e,a)=>{let s=this.currentReport?.diagrams.find(i=>i.title.toLowerCase().replace(/\s+/g,"-")===e.params.name);s?a.json(s):a.status(404).json({error:"Diagram not found"});}),this.app.post("/api/regenerate",async(e,a)=>{try{await this.regenerate(),a.json({success:!0});}catch(s){a.status(500).json({error:s.message});}});}setupSocketIO(){this.io.on("connection",o=>{o.on("disconnect",()=>{});});}async renderPage(o){let r=o.replace(/\.md$/,""),e=R.join(this.config.outputDir,`${r}.md`),a="";try{let s=await A.readFile(e,"utf-8"),i=await marked.parse(s);i=i.replace(/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g,'<div class="mermaid">$1</div>'),i=i.replace(/<table>/g,'<div class="table-wrapper"><table>'),i=i.replace(/<\/table>/g,"</table></div>"),a=i;}catch(s){let i=s;if(i.code==="ENOENT"){let k=this.currentReport?.repositories.map(x=>x.name)||[];a=`
2
2
  <h1>Page not found</h1>
3
3
  <p>The requested path <code>${r}</code> does not exist.</p>
4
4
  ${k.length>0?`
@@ -6,7 +6,7 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
6
6
  <ul>${k.map(x=>`<li><a href="/docs/repos/${x}">${x}</a></li>`).join("")}</ul>
7
7
  `:""}
8
8
  <p><a href="/">\u2190 Back to home</a></p>
9
- `;}else console.error(`\u26A0\uFE0F Error reading ${e}: ${i.message}`),a=`<h1>Error</h1><p>Failed to load page: ${i.message}</p>`;}return this.getHtmlTemplate(a)}getGraphQLData(){if(!this.currentReport)return "[]";let s=[];for(let r of this.currentReport.repositories)for(let e of r.analysis?.graphqlOperations||[])s.push({name:e.name,type:e.type,returnType:e.returnType,variables:e.variables,fields:e.fields,usedIn:e.usedIn});return JSON.stringify(s)}getApiCallsData(){if(!this.currentReport)return "[]";let s=[];for(let r of this.currentReport.repositories)for(let e of r.analysis?.apiCalls||[])s.push({id:e.id,method:e.method,url:e.url,callType:e.callType,filePath:e.filePath,line:e.line,containingFunction:e.containingFunction,requiresAuth:e.requiresAuth});return JSON.stringify(s)}getHtmlTemplate(s){let r=this.getGraphQLData(),e=this.getApiCallsData();return `<!DOCTYPE html>
9
+ `;}else console.error(`\u26A0\uFE0F Error reading ${e}: ${i.message}`),a=`<h1>Error</h1><p>Failed to load page: ${i.message}</p>`;}return this.getHtmlTemplate(a)}getGraphQLData(){if(!this.currentReport)return "[]";let o=[];for(let r of this.currentReport.repositories)for(let e of r.analysis?.graphqlOperations||[])o.push({name:e.name,type:e.type,returnType:e.returnType,variables:e.variables,fields:e.fields,usedIn:e.usedIn});return JSON.stringify(o)}getApiCallsData(){if(!this.currentReport)return "[]";let o=[];for(let r of this.currentReport.repositories)for(let e of r.analysis?.apiCalls||[])o.push({id:e.id,method:e.method,url:e.url,callType:e.callType,filePath:e.filePath,line:e.line,containingFunction:e.containingFunction,requiresAuth:e.requiresAuth});return JSON.stringify(o)}getHtmlTemplate(o){let r=this.getGraphQLData(),e=this.getApiCallsData();return `<!DOCTYPE html>
10
10
  <html lang="en">
11
11
  <head>
12
12
  <meta charset="UTF-8">
@@ -87,6 +87,7 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
87
87
  <link rel="stylesheet" href="/docs.css">
88
88
  </head>
89
89
  <body>
90
+ ${a$2}
90
91
  <header class="header">
91
92
  <div style="display:flex;align-items:center;gap:24px">
92
93
  <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${this.config.repositories[0]?.displayName||this.config.repositories[0]?.name||"Repository"}</h1>
@@ -124,7 +125,7 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
124
125
  </aside>
125
126
  <div class="content-area">
126
127
  <div class="content">
127
- ${s}
128
+ ${o}
128
129
  </div>
129
130
  </div>
130
131
  </div>
@@ -158,8 +159,8 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
158
159
 
159
160
  // Render all mermaid diagrams on page load
160
161
  document.addEventListener('DOMContentLoaded', async () => {
161
- // Snapshot raw dataflow mermaid source BEFORE first render (needed for filtering rerenders)
162
- document.querySelectorAll('.mermaid[data-mermaid-scope="dataflow"]').forEach((el) => {
162
+ // Snapshot raw mermaid source BEFORE first render (needed for copy + rerender)
163
+ document.querySelectorAll('.mermaid').forEach((el) => {
163
164
  if (!el.dataset.mermaidRaw) {
164
165
  el.dataset.mermaidRaw = el.textContent || '';
165
166
  }
@@ -171,10 +172,28 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
171
172
  container.className = 'mermaid-container';
172
173
  container.innerHTML = \`
173
174
  <div class="mermaid-controls">
174
- <button onclick="zoomDiagram(\${idx}, 0.8)" title="Zoom Out">\u2796</button>
175
- <button onclick="zoomDiagram(\${idx}, 1.25)" title="Zoom In">\u2795</button>
176
- <button onclick="zoomDiagram(\${idx}, 'reset')" title="Reset">\u{1F504}</button>
177
- <button onclick="toggleFullscreen(\${idx})" title="Fullscreen">\u26F6</button>
175
+ <button class="mermaid-iconbtn" onclick="zoomDiagram(\${idx}, 0.8)" title="Zoom Out" aria-label="Zoom Out">
176
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-zoom-out"></use><use xlink:href="#icon-zoom-out"></use></svg>
177
+ </button>
178
+ <button class="mermaid-iconbtn" onclick="zoomDiagram(\${idx}, 1.25)" title="Zoom In" aria-label="Zoom In">
179
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-zoom-in"></use><use xlink:href="#icon-zoom-in"></use></svg>
180
+ </button>
181
+ <button class="mermaid-iconbtn" onclick="zoomDiagram(\${idx}, 'reset')" title="Reset" aria-label="Reset">
182
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-reset"></use><use xlink:href="#icon-reset"></use></svg>
183
+ </button>
184
+ <button class="mermaid-iconbtn" onclick="toggleFullscreen(\${idx})" title="Fullscreen" aria-label="Fullscreen">
185
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-fullscreen"></use><use xlink:href="#icon-fullscreen"></use></svg>
186
+ </button>
187
+ <span class="mermaid-controls-spacer"></span>
188
+ <button class="mermaid-iconbtn" data-action="copy" onclick="copyMermaidSource(\${idx})" title="Copy Mermaid source" aria-label="Copy Mermaid source">
189
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-copy"></use><use xlink:href="#icon-copy"></use></svg>
190
+ </button>
191
+ <button class="mermaid-iconbtn" data-action="svg" onclick="downloadMermaidSvg(\${idx})" title="Download SVG" aria-label="Download SVG">
192
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-download"></use><use xlink:href="#icon-download"></use></svg>
193
+ </button>
194
+ <button class="mermaid-iconbtn" data-action="png" onclick="downloadMermaidPng(\${idx})" title="Download PNG" aria-label="Download PNG">
195
+ <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" class="icon"><use href="#icon-image"></use><use xlink:href="#icon-image"></use></svg>
196
+ </button>
178
197
  </div>
179
198
  <div class="mermaid-wrapper" id="wrapper-\${idx}">
180
199
  <div class="mermaid-inner" id="inner-\${idx}"></div>
@@ -418,6 +437,177 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
418
437
  }
419
438
  }
420
439
 
440
+ async function copyTextToClipboard(text) {
441
+ try {
442
+ if (navigator.clipboard && navigator.clipboard.writeText) {
443
+ await navigator.clipboard.writeText(text);
444
+ return true;
445
+ }
446
+ } catch {
447
+ // fallthrough
448
+ }
449
+ try {
450
+ const ta = document.createElement('textarea');
451
+ ta.value = text;
452
+ ta.style.position = 'fixed';
453
+ ta.style.left = '-9999px';
454
+ document.body.appendChild(ta);
455
+ ta.select();
456
+ const ok = document.execCommand('copy');
457
+ ta.remove();
458
+ return ok;
459
+ } catch {
460
+ return false;
461
+ }
462
+ }
463
+
464
+ function getMermaidContext(idx) {
465
+ const wrapper = document.getElementById(\`wrapper-\${idx}\`);
466
+ if (!wrapper) return null;
467
+ const mermaidEl = wrapper.querySelector('.mermaid');
468
+ const raw = mermaidEl?.dataset?.mermaidRaw || mermaidEl?.textContent || '';
469
+ const svg = wrapper.querySelector('svg');
470
+ return { wrapper, mermaidEl, raw, svg };
471
+ }
472
+
473
+ async function copyMermaidSource(idx) {
474
+ const ctx = getMermaidContext(idx);
475
+ if (!ctx) return;
476
+ const ok = await copyTextToClipboard((ctx.raw || '').trim());
477
+ // Simple visual feedback
478
+ const btn = ctx.wrapper.closest('.mermaid-container')?.querySelector('button[data-action="copy"]');
479
+ if (btn) {
480
+ btn.classList.toggle('is-ok', ok);
481
+ btn.classList.toggle('is-fail', !ok);
482
+ const oldTitle = btn.getAttribute('title') || '';
483
+ btn.setAttribute('title', ok ? 'Copied' : 'Copy failed');
484
+ setTimeout(() => {
485
+ btn.classList.remove('is-ok', 'is-fail');
486
+ btn.setAttribute('title', oldTitle);
487
+ }, 900);
488
+ }
489
+ }
490
+
491
+ function downloadBlob(filename, blob) {
492
+ const url = URL.createObjectURL(blob);
493
+ const a = document.createElement('a');
494
+ a.href = url;
495
+ a.download = filename;
496
+ document.body.appendChild(a);
497
+ a.click();
498
+ a.remove();
499
+ setTimeout(() => URL.revokeObjectURL(url), 500);
500
+ }
501
+
502
+ function sanitizeFilePart(s) {
503
+ return String(s || 'diagram').replace(/[^a-zA-Z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
504
+ }
505
+
506
+ function timestamp() {
507
+ const d = new Date();
508
+ const pad2 = (n) => String(n).padStart(2, '0');
509
+ return (
510
+ String(d.getFullYear()) +
511
+ pad2(d.getMonth() + 1) +
512
+ pad2(d.getDate()) +
513
+ '_' +
514
+ pad2(d.getHours()) +
515
+ pad2(d.getMinutes()) +
516
+ pad2(d.getSeconds())
517
+ );
518
+ }
519
+
520
+ function downloadMermaidSvg(idx) {
521
+ const ctx = getMermaidContext(idx);
522
+ if (!ctx || !ctx.svg) return;
523
+ const ser = new XMLSerializer();
524
+ const svgText = ser.serializeToString(ctx.svg);
525
+ const blob = new Blob([svgText], { type: 'image/svg+xml;charset=utf-8' });
526
+ downloadBlob('repomap-diagram_' + sanitizeFilePart(ctx.mermaidEl?.getAttribute('data-mermaid-page') || idx) + '_' + timestamp() + '.svg', blob);
527
+ }
528
+
529
+ function downloadMermaidPng(idx) {
530
+ const ctx = getMermaidContext(idx);
531
+ if (!ctx || !ctx.svg) return;
532
+
533
+ const ser = new XMLSerializer();
534
+ const getSvgSize = (svg) => {
535
+ try {
536
+ const vb = svg.viewBox && svg.viewBox.baseVal;
537
+ if (vb && vb.width && vb.height) return { w: vb.width, h: vb.height };
538
+ } catch {}
539
+ try {
540
+ const w = svg.width && svg.width.baseVal && svg.width.baseVal.value;
541
+ const h = svg.height && svg.height.baseVal && svg.height.baseVal.value;
542
+ if (w && h) return { w, h };
543
+ } catch {}
544
+ try {
545
+ const bb = svg.getBBox();
546
+ if (bb && bb.width && bb.height) return { w: bb.width, h: bb.height };
547
+ } catch {}
548
+ const r = svg.getBoundingClientRect();
549
+ return { w: Math.max(1, r.width), h: Math.max(1, r.height) };
550
+ };
551
+
552
+ const s = getSvgSize(ctx.svg);
553
+ const w = Math.max(1, Math.round(s.w));
554
+ const h = Math.max(1, Math.round(s.h));
555
+
556
+ // Clone and force explicit size so SVG -> image decode is stable across browsers.
557
+ const svgClone = ctx.svg.cloneNode(true);
558
+ try {
559
+ svgClone.setAttribute('width', String(w));
560
+ svgClone.setAttribute('height', String(h));
561
+ if (!svgClone.getAttribute('viewBox')) {
562
+ svgClone.setAttribute('viewBox', '0 0 ' + String(w) + ' ' + String(h));
563
+ }
564
+ } catch {
565
+ // ignore
566
+ }
567
+
568
+ let svgText = ser.serializeToString(svgClone);
569
+ if (!/xmlns=/.test(svgText)) {
570
+ svgText = svgText.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
571
+ }
572
+ if (!/xmlns:xlink=/.test(svgText)) {
573
+ svgText = svgText.replace('<svg', '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
574
+ }
575
+
576
+ const svgBase64 = btoa(unescape(encodeURIComponent(svgText)));
577
+ const dataUrl = 'data:image/svg+xml;base64,' + svgBase64;
578
+
579
+ const img = new Image();
580
+ img.decoding = 'async';
581
+ img.onload = () => {
582
+ try {
583
+ const canvas = document.createElement('canvas');
584
+ canvas.width = w;
585
+ canvas.height = h;
586
+ const g = canvas.getContext('2d');
587
+ if (!g) return;
588
+ // White background for readability
589
+ g.fillStyle = '#ffffff';
590
+ g.fillRect(0, 0, w, h);
591
+ g.drawImage(img, 0, 0, w, h);
592
+ canvas.toBlob((pngBlob) => {
593
+ if (pngBlob) {
594
+ downloadBlob(
595
+ 'repomap-diagram_' +
596
+ sanitizeFilePart(ctx.mermaidEl?.getAttribute('data-mermaid-page') || idx) +
597
+ '_' +
598
+ timestamp() +
599
+ '.png',
600
+ pngBlob
601
+ );
602
+ }
603
+ }, 'image/png');
604
+ } catch {
605
+ }
606
+ };
607
+ img.onerror = () => {};
608
+ img.src = dataUrl;
609
+ }
610
+
421
611
  function showNodeDetail(text, node) {
422
612
  const modal = document.getElementById('detailModal');
423
613
  const title = document.getElementById('modalTitle');
@@ -1112,11 +1302,11 @@ import {a as a$2}from'./chunk-LHP2OKKA.js';import {a as a$3}from'./chunk-VV3A3UE
1112
1302
  }
1113
1303
  </script>
1114
1304
  </body>
1115
- </html>`}async start(s=true){let r=this.config.repositories[0]?.path||process.cwd();this.envResult=await a$3(r),this.currentReport=await this.engine.generate(),console.log();for(let e of this.currentReport.repositories){let a=e.summary;console.log(` \u2705 ${e.displayName}`),console.log(` ${a.totalPages} pages \xB7 ${a.totalComponents} components \xB7 ${a.totalGraphQLOperations} GraphQL \xB7 ${a.totalDataFlows} data flows`);}if(this.envResult.hasRails){console.log(`
1116
- Rails...`);try{this.railsAnalysis=await k(r);let e=this.railsAnalysis.summary;console.log(` ${e.totalRoutes} routes \xB7 ${e.totalControllers} controllers \xB7 ${e.totalModels} models \xB7 ${e.totalGrpcServices} gRPC`);}catch(e){console.error(" \u26A0\uFE0F Rails analysis failed:",e.message);}}try{let e=await ue(this.port);e!==this.port&&console.log(`
1305
+ </html>`}async start(o=true){let r=this.config.repositories[0]?.path||process.cwd();this.envResult=await a$3(r),this.currentReport=await this.engine.generate(),console.log();for(let e of this.currentReport.repositories){let a=e.summary;console.log(` \u2705 ${e.displayName}`),console.log(` ${a.totalPages} pages \xB7 ${a.totalComponents} components \xB7 ${a.totalGraphQLOperations} GraphQL \xB7 ${a.totalDataFlows} data flows`);}if(this.envResult.hasRails){console.log(`
1306
+ Rails...`);try{this.railsAnalysis=await k(r);let e=this.railsAnalysis.summary;console.log(` ${e.totalRoutes} routes \xB7 ${e.totalControllers} controllers \xB7 ${e.totalModels} models \xB7 ${e.totalGrpcServices} gRPC`);}catch(e){console.error(" \u26A0\uFE0F Rails analysis failed:",e.message);}}try{let e=await fe(this.port);e!==this.port&&console.log(`
1117
1307
  \u26A0\uFE0F Port ${this.port} is in use, using port ${e} instead`),this.port=e;}catch(e){console.error(`
1118
1308
  \u274C Failed to find available port: ${e.message}`),process.exit(1);}if(this.server.listen(this.port,()=>{console.log(`
1119
1309
  \u{1F310} Documentation server running at http://localhost:${this.port}`),this.envResult?.hasRails&&this.envResult?.hasNextjs&&console.log(" \u{1F4CA} Multiple environments detected - use tabs to switch views"),console.log(` Press Ctrl+C to stop
1120
- `);}),s){let e=(await import('open')).default;await e(`http://localhost:${this.port}`);}this.config.watch.enabled&&this.watchForChanges();}async regenerate(){if(console.log(`
1121
- \u{1F504} Regenerating...`),this.currentReport=await this.engine.generate(),this.envResult?.hasRails){let s=this.config.repositories[0]?.path||process.cwd();try{this.railsAnalysis=await k(s);}catch(r){console.error("\u26A0\uFE0F Rails re-analysis failed:",r.message);}}for(let s of this.currentReport.repositories)console.log(` ${s.displayName}: ${s.summary.totalPages} pages \xB7 ${s.summary.totalComponents} components \xB7 ${s.summary.totalGraphQLOperations} GraphQL`);this.io.emit("reload"),console.log("\u2705 Done");}async watchForChanges(){let s=this.config.repositories.map(e=>e.path),r=null;for(let e of s)try{let a=L.watch(e,{recursive:!0});(async()=>{for await(let o of a)o.filename&&(o.filename.endsWith(".ts")||o.filename.endsWith(".tsx"))&&(r&&clearTimeout(r),r=setTimeout(async()=>{await this.regenerate();},this.config.watch.debounce));})();}catch(a){console.warn(`Warning: Could not watch directory ${e}:`,a.message);}}stop(){this.server.close(),console.log(`
1122
- \u{1F44B} Server stopped`);}};export{W as a,he as b};
1310
+ `);}),o){let e=(await import('open')).default;await e(`http://localhost:${this.port}`);}this.config.watch.enabled&&this.watchForChanges();}async regenerate(){if(console.log(`
1311
+ \u{1F504} Regenerating...`),this.currentReport=await this.engine.generate(),this.envResult?.hasRails){let o=this.config.repositories[0]?.path||process.cwd();try{this.railsAnalysis=await k(o);}catch(r){console.error("\u26A0\uFE0F Rails re-analysis failed:",r.message);}}for(let o of this.currentReport.repositories)console.log(` ${o.displayName}: ${o.summary.totalPages} pages \xB7 ${o.summary.totalComponents} components \xB7 ${o.summary.totalGraphQLOperations} GraphQL`);this.io.emit("reload"),console.log("\u2705 Done");}async watchForChanges(){let o=this.config.repositories.map(e=>e.path),r=null;for(let e of o)try{let a=A.watch(e,{recursive:!0});(async()=>{for await(let s of a)s.filename&&(s.filename.endsWith(".ts")||s.filename.endsWith(".tsx"))&&(r&&clearTimeout(r),r=setTimeout(async()=>{await this.regenerate();},this.config.watch.debounce));})();}catch(a){console.warn(`Warning: Could not watch directory ${e}:`,a.message);}}stop(){this.server.close(),console.log(`
1312
+ \u{1F44B} Server stopped`);}};export{W as a,ge as b};