@wtdlee/repomap 0.1.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +527 -0
  3. package/dist/analyzers/base-analyzer.d.ts +46 -0
  4. package/dist/analyzers/base-analyzer.d.ts.map +1 -0
  5. package/dist/analyzers/base-analyzer.js +48 -0
  6. package/dist/analyzers/base-analyzer.js.map +1 -0
  7. package/dist/analyzers/dataflow-analyzer.d.ts +30 -0
  8. package/dist/analyzers/dataflow-analyzer.d.ts.map +1 -0
  9. package/dist/analyzers/dataflow-analyzer.js +426 -0
  10. package/dist/analyzers/dataflow-analyzer.js.map +1 -0
  11. package/dist/analyzers/graphql-analyzer.d.ts +23 -0
  12. package/dist/analyzers/graphql-analyzer.d.ts.map +1 -0
  13. package/dist/analyzers/graphql-analyzer.js +387 -0
  14. package/dist/analyzers/graphql-analyzer.js.map +1 -0
  15. package/dist/analyzers/index.d.ts +6 -0
  16. package/dist/analyzers/index.d.ts.map +1 -0
  17. package/dist/analyzers/index.js +6 -0
  18. package/dist/analyzers/index.js.map +1 -0
  19. package/dist/analyzers/pages-analyzer.d.ts +85 -0
  20. package/dist/analyzers/pages-analyzer.d.ts.map +1 -0
  21. package/dist/analyzers/pages-analyzer.js +1696 -0
  22. package/dist/analyzers/pages-analyzer.js.map +1 -0
  23. package/dist/analyzers/rails/index.d.ts +47 -0
  24. package/dist/analyzers/rails/index.d.ts.map +1 -0
  25. package/dist/analyzers/rails/index.js +146 -0
  26. package/dist/analyzers/rails/index.js.map +1 -0
  27. package/dist/analyzers/rails/rails-controller-analyzer.d.ts +83 -0
  28. package/dist/analyzers/rails/rails-controller-analyzer.d.ts.map +1 -0
  29. package/dist/analyzers/rails/rails-controller-analyzer.js +479 -0
  30. package/dist/analyzers/rails/rails-controller-analyzer.js.map +1 -0
  31. package/dist/analyzers/rails/rails-grpc-analyzer.d.ts +45 -0
  32. package/dist/analyzers/rails/rails-grpc-analyzer.d.ts.map +1 -0
  33. package/dist/analyzers/rails/rails-grpc-analyzer.js +263 -0
  34. package/dist/analyzers/rails/rails-grpc-analyzer.js.map +1 -0
  35. package/dist/analyzers/rails/rails-model-analyzer.d.ts +89 -0
  36. package/dist/analyzers/rails/rails-model-analyzer.d.ts.map +1 -0
  37. package/dist/analyzers/rails/rails-model-analyzer.js +494 -0
  38. package/dist/analyzers/rails/rails-model-analyzer.js.map +1 -0
  39. package/dist/analyzers/rails/rails-react-analyzer.d.ts +42 -0
  40. package/dist/analyzers/rails/rails-react-analyzer.d.ts.map +1 -0
  41. package/dist/analyzers/rails/rails-react-analyzer.js +530 -0
  42. package/dist/analyzers/rails/rails-react-analyzer.js.map +1 -0
  43. package/dist/analyzers/rails/rails-routes-analyzer.d.ts +63 -0
  44. package/dist/analyzers/rails/rails-routes-analyzer.d.ts.map +1 -0
  45. package/dist/analyzers/rails/rails-routes-analyzer.js +541 -0
  46. package/dist/analyzers/rails/rails-routes-analyzer.js.map +1 -0
  47. package/dist/analyzers/rails/rails-view-analyzer.d.ts +50 -0
  48. package/dist/analyzers/rails/rails-view-analyzer.d.ts.map +1 -0
  49. package/dist/analyzers/rails/rails-view-analyzer.js +387 -0
  50. package/dist/analyzers/rails/rails-view-analyzer.js.map +1 -0
  51. package/dist/analyzers/rails/ruby-parser.d.ts +64 -0
  52. package/dist/analyzers/rails/ruby-parser.d.ts.map +1 -0
  53. package/dist/analyzers/rails/ruby-parser.js +213 -0
  54. package/dist/analyzers/rails/ruby-parser.js.map +1 -0
  55. package/dist/analyzers/rest-api-analyzer.d.ts +66 -0
  56. package/dist/analyzers/rest-api-analyzer.d.ts.map +1 -0
  57. package/dist/analyzers/rest-api-analyzer.js +480 -0
  58. package/dist/analyzers/rest-api-analyzer.js.map +1 -0
  59. package/dist/cli.d.ts +3 -0
  60. package/dist/cli.d.ts.map +1 -0
  61. package/dist/cli.js +550 -0
  62. package/dist/cli.js.map +1 -0
  63. package/dist/core/cache.d.ts +48 -0
  64. package/dist/core/cache.d.ts.map +1 -0
  65. package/dist/core/cache.js +152 -0
  66. package/dist/core/cache.js.map +1 -0
  67. package/dist/core/engine.d.ts +47 -0
  68. package/dist/core/engine.d.ts.map +1 -0
  69. package/dist/core/engine.js +320 -0
  70. package/dist/core/engine.js.map +1 -0
  71. package/dist/core/index.d.ts +3 -0
  72. package/dist/core/index.d.ts.map +1 -0
  73. package/dist/core/index.js +3 -0
  74. package/dist/core/index.js.map +1 -0
  75. package/dist/generators/assets/common.css +187 -0
  76. package/dist/generators/assets/docs.css +363 -0
  77. package/dist/generators/assets/page-map.css +305 -0
  78. package/dist/generators/assets/rails-map.css +473 -0
  79. package/dist/generators/index.d.ts +4 -0
  80. package/dist/generators/index.d.ts.map +1 -0
  81. package/dist/generators/index.js +4 -0
  82. package/dist/generators/index.js.map +1 -0
  83. package/dist/generators/markdown-generator.d.ts +26 -0
  84. package/dist/generators/markdown-generator.d.ts.map +1 -0
  85. package/dist/generators/markdown-generator.js +783 -0
  86. package/dist/generators/markdown-generator.js.map +1 -0
  87. package/dist/generators/mermaid-generator.d.ts +36 -0
  88. package/dist/generators/mermaid-generator.d.ts.map +1 -0
  89. package/dist/generators/mermaid-generator.js +365 -0
  90. package/dist/generators/mermaid-generator.js.map +1 -0
  91. package/dist/generators/page-map-generator.d.ts +23 -0
  92. package/dist/generators/page-map-generator.d.ts.map +1 -0
  93. package/dist/generators/page-map-generator.js +3563 -0
  94. package/dist/generators/page-map-generator.js.map +1 -0
  95. package/dist/generators/rails-map-generator.d.ts +22 -0
  96. package/dist/generators/rails-map-generator.d.ts.map +1 -0
  97. package/dist/generators/rails-map-generator.js +909 -0
  98. package/dist/generators/rails-map-generator.js.map +1 -0
  99. package/dist/index.d.ts +11 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +12 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/server/doc-server.d.ts +31 -0
  104. package/dist/server/doc-server.d.ts.map +1 -0
  105. package/dist/server/doc-server.js +1233 -0
  106. package/dist/server/doc-server.js.map +1 -0
  107. package/dist/server/index.d.ts +2 -0
  108. package/dist/server/index.d.ts.map +1 -0
  109. package/dist/server/index.js +2 -0
  110. package/dist/server/index.js.map +1 -0
  111. package/dist/types.d.ts +294 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +6 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/utils/env-detector.d.ts +32 -0
  116. package/dist/utils/env-detector.d.ts.map +1 -0
  117. package/dist/utils/env-detector.js +189 -0
  118. package/dist/utils/env-detector.js.map +1 -0
  119. package/dist/utils/parallel.d.ts +24 -0
  120. package/dist/utils/parallel.d.ts.map +1 -0
  121. package/dist/utils/parallel.js +71 -0
  122. package/dist/utils/parallel.js.map +1 -0
  123. package/package.json +131 -0
