@wtdlee/repomap 0.9.0 → 0.9.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.
@@ -1,4 +1,4 @@
1
- var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let r=[],u=n?.envResult,t=n?.railsAnalysis,o=n?.activeTab||"pages",e=s.repositories[0]?.displayName||s.repositories[0]?.name||"Repository";for(let p of s.repositories){this.graphqlOps.push(...p.analysis?.graphqlOperations||[]),this.apiCalls.push(...p.analysis?.apiCalls||[]);let f=p.analysis?.components||[];for(let a of f)this.components.push({name:a.name,filePath:a.filePath,type:a.type,dependencies:a.dependencies||[],hooks:a.hooks||[]});}for(let p of s.repositories){let f=p.analysis?.pages||[];for(let a of f)r.push({...a,repo:p.name,children:[],parent:null,depth:0});}let{rootPages:i,relations:l}=this.buildHierarchy(r);return this.renderPageMapHtml(r,i,l,e,{envResult:u,railsAnalysis:t,activeTab:o})}buildHierarchy(s){let n=new Map,r=[];for(let t of s)n.set(t.path,t);for(let t of s){let o=t.path.split("/").filter(Boolean);for(let e=o.length-1;e>=1;e--){let i="/"+o.slice(0,e).join("/"),l=n.get(i);if(l){t.parent=i,t.depth=l.depth+1,l.children.includes(t.path)||l.children.push(t.path),r.push({from:i,to:t.path,type:"parent-child",description:`Sub-page of ${i}`});break}}if(t.parent||(t.depth=Math.max(0,o.length-1)),t.layout)for(let e of s)e.path!==t.path&&e.layout===t.layout&&(r.find(l=>l.type==="same-layout"&&(l.from===t.path&&l.to===e.path||l.from===e.path&&l.to===t.path))||r.push({from:t.path,to:e.path,type:"same-layout",description:`Both use ${t.layout}`}));}return {rootPages:s.filter(t=>!t.parent).sort((t,o)=>t.path.localeCompare(o.path)),relations:r}}renderPageMapHtml(s,n,r,u,t){let o=t?.envResult,e=t?.railsAnalysis,i=t?.activeTab||"pages",l=JSON.stringify(this.graphqlOps.map(c=>({name:c.name,type:c.type,variables:c.variables,fields:c.fields,returnType:c.returnType,usedIn:c.usedIn}))),p=JSON.stringify(this.components),f=e?JSON.stringify(e.routes.routes):"[]",a=e?JSON.stringify(e.controllers.controllers):"[]",h=e?JSON.stringify(e.models.models):"[]",v=e?JSON.stringify(e.views):'{ "views": [], "pages": [], "summary": {} }',y=e?JSON.stringify(e.react):'{ "components": [], "entryPoints": [], "summary": {} }',d=e?JSON.stringify(e.grpc):'{ "services": [] }',C=e?JSON.stringify(e.summary):"null",m=o?.hasRails||false,b=o?.hasNextjs||false,w=o?.hasReact||false,x=new Map;for(let c of s){let g=c.path.split("/").filter(Boolean)[0]||"root";x.has(g)||x.set(g,[]),x.get(g)?.push(c);}return `<!DOCTYPE html>
1
+ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let r=[],f=n?.envResult,t=n?.railsAnalysis,l=n?.activeTab||"pages",e=s.repositories[0]?.displayName||s.repositories[0]?.name||"Repository";for(let p of s.repositories){this.graphqlOps.push(...p.analysis?.graphqlOperations||[]),this.apiCalls.push(...p.analysis?.apiCalls||[]);let u=p.analysis?.components||[];for(let a of u)this.components.push({name:a.name,filePath:a.filePath,type:a.type,dependencies:a.dependencies||[],hooks:a.hooks||[]});}for(let p of s.repositories){let u=p.analysis?.pages||[];for(let a of u)r.push({...a,repo:p.name,children:[],parent:null,depth:0});}let{rootPages:i,relations:o}=this.buildHierarchy(r);return this.renderPageMapHtml(r,i,o,e,{envResult:f,railsAnalysis:t,activeTab:l})}buildHierarchy(s){let n=new Map,r=[];for(let t of s)n.set(t.path,t);for(let t of s){let l=t.path.split("/").filter(Boolean);for(let e=l.length-1;e>=1;e--){let i="/"+l.slice(0,e).join("/"),o=n.get(i);if(o){t.parent=i,t.depth=o.depth+1,o.children.includes(t.path)||o.children.push(t.path),r.push({from:i,to:t.path,type:"parent-child",description:`Sub-page of ${i}`});break}}if(t.parent||(t.depth=Math.max(0,l.length-1)),t.layout)for(let e of s)e.path!==t.path&&e.layout===t.layout&&(r.find(o=>o.type==="same-layout"&&(o.from===t.path&&o.to===e.path||o.from===e.path&&o.to===t.path))||r.push({from:t.path,to:e.path,type:"same-layout",description:`Both use ${t.layout}`}));}return {rootPages:s.filter(t=>!t.parent).sort((t,l)=>t.path.localeCompare(l.path)),relations:r}}renderPageMapHtml(s,n,r,f,t){let l=t?.envResult,e=t?.railsAnalysis,i=t?.activeTab||"pages",o=JSON.stringify(this.graphqlOps.map(c=>({name:c.name,type:c.type,variables:c.variables,fields:c.fields,returnType:c.returnType,usedIn:c.usedIn}))),p=JSON.stringify(this.components),u=e?JSON.stringify(e.routes.routes):"[]",a=e?JSON.stringify(e.controllers.controllers):"[]",m=e?JSON.stringify(e.models.models):"[]",v=e?JSON.stringify(e.views):'{ "views": [], "pages": [], "summary": {} }',y=e?JSON.stringify(e.react):'{ "components": [], "entryPoints": [], "summary": {} }',d=e?JSON.stringify(e.grpc):'{ "services": [] }',C=e?JSON.stringify(e.summary):"null",h=l?.hasRails||false,b=l?.hasNextjs||false,w=l?.hasReact||false,x=new Map;for(let c of s){let g=c.path.split("/").filter(Boolean)[0]||"root";x.has(g)||x.set(g,[]),x.get(g)?.push(c);}return `<!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
@@ -14,17 +14,17 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
14
14
  <body>
15
15
  <header class="header">
16
16
  <div style="display:flex;align-items:center;gap:24px">
17
- <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${u}</h1>
17
+ <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${f}</h1>
18
18
  <nav style="display:flex;gap:4px">
19
19
  <a href="/page-map" class="nav-link ${i==="pages"?"active":""}">Page Map</a>
20
- ${m?`<a href="/rails-map" class="nav-link ${i==="rails"?"active":""}">Rails Map</a>`:""}
20
+ ${h?`<a href="/rails-map" class="nav-link ${i==="rails"?"active":""}">Rails Map</a>`:""}
21
21
  <a href="/docs" class="nav-link">Docs</a>
22
22
  <a href="/api/report" class="nav-link" target="_blank">API</a>
23
23
  </nav>
24
24
  </div>
25
25
  <div style="display:flex;gap:12px;align-items:center">
26
26
  <!-- Environment filter badges -->
27
- ${m&&b?`<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
27
+ ${h&&b?`<div class="env-filters" style="display:flex;gap:4px;margin-right:8px">
28
28
  <button class="env-badge env-badge-active" data-env="all" onclick="filterByEnv('all')">All</button>
29
29
  <button class="env-badge" data-env="nextjs" onclick="filterByEnv('nextjs')">\u269B\uFE0F Next.js</button>
30
30
  <button class="env-badge" data-env="rails" onclick="filterByEnv('rails')">\u{1F6E4}\uFE0F Rails</button>
@@ -69,7 +69,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
69
69
  <div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
70
70
  </div>
71
71
 
72
- ${m&&e?`
72
+ ${h&&e?`
73
73
  <!-- Rails Stats -->
74
74
  <h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px;cursor:pointer" onclick="switchToRailsTab()">Rails Backend</h3>
75
75
  <div class="stats" id="rails-stats">
@@ -86,17 +86,17 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
86
86
  <!-- Pages Tree View (for all screens - Next.js/React/Rails) -->
87
87
  <div class="tree-view ${i==="pages"?"active":""}" id="tree-view" data-tab="pages">
88
88
  ${s.length>0?this.buildTreeHtml(x,s):""}
89
- <div id="page-map-react-components-section" style="${m?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
89
+ <div id="page-map-react-components-section" style="${h?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
90
90
  </div>
91
- <div id="page-map-rails-section" style="${s.length>0&&m?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
92
- ${m&&s.length===0?'<div class="empty-state-sm">Loading screens...</div>':""}
91
+ <div id="page-map-rails-section" style="${s.length>0&&h?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
92
+ ${h&&s.length===0?'<div class="empty-state-sm">Loading screens...</div>':""}
93
93
  </div>
94
94
  </div>
95
95
 
96
96
  <!-- Rails Routes View (dedicated) -->
97
97
  <div class="tree-view ${i==="rails"?"active":""}" id="rails-tree-view" data-tab="rails">
98
98
  <div id="rails-routes-container">
99
- ${m?'<div class="empty-state-sm">Loading Rails routes...</div>':'<div class="empty-state">No Rails environment detected</div>'}
99
+ ${h?'<div class="empty-state-sm">Loading Rails routes...</div>':'<div class="empty-state">No Rails environment detected</div>'}
100
100
  </div>
101
101
  </div>
102
102
 
@@ -138,7 +138,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
138
138
  <script>
139
139
  // Environment detection results
140
140
  const envInfo = {
141
- hasRails: ${m},
141
+ hasRails: ${h},
142
142
  hasNextjs: ${b},
143
143
  hasReact: ${w}
144
144
  };
@@ -146,7 +146,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
146
146
  // Frontend data
147
147
  const pages = ${JSON.stringify(s)};
148
148
  const relations = ${JSON.stringify(r)};
149
- const graphqlOps = ${l};
149
+ const graphqlOps = ${o};
150
150
  const components = ${p};
151
151
  const apiCallsData = ${JSON.stringify(this.apiCalls)};
152
152
  window.apiCalls = apiCallsData;
@@ -155,9 +155,9 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
155
155
  const compMap = new Map(components.map(c => [c.name, c]));
156
156
 
157
157
  // Rails data (if available)
158
- const railsRoutes = ${f};
158
+ const railsRoutes = ${u};
159
159
  const railsControllers = ${a};
160
- const railsModels = ${h};
160
+ const railsModels = ${m};
161
161
  const railsViews = ${v};
162
162
  const railsReact = ${y};
163
163
  const railsGrpc = ${d};
@@ -1930,61 +1930,12 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
1930
1930
  stepsHtml += '</div></div>';
1931
1931
  }
1932
1932
 
1933
- // Data operations - show grouped by source path, sorted by depth
1934
- // Also include GraphQL from related components (including their dependencies)
1933
+ // Data operations - use page.dataFetching from engine.ts only
1934
+ // (engine.ts already enriches pages with GraphQL from components via enrichPagesWithHookGraphQL)
1935
1935
  let dataHtml = '';
1936
1936
 
1937
- // Recursively extract GraphQL from component and its dependencies
1938
- function extractComponentGraphQL(comp, visited = new Set(), depth = 0) {
1939
- const results = [];
1940
- if (!comp || visited.has(comp.name) || depth > 10) return results;
1941
- visited.add(comp.name);
1942
-
1943
- // Extract GraphQL from this component's hooks
1944
- if (comp.hooks) {
1945
- comp.hooks.forEach(hook => {
1946
- // Only match hooks with "Query: " or "Mutation: " prefix (from dataflow analyzer)
1947
- // This avoids matching unrelated hooks like useQueryParams
1948
- if (hook.startsWith('Query: ') || hook.startsWith('Mutation: ') || hook.startsWith('Subscription: ')) {
1949
- let queryName = hook.replace('Query: ', '').replace('Mutation: ', '').replace('Subscription: ', '').trim();
1950
- // Skip empty names or hooks without actual operation names
1951
- if (!queryName) {
1952
- return;
1953
- }
1954
- const isM = hook.includes('Mutation');
1955
- const depthArrows = '\u2192 '.repeat(depth + 1);
1956
- results.push({
1957
- type: isM ? 'useMutation' : 'useQuery',
1958
- operationName: depthArrows + queryName + ' (via ' + comp.name + ')',
1959
- variables: []
1960
- });
1961
- }
1962
- });
1963
- }
1964
-
1965
- // Recursively check dependencies
1966
- if (comp.dependencies) {
1967
- comp.dependencies.forEach(depName => {
1968
- const depComp = componentByName.get(depName);
1969
- if (depComp) {
1970
- results.push(...extractComponentGraphQL(depComp, visited, depth + 1));
1971
- }
1972
- });
1973
- }
1974
-
1975
- return results;
1976
- }
1977
-
1978
- // Get GraphQL from page's components (including dependencies)
1979
- const pageComps = getPageComponents(page);
1980
- const componentGraphQL = [];
1981
- const visited = new Set();
1982
- pageComps.forEach(comp => {
1983
- componentGraphQL.push(...extractComponentGraphQL(comp, visited, 0));
1984
- });
1985
-
1986
- // Combine direct dataFetching with component GraphQL
1987
- const allDataFetching = [...(page.dataFetching || []), ...componentGraphQL];
1937
+ // Use dataFetching directly from engine.ts analysis
1938
+ const allDataFetching = [...(page.dataFetching || [])];
1988
1939
 
1989
1940
  if (allDataFetching.length > 0) {
1990
1941
  // Separate actual GraphQL operations from component references
@@ -2002,6 +1953,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2002
1953
  // Extract query name - remove arrows and (via xxx) pattern
2003
1954
  let queryName = rawName.replace(/^[\u2192\\s]+/, '').trim();
2004
1955
  let sourcePath = 'Direct';
1956
+ let sourceDetail = '';
2005
1957
  let depth = 0;
2006
1958
 
2007
1959
  // Method 1: Extract from (via xxx) pattern in operationName (from extractComponentGraphQL)
@@ -2011,13 +1963,35 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2011
1963
  queryName = queryName.replace(viaMatch[0], '').trim();
2012
1964
  depth = arrowCount || 1;
2013
1965
  }
2014
- // Method 2: Use df.source field (from engine.ts enrichPagesWithHookGraphQL)
1966
+ // Method 2: Use df.source field
2015
1967
  else if (source.startsWith('component:')) {
2016
1968
  sourcePath = source.replace('component:', '');
2017
1969
  depth = 1;
2018
1970
  } else if (source.startsWith('hook:')) {
2019
1971
  sourcePath = source.replace('hook:', '');
2020
1972
  depth = 1;
1973
+ } else if (source.startsWith('usedIn:')) {
1974
+ // Evidence-based source (file where the operation reference was found)
1975
+ sourcePath = 'Indirect';
1976
+ // Keep detail for modal
1977
+ sourceDetail = source.replace('usedIn:', '');
1978
+ depth = 1;
1979
+ } else if (source.startsWith('import:')) {
1980
+ sourcePath = 'Import';
1981
+ sourceDetail = source.replace('import:', '');
1982
+ depth = 1;
1983
+ } else if (source.startsWith('common:')) {
1984
+ sourcePath = 'Common (shared)';
1985
+ sourceDetail = source.replace('common:', '');
1986
+ depth = 1;
1987
+ } else if (source.startsWith('close:')) {
1988
+ sourcePath = 'Close (related)';
1989
+ sourceDetail = source.replace('close:', '');
1990
+ depth = 1;
1991
+ } else if (source.startsWith('indirect:')) {
1992
+ sourcePath = 'Indirect';
1993
+ sourceDetail = source.replace('indirect:', '');
1994
+ depth = 1;
2021
1995
  }
2022
1996
  // "import:xxx" or no source stays as Direct
2023
1997
 
@@ -2025,15 +1999,27 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2025
1999
  ...df,
2026
2000
  queryName,
2027
2001
  sourcePath,
2028
- depth
2002
+ sourceDetail: sourceDetail || undefined,
2003
+ depth,
2029
2004
  };
2030
2005
  });
