@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.
- package/dist/analyzers/index.js +1 -1
- package/dist/chunk-2XZSFAJF.js +20 -0
- package/dist/{chunk-LDX6WPHR.js → chunk-6AZNHUOB.js} +154 -22
- package/dist/{chunk-P7MX3M5U.js → chunk-NQMJ3QRX.js} +427 -75
- package/dist/{chunk-TNUKDIO7.js → chunk-QDVE7MT3.js} +4 -4
- package/dist/{chunk-SMN6XFMS.js → chunk-WZAAA7DS.js} +123 -175
- package/dist/cli.js +3 -3
- package/dist/generators/assets/docs.css +154 -1
- package/dist/generators/assets/page-map.css +59 -0
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +1 -1
- package/dist/page-map-generator-HROGGVAQ.js +1 -0
- package/dist/rails-map-generator-DF2YAXW4.js +1 -0
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-HPBPEGHS.js +0 -19
- package/dist/page-map-generator-2XQB7RWO.js +0 -1
- package/dist/rails-map-generator-77ATUFMP.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let r=[],
|
|
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} ${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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
|
-
${
|
|
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="${
|
|
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&&
|
|
92
|
-
${
|
|
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
|
-
${
|
|
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: ${
|
|
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 = ${
|
|
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 = ${
|
|
158
|
+
const railsRoutes = ${u};
|
|
159
159
|
const railsControllers = ${a};
|
|
160
|
-
const railsModels = ${
|
|
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 -
|
|
1934
|
-
//
|
|
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
|
-
//
|
|
1938
|
-
|
|
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
|
|
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
|
-
|
|
2002
|
+
sourceDetail: sourceDetail || undefined,
|
|
2003
|
+
depth,
|
|
2029
2004
|
};
|
|
2030
2005
|
});
|
|
2031
2006
|
|
|
2032
|
-
// Sort by depth (lower first) then by source
|
|
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
|
-
|
|
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
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
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
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
2780
|
-
|
|
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
|
-
//
|
|
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"],
|
|
3625
|
-
<span class="page-type" style="--type-color:${
|
|
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">${
|
|
3586
|
+
<span class="group-count">${l.length}</span>
|
|
3639
3587
|
</div>
|
|
3640
|
-
<div class="group-content">${
|
|
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-
|
|
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-
|
|
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-
|
|
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(`
|