@wtdlee/repomap 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/analyzers/index.d.ts +8 -8
- package/dist/analyzers/index.js +1 -1
- package/dist/{chunk-SMN6XFMS.js → chunk-ATRSGO6O.js} +270 -196
- package/dist/chunk-IGRCLYVZ.js +451 -0
- package/dist/{chunk-P7MX3M5U.js → chunk-LHP2OKKA.js} +428 -76
- package/dist/chunk-QBSB6BIU.js +20 -0
- package/dist/{chunk-LDX6WPHR.js → chunk-YKPXOHWZ.js} +196 -37
- package/dist/chunk-ZWRDP37E.js +1 -0
- package/dist/cli.js +14 -14
- package/dist/{dataflow-analyzer-CJ2T0cGS.d.ts → dataflow-analyzer-DIUsRpvv.d.ts} +18 -0
- package/dist/{env-detector-BIWJ7OYF.js → env-detector-RVGPBVNJ.js} +1 -1
- package/dist/generators/assets/docs.css +176 -1
- package/dist/generators/assets/page-map.css +59 -0
- package/dist/generators/assets/rails-map.css +13 -0
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +1 -1
- package/dist/page-map-generator-LTVRHSDC.js +1 -0
- package/dist/{rails-3HNUFTQV.js → rails-57MNOGHR.js} +1 -1
- package/dist/rails-map-generator-JNU5QHX4.js +1 -0
- package/dist/server/index.js +1 -1
- package/dist/types.d.ts +34 -1
- package/package.json +1 -1
- package/dist/chunk-HPBPEGHS.js +0 -19
- package/dist/chunk-TNUKDIO7.js +0 -5
- package/dist/page-map-generator-2XQB7RWO.js +0 -1
- package/dist/rails-map-generator-77ATUFMP.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var E=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(i,n){let r=[],u=n?.envResult,t=n?.railsAnalysis,o=n?.activeTab||"pages",e=i.repositories[0]?.displayName||i.repositories[0]?.name||"Repository";for(let p of i.repositories){this.graphqlOps.push(...p.analysis?.graphqlOperations||[]),this.apiCalls.push(...p.analysis?.apiCalls||[]);let g=p.analysis?.components||[];for(let a of g)this.components.push({name:a.name,filePath:a.filePath,type:a.type,dependencies:a.dependencies||[],hooks:a.hooks||[]});}for(let p of i.repositories){let g=p.analysis?.pages||[];for(let a of g)r.push({...a,repo:p.name,children:[],parent:null,depth:0});}let{rootPages:l,relations:s}=this.buildHierarchy(r);return this.renderPageMapHtml(r,l,s,e,{envResult:u,railsAnalysis:t,activeTab:o})}buildHierarchy(i){let n=new Map,r=[];for(let t of i)n.set(t.path,t);for(let t of i){let o=t.path.split("/").filter(Boolean);for(let e=o.length-1;e>=1;e--){let l="/"+o.slice(0,e).join("/"),s=n.get(l);if(s){t.parent=l,t.depth=s.depth+1,s.children.includes(t.path)||s.children.push(t.path),r.push({from:l,to:t.path,type:"parent-child",description:`Sub-page of ${l}`});break}}if(t.parent||(t.depth=Math.max(0,o.length-1)),t.layout)for(let e of i)e.path!==t.path&&e.layout===t.layout&&(r.find(s=>s.type==="same-layout"&&(s.from===t.path&&s.to===e.path||s.from===e.path&&s.to===t.path))||r.push({from:t.path,to:e.path,type:"same-layout",description:`Both use ${t.layout}`}));}return {rootPages:i.filter(t=>!t.parent).sort((t,o)=>t.path.localeCompare(o.path)),relations:r}}renderPageMapHtml(i,n,r,u,t){let o=t?.envResult,e=t?.railsAnalysis,l=t?.activeTab||"pages",s=c=>JSON.stringify(c).replace(/</g,"\\u003c"),p=s(this.graphqlOps.map(c=>({name:c.name,type:c.type,variables:c.variables,fields:c.fields,returnType:c.returnType,usedIn:c.usedIn}))),g=s(this.components),a=s(i),h=s(r),v=s(this.apiCalls),y=e?s(e.routes.routes):"[]",d=e?s(e.controllers.controllers):"[]",w=e?s(e.models.models):"[]",C=e?s(e.views):'{ "views": [], "pages": [], "summary": {} }',P=e?s(e.react):'{ "components": [], "entryPoints": [], "summary": {} }',x=e?s(e.grpc):'{ "services": [] }',S=e?s(e.summary):"null",m=o?.hasRails||false,f=o?.hasNextjs||false,R=o?.hasReact||false,b=new Map;for(let c of i){let k=c.path.split("/").filter(Boolean)[0]||"root";b.has(k)||b.set(k,[]),b.get(k)?.push(c);}return `<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -16,15 +16,15 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
16
16
|
<div style="display:flex;align-items:center;gap:24px">
|
|
17
17
|
<h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${u}</h1>
|
|
18
18
|
<nav style="display:flex;gap:4px">
|
|
19
|
-
<a href="/page-map" class="nav-link ${
|
|
20
|
-
${m?`<a href="/rails-map" class="nav-link ${
|
|
19
|
+
<a href="/page-map" class="nav-link ${l==="pages"?"active":""}">Page Map</a>
|
|
20
|
+
${m?`<a href="/rails-map" class="nav-link ${l==="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&&
|
|
27
|
+
${m&&f?`<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>
|
|
@@ -63,7 +63,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
63
63
|
<!-- Frontend Stats -->
|
|
64
64
|
<h3 style="margin-top:16px;font-size:10px;text-transform:uppercase;color:var(--text2);letter-spacing:1px">Frontend</h3>
|
|
65
65
|
<div class="stats" id="stats-container">
|
|
66
|
-
<div class="stat" data-filter="pages"><div class="stat-val">${
|
|
66
|
+
<div class="stat" data-filter="pages"><div class="stat-val">${i.length}</div><div class="stat-label">Pages</div></div>
|
|
67
67
|
<div class="stat" data-filter="hierarchies"><div class="stat-val">${r.filter(c=>c.type==="parent-child").length}</div><div class="stat-label">Hierarchies</div></div>
|
|
68
68
|
<div class="stat" data-filter="graphql"><div class="stat-val">${this.graphqlOps.length}</div><div class="stat-label">GraphQL</div></div>
|
|
69
69
|
<div class="stat" data-filter="restapi"><div class="stat-val">${this.apiCalls.length}</div><div class="stat-label">REST API</div></div>
|
|
@@ -84,17 +84,17 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
84
84
|
|
|
85
85
|
<div class="content">
|
|
86
86
|
<!-- Pages Tree View (for all screens - Next.js/React/Rails) -->
|
|
87
|
-
<div class="tree-view ${
|
|
88
|
-
${
|
|
87
|
+
<div class="tree-view ${l==="pages"?"active":""}" id="tree-view" data-tab="pages">
|
|
88
|
+
${i.length>0?this.buildTreeHtml(b,i):""}
|
|
89
89
|
<div id="page-map-react-components-section" style="${m?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
|
|
90
90
|
</div>
|
|
91
|
-
<div id="page-map-rails-section" style="${
|
|
92
|
-
${m&&
|
|
91
|
+
<div id="page-map-rails-section" style="${i.length>0&&m?"margin-top:20px;border-top:1px solid var(--bg3);padding-top:20px":""}">
|
|
92
|
+
${m&&i.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
|
-
<div class="tree-view ${
|
|
97
|
+
<div class="tree-view ${l==="rails"?"active":""}" id="rails-tree-view" data-tab="rails">
|
|
98
98
|
<div id="rails-routes-container">
|
|
99
99
|
${m?'<div class="empty-state-sm">Loading Rails routes...</div>':'<div class="empty-state">No Rails environment detected</div>'}
|
|
100
100
|
</div>
|
|
@@ -139,32 +139,34 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
139
139
|
// Environment detection results
|
|
140
140
|
const envInfo = {
|
|
141
141
|
hasRails: ${m},
|
|
142
|
-
hasNextjs: ${
|
|
143
|
-
hasReact: ${
|
|
142
|
+
hasNextjs: ${f},
|
|
143
|
+
hasReact: ${R}
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
// Frontend data
|
|
147
|
-
const pages = ${
|
|
148
|
-
const relations = ${
|
|
149
|
-
const graphqlOps = ${
|
|
150
|
-
const components = ${
|
|
151
|
-
const apiCallsData = ${
|
|
147
|
+
const pages = ${a};
|
|
148
|
+
const relations = ${h};
|
|
149
|
+
const graphqlOps = ${p};
|
|
150
|
+
const components = ${g};
|
|
151
|
+
const apiCallsData = ${v};
|
|
152
152
|
window.apiCalls = apiCallsData;
|
|
153
153
|
const pageMap = new Map(pages.map(p => [p.path, p]));
|
|
154
154
|
const gqlMap = new Map(graphqlOps.map(op => [op.name, op]));
|
|
155
155
|
const compMap = new Map(components.map(c => [c.name, c]));
|
|
156
|
+
// Mapping metadata for UI debugging (key -> {confidence, evidence})
|
|
157
|
+
const opMetaMap = new Map();
|
|
156
158
|
|
|
157
159
|
// Rails data (if available)
|
|
158
|
-
const railsRoutes = ${
|
|
159
|
-
const railsControllers = ${
|
|
160
|
-
const railsModels = ${
|
|
161
|
-
const railsViews = ${
|
|
162
|
-
const railsReact = ${
|
|
163
|
-
const railsGrpc = ${
|
|
164
|
-
const railsSummary = ${
|
|
160
|
+
const railsRoutes = ${y};
|
|
161
|
+
const railsControllers = ${d};
|
|
162
|
+
const railsModels = ${w};
|
|
163
|
+
const railsViews = ${C};
|
|
164
|
+
const railsReact = ${P};
|
|
165
|
+
const railsGrpc = ${x};
|
|
166
|
+
const railsSummary = ${S};
|
|
165
167
|
|
|
166
168
|
// Current active tab state
|
|
167
|
-
let currentMainTab = '${
|
|
169
|
+
let currentMainTab = '${l}';
|
|
168
170
|
|
|
169
171
|
// Modal history stack for back navigation
|
|
170
172
|
const modalHistory = [];
|
|
@@ -629,12 +631,18 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
629
631
|
html += '<div class="route-stat" data-filter="grpc"><div class="route-stat-val cyan">' + totalWithGrpc + '</div><div class="route-stat-label">gRPC</div></div>';
|
|
630
632
|
html += '</div>';
|
|
631
633
|
// Analysis coverage indicator
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
const
|
|
634
|
+
{
|
|
635
|
+
const totalRoutesAnalyzed = combinedData.length;
|
|
636
|
+
const totalWithControllerMatch = combinedData.filter(r => r.controllerInfo).length;
|
|
637
|
+
const overallCoverage = totalRoutesAnalyzed > 0 ? Math.round((totalWithActionInfo / totalRoutesAnalyzed) * 100) : 0;
|
|
638
|
+
const matchedCoverage = totalWithControllerMatch > 0 ? Math.round((totalWithActionInfo / totalWithControllerMatch) * 100) : 0;
|
|
639
|
+
const overallTooltip = 'Percentage of routes that were successfully matched with controller actions to extract details. This includes routes pointing to external controllers (gems/engines) which are not analyzable from app/controllers.';
|
|
640
|
+
const matchedTooltip = 'Percentage of routes with a matched app controller (app/controllers) where the target action method was found and parsed.';
|
|
641
|
+
const overallClass = overallCoverage > 70 ? 'coverage-high' : overallCoverage > 40 ? 'coverage-mid' : 'coverage-low';
|
|
642
|
+
const matchedClass = matchedCoverage > 70 ? 'coverage-high' : matchedCoverage > 40 ? 'coverage-mid' : 'coverage-low';
|
|
636
643
|
html += '<div class="coverage-info">';
|
|
637
|
-
html += '<div class="coverage-text" title="' +
|
|
644
|
+
html += '<div class="coverage-text" title="' + overallTooltip + '">Action Details Coverage (overall): <span class="' + overallClass + '">' + overallCoverage + '%</span> (' + totalWithActionInfo + '/' + totalRoutesAnalyzed + ' routes analyzed) \u2139\uFE0F</div>';
|
|
645
|
+
html += '<div class="coverage-text" title="' + matchedTooltip + '">Action Details Coverage (matched controllers): <span class="' + matchedClass + '">' + matchedCoverage + '%</span> (' + totalWithActionInfo + '/' + totalWithControllerMatch + ' matched) \u2139\uFE0F</div>';
|
|
638
646
|
html += '</div>';
|
|
639
647
|
}
|
|
640
648
|
html += '</div>';
|
|
@@ -1930,61 +1938,12 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
1930
1938
|
stepsHtml += '</div></div>';
|
|
1931
1939
|
}
|
|
1932
1940
|
|
|
1933
|
-
// Data operations -
|
|
1934
|
-
//
|
|
1941
|
+
// Data operations - use page.dataFetching from engine.ts only
|
|
1942
|
+
// (engine.ts already enriches pages with GraphQL from components via enrichPagesWithHookGraphQL)
|
|
1935
1943
|
let dataHtml = '';
|
|
1936
1944
|
|
|
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];
|
|
1945
|
+
// Use dataFetching directly from engine.ts analysis
|
|
1946
|
+
const allDataFetching = [...(page.dataFetching || [])];
|
|
1988
1947
|
|
|
1989
1948
|
if (allDataFetching.length > 0) {
|
|
1990
1949
|
// Separate actual GraphQL operations from component references
|
|
@@ -2002,6 +1961,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2002
1961
|
// Extract query name - remove arrows and (via xxx) pattern
|
|
2003
1962
|
let queryName = rawName.replace(/^[\u2192\\s]+/, '').trim();
|
|
2004
1963
|
let sourcePath = 'Direct';
|
|
1964
|
+
let sourceDetail = '';
|
|
2005
1965
|
let depth = 0;
|
|
2006
1966
|
|
|
2007
1967
|
// Method 1: Extract from (via xxx) pattern in operationName (from extractComponentGraphQL)
|
|
@@ -2011,13 +1971,35 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2011
1971
|
queryName = queryName.replace(viaMatch[0], '').trim();
|
|
2012
1972
|
depth = arrowCount || 1;
|
|
2013
1973
|
}
|
|
2014
|
-
// Method 2: Use df.source field
|
|
1974
|
+
// Method 2: Use df.source field
|
|
2015
1975
|
else if (source.startsWith('component:')) {
|
|
2016
1976
|
sourcePath = source.replace('component:', '');
|
|
2017
1977
|
depth = 1;
|
|
2018
1978
|
} else if (source.startsWith('hook:')) {
|
|
2019
1979
|
sourcePath = source.replace('hook:', '');
|
|
2020
1980
|
depth = 1;
|
|
1981
|
+
} else if (source.startsWith('usedIn:')) {
|
|
1982
|
+
// Evidence-based source (file where the operation reference was found)
|
|
1983
|
+
sourcePath = 'Indirect';
|
|
1984
|
+
// Keep detail for modal
|
|
1985
|
+
sourceDetail = source.replace('usedIn:', '');
|
|
1986
|
+
depth = 1;
|
|
1987
|
+
} else if (source.startsWith('import:')) {
|
|
1988
|
+
sourcePath = 'Import';
|
|
1989
|
+
sourceDetail = source.replace('import:', '');
|
|
1990
|
+
depth = 1;
|
|
1991
|
+
} else if (source.startsWith('common:')) {
|
|
1992
|
+
sourcePath = 'Common (shared)';
|
|
1993
|
+
sourceDetail = source.replace('common:', '');
|
|
1994
|
+
depth = 1;
|
|
1995
|
+
} else if (source.startsWith('close:')) {
|
|
1996
|
+
sourcePath = 'Close (related)';
|
|
1997
|
+
sourceDetail = source.replace('close:', '');
|
|
1998
|
+
depth = 1;
|
|
1999
|
+
} else if (source.startsWith('indirect:')) {
|
|
2000
|
+
sourcePath = 'Indirect';
|
|
2001
|
+
sourceDetail = source.replace('indirect:', '');
|
|
2002
|
+
depth = 1;
|
|
2021
2003
|
}
|
|
2022
2004
|
// "import:xxx" or no source stays as Direct
|
|
2023
2005
|
|
|
@@ -2025,15 +2007,27 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2025
2007
|
...df,
|
|
2026
2008
|
queryName,
|
|
2027
2009
|
sourcePath,
|
|
2028
|
-
|
|
2010
|
+
sourceDetail: sourceDetail || undefined,
|
|
2011
|
+
depth,
|
|
2029
2012
|
};
|
|
2030
2013
|
});
|
|
2031
2014
|
|
|
2032
|
-
// Sort by depth (lower first) then by source
|
|
2015
|
+
// Sort by depth (lower first) then by source category priority
|
|
2016
|
+
const sourcePriority = (src) => {
|
|
2017
|
+
if (src === 'Direct') return 0;
|
|
2018
|
+
if (src === 'Close (related)') return 1;
|
|
2019
|
+
if (src === 'Import') return 2;
|
|
2020
|
+
if (src === 'Indirect') return 3;
|
|
2021
|
+
if (src === 'Common (shared)') return 4;
|
|
2022
|
+
return 5;
|
|
2023
|
+
};
|
|
2033
2024
|
parsedOps.sort((a, b) => {
|
|
2034
2025
|
if (a.depth !== b.depth) return a.depth - b.depth;
|
|
2035
2026
|
if (a.sourcePath === 'Direct') return -1;
|
|
2036
2027
|
if (b.sourcePath === 'Direct') return 1;
|
|
2028
|
+
const ap = sourcePriority(a.sourcePath);
|
|
2029
|
+
const bp = sourcePriority(b.sourcePath);
|
|
2030
|
+
if (ap !== bp) return ap - bp;
|
|
2037
2031
|
return a.sourcePath.localeCompare(b.sourcePath);
|
|
2038
2032
|
});
|
|
2039
2033
|
|
|
@@ -2104,7 +2098,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2104
2098
|
sortedPaths.forEach(pathName => {
|
|
2105
2099
|
const ops = groupedByPath.get(pathName);
|
|
2106
2100
|
const isDirect = pathName === 'Direct';
|
|
2107
|
-
|
|
2101
|
+
// Remove the "\u21B3" prefix (UI becomes cleaner with <details>/<summary>)
|
|
2108
2102
|
const pathLabel = isDirect ? 'Direct (this page)' : pathName;
|
|
2109
2103
|
// UI indent: 4px per level added to base padding (10px)
|
|
2110
2104
|
const uiLevel = pathToUiLevel.get(pathName) || 0;
|
|
@@ -2112,20 +2106,68 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2112
2106
|
const totalPadding = 10 + uiIndent;
|
|
2113
2107
|
|
|
2114
2108
|
// Group container, header aligned with detail-item content
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2109
|
+
// Non-direct groups are collapsed by default to reduce noise from shared/common operations.
|
|
2110
|
+
const isClose = pathName === 'Close (related)';
|
|
2111
|
+
const isCollapsedByDefault = !(isDirect || isClose);
|
|
2112
|
+
const detailsOpenAttr = isCollapsedByDefault ? '' : ' open';
|
|
2113
|
+
|
|
2114
|
+
dataHtml += '<details class="data-path-group" style="margin:8px 0"' + detailsOpenAttr + '>' +
|
|
2115
|
+
'<summary class="data-path-header" style="--pad-left:'+totalPadding+'px">' +
|
|
2116
|
+
'<span class="text-accent">' + pathLabel + '</span> (' + ops.length + ')' +
|
|
2117
|
+
'</summary>';
|
|
2118
|
+
|
|
2119
|
+
// Secondary grouping inside the group by concrete source file (sourceDetail).
|
|
2120
|
+
// This reduces noise when a group contains operations from many files.
|
|
2121
|
+
const bySource = new Map();
|
|
2120
2122
|
ops.forEach(op => {
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2123
|
-
|
|
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>';
|
|
2123
|
+
const key = op.sourceDetail || '';
|
|
2124
|
+
if (!bySource.has(key)) bySource.set(key, []);
|
|
2125
|
+
bySource.get(key).push(op);
|
|
2126
2126
|
});
|
|
2127
2127
|
|
|
2128
|
-
|
|
2128
|
+
const sourceKeys = Array.from(bySource.keys()).sort((a, b) => {
|
|
2129
|
+
// Put "unknown/empty" at the end
|
|
2130
|
+
if (!a && b) return 1;
|
|
2131
|
+
if (a && !b) return -1;
|
|
2132
|
+
return String(a).localeCompare(String(b));
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
sourceKeys.forEach(sourceKey => {
|
|
2136
|
+
const sourceOps = bySource.get(sourceKey) || [];
|
|
2137
|
+
const hasSourceHeader = !!sourceKey && sourceKeys.length > 1;
|
|
2138
|
+
|
|
2139
|
+
if (hasSourceHeader) {
|
|
2140
|
+
const sourceFileName = String(sourceKey).split(/[\\/]/).pop() || String(sourceKey);
|
|
2141
|
+
dataHtml += '<div class="data-source-group" style="margin:6px 0">' +
|
|
2142
|
+
'<div class="data-source-header" style="padding-left:'+totalPadding+'px">' +
|
|
2143
|
+
'<span style="opacity:0.9">' + sourceFileName + '</span> (' + sourceOps.length + ')' +
|
|
2144
|
+
'</div>';
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
sourceOps.forEach(op => {
|
|
2148
|
+
const isQ = !op.type?.includes('Mutation');
|
|
2149
|
+
const sourceForModal = op.sourceDetail || op.sourcePath;
|
|
2150
|
+
const srcArg = op.sourcePath !== 'Direct' && sourceForModal
|
|
2151
|
+
? "\\'"+sourceForModal.replace(/'/g, "\\\\'")+"\\'"
|
|
2152
|
+
: 'null';
|
|
2153
|
+
|
|
2154
|
+
// Store mapping metadata in a global map and pass only a key to onclick.
|
|
2155
|
+
const metaKey = op.queryName + '|' + (sourceForModal || '') + '|' + (op.type || '');
|
|
2156
|
+
if (op.confidence || (op.evidence && op.evidence.length)) {
|
|
2157
|
+
opMetaMap.set(metaKey, { confidence: op.confidence, evidence: op.evidence });
|
|
2158
|
+
}
|
|
2159
|
+
const metaArg = ",\\'"+metaKey.replace(/'/g, "\\\\'")+"\\'";
|
|
2160
|
+
// detail-item keeps base padding, adds indent
|
|
2161
|
+
dataHtml += '<div class="detail-item data-op" style="padding:8px 10px 8px '+totalPadding+'px" onclick="showDataDetail(\\''+op.queryName.replace(/'/g, "\\\\'")+"\\',"+srcArg+metaArg+')">' +
|
|
2162
|
+
'<span class="tag '+(isQ?'tag-query':'tag-mutation')+'" style="font-size:10px">'+(isQ?'Q':'M')+'</span> '+op.queryName+'</div>';
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
if (hasSourceHeader) {
|
|
2166
|
+
dataHtml += '</div>';
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
dataHtml += '</details>';
|
|
2129
2171
|
});
|
|
2130
2172
|
|
|
2131
2173
|
dataHtml += '</div>';
|
|
@@ -2202,75 +2244,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2202
2244
|
if (c.name) componentByName.set(c.name, c);
|
|
2203
2245
|
});
|
|
2204
2246
|
|
|
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
|
|
2247
|
+
// Include pages with direct GraphQL usage (from engine.ts enrichPagesWithHookGraphQL)
|
|
2274
2248
|
const pagesWithGraphQL = new Set([
|
|
2275
2249
|
// Pages with direct dataFetching
|
|
2276
2250
|
...pages.filter(p =>
|
|
@@ -2279,11 +2253,6 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2279
2253
|
df.type === 'getServerSideProps' || df.type === 'getStaticProps'
|
|
2280
2254
|
)
|
|
2281
2255
|
).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
2256
|
// Pages whose files are referenced in GraphQL operation usedIn
|
|
2288
2257
|
...pages.filter(p => {
|
|
2289
2258
|
if (!p.filePath) return false;
|
|
@@ -2695,12 +2664,28 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2695
2664
|
btn.remove();
|
|
2696
2665
|
};
|
|
2697
2666
|
|
|
2698
|
-
function showDataDetail(rawName, sourcePath) {
|
|
2667
|
+
function showDataDetail(rawName, sourcePath, metaKeyOrJson) {
|
|
2699
2668
|
// Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2700
2669
|
const name = rawName
|
|
2701
2670
|
.replace(/^[\u2192\\->\\s]+/, '')
|
|
2702
2671
|
.replace(/\\s*\\([^)]+\\)\\s*$/, '');
|
|
2703
2672
|
|
|
2673
|
+
let meta = null;
|
|
2674
|
+
try {
|
|
2675
|
+
// New: lookup by meta key
|
|
2676
|
+
if (metaKeyOrJson && typeof metaKeyOrJson === 'string' && opMetaMap.has(metaKeyOrJson)) {
|
|
2677
|
+
meta = opMetaMap.get(metaKeyOrJson);
|
|
2678
|
+
}
|
|
2679
|
+
// Backward compatibility: if someone passes raw JSON string
|
|
2680
|
+
else if (metaKeyOrJson && typeof metaKeyOrJson === 'string' && metaKeyOrJson.trim().startsWith('{')) {
|
|
2681
|
+
meta = JSON.parse(metaKeyOrJson);
|
|
2682
|
+
} else if (metaKeyOrJson && typeof metaKeyOrJson === 'object') {
|
|
2683
|
+
meta = metaKeyOrJson;
|
|
2684
|
+
}
|
|
2685
|
+
} catch {
|
|
2686
|
+
meta = null;
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2704
2689
|
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
|
|
2705
2690
|
const toPascalCase = (str) => {
|
|
2706
2691
|
if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
|
|
@@ -2772,12 +2757,110 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2772
2757
|
|
|
2773
2758
|
if (op) {
|
|
2774
2759
|
// Found GraphQL operation
|
|
2775
|
-
|
|
2760
|
+
const confidence = meta?.confidence || '';
|
|
2761
|
+
const confidenceTheme = (level) => {
|
|
2762
|
+
if (level === 'certain') {
|
|
2763
|
+
return {
|
|
2764
|
+
label: 'CERTAIN',
|
|
2765
|
+
bg: '#22c55e',
|
|
2766
|
+
title: 'Certain: reached via a very close import path from the page (0\u20132 steps)',
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
if (level === 'likely') {
|
|
2770
|
+
return {
|
|
2771
|
+
label: 'LIKELY',
|
|
2772
|
+
bg: '#f59e0b',
|
|
2773
|
+
title: 'Likely: reachable via the import graph, but indirect (3+ steps)',
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
if (level === 'unknown') {
|
|
2777
|
+
return {
|
|
2778
|
+
label: 'COMMON',
|
|
2779
|
+
bg: '#64748b',
|
|
2780
|
+
title: 'Common: reached via widely shared modules across many pages',
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
return null;
|
|
2784
|
+
};
|
|
2785
|
+
|
|
2786
|
+
const conf = confidenceTheme(confidence);
|
|
2787
|
+
const confidenceBadge =
|
|
2788
|
+
confidence === 'likely' && conf
|
|
2789
|
+
? '<span class="tag" title="' +
|
|
2790
|
+
conf.title +
|
|
2791
|
+
'" style="cursor:help;font-size:10px;display:inline-flex;align-items:center;justify-content:center;height:18px;line-height:18px;padding:0 8px;border-radius:999px;background:' +
|
|
2792
|
+
conf.bg +
|
|
2793
|
+
';color:white;opacity:0.95">' +
|
|
2794
|
+
conf.label +
|
|
2795
|
+
'</span>'
|
|
2796
|
+
: '';
|
|
2797
|
+
|
|
2798
|
+
html =
|
|
2799
|
+
'<div class="detail-section"><h4>Type</h4><div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap">' +
|
|
2800
|
+
'<span class="tag ' +
|
|
2801
|
+
(op.type === 'mutation' ? 'tag-mutation' : 'tag-query') +
|
|
2802
|
+
'" style="display:inline-flex;align-items:center;justify-content:center;height:18px;line-height:18px;padding:0 8px;border-radius:999px">' +
|
|
2803
|
+
op.type.toUpperCase() +
|
|
2804
|
+
'</span>' +
|
|
2805
|
+
confidenceBadge +
|
|
2806
|
+
'</div></div>';
|
|
2807
|
+
|
|
2808
|
+
const escapeHtml = (s) => String(s ?? '')
|
|
2809
|
+
.replace(/&/g, '&')
|
|
2810
|
+
.replace(/</g, '<')
|
|
2811
|
+
.replace(/>/g, '>')
|
|
2812
|
+
.replace(/"/g, '"')
|
|
2813
|
+
.replace(/'/g, ''');
|
|
2814
|
+
|
|
2815
|
+
// Prepare Evidence section HTML (render later, after GraphQL section)
|
|
2816
|
+
let evidenceSectionHtml = '';
|
|
2817
|
+
if (meta && meta.evidence && Array.isArray(meta.evidence) && meta.evidence.length > 0) {
|
|
2818
|
+
evidenceSectionHtml += '<div class="detail-section"><h4>Evidence</h4>';
|
|
2819
|
+
meta.evidence.slice(0, 12).forEach(ev => {
|
|
2820
|
+
const file = ev.file || '';
|
|
2821
|
+
const line = ev.line ? ':' + ev.line : '';
|
|
2822
|
+
const detailRaw = ev.detail ? String(ev.detail) : '';
|
|
2823
|
+
|
|
2824
|
+
// Escape then enhance arrows for readability
|
|
2825
|
+
const detailEsc = escapeHtml(detailRaw);
|
|
2826
|
+
const detailPretty = detailEsc.replace(/->|->/g, '<span style="color:#60a5fa;font-weight:700;padding:0 4px">\u2192</span>');
|
|
2827
|
+
|
|
2828
|
+
evidenceSectionHtml += '<div class="detail-item" style="font-size:11px;display:flex;flex-direction:column;gap:6px;align-items:flex-start;max-width:100%;overflow:hidden">' +
|
|
2829
|
+
'<code style="background:#0f172a;color:#93c5fd;padding:2px 6px;border-radius:3px;font-size:10px;display:block;max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word">' +
|
|
2830
|
+
escapeHtml(file + line) +
|
|
2831
|
+
'</code>' +
|
|
2832
|
+
(detailRaw
|
|
2833
|
+
? '<div style="opacity:0.92;max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word">' + detailPretty + '</div>'
|
|
2834
|
+
: '') +
|
|
2835
|
+
'</div>';
|
|
2836
|
+
});
|
|
2837
|
+
if (meta.evidence.length > 12) {
|
|
2838
|
+
evidenceSectionHtml += '<div class="detail-item" style="font-size:11px;opacity:0.8">... '+(meta.evidence.length - 12)+' more</div>';
|
|
2839
|
+
}
|
|
2840
|
+
evidenceSectionHtml += '</div>';
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
// Confidence is shown as a small badge next to Type to avoid taking extra vertical space.
|
|
2776
2844
|
|
|
2777
2845
|
// Source info
|
|
2778
2846
|
if (sourcePath) {
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2847
|
+
const looksLikeFile =
|
|
2848
|
+
sourcePath.includes('/') ||
|
|
2849
|
+
sourcePath.endsWith('.ts') ||
|
|
2850
|
+
sourcePath.endsWith('.tsx') ||
|
|
2851
|
+
sourcePath.endsWith('.js') ||
|
|
2852
|
+
sourcePath.endsWith('.jsx');
|
|
2853
|
+
const isHook = !looksLikeFile && sourcePath.startsWith('use');
|
|
2854
|
+
const label = looksLikeFile ? 'File' : isHook ? 'Hook' : 'Component';
|
|
2855
|
+
html += '<div class="detail-section">' +
|
|
2856
|
+
'<h4 style="display:flex;justify-content:space-between;align-items:center">' +
|
|
2857
|
+
'Source' +
|
|
2858
|
+
'<span class="tag tag-default" style="font-size:10px">via ' + label + '</span>' +
|
|
2859
|
+
'</h4>' +
|
|
2860
|
+
'<code style="background:#0f172a;color:#93c5fd;padding:8px 10px;border-radius:6px;font-family:monospace;font-size:11px;display:block;max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word">' +
|
|
2861
|
+
escapeHtml(sourcePath) +
|
|
2862
|
+
'</code>' +
|
|
2863
|
+
'</div>';
|
|
2781
2864
|
}
|
|
2782
2865
|
|
|
2783
2866
|
// Operation Name with copy button
|
|
@@ -2802,10 +2885,14 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2802
2885
|
|
|
2803
2886
|
html += '<div class="detail-section"><h4 style="display:flex;justify-content:space-between;align-items:center">GraphQL<button class="copy-btn" onclick="copyGqlCode(this)" data-code="'+gqlCodeEscaped+'" title="Copy GraphQL">\u{1F4CB}</button></h4>';
|
|
2804
2887
|
html += '<pre style="background:#0f172a;color:#e2e8f0;padding:12px;border-radius:6px;font-size:11px;overflow-x:auto;white-space:pre;max-height:300px;overflow-y:auto">' + gqlCode + '</pre></div>';
|
|
2888
|
+
// Evidence should appear right after GraphQL section
|
|
2889
|
+
if (evidenceSectionHtml) html += evidenceSectionHtml;
|
|
2805
2890
|
} else if (op.variables?.length) {
|
|
2806
2891
|
html += '<div class="detail-section"><h4>Variables</h4>';
|
|
2807
2892
|
op.variables.forEach(v => { html += '<div class="detail-item">'+v.name+': <code style="background:#0f172a;color:#93c5fd;padding:2px 6px;border-radius:3px;font-family:monospace">'+v.type+'</code>'+(v.required?' (required)':'')+'</div>'; });
|
|
2808
2893
|
html += '</div>';
|
|
2894
|
+
// Evidence should appear right after Variables section (when GraphQL block is absent)
|
|
2895
|
+
if (evidenceSectionHtml) html += evidenceSectionHtml;
|
|
2809
2896
|
}
|
|
2810
2897
|
if (op.usedIn?.length) {
|
|
2811
2898
|
html += '<div class="detail-section"><h4>Used In ('+op.usedIn.length+' files)</h4>';
|
|
@@ -3568,13 +3655,10 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3568
3655
|
const page = pageMap.get(pagePath);
|
|
3569
3656
|
if (!page) return;
|
|
3570
3657
|
|
|
3571
|
-
//
|
|
3572
|
-
const pageComps = getPageComponents(page);
|
|
3573
|
-
const visited = new Set();
|
|
3658
|
+
// Count GraphQL from dataFetching only (already enriched by engine.ts)
|
|
3574
3659
|
let queries = 0;
|
|
3575
3660
|
let mutations = 0;
|
|
3576
3661
|
|
|
3577
|
-
// Count from direct dataFetching
|
|
3578
3662
|
(page.dataFetching || []).forEach(df => {
|
|
3579
3663
|
if (df.type?.includes('Mutation')) {
|
|
3580
3664
|
mutations++;
|
|
@@ -3583,16 +3667,6 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3583
3667
|
}
|
|
3584
3668
|
});
|
|
3585
3669
|
|
|
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
3670
|
// Update Q tag
|
|
3597
3671
|
const qTag = item.querySelector('.tag-query');
|
|
3598
3672
|
if (qTag) {
|
|
@@ -3621,12 +3695,12 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3621
3695
|
setTimeout(updatePageGqlCounts, 100);
|
|
3622
3696
|
</script>
|
|
3623
3697
|
</body>
|
|
3624
|
-
</html>`}buildTreeHtml(
|
|
3698
|
+
</html>`}buildTreeHtml(i,n){let r=["#ef4444","#f97316","#eab308","#22c55e","#14b8a6","#3b82f6","#8b5cf6","#ec4899"],u=0;return Array.from(i.entries()).sort((t,o)=>t[0].localeCompare(o[0])).map(([t,o])=>{let e=r[u++%r.length],l=o.sort((a,h)=>a.path.localeCompare(h.path)),s=new Set(l.map(a=>a.path)),p=new Map;for(let a of l){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(s.has(d)){v=(p.get(d)??0)+1;break}}p.set(a.path,v);}let g=l.map(a=>{let h=this.getPageType(a.path),v=p.get(a.path)??0,d=a.repo||"",w=n.some(f=>f.repo&&f.repo!==d),C=d.split("/").pop()?.split("-").map(f=>f.substring(0,4)).join("-")||d.substring(0,8),P=w&&d?`<span class="tag tag-repo" title="${d}">${C}</span>`:"",x=/^\/[A-Z]/.test(a.path)||a.filePath&&a.filePath.includes("components/pages"),S=x&&a.filePath?a.filePath.replace(/\.tsx?$/,"").replace(/^(frontend\/src\/|src\/)/,""):a.path,m=x?'<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
3699
|
<span class="page-type" style="--type-color:${h.color}">${h.label}</span>
|
|
3626
|
-
<span class="page-path">${
|
|
3700
|
+
<span class="page-path">${S}</span>
|
|
3627
3701
|
<div class="page-tags">
|
|
3628
|
-
${
|
|
3629
|
-
${
|
|
3702
|
+
${P}
|
|
3703
|
+
${m}
|
|
3630
3704
|
${a.authentication?.required?'<span class="tag tag-auth">AUTH</span>':""}
|
|
3631
3705
|
<span class="tag tag-query gql-count" data-page-path="${a.path}" style="display:none">Q:0</span>
|
|
3632
3706
|
<span class="tag tag-mutation gql-count-m" data-page-path="${a.path}" style="display:none">M:0</span>
|
|
@@ -3637,5 +3711,5 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3637
3711
|
<span class="group-name">/${t}</span>
|
|
3638
3712
|
<span class="group-count">${o.length}</span>
|
|
3639
3713
|
</div>
|
|
3640
|
-
<div class="group-content">${
|
|
3641
|
-
</div>`}).join("")}getPageType(
|
|
3714
|
+
<div class="group-content">${g}</div>
|
|
3715
|
+
</div>`}).join("")}getPageType(i){let n=i.split("/").filter(Boolean).pop()||"";return n==="new"||i.endsWith("/new")?{label:"CREATE",color:"#22c55e"}:n==="edit"||i.includes("/edit")?{label:"EDIT",color:"#f59e0b"}:n.startsWith("[")||n.startsWith(":")?{label:"DETAIL",color:"#3b82f6"}:i.includes("setting")?{label:"SETTINGS",color:"#6b7280"}:{label:"LIST",color:"#06b6d4"}}};export{E as a};
|