2031
2006
 
2032
- // Sort by depth (lower first) then by source path
2007
+ // Sort by depth (lower first) then by source category priority
2008
+ const sourcePriority = (src) => {
2009
+ if (src === 'Direct') return 0;
2010
+ if (src === 'Close (related)') return 1;
2011
+ if (src === 'Import') return 2;
2012
+ if (src === 'Indirect') return 3;
2013
+ if (src === 'Common (shared)') return 4;
2014
+ return 5;
2015
+ };
2033
2016
  parsedOps.sort((a, b) => {
2034
2017
  if (a.depth !== b.depth) return a.depth - b.depth;
2035
2018
  if (a.sourcePath === 'Direct') return -1;
2036
2019
  if (b.sourcePath === 'Direct') return 1;
2020
+ const ap = sourcePriority(a.sourcePath);
2021
+ const bp = sourcePriority(b.sourcePath);
2022
+ if (ap !== bp) return ap - bp;
2037
2023
  return a.sourcePath.localeCompare(b.sourcePath);
2038
2024
  });
2039
2025
 
@@ -2104,7 +2090,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2104
2090
  sortedPaths.forEach(pathName => {
2105
2091
  const ops = groupedByPath.get(pathName);
2106
2092
  const isDirect = pathName === 'Direct';
2107
- const depthIndicator = isDirect ? '' : '\u21B3 ';
2093
+ // Remove the "\u21B3" prefix (UI becomes cleaner with <details>/<summary>)
2108
2094
  const pathLabel = isDirect ? 'Direct (this page)' : pathName;
2109
2095
  // UI indent: 4px per level added to base padding (10px)
2110
2096
  const uiLevel = pathToUiLevel.get(pathName) || 0;
@@ -2112,20 +2098,61 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2112
2098
  const totalPadding = 10 + uiIndent;
2113
2099
 
2114
2100
  // Group container, header aligned with detail-item content
2115
- dataHtml += '<div class="data-path-group" style="margin:8px 0">' +
2116
- '<div class="data-path-header" style="font-size:11px;color:var(--text2);margin-bottom:4px;padding-left:'+totalPadding+'px">' +
2117
- depthIndicator + '<span class="text-accent">' + pathLabel + '</span> (' + ops.length + ')' +
2118
- '</div>';
2119
-
2101
+ // Non-direct groups are collapsed by default to reduce noise from shared/common operations.
2102
+ const isClose = pathName === 'Close (related)';
2103
+ const isCollapsedByDefault = !(isDirect || isClose);
2104
+ const detailsOpenAttr = isCollapsedByDefault ? '' : ' open';
2105
+
2106
+ dataHtml += '<details class="data-path-group" style="margin:8px 0"' + detailsOpenAttr + '>' +
2107
+ '<summary class="data-path-header" style="--pad-left:'+totalPadding+'px">' +
2108
+ '<span class="text-accent">' + pathLabel + '</span> (' + ops.length + ')' +
2109
+ '</summary>';
2110
+
2111
+ // Secondary grouping inside the group by concrete source file (sourceDetail).
2112
+ // This reduces noise when a group contains operations from many files.
2113
+ const bySource = new Map();
2120
2114
  ops.forEach(op => {
2121
- const isQ = !op.type?.includes('Mutation');
2122
- const srcArg = op.sourcePath !== 'Direct' ? ",\\'"+op.sourcePath.replace(/'/g, "\\\\'")+"\\'": '';
2123
- // detail-item keeps base padding, adds indent
2124
- dataHtml += '<div class="detail-item data-op" style="padding:8px 10px 8px '+totalPadding+'px" onclick="showDataDetail(\\''+op.queryName.replace(/'/g, "\\\\'")+"\\'"+srcArg+')">' +
2125
- '<span class="tag '+(isQ?'tag-query':'tag-mutation')+'" style="font-size:10px">'+(isQ?'Q':'M')+'</span> '+op.queryName+'</div>';
2115
+ const key = op.sourceDetail || '';
2116
+ if (!bySource.has(key)) bySource.set(key, []);
2117
+ bySource.get(key).push(op);
2118
+ });
2119
+
2120
+ const sourceKeys = Array.from(bySource.keys()).sort((a, b) => {
2121
+ // Put "unknown/empty" at the end
2122
+ if (!a && b) return 1;
2123
+ if (a && !b) return -1;
2124
+ return String(a).localeCompare(String(b));
2126
2125
  });
2127
2126
 
2128
- dataHtml += '</div>';
2127
+ sourceKeys.forEach(sourceKey => {
2128
+ const sourceOps = bySource.get(sourceKey) || [];
2129
+ const hasSourceHeader = !!sourceKey && sourceKeys.length > 1;
2130
+
2131
+ if (hasSourceHeader) {
2132
+ const sourceFileName = String(sourceKey).split(/[\\/]/).pop() || String(sourceKey);
2133
+ dataHtml += '<div class="data-source-group" style="margin:6px 0">' +
2134
+ '<div class="data-source-header" style="padding-left:'+totalPadding+'px">' +
2135
+ '<span style="opacity:0.9">' + sourceFileName + '</span> (' + sourceOps.length + ')' +
2136
+ '</div>';
2137
+ }
2138
+
2139
+ sourceOps.forEach(op => {
2140
+ const isQ = !op.type?.includes('Mutation');
2141
+ const sourceForModal = op.sourceDetail || op.sourcePath;
2142
+ const srcArg = op.sourcePath !== 'Direct' && sourceForModal
2143
+ ? ",\\'"+sourceForModal.replace(/'/g, "\\\\'")+"\\'"
2144
+ : '';
2145
+ // detail-item keeps base padding, adds indent
2146
+ dataHtml += '<div class="detail-item data-op" style="padding:8px 10px 8px '+totalPadding+'px" onclick="showDataDetail(\\''+op.queryName.replace(/'/g, "\\\\'")+"\\'"+srcArg+')">' +
2147
+ '<span class="tag '+(isQ?'tag-query':'tag-mutation')+'" style="font-size:10px">'+(isQ?'Q':'M')+'</span> '+op.queryName+'</div>';
2148
+ });
2149
+
2150
+ if (hasSourceHeader) {
2151
+ dataHtml += '</div>';
2152
+ }
2153
+ });
2154
+
2155
+ dataHtml += '</details>';
2129
2156
  });
