@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 +1 -1
- package/README.md +1 -1
- package/dist/analyzers/index.js +1 -1
- package/dist/{chunk-YKPXOHWZ.js → chunk-54DEMJO2.js} +204 -14
- package/dist/{chunk-LHP2OKKA.js → chunk-DSYVU23K.js} +233 -11
- package/dist/{chunk-ATRSGO6O.js → chunk-JBPSEUZS.js} +169 -6
- package/dist/chunk-O2SKKQVN.js +3 -0
- package/dist/cli.js +15 -15
- package/dist/{env-detector-RVGPBVNJ.js → env-detector-BIWJ7OYF.js} +1 -1
- package/dist/generators/assets/docs.css +40 -3
- package/dist/generators/assets/page-map.css +24 -0
- package/dist/generators/assets/rails-map.css +49 -0
- package/dist/generators/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/page-map-generator-SL3H6CKF.js +1 -0
- package/dist/{rails-57MNOGHR.js → rails-3HNUFTQV.js} +1 -1
- package/dist/rails-map-generator-GV4ZIVJJ.js +1 -0
- package/dist/server/index.js +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +16 -7
- package/dist/chunk-IGRCLYVZ.js +0 -451
- package/dist/chunk-ZWRDP37E.js +0 -1
- package/dist/page-map-generator-LTVRHSDC.js +0 -1
- package/dist/rails-map-generator-JNU5QHX4.js +0 -1
package/LICENSE
CHANGED
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://
|
|
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
|
|
package/dist/analyzers/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
${
|
|
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
|
|
162
|
-
document.querySelectorAll('.mermaid
|
|
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"
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<button onclick="
|
|
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(
|
|
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
|
|
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
|
-
`);}),
|
|
1121
|
-
\u{1F504} Regenerating...`),this.currentReport=await this.engine.generate(),this.envResult?.hasRails){let
|
|
1122
|
-
\u{1F44B} Server stopped`);}};export{W as a,
|
|
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};
|