@wtdlee/repomap 0.9.1 → 0.10.1
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/README.md +12 -0
- package/dist/analyzers/index.d.ts +8 -8
- package/dist/analyzers/index.js +1 -1
- package/dist/{chunk-6AZNHUOB.js → chunk-7BQRBJUG.js} +52 -25
- package/dist/{chunk-WZAAA7DS.js → chunk-ATRSGO6O.js} +175 -49
- package/dist/{chunk-NQMJ3QRX.js → chunk-LHP2OKKA.js} +1 -1
- package/dist/{chunk-2XZSFAJF.js → chunk-QBSB6BIU.js} +6 -6
- package/dist/chunk-YO6EB4OI.js +3 -0
- package/dist/cli.js +14 -14
- package/dist/{dataflow-analyzer-CJ2T0cGS.d.ts → dataflow-analyzer-DIUsRpvv.d.ts} +18 -0
- package/dist/generators/assets/docs.css +22 -0
- package/dist/generators/assets/rails-map.css +13 -0
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/page-map-generator-BQ3SSOJG.js +1 -0
- package/dist/rails-map-generator-RY6KXIBU.js +1 -0
- package/dist/server/index.js +1 -1
- package/dist/types.d.ts +34 -1
- package/package.json +3 -3
- package/dist/chunk-QDVE7MT3.js +0 -5
- package/dist/page-map-generator-HROGGVAQ.js +0 -1
- package/dist/rails-map-generator-DF2YAXW4.js +0 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Powered by [SWC](https://swc.rs/) for blazing-fast AST parsing. Analyze large-sc
|
|
|
50
50
|
- **Field details** - View all fields with types and arguments
|
|
51
51
|
- **Usage tracking** - See where operations are used in components
|
|
52
52
|
- **Component integration** - Track GraphQL usage through component dependencies
|
|
53
|
+
- **Custom wrapper hooks** - Configurable hook presets/patterns (Apollo/urql/Relay/custom)
|
|
53
54
|
|
|
54
55
|
### 📊 Data Flow
|
|
55
56
|
- **Visual diagrams** - Mermaid-generated flowcharts
|
|
@@ -425,6 +426,7 @@ export default config;
|
|
|
425
426
|
- Auto-generated markdown documentation
|
|
426
427
|
- Navigation sidebar
|
|
427
428
|
- Syntax-highlighted code blocks
|
|
429
|
+
- Coverage metrics (parse failures, codegen detection) to prevent silent omissions
|
|
428
430
|
|
|
429
431
|
## Supported Frameworks
|
|
430
432
|
|
|
@@ -466,6 +468,7 @@ interface AnalysisResult {
|
|
|
466
468
|
timestamp: string;
|
|
467
469
|
version: string;
|
|
468
470
|
commitHash: string;
|
|
471
|
+
coverage?: CoverageMetrics;
|
|
469
472
|
pages: PageInfo[];
|
|
470
473
|
graphqlOperations: GraphQLOperation[];
|
|
471
474
|
apiCalls: APICall[];
|
|
@@ -476,6 +479,15 @@ interface AnalysisResult {
|
|
|
476
479
|
crossRepoLinks: CrossRepoLink[];
|
|
477
480
|
}
|
|
478
481
|
|
|
482
|
+
interface CoverageMetrics {
|
|
483
|
+
tsFilesScanned: number;
|
|
484
|
+
tsParseFailures: number;
|
|
485
|
+
graphqlParseFailures: number;
|
|
486
|
+
codegenFilesDetected: number;
|
|
487
|
+
codegenFilesParsed: number;
|
|
488
|
+
codegenExportsFound: number;
|
|
489
|
+
}
|
|
490
|
+
|
|
479
491
|
// Report
|
|
480
492
|
interface DocumentationReport {
|
|
481
493
|
generatedAt: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BaseAnalyzer } from '../dataflow-analyzer-
|
|
2
|
-
export { D as DataFlowAnalyzer, G as GraphQLAnalyzer, P as PagesAnalyzer } from '../dataflow-analyzer-
|
|
1
|
+
import { B as BaseAnalyzer } from '../dataflow-analyzer-DIUsRpvv.js';
|
|
2
|
+
export { D as DataFlowAnalyzer, G as GraphQLAnalyzer, P as PagesAnalyzer } from '../dataflow-analyzer-DIUsRpvv.js';
|
|
3
3
|
import { Module, CallExpression } from '@swc/core';
|
|
4
4
|
import { DataFetchingInfo, RepositoryConfig, AnalysisResult } from '../types.js';
|
|
5
5
|
|
|
@@ -33,7 +33,7 @@ declare const HOOK_TYPE_MAP: Record<string, DataFetchingInfo['type']>;
|
|
|
33
33
|
/**
|
|
34
34
|
* Keywords that indicate GraphQL usage in a file
|
|
35
35
|
*/
|
|
36
|
-
declare const GRAPHQL_INDICATORS: readonly ["Document", "useQuery", "useMutation", "useLazyQuery", "useSuspenseQuery", "useBackgroundQuery", "useSubscription", "Query", "Mutation", "gql", "graphql", "GET_", "FETCH_", "SEARCH_", "CREATE_", "UPDATE_", "DELETE_", "SUBSCRIBE_", "@apollo", "ApolloClient"];
|
|
36
|
+
declare const GRAPHQL_INDICATORS: readonly ["Document", "useQuery", "useMutation", "useLazyQuery", "useSuspenseQuery", "useBackgroundQuery", "useSubscription", "Query", "Mutation", "gql", "graphql", "GET_", "FETCH_", "SEARCH_", "CREATE_", "UPDATE_", "DELETE_", "SUBSCRIBE_", "@apollo", "ApolloClient", "@urql", "urql", "react-relay", "Relay"];
|
|
37
37
|
/**
|
|
38
38
|
* Context extracted from a file for GraphQL operation resolution
|
|
39
39
|
*/
|
|
@@ -62,23 +62,23 @@ interface ExtractedGraphQLOperation {
|
|
|
62
62
|
/**
|
|
63
63
|
* Check if a hook name is a GraphQL query hook
|
|
64
64
|
*/
|
|
65
|
-
declare function isQueryHook(hookName: string): boolean;
|
|
65
|
+
declare function isQueryHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
66
66
|
/**
|
|
67
67
|
* Check if a hook name is a GraphQL mutation hook
|
|
68
68
|
*/
|
|
69
|
-
declare function isMutationHook(hookName: string): boolean;
|
|
69
|
+
declare function isMutationHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
70
70
|
/**
|
|
71
71
|
* Check if a hook name is a GraphQL subscription hook
|
|
72
72
|
*/
|
|
73
|
-
declare function isSubscriptionHook(hookName: string): boolean;
|
|
73
|
+
declare function isSubscriptionHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
74
74
|
/**
|
|
75
75
|
* Check if a hook name is any GraphQL hook
|
|
76
76
|
*/
|
|
77
|
-
declare function isGraphQLHook(hookName: string): boolean;
|
|
77
|
+
declare function isGraphQLHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
78
78
|
/**
|
|
79
79
|
* Get the data fetching type for a hook
|
|
80
80
|
*/
|
|
81
|
-
declare function getHookType(hookName: string): DataFetchingInfo['type'];
|
|
81
|
+
declare function getHookType(hookName: string, extraHookPatterns?: string[]): DataFetchingInfo['type'];
|
|
82
82
|
/**
|
|
83
83
|
* Check if content has any GraphQL indicators
|
|
84
84
|
*/
|
package/dist/analyzers/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
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-YO6EB4OI.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE3.js';import {B,A,z,y}from'./chunk-QDVE7MT3.js';import {a,b}from'./chunk-2XZSFAJF.js';import {a as a$1}from'./chunk-WZAAA7DS.js';import {k}from'./chunk-H7VVRHQZ.js';import {simpleGit}from'simple-git';import*as T from'fs/promises';import*as R from'path';import {parseSync}from'@swc/core';import me from'express';import {Server}from'socket.io';import*as fe from'http';import {marked}from'marked';import*as ce from'net';var U=class{config;mermaidGenerator;markdownGenerator;constructor(t){this.config=t,this.mermaidGenerator=new a,this.markdownGenerator=new b;}async generate(){let t=[];for(let w of this.config.repositories)try{let g=await this.analyzeRepository(w);t.push(g);}catch(g){console.error(`\u274C ${w.name}: ${g.message}`);}let r=this.analyzeCrossRepo(t),e=t.map(w=>w.analysis),n=this.extractCrossRepoLinks(e),s=this.mermaidGenerator.generateAll(e,n),i={generatedAt:new Date().toISOString(),repositories:t,crossRepoAnalysis:r,diagrams:s};return await this.writeDocumentation(i),i}async analyzeRepository(t){let{version:r,commitHash:e}=await this.getRepoInfo(t),n=t.analyzers.map(y=>this.createAnalyzer(y,t)).filter(y=>y!==null),s=Date.now(),i=await Promise.all(n.map(y=>y.analyze())),w=((Date.now()-s)/1e3).toFixed(1);console.log(` Analyzed ${t.displayName} in ${w}s`);let g=this.mergeAnalysisResults(i,t.name,r,e);await this.enrichPagesWithHookGraphQL(g,t.path);let k={totalPages:g.pages.length,totalComponents:g.components.length,totalGraphQLOperations:g.graphqlOperations.length,totalDataFlows:g.dataFlows.length,authRequiredPages:g.pages.filter(y=>y.authentication.required).length,publicPages:g.pages.filter(y=>!y.authentication.required).length};return {name:t.name,displayName:t.displayName,version:r,commitHash:e,analysis:g,summary:k}}async getRepoInfo(t){try{let n=(await simpleGit(t.path).log({n:1})).latest?.hash||"unknown",s="unknown";try{let i=R.join(t.path,"package.json");s=JSON.parse(await T.readFile(i,"utf-8")).version||"unknown";}catch{}return {version:s,commitHash:n}}catch{return {version:"unknown",commitHash:"unknown"}}}createAnalyzer(t,r){switch(t){case "pages":if(r.type==="nextjs"||r.type==="rails"||r.type==="generic")return new y(r);break;case "graphql":return new z(r);case "dataflow":case "components":return new A(r);case "rest-api":case "api":return new B(r)}return null}mergeAnalysisResults(t,r,e,n){let s={repository:r,timestamp:new Date().toISOString(),version:e,commitHash:n,pages:[],graphqlOperations:[],apiCalls:[],components:[],dataFlows:[],apiEndpoints:[],models:[],crossRepoLinks:[]};for(let i of t)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(t){let r=[],e=[],n=[],s=[],i=new Map;for(let k of t)for(let y of k.analysis.graphqlOperations){let $=i.get(y.name)||[];$.push(k.name),i.set(y.name,$);}for(let[k,y]of i)y.length>1&&r.push(k);let w=t.filter(k=>k.analysis.pages.length>0),g=t.filter(k=>k.analysis.apiEndpoints.length>0);for(let k of w)for(let y of g)for(let $ of y.analysis.apiEndpoints)e.push({frontend:k.name,backend:y.name,endpoint:$.path,operations:k.analysis.graphqlOperations.filter(A=>A.usedIn.length>0).map(A=>A.name)});return {sharedTypes:r,apiConnections:e,navigationFlows:n,dataFlowAcrossRepos:s}}extractCrossRepoLinks(t){let r=[],e=new Map;for(let n of t)for(let s of n.graphqlOperations){let i=e.get(s.name)||[];i.push(n),e.set(s.name,i);}for(let[n,s]of e)s.length>1&&r.push({sourceRepo:s[0].repository,sourcePath:`graphql/${n}`,targetRepo:s[1].repository,targetPath:`graphql/${n}`,linkType:"graphql-operation",description:`Shared GraphQL operation: ${n}`});return r}async enrichPagesWithHookGraphQL(t,r){let e=t.graphqlOperations.filter(a=>a.type==="query"||a.type==="mutation"||a.type==="subscription"),n=/\.(ts|tsx|js|jsx)$/,s=a=>R.normalize(a).replace(/\\/g,"/"),i=(this.config.analysis?.include||["**/*.ts","**/*.tsx"]).map(String),w=(this.config.analysis?.exclude||[]).map(String),g=(await import('fast-glob')).default,k=await g(i,{cwd:r,ignore:["**/node_modules/**","**/.next/**","**/dist/**","**/build/**","**/coverage/**",...w],onlyFiles:true,unique:true,dot:false}),y=new Set(k.map(s)),$=new Map;for(let a of y){let o=a.replace(n,"");$.has(o)||$.set(o,a);}let A=new Set(["src/"]);for(let a of y){let o=a.indexOf("/src/");o!==-1&&A.add(a.slice(0,o+5));}let N=a=>{let o=s(a).replace(n,""),c=$.get(o);if(c)return c;let l=$.get(o+"/index");return l||null},he=async()=>{let a=["tsconfig.json","jsconfig.json"];for(let o of a)try{let c=await T.readFile(R.join(r,o),"utf-8"),p=JSON.parse(c)?.compilerOptions||{},f=typeof p.baseUrl=="string"?p.baseUrl:void 0,q=typeof p.paths=="object"&&p.paths?p.paths:void 0;return {baseUrl:f,paths:q}}catch{}return {}},{baseUrl:O,paths:X}=await he(),ge=a=>{if(!X)return [];let o=[];for(let[c,l]of Object.entries(X)){if(!c.includes("*")){a===c&&o.push(...l);continue}let[p,f]=c.split("*");if(!a.startsWith(p)||!a.endsWith(f))continue;let q=a.slice(p.length,a.length-f.length);for(let d of l)d.includes("*")?o.push(d.replace("*",q)):o.push(d);}return o},B=(a,o)=>{if(!o)return null;if(o.startsWith(".")){let l=R.dirname(a);return N(R.join(l,o))}if(o.startsWith("@/")){let l=o.replace("@/","");if(O){let p=N(R.join(O,l));if(p)return p}for(let p of A){let f=N(p+l);if(f)return f}return null}let c=ge(o);if(c.length>0)for(let l of c){let p=N(O?R.join(O,l):l);if(p)return p}if(O){let l=N(R.join(O,o));if(l)return l}return null},j=new Map,W=new Map,Z=async a=>{let o=s(a),c=W.get(o);if(c!==void 0)return c;try{let l=R.join(r,o),p=await T.readFile(l,"utf-8");return W.set(o,p),p}catch{return W.set(o,""),null}},ye=async a=>{let o=s(a),c=j.get(o);if(c)return c.map(m=>({spec:m,names:null}));let l=await Z(o);if(!l)return j.set(o,[]),[];let p;try{let m=o.endsWith(".ts")||o.endsWith(".tsx"),h=o.endsWith(".tsx")||o.endsWith(".jsx");p=parseSync(l,{syntax:m?"typescript":"ecmascript",tsx:h,jsx:h,comments:!1});}catch{return j.set(o,[]),[]}let f=new Set,q=[],d=(m,h)=>{typeof m!="string"||m.length===0||(f.add(m),q.push({spec:m,names:h}));},b=m=>{if(!m||typeof m!="object")return;let h=m,v=h;if(v.type==="ImportDeclaration"){if(!v.typeOnly){let x=v.source?.value,u=[],E=v.specifiers||[];for(let S of E){let C=S,I=C.type;if(I==="ImportDefaultSpecifier"||I==="ImportNamespaceSpecifier"){u=null;break}if(I==="ImportSpecifier"){let G=C.imported?.value,P=C.local?.value,z=G||P;z&&u.push(z);}}Array.isArray(u)&&u.length===0&&(u=null),d(x,u);}}else if(v.type==="ExportAllDeclaration")d(v.source?.value,null);else if(v.type==="ExportNamedDeclaration"){let x=v.source?.value,u=v.specifiers||[],E=[];for(let S of u){let C=S;if(C.type==="ExportSpecifier"){let G=C.exported?.value,P=C.orig?.value,z=G||P;z&&E.push(z);}}d(x,E.length>0?E:null);}else if(v.type==="CallExpression"){let x=v.callee||null;if(x?.type==="Identifier"&&x.value==="require"){let u=v.arguments?.[0]?.expression;u?.type==="StringLiteral"&&d(u.value,null);}if(x?.type==="Import"){let u=v.arguments?.[0]?.expression;u?.type==="StringLiteral"&&d(u.value,null);}}for(let x of Object.keys(h)){let u=h[x];if(Array.isArray(u))for(let E of u)b(E);else u&&typeof u=="object"&&b(u);}};b(p);let D=Array.from(f);return j.set(o,D),q.filter(m=>typeof m.spec=="string"&&m.spec.length>0)},Q=new Map,_=async a=>{let o=s(a),c=Q.get(o);if(c)return c;let l=await Z(o);if(!l){let m={named:new Map,stars:[],isBarrel:false};return Q.set(o,m),m}let p;try{let m=o.endsWith(".ts")||o.endsWith(".tsx"),h=o.endsWith(".tsx")||o.endsWith(".jsx");p=parseSync(l,{syntax:m?"typescript":"ecmascript",tsx:h,jsx:h,comments:!1});}catch{let m={named:new Map,stars:[],isBarrel:false};return Q.set(o,m),m}let f=new Map,q=[],d=true,b=p?.body;if(Array.isArray(b))for(let m of b){let h=m,v=h.type;if(v&&v!=="ImportDeclaration"){if(v==="ExportAllDeclaration"){let x=h.source?.value;typeof x=="string"&&q.push(x);continue}if(v==="ExportNamedDeclaration"){let x=h.source?.value,u=h.specifiers||[];if(typeof x=="string"&&Array.isArray(u))for(let E of u){let S=E;if(S.type!=="ExportSpecifier")continue;let I=S.exported?.value,G=S.orig?.value,P=I||G;P&&f.set(P,x);}continue}d=false;}}else d=false;let D={named:f,stars:q,isBarrel:d};return Q.set(o,D),D},V=async(a,o,c)=>{let l=s(a);if(c.has(l))return null;c.add(l);let p=await _(l),f=p.named.get(o);if(f)return B(l,f);for(let q of p.stars){let d=B(l,q);if(!d)continue;let b=await V(d,o,c);if(b)return b}return null},Y=new Map;for(let a of e){let o=new Set;a.filePath&&o.add(a.filePath);for(let c of a.usedIn||[])o.add(c);for(let c of o){let l=Y.get(c)||[];l.push({opName:a.name,opType:a.type}),Y.set(c,l);}}let ve=a=>{if(!a)return null;let o=[`src/pages/${a}`,`pages/${a}`,`src/app/${a}`,`app/${a}`,`frontend/src/pages/${a}`,`frontend/src/app/${a}`,`app/javascript/pages/${a}`,`app/javascript/app/${a}`];for(let p of o){if(y.has(p))return p;let f=N(p);if(f)return f}let c=[],l=[`/pages/${a}`,`/app/${a}`,`/${a}`];for(let p of y)for(let f of l)if(p.endsWith(f)&&!(f.startsWith("/pages/")&&!p.includes("/pages/"))&&!(f.startsWith("/app/")&&!p.includes("/app/"))){c.push(p);break}return c.length===0?null:(c.sort((p,f)=>p.length-f.length),c[0])},J=new Map,H=new Map;for(let a of t.pages){let o=ve(a.filePath);if(!o)continue;let c=new Map,l=new Set,p=[{f:o,depth:0}],f=30,q=2e4;for(;p.length>0;){let d=p.shift();if(!d)break;if(l.has(d.f))continue;if(l.add(d.f),l.size>=q)break;let b=Y.get(d.f);if(b)for(let m of b){let h=c.get(m.opName);(!h||d.depth<h.distance)&&c.set(m.opName,{opName:m.opName,opType:m.opType,sourceFile:d.f,distance:d.depth});}if(d.depth>=f)continue;let D=await ye(d.f);for(let m of D){let h=B(d.f,m.spec);if(h){if(l.has(h)||p.push({f:h,depth:d.depth+1}),Array.isArray(m.names)&&m.names.length>0){if((await _(h)).isBarrel)for(let x of m.names){let u=await V(h,x,new Set);u&&!l.has(u)&&p.push({f:u,depth:d.depth+2});}}else if(m.names===null){let v=await _(h);if(v.isBarrel){for(let x of v.stars){let u=B(h,x);u&&!l.has(u)&&p.push({f:u,depth:d.depth+2});}for(let x of v.named.values()){let u=B(h,x);u&&!l.has(u)&&p.push({f:u,depth:d.depth+2});}}}}}}J.set(a.path,{page:a,entryFile:o,bestByOp:c});for(let d of l)H.set(d,(H.get(d)||0)+1);}let we=J.size,xe=Array.from(H.values()).sort((a,o)=>a-o),be=((a,o)=>{if(a.length===0)return 0;let c=Math.min(a.length-1,Math.max(0,Math.floor(a.length*o)));return a[c]})(xe,.9),ke=Math.max(10,be),qe=Math.max(2,Math.floor(we*.05));for(let{page:a,bestByOp:o}of J.values()){let c=new Set((a.dataFetching||[]).map(l=>l.operationName?.replace(/^[→\->\s]+/,"")||""));for(let{opName:l,opType:p,sourceFile:f,distance:q}of o.values()){if(c.has(l))continue;c.add(l);let d=H.get(f)||0,b;q===0?b=void 0:d>=ke?b=`common:${f}`:q<=2||d<=qe?b=`close:${f}`:b=`indirect:${f}`,a.dataFetching.push({type:p==="mutation"?"useMutation":p==="subscription"?"useSubscription":"useQuery",operationName:l,source:b});}}}async writeDocumentation(t){let r=this.config.outputDir;await T.mkdir(r,{recursive:true});let e=this.markdownGenerator.generateDocumentation(t);for(let[s,i]of e){let w=R.join(r,s),g=R.dirname(w);await T.mkdir(g,{recursive:true}),await T.writeFile(w,i,"utf-8");}let n=R.join(r,"report.json");await T.writeFile(n,JSON.stringify(t,null,2),"utf-8");}};function Me(L){return new Promise(t=>{let r=ce.createServer();r.once("error",e=>{e.code,t(false);}),r.once("listening",()=>{r.close(),t(true);}),r.listen(L);})}async function de(L,t=10){for(let r=0;r<t;r++){let e=L+r;if(await Me(e))return e}throw new Error(`No available port found between ${L} and ${L+t-1}`)}var ue=class{config;port;app;server;io;engine;currentReport=null;envResult=null;railsAnalysis=null;constructor(t,r=3030){this.config=t,this.port=r,this.app=me(),this.server=fe.createServer(this.app),this.io=new Server(this.server),this.engine=new U(t),this.setupRoutes(),this.setupSocketIO();}setupRoutes(){this.app.use("/assets",me.static(R.join(this.config.outputDir,"assets"))),["common.css","page-map.css","docs.css","rails-map.css"].forEach(e=>{this.app.get(`/${e}`,async(n,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 w of i)try{let g=await T.readFile(w,"utf-8");s.type("text/css").send(g);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,w)=>{let g=[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 k of g)try{let y=await T.readFile(k),$=e.split(".").pop(),A={ico:"image/x-icon",svg:"image/svg+xml",png:"image/png",webmanifest:"application/manifest+json"};w.type(A[$||""]||"application/octet-stream").send(y);return}catch{}w.status(404).send("File not found");});});}),this.app.get("/",(e,n)=>{n.redirect("/page-map");}),this.app.get("/page-map",(e,n)=>{if(!this.currentReport){n.status(503).send("Documentation not ready yet");return}let s=new a$1;n.send(s.generatePageMapHtml(this.currentReport,{envResult:this.envResult,railsAnalysis:this.railsAnalysis}));}),this.app.get("/rails-map",(e,n)=>{if(!this.railsAnalysis){n.status(404).send("No Rails environment detected");return}let s=new a$2;n.send(s.generateFromResult(this.railsAnalysis));}),this.app.get("/docs",async(e,n)=>{n.send(await this.renderPage("index"));}),this.app.get("/docs/*path",async(e,n)=>{let s=e.params.path,i=Array.isArray(s)?s.join("/"):s||"index";n.send(await this.renderPage(i));}),this.app.get("/api/report",(e,n)=>{n.json(this.currentReport);}),this.app.get("/api/env",(e,n)=>{n.json(this.envResult);}),this.app.get("/api/rails",(e,n)=>{this.railsAnalysis?n.json(this.railsAnalysis):n.status(404).json({error:"No Rails analysis available"});}),this.app.get("/api/diagram/:name",(e,n)=>{let s=this.currentReport?.diagrams.find(i=>i.title.toLowerCase().replace(/\s+/g,"-")===e.params.name);s?n.json(s):n.status(404).json({error:"Diagram not found"});}),this.app.post("/api/regenerate",async(e,n)=>{try{await this.regenerate(),n.json({success:!0});}catch(s){n.status(500).json({error:s.message});}});}setupSocketIO(){this.io.on("connection",t=>{t.on("disconnect",()=>{});});}async renderPage(t){let r=t.replace(/\.md$/,""),e=R.join(this.config.outputDir,`${r}.md`),n="";try{let s=await T.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>"),n=i;}catch(s){let i=s;if(i.code==="ENOENT"){let w=this.currentReport?.repositories.map(g=>g.name)||[];n=`
|
|
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-YO6EB4OI.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=`
|
|
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?`
|
|
5
5
|
<p>Available repositories:</p>
|
|
6
|
-
<ul>${
|
|
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}`),
|
|
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>
|
|
10
10
|
<html lang="en">
|
|
11
11
|
<head>
|
|
12
12
|
<meta charset="UTF-8">
|
|
@@ -104,11 +104,11 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
104
104
|
<div class="nav-group">
|
|
105
105
|
<span class="nav-group-title">Documentation</span>
|
|
106
106
|
<div class="nav-subitems">
|
|
107
|
-
${this.config.repositories.map(
|
|
108
|
-
<a href="/docs/repos/${
|
|
109
|
-
<a href="/docs/repos/${
|
|
110
|
-
<a href="/docs/repos/${
|
|
111
|
-
<a href="/docs/repos/${
|
|
107
|
+
${this.config.repositories.map(a=>`
|
|
108
|
+
<a href="/docs/repos/${a.name}/pages">Pages</a>
|
|
109
|
+
<a href="/docs/repos/${a.name}/components">Components</a>
|
|
110
|
+
<a href="/docs/repos/${a.name}/graphql">GraphQL</a>
|
|
111
|
+
<a href="/docs/repos/${a.name}/dataflow">Data Flow</a>
|
|
112
112
|
`).join("")}
|
|
113
113
|
</div>
|
|
114
114
|
</div>
|
|
@@ -124,7 +124,7 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
124
124
|
</aside>
|
|
125
125
|
<div class="content-area">
|
|
126
126
|
<div class="content">
|
|
127
|
-
${
|
|
127
|
+
${s}
|
|
128
128
|
</div>
|
|
129
129
|
</div>
|
|
130
130
|
</div>
|
|
@@ -469,7 +469,9 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
469
469
|
|
|
470
470
|
if (op.usedIn?.length) {
|
|
471
471
|
html += '<div class="detail-section"><h4>Used In</h4><div style="max-height:100px;overflow-y:auto">';
|
|
472
|
-
op.usedIn.forEach(f => {
|
|
472
|
+
op.usedIn.forEach(f => {
|
|
473
|
+
html += \`<p><code>\${escapeHtml(f)}</code></p>\`;
|
|
474
|
+
});
|
|
473
475
|
html += '</div></div>';
|
|
474
476
|
}
|
|
475
477
|
} else {
|
|
@@ -508,7 +510,9 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
508
510
|
|
|
509
511
|
if (partialMatch.usedIn?.length) {
|
|
510
512
|
html += '<div class="detail-section"><h4>Used In</h4><div style="max-height:100px;overflow-y:auto">';
|
|
511
|
-
partialMatch.usedIn.forEach(f => {
|
|
513
|
+
partialMatch.usedIn.forEach(f => {
|
|
514
|
+
html += \`<p><code>\${escapeHtml(f)}</code></p>\`;
|
|
515
|
+
});
|
|
512
516
|
html += '</div></div>';
|
|
513
517
|
}
|
|
514
518
|
} else {
|
|
@@ -590,6 +594,15 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
590
594
|
return badges[type] || badges.unknown;
|
|
591
595
|
}
|
|
592
596
|
|
|
597
|
+
function escapeHtml(str) {
|
|
598
|
+
return String(str ?? '')
|
|
599
|
+
.replace(/&/g, '&')
|
|
600
|
+
.replace(/</g, '<')
|
|
601
|
+
.replace(/>/g, '>')
|
|
602
|
+
.replace(/"/g, '"')
|
|
603
|
+
.replace(/'/g, ''');
|
|
604
|
+
}
|
|
605
|
+
|
|
593
606
|
function closeModal() {
|
|
594
607
|
document.getElementById('detailModal').classList.remove('open');
|
|
595
608
|
modalHistory = [];
|
|
@@ -634,7 +647,8 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
634
647
|
el.addEventListener('click', (e) => {
|
|
635
648
|
e.stopPropagation();
|
|
636
649
|
const opName = el.dataset.op || el.textContent?.replace(/^[QM]:\\s*/, '') || '';
|
|
637
|
-
|
|
650
|
+
const confidence = el.dataset.confidence || '';
|
|
651
|
+
if (opName) showGraphQLDetail(opName, el, confidence);
|
|
638
652
|
});
|
|
639
653
|
});
|
|
640
654
|
|
|
@@ -668,7 +682,7 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
668
682
|
});
|
|
669
683
|
});
|
|
670
684
|
|
|
671
|
-
function showGraphQLDetail(name, el) {
|
|
685
|
+
function showGraphQLDetail(name, el, confidence) {
|
|
672
686
|
const modal = document.getElementById('detailModal');
|
|
673
687
|
const title = document.getElementById('modalTitle');
|
|
674
688
|
const body = document.getElementById('modalBody');
|
|
@@ -685,9 +699,16 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
685
699
|
|
|
686
700
|
if (op) {
|
|
687
701
|
titleText = op.name;
|
|
702
|
+
const confBadge =
|
|
703
|
+
confidence === 'likely'
|
|
704
|
+
? '<span class="detail-badge confidence likely" title="Likely: reachable via the import graph, but indirect (3+ steps)">LIKELY</span>'
|
|
705
|
+
: '';
|
|
688
706
|
html = \`<div class="detail-section">
|
|
689
707
|
<h4>Type</h4>
|
|
690
|
-
<
|
|
708
|
+
<div class="detail-badges">
|
|
709
|
+
<span class="detail-badge \${op.type}">\${op.type.toUpperCase()}</span>
|
|
710
|
+
\${confBadge}
|
|
711
|
+
</div>
|
|
691
712
|
</div>\`;
|
|
692
713
|
|
|
693
714
|
if (op.returnType) {
|
|
@@ -716,8 +737,10 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
716
737
|
}
|
|
717
738
|
|
|
718
739
|
if (op.usedIn?.length) {
|
|
719
|
-
html += '<div class="detail-section"><h4>Used In</h4><div style="
|
|
720
|
-
op.usedIn.forEach(f => {
|
|
740
|
+
html += '<div class="detail-section"><h4>Used In</h4><div style="max-height:100px;overflow-y:auto">';
|
|
741
|
+
op.usedIn.forEach(f => {
|
|
742
|
+
html += \`<p><code>\${escapeHtml(f)}</code></p>\`;
|
|
743
|
+
});
|
|
721
744
|
html += '</div></div>';
|
|
722
745
|
}
|
|
723
746
|
} else {
|
|
@@ -730,7 +753,9 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
730
753
|
html = \`
|
|
731
754
|
<div class="detail-section">
|
|
732
755
|
<h4>Type</h4>
|
|
733
|
-
<
|
|
756
|
+
<div class="detail-badges">
|
|
757
|
+
<span class="detail-badge \${type}">\${type.toUpperCase()}</span>
|
|
758
|
+
</div>
|
|
734
759
|
</div>
|
|
735
760
|
<div class="detail-section">
|
|
736
761
|
<h4>Operation Name</h4>
|
|
@@ -856,8 +881,10 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
856
881
|
}
|
|
857
882
|
|
|
858
883
|
if (op.usedIn?.length) {
|
|
859
|
-
html += '<div class="detail-section"><h4>Used In</h4><div style="
|
|
860
|
-
op.usedIn.forEach(f => {
|
|
884
|
+
html += '<div class="detail-section"><h4>Used In</h4><div style="max-height:100px;overflow-y:auto">';
|
|
885
|
+
op.usedIn.forEach(f => {
|
|
886
|
+
html += \`<p><code>\${escapeHtml(f)}</code></p>\`;
|
|
887
|
+
});
|
|
861
888
|
html += '</div></div>';
|
|
862
889
|
}
|
|
863
890
|
|
|
@@ -1085,11 +1112,11 @@ import {a as a$2}from'./chunk-NQMJ3QRX.js';import {a as a$3}from'./chunk-VV3A3UE
|
|
|
1085
1112
|
}
|
|
1086
1113
|
</script>
|
|
1087
1114
|
</body>
|
|
1088
|
-
</html>`}async start(
|
|
1089
|
-
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
|
|
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(`
|
|
1090
1117
|
\u26A0\uFE0F Port ${this.port} is in use, using port ${e} instead`),this.port=e;}catch(e){console.error(`
|
|
1091
1118
|
\u274C Failed to find available port: ${e.message}`),process.exit(1);}if(this.server.listen(this.port,()=>{console.log(`
|
|
1092
1119
|
\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
|
|
1093
|
-
`);}),
|
|
1094
|
-
\u{1F504} Regenerating...`),this.currentReport=await this.engine.generate(),this.envResult?.hasRails){let
|
|
1095
|
-
\u{1F44B} Server stopped`);}};export{
|
|
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};
|