@wtdlee/repomap 0.9.1 → 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-WZAAA7DS.js → chunk-ATRSGO6O.js} +175 -49
- package/dist/chunk-IGRCLYVZ.js +451 -0
- package/dist/{chunk-NQMJ3QRX.js → chunk-LHP2OKKA.js} +1 -1
- package/dist/{chunk-2XZSFAJF.js → chunk-QBSB6BIU.js} +6 -6
- package/dist/{chunk-6AZNHUOB.js → chunk-YKPXOHWZ.js} +52 -25
- 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 +22 -0
- package/dist/generators/assets/rails-map.css +13 -0
- package/dist/generators/index.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/page-map-generator-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-QDVE7MT3.js +0 -5
- package/dist/page-map-generator-HROGGVAQ.js +0 -1
- package/dist/rails-map-generator-DF2YAXW4.js +0 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Powered by [SWC](https://swc.rs/) for blazing-fast AST parsing. Analyze large-sc
|
|
|
50
50
|
- **Field details** - View all fields with types and arguments
|
|
51
51
|
- **Usage tracking** - See where operations are used in components
|
|
52
52
|
- **Component integration** - Track GraphQL usage through component dependencies
|
|
53
|
+
- **Custom wrapper hooks** - Configurable hook presets/patterns (Apollo/urql/Relay/custom)
|
|
53
54
|
|
|
54
55
|
### 📊 Data Flow
|
|
55
56
|
- **Visual diagrams** - Mermaid-generated flowcharts
|
|
@@ -425,6 +426,7 @@ export default config;
|
|
|
425
426
|
- Auto-generated markdown documentation
|
|
426
427
|
- Navigation sidebar
|
|
427
428
|
- Syntax-highlighted code blocks
|
|
429
|
+
- Coverage metrics (parse failures, codegen detection) to prevent silent omissions
|
|
428
430
|
|
|
429
431
|
## Supported Frameworks
|
|
430
432
|
|
|
@@ -466,6 +468,7 @@ interface AnalysisResult {
|
|
|
466
468
|
timestamp: string;
|
|
467
469
|
version: string;
|
|
468
470
|
commitHash: string;
|
|
471
|
+
coverage?: CoverageMetrics;
|
|
469
472
|
pages: PageInfo[];
|
|
470
473
|
graphqlOperations: GraphQLOperation[];
|
|
471
474
|
apiCalls: APICall[];
|
|
@@ -476,6 +479,15 @@ interface AnalysisResult {
|
|
|
476
479
|
crossRepoLinks: CrossRepoLink[];
|
|
477
480
|
}
|
|
478
481
|
|
|
482
|
+
interface CoverageMetrics {
|
|
483
|
+
tsFilesScanned: number;
|
|
484
|
+
tsParseFailures: number;
|
|
485
|
+
graphqlParseFailures: number;
|
|
486
|
+
codegenFilesDetected: number;
|
|
487
|
+
codegenFilesParsed: number;
|
|
488
|
+
codegenExportsFound: number;
|
|
489
|
+
}
|
|
490
|
+
|
|
479
491
|
// Report
|
|
480
492
|
interface DocumentationReport {
|
|
481
493
|
generatedAt: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { B as BaseAnalyzer } from '../dataflow-analyzer-
|
|
2
|
-
export { D as DataFlowAnalyzer, G as GraphQLAnalyzer, P as PagesAnalyzer } from '../dataflow-analyzer-
|
|
1
|
+
import { B as BaseAnalyzer } from '../dataflow-analyzer-DIUsRpvv.js';
|
|
2
|
+
export { D as DataFlowAnalyzer, G as GraphQLAnalyzer, P as PagesAnalyzer } from '../dataflow-analyzer-DIUsRpvv.js';
|
|
3
3
|
import { Module, CallExpression } from '@swc/core';
|
|
4
4
|
import { DataFetchingInfo, RepositoryConfig, AnalysisResult } from '../types.js';
|
|
5
5
|
|
|
@@ -33,7 +33,7 @@ declare const HOOK_TYPE_MAP: Record<string, DataFetchingInfo['type']>;
|
|
|
33
33
|
/**
|
|
34
34
|
* Keywords that indicate GraphQL usage in a file
|
|
35
35
|
*/
|
|
36
|
-
declare const GRAPHQL_INDICATORS: readonly ["Document", "useQuery", "useMutation", "useLazyQuery", "useSuspenseQuery", "useBackgroundQuery", "useSubscription", "Query", "Mutation", "gql", "graphql", "GET_", "FETCH_", "SEARCH_", "CREATE_", "UPDATE_", "DELETE_", "SUBSCRIBE_", "@apollo", "ApolloClient"];
|
|
36
|
+
declare const GRAPHQL_INDICATORS: readonly ["Document", "useQuery", "useMutation", "useLazyQuery", "useSuspenseQuery", "useBackgroundQuery", "useSubscription", "Query", "Mutation", "gql", "graphql", "GET_", "FETCH_", "SEARCH_", "CREATE_", "UPDATE_", "DELETE_", "SUBSCRIBE_", "@apollo", "ApolloClient", "@urql", "urql", "react-relay", "Relay"];
|
|
37
37
|
/**
|
|
38
38
|
* Context extracted from a file for GraphQL operation resolution
|
|
39
39
|
*/
|
|
@@ -62,23 +62,23 @@ interface ExtractedGraphQLOperation {
|
|
|
62
62
|
/**
|
|
63
63
|
* Check if a hook name is a GraphQL query hook
|
|
64
64
|
*/
|
|
65
|
-
declare function isQueryHook(hookName: string): boolean;
|
|
65
|
+
declare function isQueryHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
66
66
|
/**
|
|
67
67
|
* Check if a hook name is a GraphQL mutation hook
|
|
68
68
|
*/
|
|
69
|
-
declare function isMutationHook(hookName: string): boolean;
|
|
69
|
+
declare function isMutationHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
70
70
|
/**
|
|
71
71
|
* Check if a hook name is a GraphQL subscription hook
|
|
72
72
|
*/
|
|
73
|
-
declare function isSubscriptionHook(hookName: string): boolean;
|
|
73
|
+
declare function isSubscriptionHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
74
74
|
/**
|
|
75
75
|
* Check if a hook name is any GraphQL hook
|
|
76
76
|
*/
|
|
77
|
-
declare function isGraphQLHook(hookName: string): boolean;
|
|
77
|
+
declare function isGraphQLHook(hookName: string, extraHookPatterns?: string[]): boolean;
|
|
78
78
|
/**
|
|
79
79
|
* Get the data fetching type for a hook
|
|
80
80
|
*/
|
|
81
|
-
declare function getHookType(hookName: string): DataFetchingInfo['type'];
|
|
81
|
+
declare function getHookType(hookName: string, extraHookPatterns?: string[]): DataFetchingInfo['type'];
|
|
82
82
|
/**
|
|
83
83
|
* Check if content has any GraphQL indicators
|
|
84
84
|
*/
|
package/dist/analyzers/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{f as ALL_GRAPHQL_HOOKS,a as BaseAnalyzer,B as DataFlowAnalyzer,h as GRAPHQL_INDICATORS,d as GRAPHQL_MUTATION_HOOKS,e as GRAPHQL_OTHER_HOOKS,c as GRAPHQL_QUERY_HOOKS,A as GraphQLAnalyzer,g as HOOK_TYPE_MAP,z as PagesAnalyzer,C as RestApiAnalyzer,o as cleanOperationName,s as extractGraphQLContext,x as extractGraphQLOperationsFromFile,t as extractOperationNameFromGqlCall,u as extractOperationNameFromTemplate,q as getCalleeName,y as getHookInfoString,m as getHookType,v as hasGraphQLArgument,n as hasGraphQLIndicators,l as isGraphQLHook,j as isMutationHook,i as isQueryHook,k as isSubscriptionHook,p as parseToAst,w as resolveOperationName,r as traverseAst}from'../chunk-IGRCLYVZ.js';import'../chunk-ZWRDP37E.js';
|
|
@@ -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">
|
|
@@ -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} ${u}</h1>
|
|
18
18
|
<nav style="display:flex;gap:4px">
|
|
19
|
-
<a href="/page-map" class="nav-link ${
|
|
20
|
-
${
|
|
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
|
-
${
|
|
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,13 +63,13 @@ 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>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
|
-
${
|
|
72
|
+
${m&&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">
|
|
@@ -84,19 +84,19 @@ 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
|
-
${
|
|
89
|
-
<div id="page-map-react-components-section" style="${
|
|
87
|
+
<div class="tree-view ${l==="pages"?"active":""}" id="tree-view" data-tab="pages">
|
|
88
|
+
${i.length>0?this.buildTreeHtml(b,i):""}
|
|
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
|
-
${
|
|
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>
|
|
101
101
|
</div>
|
|
102
102
|
|
|
@@ -138,33 +138,35 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
138
138
|
<script>
|
|
139
139
|
// Environment detection results
|
|
140
140
|
const envInfo = {
|
|
141
|
-
hasRails: ${
|
|
142
|
-
hasNextjs: ${
|
|
143
|
-
hasReact: ${
|
|
141
|
+
hasRails: ${m},
|
|
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>';
|
|
@@ -2140,10 +2148,17 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2140
2148
|
const isQ = !op.type?.includes('Mutation');
|
|
2141
2149
|
const sourceForModal = op.sourceDetail || op.sourcePath;
|
|
2142
2150
|
const srcArg = op.sourcePath !== 'Direct' && sourceForModal
|
|
2143
|
-
? "
|
|
2144
|
-
: '';
|
|
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, "\\\\'")+"\\'";
|
|
2145
2160
|
// 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+')">' +
|
|
2161
|
+
dataHtml += '<div class="detail-item data-op" style="padding:8px 10px 8px '+totalPadding+'px" onclick="showDataDetail(\\''+op.queryName.replace(/'/g, "\\\\'")+"\\',"+srcArg+metaArg+')">' +
|
|
2147
2162
|
'<span class="tag '+(isQ?'tag-query':'tag-mutation')+'" style="font-size:10px">'+(isQ?'Q':'M')+'</span> '+op.queryName+'</div>';
|
|
2148
2163
|
});
|
|
2149
2164
|
|
|
@@ -2649,12 +2664,28 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2649
2664
|
btn.remove();
|
|
2650
2665
|
};
|
|
2651
2666
|
|
|
2652
|
-
function showDataDetail(rawName, sourcePath) {
|
|
2667
|
+
function showDataDetail(rawName, sourcePath, metaKeyOrJson) {
|
|
2653
2668
|
// Clean up name: remove "\u2192 " prefix and " (ComponentName)" suffix
|
|
2654
2669
|
const name = rawName
|
|
2655
2670
|
.replace(/^[\u2192\\->\\s]+/, '')
|
|
2656
2671
|
.replace(/\\s*\\([^)]+\\)\\s*$/, '');
|
|
2657
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
|
+
|
|
2658
2689
|
// Convert SCREAMING_CASE to PascalCase (e.g., COMPANY_QUERY \u2192 CompanyQuery)
|
|
2659
2690
|
const toPascalCase = (str) => {
|
|
2660
2691
|
if (!/^[A-Z][A-Z0-9_]*$/.test(str)) return str;
|
|
@@ -2726,7 +2757,90 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2726
2757
|
|
|
2727
2758
|
if (op) {
|
|
2728
2759
|
// Found GraphQL operation
|
|
2729
|
-
|
|
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.
|
|
2730
2844
|
|
|
2731
2845
|
// Source info
|
|
2732
2846
|
if (sourcePath) {
|
|
@@ -2738,7 +2852,15 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2738
2852
|
sourcePath.endsWith('.jsx');
|
|
2739
2853
|
const isHook = !looksLikeFile && sourcePath.startsWith('use');
|
|
2740
2854
|
const label = looksLikeFile ? 'File' : isHook ? 'Hook' : 'Component';
|
|
2741
|
-
html += '<div class="detail-section"
|
|
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>';
|
|
2742
2864
|
}
|
|
2743
2865
|
|
|
2744
2866
|
// Operation Name with copy button
|
|
@@ -2763,10 +2885,14 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
2763
2885
|
|
|
2764
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>';
|
|
2765
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;
|
|
2766
2890
|
} else if (op.variables?.length) {
|
|
2767
2891
|
html += '<div class="detail-section"><h4>Variables</h4>';
|
|
2768
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>'; });
|
|
2769
2893
|
html += '</div>';
|
|
2894
|
+
// Evidence should appear right after Variables section (when GraphQL block is absent)
|
|
2895
|
+
if (evidenceSectionHtml) html += evidenceSectionHtml;
|
|
2770
2896
|
}
|
|
2771
2897
|
if (op.usedIn?.length) {
|
|
2772
2898
|
html += '<div class="detail-section"><h4>Used In ('+op.usedIn.length+' files)</h4>';
|
|
@@ -3569,12 +3695,12 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3569
3695
|
setTimeout(updatePageGqlCounts, 100);
|
|
3570
3696
|
</script>
|
|
3571
3697
|
</body>
|
|
3572
|
-
</html>`}buildTreeHtml(
|
|
3573
|
-
<span class="page-type" style="--type-color:${
|
|
3574
|
-
<span class="page-path">${
|
|
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}">
|
|
3699
|
+
<span class="page-type" style="--type-color:${h.color}">${h.label}</span>
|
|
3700
|
+
<span class="page-path">${S}</span>
|
|
3575
3701
|
<div class="page-tags">
|
|
3576
|
-
${
|
|
3577
|
-
${
|
|
3702
|
+
${P}
|
|
3703
|
+
${m}
|
|
3578
3704
|
${a.authentication?.required?'<span class="tag tag-auth">AUTH</span>':""}
|
|
3579
3705
|
<span class="tag tag-query gql-count" data-page-path="${a.path}" style="display:none">Q:0</span>
|
|
3580
3706
|
<span class="tag tag-mutation gql-count-m" data-page-path="${a.path}" style="display:none">M:0</span>
|
|
@@ -3583,7 +3709,7 @@ var P=class{graphqlOps=[];apiCalls=[];components=[];generatePageMapHtml(s,n){let
|
|
|
3583
3709
|
<div class="group-header" onclick="toggleGroup(this)" style="--group-color:${e}">
|
|
3584
3710
|
<span class="group-arrow">\u25BC</span>
|
|
3585
3711
|
<span class="group-name">/${t}</span>
|
|
3586
|
-
<span class="group-count">${
|
|
3712
|
+
<span class="group-count">${o.length}</span>
|
|
3587
3713
|
</div>
|
|
3588
|
-
<div class="group-content">${
|
|
3589
|
-
</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};
|