2130
2157
 
2131
2158
  dataHtml += '</div>';
@@ -2202,75 +2229,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2202
2229
  if (c.name) componentByName.set(c.name, c);
2203
2230
  });
2204
2231
 
2205
- // Check if a component (or its dependencies) uses GraphQL
2206
- function componentUsesGraphQL(comp, visited = new Set()) {
2207
- if (!comp || visited.has(comp.name)) return false;
2208
- visited.add(comp.name);
2209
-
2210
- // Check hooks for GraphQL queries (only match "Query: X" or "Mutation: X" format)
2211
- if (comp.hooks && comp.hooks.some(h =>
2212
- h.startsWith('Query: ') || h.startsWith('Mutation: ') || h.startsWith('Subscription: ')
2213
- )) {
2214
- return true;
2215
- }
2216
-
2217
- // Check dependencies recursively (limit depth to avoid infinite loops)
2218
- if (comp.dependencies && visited.size < 20) {
2219
- for (const dep of comp.dependencies) {
2220
- const depComp = componentByName.get(dep);
2221
- if (depComp && componentUsesGraphQL(depComp, visited)) {
2222
- return true;
2223
- }
2224
- }
2225
- }
2226
-
2227
- return false;
2228
- }
2229
-
2230
- // Find components related to a page (strict matching only)
2231
- function getPageComponents(page) {
2232
- const relatedComps = [];
2233
-
2234
- // 1. Find by page file path (exact match)
2235
- if (page.filePath) {
2236
- const pageComp = componentByFile.get(page.filePath);
2237
- if (pageComp) relatedComps.push(pageComp);
2238
-
2239
- // Check for PageContainer pattern based on file name
2240
- const baseName = page.filePath.split('/').pop()?.replace(/\\.(tsx?|jsx?)$/, '') || '';
2241
- const containerName = baseName.charAt(0).toUpperCase() + baseName.slice(1) + 'PageContainer';
2242
- const container = componentByName.get(containerName);
2243
- if (container) relatedComps.push(container);
2244
-
2245
- // Check for feature-based container: /pages/app/agencies \u2192 AgenciesPageContainer
2246
- const pathParts = page.filePath.split('/');
2247
- const pageIndex = pathParts.indexOf('pages');
2248
- if (pageIndex >= 0 && pathParts.length > pageIndex + 2) {
2249
- // Get the main feature segment (e.g., 'agencies' from '/pages/app/agencies/...')
2250
- const featureSegment = pathParts[pageIndex + 2];
2251
- if (featureSegment && !featureSegment.startsWith('[')) {
2252
- const featurePascal = featureSegment.split(/[-_]/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
2253
- const featureContainer = componentByName.get(featurePascal + 'PageContainer');
2254
- if (featureContainer) relatedComps.push(featureContainer);
2255
- }
2256
- }
2257
- }
2258
-
2259
- // 2. Find by component name from page data
2260
- if (page.component) {
2261
- const comp = componentByName.get(page.component);
2262
- if (comp) relatedComps.push(comp);
2263
-
2264
- // Also check for Container pattern
2265
- const containerName = page.component + 'Container';
2266
- const container = componentByName.get(containerName);
2267
- if (container) relatedComps.push(container);
2268
- }
2269
-
2270
- return relatedComps;
2271
- }
2272
-
2273
- // Include pages with direct GraphQL usage OR pages whose components use GraphQL
2232
+ // Include pages with direct GraphQL usage (from engine.ts enrichPagesWithHookGraphQL)
2274
2233
  const pagesWithGraphQL = new Set([
2275
2234
  // Pages with direct dataFetching
2276
2235
  ...pages.filter(p =>
@@ -2279,11 +2238,6 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2279
2238
  df.type === 'getServerSideProps' || df.type === 'getStaticProps'
2280
2239
  )
2281
2240
  ).map(p => p.path),
2282
- // Pages whose components (or dependencies) use GraphQL
2283
- ...pages.filter(p => {
2284
- const pageComps = getPageComponents(p);
2285
- return pageComps.some(comp => componentUsesGraphQL(comp));
2286
- }).map(p => p.path),
2287
2241
  // Pages whose files are referenced in GraphQL operation usedIn
2288
2242
  ...pages.filter(p => {
2289
2243
  if (!p.filePath) return false;
@@ -2776,8 +2730,15 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
2776
2730
 
2777
2731
  // Source info
2778
2732
  if (sourcePath) {
2779
- const isHook = sourcePath.startsWith('use');
2780
- html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+(isHook?'Hook':'Component')+': <span class="text-accent">'+sourcePath+'</span></div></div>';
2733
+ const looksLikeFile =
2734
+ sourcePath.includes('/') ||
2735
+ sourcePath.endsWith('.ts') ||
2736
+ sourcePath.endsWith('.tsx') ||
2737
+ sourcePath.endsWith('.js') ||
2738
+ sourcePath.endsWith('.jsx');
2739
+ const isHook = !looksLikeFile && sourcePath.startsWith('use');
2740
+ const label = looksLikeFile ? 'File' : isHook ? 'Hook' : 'Component';
2741
+ html += '<div class="detail-section"><h4>Source</h4><div class="detail-item" style="font-size:12px">via '+label+': <span class="text-accent">'+sourcePath+'</span></div></div>';
2781
2742
  }
2782
2743
 
2783
2744
  // Operation Name with copy button
@@ -3568,13 +3529,10 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
3568
3529
  const page = pageMap.get(pagePath);
3569
3530
  if (!page) return;
3570
3531
 
3571
- // Get page's components and calculate total GraphQL
3572
- const pageComps = getPageComponents(page);
3573
- const visited = new Set();
3532
+ // Count GraphQL from dataFetching only (already enriched by engine.ts)
3574
3533
  let queries = 0;
3575
3534
  let mutations = 0;
3576
3535
 
3577
- // Count from direct dataFetching
3578
3536
  (page.dataFetching || []).forEach(df => {
3579
3537
  if (df.type?.includes('Mutation')) {
3580
3538
  mutations++;
@@ -3583,16 +3541,6 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
3583
3541
  }
3584
3542
  });
3585
3543
 
3586
- // Count from component hooks (simplified - just count hooks)
3587
- pageComps.forEach(comp => {
3588
- if (!comp || visited.has(comp.name)) return;
3589
- visited.add(comp.name);
3590
- (comp.hooks || []).forEach(hook => {
3591
- if (hook.includes('Mutation:')) mutations++;
3592
- else if (hook.includes('Query:')) queries++;
3593
- });
3594
- });
3595
-
3596
3544
  // Update Q tag
3597
3545
  const qTag = item.querySelector('.tag-query');
3598
3546
  if (qTag) {
@@ -3621,8 +3569,8 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
3621
3569
  setTimeout(updatePageGqlCounts, 100);
3622
3570
  </script>
3623
3571
  </body>
3624
- </html>`}buildTreeHtml(s,n){let r=["#ef4444","#f97316","#eab308","#22c55e","#14b8a6","#3b82f6","#8b5cf6","#ec4899"],u=0;return Array.from(s.entries()).sort((t,o)=>t[0].localeCompare(o[0])).map(([t,o])=>{let e=r[u++%r.length],i=o.sort((a,h)=>a.path.localeCompare(h.path)),l=new Set(i.map(a=>a.path)),p=new Map;for(let a of i){let h=a.path.split("/").filter(Boolean),v=0;for(let y=h.length-1;y>=1;y--){let d="/"+h.slice(0,y).join("/");if(l.has(d)){v=(p.get(d)??0)+1;break}}p.set(a.path,v);}let f=i.map(a=>{let h=this.getPageType(a.path),v=p.get(a.path)??0,d=a.repo||"",C=n.some(g=>g.repo&&g.repo!==d),m=d.split("/").pop()?.split("-").map(g=>g.substring(0,4)).join("-")||d.substring(0,8),b=C&&d?`<span class="tag tag-repo" title="${d}">${m}</span>`:"",w=/^\/[A-Z]/.test(a.path)||a.filePath&&a.filePath.includes("components/pages"),x=w&&a.filePath?a.filePath.replace(/\.tsx?$/,"").replace(/^(frontend\/src\/|src\/)/,""):a.path,c=w?'<span class="tag tag-info" title="SPA Component Page">SPA</span>':"";return `<div class="page-item" data-path="${a.path}" data-repo="${d}" onclick="selectPage('${a.path}')" style="--depth:${v}">
3625
- <span class="page-type" style="--type-color:${h.color}">${h.label}</span>
3572
+ </html>`}buildTreeHtml(s,n){let r=["#ef4444","#f97316","#eab308","#22c55e","#14b8a6","#3b82f6","#8b5cf6","#ec4899"],f=0;return Array.from(s.entries()).sort((t,l)=>t[0].localeCompare(l[0])).map(([t,l])=>{let e=r[f++%r.length],i=l.sort((a,m)=>a.path.localeCompare(m.path)),o=new Set(i.map(a=>a.path)),p=new Map;for(let a of i){let m=a.path.split("/").filter(Boolean),v=0;for(let y=m.length-1;y>=1;y--){let d="/"+m.slice(0,y).join("/");if(o.has(d)){v=(p.get(d)??0)+1;break}}p.set(a.path,v);}let u=i.map(a=>{let m=this.getPageType(a.path),v=p.get(a.path)??0,d=a.repo||"",C=n.some(g=>g.repo&&g.repo!==d),h=d.split("/").pop()?.split("-").map(g=>g.substring(0,4)).join("-")||d.substring(0,8),b=C&&d?`<span class="tag tag-repo" title="${d}">${h}</span>`:"",w=/^\/[A-Z]/.test(a.path)||a.filePath&&a.filePath.includes("components/pages"),x=w&&a.filePath?a.filePath.replace(/\.tsx?$/,"").replace(/^(frontend\/src\/|src\/)/,""):a.path,c=w?'<span class="tag tag-info" title="SPA Component Page">SPA</span>':"";return `<div class="page-item" data-path="${a.path}" data-repo="${d}" onclick="selectPage('${a.path}')" style="--depth:${v}">
3573
+ <span class="page-type" style="--type-color:${m.color}">${m.label}</span>
3626
3574
  <span class="page-path">${x}</span>
3627
3575
  <div class="page-tags">
3628
3576
  ${b}
@@ -3635,7 +3583,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
3635
3583
  <div class="group-header" onclick="toggleGroup(this)" style="--group-color:${e}">
3636
3584
  <span class="group-arrow">\u25BC</span>
3637
3585
  <span class="group-name">/${t}</span>
3638
- <span class="group-count">${o.length}</span>
3586
+ <span class="group-count">${l.length}</span>
3639
3587
  </div>
3640
- <div class="group-content">${f}</div>
3588
+ <div class="group-content">${u}</div>
3641
3589
  </div>`}).join("")}getPageType(s){let n=s.split("/").filter(Boolean).pop()||"";return n==="new"||s.endsWith("/new")?{label:"CREATE",color:"#22c55e"}:n==="edit"||s.includes("/edit")?{label:"EDIT",color:"#f59e0b"}:n.startsWith("[")||n.startsWith(":")?{label:"DETAIL",color:"#3b82f6"}:s.includes("setting")?{label:"SETTINGS",color:"#6b7280"}:{label:"LIST",color:"#06b6d4"}}};export{P as a};
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import {a,b}from'./chunk-LDX6WPHR.js';import'./chunk-P7MX3M5U.js';import'./chunk-VV3A3UE3.js';import'./chunk-TNUKDIO7.js';import'./chunk-HPBPEGHS.js';import'./chunk-SMN6XFMS.js';import'./chunk-H7VVRHQZ.js';import {Command}from'commander';import s from'chalk';import*as c from'path';import*as n from'fs/promises';import*as v from'os';function P(){let e=v.tmpdir();return c.join(e,`repomap-${Date.now()}`)}function C(e){let t=async()=>{try{await n.rm(e,{recursive:!0,force:!0});}catch{}process.exit(0);};process.on("SIGINT",t),process.on("SIGTERM",t);}var d=new Command;d.name("repomap").description("Interactive documentation generator for code repositories").version("0.6.0");async function x(e){let t=c.basename(e),a=false,o=c.join(e,"Gemfile"),p=c.join(e,"config","routes.rb");try{await n.access(o),await n.access(p);let g=await n.readFile(o,"utf-8");a=g.includes("gem 'rails'")||g.includes('gem "rails"');}catch{}let r=c.join(e,"package.json"),i=false,l=false,m={};try{let g=JSON.parse(await n.readFile(r,"utf-8")),w={...g.dependencies,...g.devDependencies};i=!!w.react,l=!!w.next;let f=["src/pages","pages","app","src/app","frontend/src"];for(let u of f)try{await n.access(c.join(e,u)),m.pagesDir=u;break}catch{}let b=["src/features","features","src/modules","modules","frontend/src"];for(let u of b)try{await n.access(c.join(e,u)),m.featuresDir=u;break}catch{}let D=["src/components","components","src/common/components","frontend/src"];for(let u of D)try{await n.access(c.join(e,u)),m.componentsDir=u;break}catch{}}catch{}let h=[];(i||l)&&h.push("pages","graphql","dataflow","rest-api");let y="generic";return l?y="nextjs":a&&(y="rails"),!a&&!i&&!l?null:{name:t,displayName:t,description:a&&i?"Rails + React application":a?"Rails application":"",path:e,branch:"main",type:y,analyzers:h,settings:m}}async function E(e){let t=await x(e);if(!t)throw new Error("Could not detect project. Please create a repomap.config.ts file or run 'repomap init'.");return {outputDir:"./.repomap",site:{title:`${t.displayName} Documentation`,description:"Auto-generated documentation",baseUrl:"/docs"},repositories:[t],analysis:{include:["**/*.tsx","**/*.ts"],exclude:["**/node_modules/**","**/__tests__/**","**/*.test.*","**/*.spec.*","**/dist/**","**/.next/**"],maxDepth:5},diagrams:{enabled:true,types:["flowchart","sequence"],theme:"default"},watch:{enabled:false,debounce:1e3},integrations:{github:{enabled:false,organization:""},slack:{enabled:false}}}}async function $(e,t){let a=e?[e]:["repomap.config.ts","repomap.config.js","repomap.config.mjs"];for(let o of a){let p=c.resolve(t,o);try{await n.access(p);let r=await import(p);return r.config||r.default}catch{}}return E(t)}d.command("generate").description("Generate documentation from source code").option("-c, --config <path>","Path to config file").option("-o, --output <path>","Output directory").option("--temp","Use temporary directory (no files in repository)").option("--repo <name>","Analyze specific repository only").option("--watch","Watch for changes and regenerate").option("--format <type>","Output format: json, html, markdown (default: all)","all").option("--ci","CI mode: minimal output, exit codes for errors").option("--static","Generate standalone HTML files (for GitHub Pages)").action(async e=>{let t=e.ci||process.env.CI==="true";t||console.log(s.blue.bold(`
2
+ import {a,b}from'./chunk-6AZNHUOB.js';import'./chunk-NQMJ3QRX.js';import'./chunk-VV3A3UE3.js';import'./chunk-QDVE7MT3.js';import'./chunk-2XZSFAJF.js';import'./chunk-WZAAA7DS.js';import'./chunk-H7VVRHQZ.js';import {Command}from'commander';import s from'chalk';import*as c from'path';import*as n from'fs/promises';import*as v from'os';function P(){let e=v.tmpdir();return c.join(e,`repomap-${Date.now()}`)}function C(e){let t=async()=>{try{await n.rm(e,{recursive:!0,force:!0});}catch{}process.exit(0);};process.on("SIGINT",t),process.on("SIGTERM",t);}var d=new Command;d.name("repomap").description("Interactive documentation generator for code repositories").version("0.6.0");async function x(e){let t=c.basename(e),a=false,o=c.join(e,"Gemfile"),p=c.join(e,"config","routes.rb");try{await n.access(o),await n.access(p);let g=await n.readFile(o,"utf-8");a=g.includes("gem 'rails'")||g.includes('gem "rails"');}catch{}let r=c.join(e,"package.json"),i=false,l=false,m={};try{let g=JSON.parse(await n.readFile(r,"utf-8")),w={...g.dependencies,...g.devDependencies};i=!!w.react,l=!!w.next;let f=["src/pages","pages","app","src/app","frontend/src"];for(let u of f)try{await n.access(c.join(e,u)),m.pagesDir=u;break}catch{}let b=["src/features","features","src/modules","modules","frontend/src"];for(let u of b)try{await n.access(c.join(e,u)),m.featuresDir=u;break}catch{}let D=["src/components","components","src/common/components","frontend/src"];for(let u of D)try{await n.access(c.join(e,u)),m.componentsDir=u;break}catch{}}catch{}let h=[];(i||l)&&h.push("pages","graphql","dataflow","rest-api");let y="generic";return l?y="nextjs":a&&(y="rails"),!a&&!i&&!l?null:{name:t,displayName:t,description:a&&i?"Rails + React application":a?"Rails application":"",path:e,branch:"main",type:y,analyzers:h,settings:m}}async function E(e){let t=await x(e);if(!t)throw new Error("Could not detect project. Please create a repomap.config.ts file or run 'repomap init'.");return {outputDir:"./.repomap",site:{title:`${t.displayName} Documentation`,description:"Auto-generated documentation",baseUrl:"/docs"},repositories:[t],analysis:{include:["**/*.tsx","**/*.ts"],exclude:["**/node_modules/**","**/__tests__/**","**/*.test.*","**/*.spec.*","**/dist/**","**/.next/**"],maxDepth:5},diagrams:{enabled:true,types:["flowchart","sequence"],theme:"default"},watch:{enabled:false,debounce:1e3},integrations:{github:{enabled:false,organization:""},slack:{enabled:false}}}}async function $(e,t){let a=e?[e]:["repomap.config.ts","repomap.config.js","repomap.config.mjs"];for(let o of a){let p=c.resolve(t,o);try{await n.access(p);let r=await import(p);return r.config||r.default}catch{}}return E(t)}d.command("generate").description("Generate documentation from source code").option("-c, --config <path>","Path to config file").option("-o, --output <path>","Output directory").option("--temp","Use temporary directory (no files in repository)").option("--repo <name>","Analyze specific repository only").option("--watch","Watch for changes and regenerate").option("--format <type>","Output format: json, html, markdown (default: all)","all").option("--ci","CI mode: minimal output, exit codes for errors").option("--static","Generate standalone HTML files (for GitHub Pages)").action(async e=>{let t=e.ci||process.env.CI==="true";t||console.log(s.blue.bold(`
3
3
  \u{1F4DA} Repomap
4
4
  `));try{let a$1=process.cwd(),o=await $(e.config,a$1);e.temp&&(o.outputDir=P(),t||console.log(s.cyan(`\u{1F4C1} Using temp directory: ${o.outputDir}
5
5
  `))),e.output&&(o.outputDir=e.output),e.repo&&(o.repositories=o.repositories.filter(r=>r.name===e.repo),o.repositories.length===0&&(console.error(s.red(`Repository "${e.repo}" not found in config`)),process.exit(1)));let p=new a(o);if(e.watch)console.log(s.yellow(`\u{1F440} Watch mode enabled. Press Ctrl+C to stop.
6
- `)),await F(p,o);else {let r=await p.generate();if(e.format==="json"||e.static){let i=c.join(o.outputDir,"report.json");await n.mkdir(o.outputDir,{recursive:!0}),await n.writeFile(i,JSON.stringify(r,null,2)),t||console.log(s.gray(` \u2192 ${i}`));}if(e.static&&await N(o,r,t),!t)S(r);else {let i=r.repositories.reduce((l,m)=>l+m.summary.totalPages,0);console.log(`\u2705 Generated: ${i} pages, ${r.repositories.length} repos`);}}}catch(a){console.error(t?`Error: ${a.message}`:s.red("Error:"),a.message),process.exit(1);}});async function N(e,t,a){let{PageMapGenerator:o}=await import('./page-map-generator-2XQB7RWO.js'),{detectEnvironments:p}=await import('./env-detector-BIWJ7OYF.js'),r=e.outputDir;await n.mkdir(r,{recursive:true});let i=e.repositories[0]?.path||process.cwd(),l=await p(i),m=null;if(l.hasRails){let{analyzeRailsApp:f}=await import('./rails-3HNUFTQV.js');m=await f(i);}let y=new o().generatePageMapHtml(t,{envResult:l,railsAnalysis:m,staticMode:true});if(await n.writeFile(c.join(r,"index.html"),y),a||console.log(s.gray(` \u2192 ${c.join(r,"index.html")}`)),m){let{RailsMapGenerator:f}=await import('./rails-map-generator-77ATUFMP.js'),D=new f().generateFromResult(m);await n.writeFile(c.join(r,"rails-map.html"),D),a||console.log(s.gray(` \u2192 ${c.join(r,"rails-map.html")}`));}let g=["common.css","page-map.css","docs.css","rails-map.css"],w=c.join(r,"assets");await n.mkdir(w,{recursive:true});for(let f of g)try{let b=new URL(`./generators/assets/${f}`,import.meta.url),D=await n.readFile(b,"utf-8");await n.writeFile(c.join(w,f),D);}catch{}a||console.log(s.green(`
6
+ `)),await F(p,o);else {let r=await p.generate();if(e.format==="json"||e.static){let i=c.join(o.outputDir,"report.json");await n.mkdir(o.outputDir,{recursive:!0}),await n.writeFile(i,JSON.stringify(r,null,2)),t||console.log(s.gray(` \u2192 ${i}`));}if(e.static&&await N(o,r,t),!t)S(r);else {let i=r.repositories.reduce((l,m)=>l+m.summary.totalPages,0);console.log(`\u2705 Generated: ${i} pages, ${r.repositories.length} repos`);}}}catch(a){console.error(t?`Error: ${a.message}`:s.red("Error:"),a.message),process.exit(1);}});async function N(e,t,a){let{PageMapGenerator:o}=await import('./page-map-generator-HROGGVAQ.js'),{detectEnvironments:p}=await import('./env-detector-BIWJ7OYF.js'),r=e.outputDir;await n.mkdir(r,{recursive:true});let i=e.repositories[0]?.path||process.cwd(),l=await p(i),m=null;if(l.hasRails){let{analyzeRailsApp:f}=await import('./rails-3HNUFTQV.js');m=await f(i);}let y=new o().generatePageMapHtml(t,{envResult:l,railsAnalysis:m,staticMode:true});if(await n.writeFile(c.join(r,"index.html"),y),a||console.log(s.gray(` \u2192 ${c.join(r,"index.html")}`)),m){let{RailsMapGenerator:f}=await import('./rails-map-generator-DF2YAXW4.js'),D=new f().generateFromResult(m);await n.writeFile(c.join(r,"rails-map.html"),D),a||console.log(s.gray(` \u2192 ${c.join(r,"rails-map.html")}`));}let g=["common.css","page-map.css","docs.css","rails-map.css"],w=c.join(r,"assets");await n.mkdir(w,{recursive:true});for(let f of g)try{let b=new URL(`./generators/assets/${f}`,import.meta.url),D=await n.readFile(b,"utf-8");await n.writeFile(c.join(w,f),D);}catch{}a||console.log(s.green(`
7
7
  \u2705 Static site generated: ${r}`));}d.command("serve").description("Start local documentation server with live reload").option("-c, --config <path>","Path to config file").option("--path <path>","Path to repository to analyze (auto-detect if no config)").option("-o, --output <path>","Output directory (default: .repomap in target path)").option("-p, --port <number>","Server port","3030").option("--temp","Use temporary directory (no files in repository)").option("--no-open","Don't open browser automatically").action(async e=>{console.log(s.blue.bold(`
8
8
  \u{1F310} Repomap
9
9
  `));try{let t=e.path||process.cwd(),a=await $(e.config,t);e.temp&&(a.outputDir=P(),console.log(s.cyan(`\u{1F4C1} Using temp directory: ${a.outputDir}
@@ -56,7 +56,7 @@ export default config;
56
56
  `;await n.writeFile(t,h,"utf-8"),console.log(s.green(`\u2705 Created ${t}`)),console.log(s.gray(`
57
57
  Run 'npx repomap serve' to start the documentation server.`));}catch(a){console.error(s.red("Failed to create config:"),a.message);}});d.command("rails").description("Analyze a Rails application and generate interactive map").option("--path <path>","Path to Rails application").option("-o, --output <path>","Output HTML file path").action(async e=>{console.log(s.blue.bold(`
58
58
  \u{1F6E4}\uFE0F Repomap Rails
59
- `));try{let t=e.path||process.cwd();try{await n.access(c.join(t,"config","routes.rb"));}catch{console.error(s.red("Not a Rails project (config/routes.rb not found)")),process.exit(1);}let{RailsMapGenerator:a}=await import('./rails-map-generator-77ATUFMP.js'),o=e.output||c.join(t,"rails-map.html");await new a(t).generate({title:`${c.basename(t)} - Rails Map`,outputPath:o}),console.log(s.green(`\u2705 Rails map generated: ${o}`));let{exec:r}=await import('child_process');r(`open "${o}"`);}catch(t){console.error(s.red("Error:"),t.message),process.exit(1);}});d.command("diff").description("Show documentation changes since last generation").option("-c, --config <path>","Path to config file").action(async e=>{console.log(s.blue.bold(`
59
+ `));try{let t=e.path||process.cwd();try{await n.access(c.join(t,"config","routes.rb"));}catch{console.error(s.red("Not a Rails project (config/routes.rb not found)")),process.exit(1);}let{RailsMapGenerator:a}=await import('./rails-map-generator-DF2YAXW4.js'),o=e.output||c.join(t,"rails-map.html");await new a(t).generate({title:`${c.basename(t)} - Rails Map`,outputPath:o}),console.log(s.green(`\u2705 Rails map generated: ${o}`));let{exec:r}=await import('child_process');r(`open "${o}"`);}catch(t){console.error(s.red("Error:"),t.message),process.exit(1);}});d.command("diff").description("Show documentation changes since last generation").option("-c, --config <path>","Path to config file").action(async e=>{console.log(s.blue.bold(`
60
60
  \u{1F4CA} Documentation Diff
61
61
  `));try{let t=process.cwd(),a$1=await $(e.config,t),o=c.join(a$1.outputDir,"report.json");if(!await n.access(o).then(()=>!0).catch(()=>!1)){console.log(s.yellow("No previous report found. Run 'generate' first."));return}let r=JSON.parse(await n.readFile(o,"utf-8")),l=await new a(a$1).generate();k(r,l);}catch(t){console.error(s.red("Failed to generate diff:"),t.message);}});async function F(e,t){await e.generate();let a=t.repositories.map(o=>o.path);for(let o of a){let p=n.watch(o,{recursive:true}),r=null;for await(let i of p)i.filename&&(i.filename.endsWith(".ts")||i.filename.endsWith(".tsx"))&&(r&&clearTimeout(r),r=setTimeout(async()=>{console.log(s.yellow(`
62
62
  \u{1F504} Change detected: ${i.filename}`)),await e.generate();},t.watch.debounce));}}function S(e){console.log(s.green.bold(`