@@ -0,0 +1,783 @@
1
+ /**
2
+ * Markdown documentation generator
3
+ * Markdownドキュメント生成器
4
+ */
5
+ export class MarkdownGenerator {
6
+ /**
7
+ * Generate complete documentation
8
+ */
9
+ generateDocumentation(report) {
10
+ const docs = new Map();
11
+ // Index page
12
+ docs.set('index.md', this.generateIndex(report));
13
+ // Repository pages
14
+ for (const repo of report.repositories) {
15
+ docs.set(`repos/${repo.name}/index.md`, this.generateRepoIndex(repo));
16
+ docs.set(`repos/${repo.name}/pages.md`, this.generatePagesDoc(repo));
17
+ docs.set(`repos/${repo.name}/components.md`, this.generateComponentsDoc(repo));
18
+ docs.set(`repos/${repo.name}/graphql.md`, this.generateGraphQLDoc(repo));
19
+ docs.set(`repos/${repo.name}/dataflow.md`, this.generateDataFlowDoc(repo));
20
+ }
21
+ // Cross-repo analysis
22
+ docs.set('cross-repo.md', this.generateCrossRepoDoc(report));
23
+ // Diagrams
24
+ docs.set('diagrams.md', this.generateDiagramsDoc(report.diagrams));
25
+ return docs;
26
+ }
27
+ generateIndex(report) {
28
+ // Use first repository name or generic title
29
+ const title = report.repositories.length === 1
30
+ ? `${report.repositories[0].displayName} Documentation`
31
+ : 'Project Documentation';
32
+ const lines = [
33
+ `# ${title}`,
34
+ '',
35
+ `Generated: ${report.generatedAt}`,
36
+ '',
37
+ report.repositories.length > 1 ? '## Repositories' : '## Overview',
38
+ '',
39
+ ];
40
+ for (const repo of report.repositories) {
41
+ lines.push(`### [${repo.displayName}](/docs/repos/${repo.name}/index)`);
42
+ lines.push('');
43
+ lines.push(`- **Version**: ${repo.version}`);
44
+ lines.push(`- **Commit**: \`${repo.commitHash.substring(0, 7)}\``);
45
+ lines.push(`- **Pages**: ${repo.summary.totalPages}`);
46
+ lines.push(`- **Components**: ${repo.summary.totalComponents}`);
47
+ lines.push(`- **GraphQL Ops**: ${repo.summary.totalGraphQLOperations}`);
48
+ lines.push('');
49
+ }
50
+ lines.push('## Quick Links');
51
+ lines.push('');
52
+ lines.push('- [Cross Repository](/docs/cross-repo)');
53
+ lines.push('- [Diagrams](/docs/diagrams)');
54
+ lines.push('- [Page Map (Interactive)](/page-map)');
55
+ lines.push('');
56
+ return lines.join('\n');
57
+ }
58
+ generateRepoIndex(repo) {
59
+ const lines = [
60
+ `# ${repo.displayName}`,
61
+ '',
62
+ `Version: ${repo.version} | Commit: \`${repo.commitHash.substring(0, 7)}\``,
63
+ '',
64
+ '## Overview',
65
+ '',
66
+ `| Metric | Count |`,
67
+ `|--------|-------|`,
68
+ `| Pages | ${repo.summary.totalPages} |`,
69
+ `| Components | ${repo.summary.totalComponents} |`,
70
+ `| GraphQL Operations | ${repo.summary.totalGraphQLOperations} |`,
71
+ `| Data Flows | ${repo.summary.totalDataFlows} |`,
72
+ `| Auth Required | ${repo.summary.authRequiredPages} |`,
73
+ `| Public | ${repo.summary.publicPages} |`,
74
+ '',
75
+ '## Documentation',
76
+ '',
77
+ `- [Pages](/docs/repos/${repo.name}/pages)`,
78
+ `- [Components](/docs/repos/${repo.name}/components)`,
79
+ `- [GraphQL](/docs/repos/${repo.name}/graphql)`,
80
+ `- [Data Flow](/docs/repos/${repo.name}/dataflow)`,
81
+ '',
82
+ '## Quick Access',
83
+ '',
84
+ `- [Page Map](/page-map)`,
85
+ `- [Diagrams](/docs/diagrams)`,
86
+ '',
87
+ ];
88
+ return lines.join('\n');
89
+ }
90
+ generatePagesDoc(repo) {
91
+ const lines = [`# ${repo.displayName} - Pages`, ''];
92
+ // Overview statistics
93
+ const authRequired = repo.analysis.pages.filter((p) => p.authentication.required).length;
94
+ const withQueries = repo.analysis.pages.filter((p) => p.dataFetching.some((df) => !df.type.includes('Mutation'))).length;
95
+ const withMutations = repo.analysis.pages.filter((p) => p.dataFetching.some((df) => df.type.includes('Mutation'))).length;
96
+ lines.push('| Metric | Value |');
97
+ lines.push('|--------|-------|');
98
+ lines.push(`| Total | **${repo.analysis.pages.length}** |`);
99
+ lines.push(`| Auth Required | ${authRequired} |`);
100
+ lines.push(`| With Queries | ${withQueries} |`);
101
+ lines.push(`| With Mutations | ${withMutations} |`);
102
+ lines.push('');
103
+ // Group by URL category
104
+ const byCategory = new Map();
105
+ for (const page of repo.analysis.pages) {
106
+ const category = page.path.split('/')[1] || 'root';
107
+ const existing = byCategory.get(category) || [];
108
+ existing.push(page);
109
+ byCategory.set(category, existing);
110
+ }
111
+ for (const [category, pages] of byCategory) {
112
+ lines.push(`## /${category}`);
113
+ lines.push('');
114
+ lines.push('| Page | Auth | Layout | Data |');
115
+ lines.push('|------|------|--------|------|');
116
+ for (const page of pages) {
117
+ const pathDisplay = page.path.replace(`/${category}`, '') || '/';
118
+ const auth = page.authentication.required ? 'Required' : 'Public';
119
+ const layout = page.layout || '-';
120
+ const dataOps = [];
121
+ const queries = page.dataFetching.filter((df) => !df.type.includes('Mutation'));
122
+ const mutations = page.dataFetching.filter((df) => df.type.includes('Mutation'));
123
+ // Show first 2 queries - use original name for consistency, filter empty
124
+ for (const q of queries.slice(0, 2)) {
125
+ const rawName = q.operationName || '';
126
+ if (!rawName || rawName.trim().length < 2)
127
+ continue;
128
+ const isRef = rawName.startsWith('→') || rawName.startsWith('->');
129
+ const cleanName = rawName.replace(/^[→\->\s]+/, '');
130
+ if (cleanName && cleanName.trim().length > 0) {
131
+ if (isRef) {
132
+ dataOps.push(`<span class="gql-ref" data-ref="${cleanName}" title="Component">${cleanName}</span>`);
133
+ }
134
+ else {
135
+ dataOps.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
136
+ }
137
+ }
138
+ }
139
+ // Show first 2 mutations - use original name for consistency, filter empty
140
+ for (const m of mutations.slice(0, 2)) {
141
+ const rawName = m.operationName || '';
142
+ if (!rawName || rawName.trim().length < 2)
143
+ continue;
144
+ const isRef = rawName.startsWith('→') || rawName.startsWith('->');
145
+ const cleanName = rawName.replace(/^[→\->\s]+/, '');
146
+ if (cleanName && cleanName.trim().length > 0) {
147
+ if (isRef) {
148
+ dataOps.push(`<span class="gql-ref mutation" data-ref="${cleanName}" title="Component">${cleanName}</span>`);
149
+ }
150
+ else {
151
+ dataOps.push(`<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`);
152
+ }
153
+ }
154
+ }
155
+ // If there are more, add a "more" button
156
+ const remaining = queries.length +
157
+ mutations.length -
158
+ Math.min(queries.length, 2) -
159
+ Math.min(mutations.length, 2);
160
+ if (remaining > 0) {
161
+ dataOps.push(`<span class="gql-more" data-type="all" data-page="${page.path}">+${remaining} more</span>`);
162
+ }
163
+ const data = dataOps.length > 0 ? dataOps.join(' ') : '-';
164
+ lines.push(`| \`${pathDisplay}\` | ${auth} | ${layout} | ${data} |`);
165
+ }
166
+ lines.push('');
167
+ // Details for each page with GraphQL operations
168
+ for (const page of pages) {
169
+ const queries = page.dataFetching.filter((df) => !df.type.includes('Mutation'));
170
+ const mutations = page.dataFetching.filter((df) => df.type.includes('Mutation'));
171
+ if (queries.length > 0 || mutations.length > 0) {
172
+ lines.push(`### ${page.path}`);
173
+ lines.push('');
174
+ lines.push(`> ${page.filePath}`);
175
+ lines.push('');
176
+ // Queries section - filter empty
177
+ const validQs = queries.filter((q) => q.operationName && q.operationName.trim().length >= 2);
178
+ if (validQs.length > 0) {
179
+ lines.push(`**Queries (${validQs.length})**`);
180
+ lines.push('');
181
+ lines.push('<div class="gql-ops-list">');
182
+ for (const q of validQs) {
183
+ const rawName = q.operationName || '';
184
+ const isRef = rawName.startsWith('→') || rawName.startsWith('->');
185
+ const cleanName = rawName.replace(/^[→\->\s]+/, '');
186
+ if (isRef) {
187
+ lines.push(`<span class="gql-ref" data-ref="${cleanName}" title="Component">${cleanName}</span>`);
188
+ }
189
+ else {
190
+ lines.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
191
+ }
192
+ }
193
+ lines.push('</div>');
194
+ lines.push('');
195
+ }
196
+ // Mutations section - filter empty
197
+ const validMuts = mutations.filter((m) => m.operationName && m.operationName.trim().length >= 2);
198
+ if (validMuts.length > 0) {
199
+ lines.push(`**Mutations (${validMuts.length})**`);
200
+ lines.push('');
201
+ lines.push('<div class="gql-ops-list">');
202
+ for (const m of validMuts) {
203
+ const rawName = m.operationName || '';
204
+ const isRef = rawName.startsWith('→') || rawName.startsWith('->');
205
+ const cleanName = rawName.replace(/^[→\->\s]+/, '');
206
+ if (isRef) {
207
+ lines.push(`<span class="gql-ref mutation" data-ref="${cleanName}" title="Component">${cleanName}</span>`);
208
+ }
209
+ else {
210
+ lines.push(`<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`);
211
+ }
212
+ }
213
+ lines.push('</div>');
214
+ lines.push('');
215
+ }
216
+ lines.push('');
217
+ }
218
+ }
219
+ }
220
+ return lines.join('\n');
221
+ }
222
+ generateComponentsDoc(repo) {
223
+ const lines = [`# ${repo.displayName} - Components`, ''];
224
+ // Statistics
225
+ const byType = new Map();
226
+ for (const comp of repo.analysis.components) {
227
+ const existing = byType.get(comp.type) || [];
228
+ existing.push(comp);
229
+ byType.set(comp.type, existing);
230
+ }
231
+ lines.push('| Type | Count |');
232
+ lines.push('|------|-------|');
233
+ lines.push(`| Container | ${byType.get('container')?.length || 0} |`);
234
+ lines.push(`| Presentational | ${byType.get('presentational')?.length || 0} |`);
235
+ lines.push(`| Layout | ${byType.get('layout')?.length || 0} |`);
236
+ lines.push(`| Hook | ${byType.get('hook')?.length || 0} |`);
237
+ lines.push(`| **Total** | **${repo.analysis.components.length}** |`);
238
+ lines.push('');
239
+ // Build page -> component mapping
240
+ const pageComponents = new Map();
241
+ for (const page of repo.analysis.pages) {
242
+ pageComponents.set(page.path, []);
243
+ }
244
+ for (const comp of repo.analysis.components) {
245
+ for (const page of repo.analysis.pages) {
246
+ const pageFeature = this.extractFeatureFromPage(page.filePath);
247
+ const compFeature = this.extractFeatureFromComponent(comp.filePath);
248
+ if (pageFeature && compFeature && pageFeature === compFeature) {
249
+ pageComponents.get(page.path)?.push(comp);
250
+ }
251
+ }
252
+ }
253
+ // Page-based component structure
254
+ lines.push('## By Page');
255
+ lines.push('');
256
+ for (const [pagePath, components] of pageComponents) {
257
+ if (components.length === 0)
258
+ continue;
259
+ const containers = components.filter((c) => c.type === 'container');
260
+ const presentationals = components.filter((c) => c.type === 'presentational');
261
+ const hooks = components.filter((c) => c.type === 'hook');
262
+ lines.push(`### ${pagePath}`);
263
+ lines.push('');
264
+ lines.push('| Component | Type | Data |');
265
+ lines.push('|-----------|------|------|');
266
+ for (const comp of containers) {
267
+ const dataOps = this.formatComponentDataOps(comp);
268
+ lines.push(`| ${comp.name} | Container | ${dataOps || '-'} |`);
269
+ }
270
+ for (const comp of presentationals.slice(0, 10)) {
271
+ lines.push(`| ${comp.name} | UI | - |`);
272
+ }
273
+ if (presentationals.length > 10) {
274
+ lines.push(`| *+${presentationals.length - 10} more* | UI | - |`);
275
+ }
276
+ for (const comp of hooks) {
277
+ lines.push(`| ${comp.name} | Hook | - |`);
278
+ }
279
+ lines.push('');
280
+ }
281
+ // Type summary tables
282
+ lines.push('## By Type');
283
+ lines.push('');
284
+ for (const [type, components] of byType) {
285
+ lines.push(`### ${type.charAt(0).toUpperCase() + type.slice(1)} (${components.length})`);
286
+ lines.push('');
287
+ lines.push('| Name | File |');
288
+ lines.push('|------|------|');
289
+ for (const comp of components.slice(0, 25)) {
290
+ const shortPath = comp.filePath.replace('src/features/', '').replace('src/', '');
291
+ lines.push(`| ${comp.name} | ${shortPath} |`);
292
+ }
293
+ if (components.length > 25) {
294
+ lines.push(`| *+${components.length - 25} more* | |`);
295
+ }
296
+ lines.push('');
297
+ }
298
+ return lines.join('\n');
299
+ }
300
+ extractFeatureFromPage(filePath) {
301
+ // Extract feature name from page file like "contract/billing_information.tsx" -> "contract"
302
+ const parts = filePath.split('/');
303
+ if (parts.length > 1) {
304
+ return parts[0];
305
+ }
306
+ return null;
307
+ }
308
+ extractFeatureFromComponent(filePath) {
309
+ // Extract feature name from component file like "src/features/contract/..." -> "contract"
310
+ const match = filePath.match(/src\/features\/([^/]+)/);
311
+ if (match) {
312
+ return match[1];
313
+ }
314
+ return null;
315
+ }
316
+ formatComponentDataOps(comp) {
317
+ const queries = [];
318
+ const mutations = [];
319
+ for (const hook of comp.hooks) {
320
+ const queryMatch = hook.match(/(?:useQuery|Query):\s*(\w+)/);
321
+ const mutationMatch = hook.match(/(?:useMutation|Mutation):\s*(\w+)/);
322
+ if (queryMatch && queryMatch[1] && queryMatch[1].trim().length >= 2) {
323
+ queries.push(queryMatch[1]);
324
+ }
325
+ else if (mutationMatch && mutationMatch[1] && mutationMatch[1].trim().length >= 2) {
326
+ mutations.push(mutationMatch[1]);
327
+ }
328
+ }
329
+ // Filter out empty or invalid names
330
+ const validQueries = queries.filter((q) => q && q.trim().length >= 2);
331
+ const validMutations = mutations.filter((m) => m && m.trim().length >= 2);
332
+ if (validQueries.length === 0 && validMutations.length === 0) {
333
+ return '';
334
+ }
335
+ const ops = [];
336
+ const maxShowQueries = 2;
337
+ const maxShowMutations = 2;
338
+ // Show first N queries - use original name for consistency
339
+ const shownQueries = validQueries.slice(0, maxShowQueries);
340
+ for (const name of shownQueries) {
341
+ ops.push(`<span class="gql-op" data-op="${name}">${name}</span>`);
342
+ }
343
+ // Show first N mutations - use original name for consistency
344
+ const shownMutations = validMutations.slice(0, maxShowMutations);
345
+ for (const name of shownMutations) {
346
+ ops.push(`<span class="gql-op mutation" data-op="${name}">${name}</span>`);
347
+ }
348
+ // Calculate remaining correctly
349
+ const hiddenQueries = validQueries.slice(maxShowQueries);
350
+ const hiddenMutations = validMutations.slice(maxShowMutations);
351
+ const remaining = hiddenQueries.length + hiddenMutations.length;
352
+ if (remaining > 0) {
353
+ // Store all hidden operations in data attributes for accurate display
354
+ const allQueries = JSON.stringify(validQueries).replace(/"/g, '&quot;');
355
+ const allMutations = JSON.stringify(validMutations).replace(/"/g, '&quot;');
356
+ ops.push(`<span class="gql-ref" data-ref="${comp.name}" data-queries="${allQueries}" data-mutations="${allMutations}" title="View all ${validQueries.length} queries and ${validMutations.length} mutations">+${remaining} more</span>`);
357
+ }
358
+ return `<div class="gql-ops-inline">${ops.join(' ')}</div>`;
359
+ }
360
+ generateGraphQLDoc(repo) {
361
+ const lines = [`# ${repo.displayName} - GraphQL`, ''];
362
+ const queries = repo.analysis.graphqlOperations.filter((op) => op.type === 'query');
363
+ const mutations = repo.analysis.graphqlOperations.filter((op) => op.type === 'mutation');
364
+ const fragments = repo.analysis.graphqlOperations.filter((op) => op.type === 'fragment');
365
+ lines.push('| Type | Count |');
366
+ lines.push('|------|-------|');
367
+ lines.push(`| Query | ${queries.length} |`);
368
+ lines.push(`| Mutation | ${mutations.length} |`);
369
+ lines.push(`| Fragment | ${fragments.length} |`);
370
+ lines.push(`| **Total** | **${repo.analysis.graphqlOperations.length}** |`);
371
+ lines.push('');
372
+ // Queries
373
+ if (queries.length > 0) {
374
+ lines.push(`## Queries`);
375
+ lines.push('');
376
+ for (const query of queries.slice(0, 80)) {
377
+ lines.push(`### ${query.name}`);
378
+ lines.push('');
379
+ const returnType = query.returnType || 'unknown';
380
+ const varsCount = query.variables.length;
381
+ const usedCount = query.usedIn.length;
382
+ lines.push(`> Return: \`${returnType}\` | Variables: ${varsCount} | Used: ${usedCount} files`);
383
+ lines.push('');
384
+ if (query.variables.length > 0) {
385
+ lines.push('| Variable | Type |');
386
+ lines.push('|----------|------|');
387
+ for (const v of query.variables) {
388
+ lines.push(`| ${v.name} | \`${v.type}\` |`);
389
+ }
390
+ lines.push('');
391
+ }
392
+ if (query.fields && query.fields.length > 0) {
393
+ lines.push('```graphql');
394
+ lines.push(this.formatGraphQLFields(query.fields, 0));
395
+ lines.push('```');
396
+ lines.push('');
397
+ }
398
+ }
399
+ if (queries.length > 80)
400
+ lines.push(`*+${queries.length - 80} more queries*\n`);
401
+ }
402
+ // Mutations
403
+ if (mutations.length > 0) {
404
+ lines.push(`## Mutations`);
405
+ lines.push('');
406
+ for (const mutation of mutations.slice(0, 80)) {
407
+ lines.push(`### ${mutation.name}`);
408
+ lines.push('');
409
+ const varsCount = mutation.variables.length;
410
+ const usedCount = mutation.usedIn.length;
411
+ lines.push(`> Variables: ${varsCount} | Used: ${usedCount} files`);
412
+ lines.push('');
413
+ if (mutation.variables.length > 0) {
414
+ lines.push('| Variable | Type |');
415
+ lines.push('|----------|------|');
416
+ for (const v of mutation.variables) {
417
+ lines.push(`| ${v.name} | \`${v.type}\` |`);
418
+ }
419
+ lines.push('');
420
+ }
421
+ if (mutation.fields && mutation.fields.length > 0) {
422
+ lines.push('```graphql');
423
+ lines.push(this.formatGraphQLFields(mutation.fields, 0));
424
+ lines.push('```');
425
+ lines.push('');
426
+ }
427
+ }
428
+ if (mutations.length > 80)
429
+ lines.push(`*+${mutations.length - 80} more mutations*\n`);
430
+ }
431
+ // Fragments
432
+ if (fragments.length > 0) {
433
+ lines.push(`## Fragments`);
434
+ lines.push('');
435
+ lines.push('| Name | Type | Fields |');
436
+ lines.push('|------|------|--------|');
437
+ for (const fragment of fragments.slice(0, 50)) {
438
+ const fieldCount = fragment.fields?.length || 0;
439
+ lines.push(`| ${fragment.name} | ${fragment.returnType || '-'} | ${fieldCount} |`);
440
+ }
441
+ if (fragments.length > 50)
442
+ lines.push(`| *+${fragments.length - 50} more* | | |`);
443
+ lines.push('');
444
+ }
445
+ return lines.join('\n');
446
+ }
447
+ formatGraphQLFields(fields, indent) {
448
+ if (!fields || fields.length === 0)
449
+ return '';
450
+ const lines = [];
451
+ for (const field of fields) {
452
+ const prefix = ' '.repeat(indent);
453
+ if (field.fields && field.fields.length > 0) {
454
+ lines.push(`${prefix}${field.name} {`);
455
+ lines.push(this.formatGraphQLFields(field.fields, indent + 1));
456
+ lines.push(`${prefix}}`);
457
+ }
458
+ else {
459
+ lines.push(`${prefix}${field.name}`);
460
+ }
461
+ }
462
+ return lines.join('\n');
463
+ }
464
+ generateDataFlowDoc(repo) {
465
+ const lines = [`# ${repo.displayName} - Data Flow`, ''];
466
+ // Overview
467
+ const queryFlows = repo.analysis.dataFlows.filter((f) => f.name.includes('📡') || f.operations.some((o) => o.includes('Query')));
468
+ const mutationFlows = repo.analysis.dataFlows.filter((f) => f.name.includes('✏️') || f.operations.some((o) => o.includes('Mutation')));
469
+ const contextFlows = repo.analysis.dataFlows.filter((f) => f.source.type === 'context');
470
+ lines.push('## Overview');
471
+ lines.push('');
472
+ lines.push('| Type | Count | Direction |');
473
+ lines.push('|------|-------|-----------|');
474
+ lines.push(`| \`QUERY\` | ${queryFlows.length} | Server → Component |`);
475
+ lines.push(`| \`MUTATION\` | ${mutationFlows.length} | Component → Server |`);
476
+ lines.push(`| \`CONTEXT\` | ${contextFlows.length} | Provider → Consumer |`);
477
+ lines.push(`| **Total** | **${repo.analysis.dataFlows.length}** | |`);
478
+ lines.push('');
479
+ // Architecture diagram
480
+ lines.push('## Architecture');
481
+ lines.push('');
482
+ lines.push('```mermaid');
483
+ lines.push('flowchart LR');
484
+ lines.push(' subgraph Server["GraphQL Server"]');
485
+ lines.push(' API[(API)]');
486
+ lines.push(' end');
487
+ lines.push(' subgraph Client["React Application"]');
488
+ lines.push(' Apollo[Apollo Client]');
489
+ lines.push(' Container[Container Component]');
490
+ lines.push(' View[View Component]');
491
+ lines.push(' end');
492
+ lines.push(' API -->|Query Response| Apollo');
493
+ lines.push(' Apollo -->|Cache/Data| Container');
494
+ lines.push(' Container -->|Props| View');
495
+ lines.push(' View -->|User Action| Container');
496
+ lines.push(' Container -->|Mutation| Apollo');
497
+ lines.push(' Apollo -->|GraphQL Request| API');
498
+ lines.push('```');
499
+ lines.push('');
500
+ // Page-based data flow
501
+ lines.push('## Page Data Flows');
502
+ lines.push('');
503
+ for (const page of repo.analysis.pages) {
504
+ const pageFeature = this.extractFeatureFromPage(page.filePath);
505
+ const relatedComponents = repo.analysis.components.filter((c) => {
506
+ const compFeature = this.extractFeatureFromComponent(c.filePath);
507
+ return compFeature === pageFeature;
508
+ });
509
+ const hasDataOps = page.dataFetching.length > 0 ||
510
+ relatedComponents.some((c) => c.stateManagement.some((s) => s.includes('Apollo') || s.includes('Context')));
511
+ if (!hasDataOps)
512
+ continue;
513
+ lines.push(`### ${page.path}`);
514
+ lines.push('');
515
+ lines.push(`\`FILE: ${page.filePath}\``);
516
+ lines.push('');
517
+ // Mermaid flow diagram for this page
518
+ const pageOps = this.getPageOperations(page, relatedComponents, repo.analysis.graphqlOperations);
519
+ // Filter out empty operation names before creating diagram
520
+ const validQueries = pageOps.queries.filter((q) => q && q.trim().length > 0);
521
+ const validMutations = pageOps.mutations.filter((m) => m && m.trim().length > 0);
522
+ if (validQueries.length > 0 || validMutations.length > 0) {
523
+ lines.push('```mermaid');
524
+ lines.push('flowchart LR');
525
+ const pageId = page.path.replace(/[^a-zA-Z0-9]/g, '_');
526
+ // Escape special characters in path for Mermaid
527
+ const safePath = page.path.replace(/"/g, "'");
528
+ lines.push(` Page${pageId}["${safePath}"]`);
529
+ validQueries.slice(0, 5).forEach((q, i) => {
530
+ const qId = `Q${pageId}_${i}`;
531
+ // Escape special characters in operation name
532
+ const safeQ = q.replace(/"/g, "'").replace(/[<>]/g, '');
533
+ lines.push(` ${qId}["${safeQ}"]:::query --> Page${pageId}`);
534
+ });
535
+ validMutations.slice(0, 5).forEach((m, i) => {
536
+ const mId = `M${pageId}_${i}`;
537
+ // Escape special characters in operation name
538
+ const safeM = m.replace(/"/g, "'").replace(/[<>]/g, '');
539
+ lines.push(` Page${pageId} --> ${mId}["${safeM}"]:::mutation`);
540
+ });
541
+ lines.push(' classDef query fill:#dbeafe,stroke:#1d4ed8,color:#1e40af');
542
+ lines.push(' classDef mutation fill:#fce7f3,stroke:#be185d,color:#9d174d');
543
+ lines.push('```');
544
+ lines.push('');
545
+ }
546
+ // Operations list with clickable tags - grouped by type
547
+ if (pageOps.queries.length > 0 || pageOps.mutations.length > 0) {
548
+ // Queries section - use original name for consistency, filter empty
549
+ if (validQueries.length > 0) {
550
+ lines.push(`**Queries (${validQueries.length})**`);
551
+ lines.push('');
552
+ lines.push('<div class="gql-ops-list">');
553
+ for (const q of validQueries) {
554
+ const isRef = q.startsWith('→') || q.startsWith('->');
555
+ const cleanName = q.replace(/^[→\->\s]+/, '');
556
+ if (cleanName && cleanName.trim().length > 0) {
557
+ if (isRef) {
558
+ lines.push(`<span class="gql-ref" data-ref="${cleanName}" title="Component: ${cleanName}">${cleanName}</span>`);
559
+ }
560
+ else {
561
+ lines.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
562
+ }
563
+ }
564
+ }
565
+ lines.push('</div>');
566
+ lines.push('');
567
+ }
568
+ // Mutations section - use original name for consistency, filter empty
569
+ if (validMutations.length > 0) {
570
+ lines.push(`**Mutations (${validMutations.length})**`);
571
+ lines.push('');
572
+ lines.push('<div class="gql-ops-list">');
573
+ for (const m of validMutations) {
574
+ const isRef = m.startsWith('→') || m.startsWith('->');
575
+ const cleanName = m.replace(/^[→\->\s]+/, '');
576
+ if (cleanName && cleanName.trim().length > 0) {
577
+ if (isRef) {
578
+ lines.push(`<span class="gql-ref mutation" data-ref="${cleanName}" title="Component: ${cleanName}">${cleanName}</span>`);
579
+ }
580
+ else {
581
+ lines.push(`<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`);
582
+ }
583
+ }
584
+ }
585
+ lines.push('</div>');
586
+ lines.push('');
587
+ }
588
+ }
589
+ lines.push('---');
590
+ lines.push('');
591
+ }
592
+ // Context providers
593
+ const providers = new Set();
594
+ for (const flow of contextFlows) {
595
+ providers.add(flow.source.name);
596
+ }
597
+ if (providers.size > 0) {
598
+ lines.push('## Context Providers');
599
+ lines.push('');
600
+ lines.push('| Provider | Description |');
601
+ lines.push('|----------|-------------|');
602
+ for (const provider of providers) {
603
+ lines.push(`| \`${provider}\` | Provides shared state |`);
604
+ }
605
+ lines.push('');
606
+ }
607
+ return lines.join('\n');
608
+ }
609
+ getPageOperations(page, relatedComponents, _allOperations) {
610
+ const queries = new Set();
611
+ const mutations = new Set();
612
+ // Helper to validate operation name
613
+ const isValidOpName = (name) => {
614
+ if (!name)
615
+ return false;
616
+ const trimmed = name.trim();
617
+ // Must have at least 2 alphanumeric characters
618
+ return trimmed.length >= 2 && /[a-zA-Z]/.test(trimmed);
619
+ };
620
+ // From page dataFetching
621
+ for (const df of page.dataFetching) {
622
+ const rawName = df.operationName?.replace(/^[→\->\s]+/, '') || '';
623
+ const name = rawName.replace(/Document$/g, '');
624
+ if (isValidOpName(name)) {
625
+ if (df.type?.includes('Mutation')) {
626
+ mutations.add(name);
627
+ }
628
+ else {
629
+ queries.add(name);
630
+ }
631
+ }
632
+ }
633
+ // From related components
634
+ for (const comp of relatedComponents) {
635
+ for (const hook of comp.hooks) {
636
+ if (hook.includes('Query')) {
637
+ const match = hook.match(/:\s*(.+)$/);
638
+ if (match && isValidOpName(match[1])) {
639
+ queries.add(match[1].trim());
640
+ }
641
+ }
642
+ if (hook.includes('Mutation')) {
643
+ const match = hook.match(/:\s*(.+)$/);
644
+ if (match && isValidOpName(match[1])) {
645
+ mutations.add(match[1].trim());
646
+ }
647
+ }
648
+ }
649
+ }
650
+ // Filter out any remaining invalid names
651
+ return {
652
+ queries: Array.from(queries).filter(isValidOpName),
653
+ mutations: Array.from(mutations).filter(isValidOpName),
654
+ };
655
+ }
656
+ generateCrossRepoDoc(report) {
657
+ const lines = [
658
+ '# クロスリポジトリ分析',
659
+ '',
660
+ '## アーキテクチャ概要',
661
+ '',
662
+ '```mermaid',
663
+ 'flowchart TB',
664
+ ];
665
+ for (const repo of report.repositories) {
666
+ const repoId = repo.name.replace(/[^a-zA-Z0-9]/g, '_');
667
+ lines.push(` subgraph ${repoId}["${repo.displayName}"]`);
668
+ lines.push(` ${repoId}_core["Core"]`);
669
+ lines.push(' end');
670
+ }
671
+ lines.push('```');
672
+ lines.push('');
673
+ // API Connections
674
+ lines.push('## API接続');
675
+ lines.push('');
676
+ for (const conn of report.crossRepoAnalysis.apiConnections) {
677
+ lines.push(`- **${conn.frontend}** → **${conn.backend}**: \`${conn.endpoint}\``);
678
+ }
679
+ lines.push('');
680
+ // Shared types
681
+ lines.push('## 共有型');
682
+ lines.push('');
683
+ for (const type of report.crossRepoAnalysis.sharedTypes) {
684
+ lines.push(`- \`${type}\``);
685
+ }
686
+ lines.push('');
687
+ return lines.join('\n');
688
+ }
689
+ createPageDataFlowDiagram(page, relatedComponents, _dataFlows) {
690
+ const lines = [];
691
+ // Extract queries and mutations from related components
692
+ const queries = [];
693
+ const mutations = [];
694
+ const containers = [];
695
+ for (const comp of relatedComponents) {
696
+ if (comp.type === 'container') {
697
+ containers.push(comp.name);
698
+ }
699
+ for (const hook of comp.hooks) {
700
+ if (hook.includes('Query') || hook.includes('📡')) {
701
+ const name = hook.includes(':') ? hook.split(':')[1].trim() : hook;
702
+ if (!queries.includes(name))
703
+ queries.push(name);
704
+ }
705
+ if (hook.includes('Mutation') || hook.includes('✏️')) {
706
+ const name = hook.includes(':') ? hook.split(':')[1].trim() : hook;
707
+ if (!mutations.includes(name))
708
+ mutations.push(name);
709
+ }
710
+ }
711
+ }
712
+ // Also check page's own data fetching
713
+ for (const df of page.dataFetching) {
714
+ const name = df.operationName.replace(/^→\s*/, '');
715
+ if (df.type.includes('Query') && !queries.includes(name)) {
716
+ queries.push(name);
717
+ }
718
+ else if (df.type.includes('Mutation') && !mutations.includes(name)) {
719
+ mutations.push(name);
720
+ }
721
+ }
722
+ // Build ASCII diagram
723
+ lines.push(`[Page: ${page.path}]`);
724
+ lines.push('│');
725
+ if (queries.length > 0 || mutations.length > 0) {
726
+ lines.push('├─ 📡 データ取得 (Query)');
727
+ for (const q of queries.slice(0, 5)) {
728
+ lines.push(`│ ├─ ${q.substring(0, 40)}`);
729
+ lines.push(`│ │ └─ GraphQL Server → Apollo Cache → Component`);
730
+ }
731
+ if (queries.length > 5) {
732
+ lines.push(`│ └─ ... 他 ${queries.length - 5} 件`);
733
+ }
734
+ if (mutations.length > 0) {
735
+ lines.push('│');
736
+ lines.push('├─ ✏️ データ更新 (Mutation)');
737
+ for (const m of mutations.slice(0, 5)) {
738
+ lines.push(`│ ├─ ${m.substring(0, 40)}`);
739
+ lines.push(`│ │ └─ Component → GraphQL Server → Apollo Cache`);
740
+ }
741
+ if (mutations.length > 5) {
742
+ lines.push(`│ └─ ... 他 ${mutations.length - 5} 件`);
743
+ }
744
+ }
745
+ }
746
+ if (containers.length > 0) {
747
+ lines.push('│');
748
+ lines.push('├─ 📦 Container Components');
749
+ for (const c of containers.slice(0, 5)) {
750
+ lines.push(`│ └─ ${c}`);
751
+ }
752
+ if (containers.length > 5) {
753
+ lines.push(`│ └─ ... 他 ${containers.length - 5} 件`);
754
+ }
755
+ }
756
+ lines.push('│');
757
+ lines.push('└─ [Render]');
758
+ return lines.join('\n');
759
+ }
760
+ generateDiagramsDoc(diagrams) {
761
+ const lines = ['# Diagrams', ''];
762
+ lines.push('## Overview');
763
+ lines.push('');
764
+ lines.push('| Diagram | Type | Description |');
765
+ lines.push('|---------|------|-------------|');
766
+ for (const diagram of diagrams) {
767
+ lines.push(`| ${diagram.title} | \`${diagram.type.toUpperCase()}\` | Auto-generated |`);
768
+ }
769
+ lines.push('');
770
+ for (const diagram of diagrams) {
771
+ lines.push(`## ${diagram.title}`);
772
+ lines.push('');
773
+ lines.push(`\`TYPE: ${diagram.type.toUpperCase()}\``);
774
+ lines.push('');
775
+ lines.push('```mermaid');
776
+ lines.push(diagram.content);
777
+ lines.push('```');
778
+ lines.push('');
779
+ }
780
+ return lines.join('\n');
781
+ }
782
+ }
783
+ //# sourceMappingURL=markdown-generator.js.map