@wtdlee/repomap 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1058 +0,0 @@
1
- // src/generators/mermaid-generator.ts
2
- var MermaidGenerator = class {
3
- /**
4
- * Generate all diagrams from analysis result
5
- */
6
- generateAll(results, crossRepoLinks) {
7
- const diagrams = [];
8
- for (const result of results) {
9
- diagrams.push(this.generateNavigationDiagram(result));
10
- diagrams.push(this.generateDataFlowDiagram(result));
11
- diagrams.push(this.generateComponentDiagram(result));
12
- diagrams.push(this.generateGraphQLDiagram(result));
13
- }
14
- if (results.length > 1) {
15
- diagrams.push(this.generateCrossRepoDiagram(results, crossRepoLinks));
16
- }
17
- return diagrams;
18
- }
19
- /**
20
- * Generate navigation flowchart - grouped by URL category
21
- */
22
- generateNavigationDiagram(result) {
23
- const lines = ["flowchart TB", " %% Page Navigation Flow - Grouped by Category"];
24
- const nodeIds = /* @__PURE__ */ new Map();
25
- let nodeCounter = 0;
26
- const byCategory = /* @__PURE__ */ new Map();
27
- for (const page of result.pages) {
28
- const parts = page.path.split("/").filter(Boolean);
29
- const category = parts[0] || "root";
30
- const existing = byCategory.get(category) || [];
31
- existing.push(page);
32
- byCategory.set(category, existing);
33
- }
34
- for (const [category, pages] of byCategory) {
35
- const categoryId = category.replace(/[^a-zA-Z0-9]/g, "_");
36
- const categoryLabel = category === "root" ? "Root Pages" : `/${category}`;
37
- lines.push("");
38
- lines.push(` subgraph ${categoryId}["${categoryLabel}"]`);
39
- lines.push(" direction TB");
40
- for (const page of pages) {
41
- const nodeId = `P${nodeCounter++}`;
42
- nodeIds.set(page.path, nodeId);
43
- const pathParts = page.path.split("/").filter(Boolean);
44
- const shortLabel = pathParts.length > 1 ? pathParts.slice(1).join("/") : page.path;
45
- const authTag = page.authentication.required ? " AUTH" : "";
46
- lines.push(` ${nodeId}["${shortLabel}${authTag}"]`);
47
- }
48
- lines.push(" end");
49
- }
50
- let edgeCount = 0;
51
- const maxEdges = 30;
52
- lines.push("");
53
- lines.push(" %% Navigation Links");
54
- for (const page of result.pages) {
55
- if (edgeCount >= maxEdges) break;
56
- const sourceId = nodeIds.get(page.path);
57
- for (const linkedPath of page.linkedPages.slice(0, 2)) {
58
- if (edgeCount >= maxEdges) break;
59
- const targetId = nodeIds.get(linkedPath);
60
- if (sourceId && targetId && sourceId !== targetId) {
61
- lines.push(` ${sourceId} --> ${targetId}`);
62
- edgeCount++;
63
- }
64
- }
65
- }
66
- lines.push("");
67
- lines.push(" %% Styling");
68
- lines.push(" classDef authRequired fill:#fee2e2,stroke:#ef4444,color:#991b1b");
69
- lines.push(" classDef public fill:#dcfce7,stroke:#22c55e,color:#166534");
70
- for (const page of result.pages) {
71
- const nodeId = nodeIds.get(page.path);
72
- if (nodeId) {
73
- if (page.authentication.required) {
74
- lines.push(` class ${nodeId} authRequired`);
75
- } else {
76
- lines.push(` class ${nodeId} public`);
77
- }
78
- }
79
- }
80
- return {
81
- type: "flowchart",
82
- title: `${result.repository} - Page Navigation`,
83
- content: lines.join("\n"),
84
- relatedFiles: result.pages.map((p) => p.filePath)
85
- };
86
- }
87
- /**
88
- * Generate data flow diagram
89
- */
90
- generateDataFlowDiagram(result) {
91
- const lines = ["flowchart LR", " %% Data Flow Diagram"];
92
- const nodeIds = /* @__PURE__ */ new Map();
93
- let nodeCounter = 0;
94
- const getNodeId = (node) => {
95
- const key = `${node.type}:${node.name}`;
96
- if (!nodeIds.has(key)) {
97
- const prefix = node.type.charAt(0).toUpperCase();
98
- nodeIds.set(key, `${prefix}${nodeCounter++}`);
99
- }
100
- return nodeIds.get(key) ?? `N${nodeCounter++}`;
101
- };
102
- const cleanLabel = (name) => {
103
- return name.replace(/[\u{1F4E1}\u{270F}\u{FE0F}\u{1F504}\u{1F4E6}]/gu, "").trim().substring(0, 40);
104
- };
105
- const queryFlows = result.dataFlows.filter(
106
- (f) => f.name.includes("\u{1F4E1}") || f.operations.some((o) => o.includes("Query"))
107
- );
108
- const mutationFlows = result.dataFlows.filter(
109
- (f) => f.name.includes("\u270F\uFE0F") || f.operations.some((o) => o.includes("Mutation"))
110
- );
111
- const contextFlows = result.dataFlows.filter(
112
- (f) => f.name.includes("\u{1F504}") || f.source.type === "context" || f.operations.some((o) => o.includes("Context"))
113
- );
114
- if (queryFlows.length > 0) {
115
- lines.push("");
116
- lines.push(" subgraph Queries[\u{1F4E1} Queries]");
117
- lines.push(" direction TB");
118
- for (const flow of queryFlows.slice(0, 20)) {
119
- const sourceId = getNodeId(flow.source);
120
- const targetId = getNodeId(flow.target);
121
- const sourceName = cleanLabel(flow.source.name);
122
- const targetName = cleanLabel(flow.target.name);
123
- lines.push(` ${sourceId}(("${sourceName}"))`);
124
- lines.push(` ${targetId}["${targetName}"]`);
125
- lines.push(` ${sourceId} --> ${targetId}`);
126
- }
127
- lines.push(" end");
128
- }
129
- if (mutationFlows.length > 0) {
130
- lines.push("");
131
- lines.push(" subgraph Mutations[\u270F\uFE0F Mutations]");
132
- lines.push(" direction TB");
133
- for (const flow of mutationFlows.slice(0, 20)) {
134
- const sourceId = getNodeId(flow.source);
135
- const targetId = getNodeId(flow.target);
136
- const sourceName = cleanLabel(flow.source.name);
137
- const targetName = cleanLabel(flow.target.name);
138
- lines.push(` ${sourceId}["${sourceName}"]`);
139
- lines.push(` ${targetId}(("${targetName}"))`);
140
- lines.push(` ${sourceId} --> ${targetId}`);
141
- }
142
- lines.push(" end");
143
- }
144
- if (contextFlows.length > 0) {
145
- lines.push("");
146
- lines.push(" subgraph Context[\u{1F504} Context]");
147
- lines.push(" direction TB");
148
- for (const flow of contextFlows.slice(0, 15)) {
149
- const sourceId = getNodeId(flow.source);
150
- const targetId = getNodeId(flow.target);
151
- const sourceName = cleanLabel(flow.source.name);
152
- const targetName = cleanLabel(flow.target.name);
153
- lines.push(` ${sourceId}{{"${sourceName}"}}`);
154
- lines.push(` ${targetId}["${targetName}"]`);
155
- lines.push(` ${sourceId} -.-> ${targetId}`);
156
- }
157
- lines.push(" end");
158
- }
159
- lines.push("");
160
- lines.push(" %% Styling");
161
- lines.push(" classDef query fill:#dbeafe,stroke:#3b82f6,color:#1e40af");
162
- lines.push(" classDef mutation fill:#fce7f3,stroke:#ec4899,color:#9d174d");
163
- lines.push(" classDef context fill:#d1fae5,stroke:#10b981,color:#065f46");
164
- return {
165
- type: "flowchart",
166
- title: `${result.repository} - Data Flow`,
167
- content: lines.join("\n"),
168
- relatedFiles: result.dataFlows.map((f) => f.source.name)
169
- };
170
- }
171
- /**
172
- * Generate component hierarchy diagram
173
- */
174
- generateComponentDiagram(result) {
175
- const lines = ["flowchart TB", " %% Component Hierarchy"];
176
- const byType = /* @__PURE__ */ new Map();
177
- for (const comp of result.components) {
178
- const existing = byType.get(comp.type) || [];
179
- existing.push(comp);
180
- byType.set(comp.type, existing);
181
- }
182
- for (const [type, components] of byType) {
183
- lines.push(` subgraph ${type.charAt(0).toUpperCase() + type.slice(1)}s`);
184
- for (const comp of components.slice(0, 20)) {
185
- const nodeId = comp.name.replace(/[^a-zA-Z0-9]/g, "_");
186
- lines.push(` ${nodeId}["${comp.name}"]`);
187
- }
188
- lines.push(" end");
189
- }
190
- let edgeCount = 0;
191
- const maxEdges = 50;
192
- for (const comp of result.components) {
193
- if (edgeCount >= maxEdges) break;
194
- const sourceId = comp.name.replace(/[^a-zA-Z0-9]/g, "_");
195
- for (const dep of comp.dependencies.slice(0, 3)) {
196
- if (edgeCount >= maxEdges) break;
197
- const targetId = dep.replace(/[^a-zA-Z0-9]/g, "_");
198
- lines.push(` ${sourceId} --> ${targetId}`);
199
- edgeCount++;
200
- }
201
- }
202
- return {
203
- type: "flowchart",
204
- title: `${result.repository} - Component Hierarchy`,
205
- content: lines.join("\n"),
206
- relatedFiles: result.components.map((c) => c.filePath)
207
- };
208
- }
209
- /**
210
- * Generate GraphQL operations diagram
211
- */
212
- generateGraphQLDiagram(result) {
213
- const lines = ["flowchart LR", " %% GraphQL Operations"];
214
- const queries = result.graphqlOperations.filter((op) => op.type === "query");
215
- const mutations = result.graphqlOperations.filter((op) => op.type === "mutation");
216
- const fragments = result.graphqlOperations.filter((op) => op.type === "fragment");
217
- lines.push(' API[("GraphQL API")]');
218
- if (queries.length > 0) {
219
- lines.push(" subgraph Queries");
220
- for (const query of queries.slice(0, 15)) {
221
- const nodeId = `Q_${query.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
222
- lines.push(` ${nodeId}["${query.name}"]`);
223
- lines.push(` ${nodeId} --> API`);
224
- }
225
- lines.push(" end");
226
- }
227
- if (mutations.length > 0) {
228
- lines.push(" subgraph Mutations");
229
- for (const mutation of mutations.slice(0, 15)) {
230
- const nodeId = `M_${mutation.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
231
- lines.push(` ${nodeId}["${mutation.name}"]`);
232
- lines.push(` ${nodeId} --> API`);
233
- }
234
- lines.push(" end");
235
- }
236
- if (fragments.length > 0) {
237
- lines.push(" subgraph Fragments");
238
- for (const fragment of fragments.slice(0, 10)) {
239
- const nodeId = `F_${fragment.name.replace(/[^a-zA-Z0-9]/g, "_")}`;
240
- lines.push(` ${nodeId}[/"${fragment.name}"/]`);
241
- }
242
- lines.push(" end");
243
- }
244
- return {
245
- type: "flowchart",
246
- title: `${result.repository} - GraphQL Operations`,
247
- content: lines.join("\n"),
248
- relatedFiles: result.graphqlOperations.map((op) => op.filePath)
249
- };
250
- }
251
- /**
252
- * Generate cross-repository diagram
253
- */
254
- generateCrossRepoDiagram(results, crossRepoLinks) {
255
- const lines = ["flowchart TB", " %% Cross-Repository Architecture"];
256
- for (const result of results) {
257
- const repoId = result.repository.replace(/[^a-zA-Z0-9]/g, "_");
258
- lines.push(` subgraph ${repoId}["${result.repository}"]`);
259
- lines.push(` ${repoId}_pages["\u{1F4C4} ${result.pages.length} Pages"]`);
260
- lines.push(` ${repoId}_gql["\u{1F537} ${result.graphqlOperations.length} GraphQL Ops"]`);
261
- lines.push(` ${repoId}_comp["\u{1F9E9} ${result.components.length} Components"]`);
262
- lines.push(" end");
263
- }
264
- for (const link of crossRepoLinks) {
265
- const sourceRepo = link.sourceRepo.replace(/[^a-zA-Z0-9]/g, "_");
266
- const targetRepo = link.targetRepo.replace(/[^a-zA-Z0-9]/g, "_");
267
- let linkStyle = "-->";
268
- if (link.linkType === "api-call") {
269
- linkStyle = "==>";
270
- } else if (link.linkType === "graphql-operation") {
271
- linkStyle = "-..->";
272
- }
273
- lines.push(` ${sourceRepo}_gql ${linkStyle}|"${link.linkType}"| ${targetRepo}_gql`);
274
- }
275
- return {
276
- type: "flowchart",
277
- title: "Cross-Repository Architecture",
278
- content: lines.join("\n"),
279
- relatedFiles: results.map((r) => r.repository)
280
- };
281
- }
282
- /**
283
- * Generate sequence diagram for a specific flow
284
- */
285
- generateSequenceDiagram(flow) {
286
- const lines = ["sequenceDiagram", ` %% ${flow.name}`];
287
- const participants = [flow.source, ...flow.via, flow.target];
288
- for (const p of participants) {
289
- lines.push(` participant ${p.name}`);
290
- }
291
- let current = flow.source;
292
- for (let i = 0; i < flow.via.length; i++) {
293
- const next = flow.via[i];
294
- const op = flow.operations[i] || "data";
295
- lines.push(` ${current.name}->>+${next.name}: ${op}`);
296
- current = next;
297
- }
298
- const lastOp = flow.operations[flow.operations.length - 1] || "data";
299
- lines.push(` ${current.name}->>+${flow.target.name}: ${lastOp}`);
300
- lines.push(` ${flow.target.name}-->>-${flow.source.name}: response`);
301
- return {
302
- type: "sequence",
303
- title: flow.name,
304
- content: lines.join("\n"),
305
- relatedFiles: []
306
- };
307
- }
308
- };
309
-
310
- // src/generators/markdown-generator.ts
311
- var MarkdownGenerator = class {
312
- /**
313
- * Generate complete documentation
314
- */
315
- generateDocumentation(report) {
316
- const docs = /* @__PURE__ */ new Map();
317
- docs.set("index.md", this.generateIndex(report));
318
- for (const repo of report.repositories) {
319
- docs.set(`repos/${repo.name}/index.md`, this.generateRepoIndex(repo));
320
- docs.set(`repos/${repo.name}/pages.md`, this.generatePagesDoc(repo));
321
- docs.set(`repos/${repo.name}/components.md`, this.generateComponentsDoc(repo));
322
- docs.set(`repos/${repo.name}/graphql.md`, this.generateGraphQLDoc(repo));
323
- docs.set(`repos/${repo.name}/dataflow.md`, this.generateDataFlowDoc(repo));
324
- }
325
- docs.set("cross-repo.md", this.generateCrossRepoDoc(report));
326
- docs.set("diagrams.md", this.generateDiagramsDoc(report.diagrams));
327
- return docs;
328
- }
329
- generateIndex(report) {
330
- const title = report.repositories.length === 1 ? `${report.repositories[0].displayName} Documentation` : "Project Documentation";
331
- const lines = [
332
- `# ${title}`,
333
- "",
334
- `Generated: ${report.generatedAt}`,
335
- "",
336
- report.repositories.length > 1 ? "## Repositories" : "## Overview",
337
- ""
338
- ];
339
- for (const repo of report.repositories) {
340
- lines.push(`### [${repo.displayName}](/docs/repos/${repo.name}/index)`);
341
- lines.push("");
342
- lines.push(`- **Version**: ${repo.version}`);
343
- lines.push(`- **Commit**: \`${repo.commitHash.substring(0, 7)}\``);
344
- lines.push(`- **Pages**: ${repo.summary.totalPages}`);
345
- lines.push(`- **Components**: ${repo.summary.totalComponents}`);
346
- lines.push(`- **GraphQL Ops**: ${repo.summary.totalGraphQLOperations}`);
347
- lines.push("");
348
- }
349
- lines.push("## Quick Links");
350
- lines.push("");
351
- lines.push("- [Cross Repository](/docs/cross-repo)");
352
- lines.push("- [Diagrams](/docs/diagrams)");
353
- lines.push("- [Page Map (Interactive)](/page-map)");
354
- lines.push("");
355
- return lines.join("\n");
356
- }
357
- generateRepoIndex(repo) {
358
- const lines = [
359
- `# ${repo.displayName}`,
360
- "",
361
- `Version: ${repo.version} | Commit: \`${repo.commitHash.substring(0, 7)}\``,
362
- "",
363
- "## Overview",
364
- "",
365
- `| Metric | Count |`,
366
- `|--------|-------|`,
367
- `| Pages | ${repo.summary.totalPages} |`,
368
- `| Components | ${repo.summary.totalComponents} |`,
369
- `| GraphQL Operations | ${repo.summary.totalGraphQLOperations} |`,
370
- `| Data Flows | ${repo.summary.totalDataFlows} |`,
371
- `| Auth Required | ${repo.summary.authRequiredPages} |`,
372
- `| Public | ${repo.summary.publicPages} |`,
373
- "",
374
- "## Documentation",
375
- "",
376
- `- [Pages](/docs/repos/${repo.name}/pages)`,
377
- `- [Components](/docs/repos/${repo.name}/components)`,
378
- `- [GraphQL](/docs/repos/${repo.name}/graphql)`,
379
- `- [Data Flow](/docs/repos/${repo.name}/dataflow)`,
380
- "",
381
- "## Quick Access",
382
- "",
383
- `- [Page Map](/page-map)`,
384
- `- [Diagrams](/docs/diagrams)`,
385
- ""
386
- ];
387
- return lines.join("\n");
388
- }
389
- generatePagesDoc(repo) {
390
- const lines = [`# ${repo.displayName} - Pages`, ""];
391
- const authRequired = repo.analysis.pages.filter((p) => p.authentication.required).length;
392
- const withQueries = repo.analysis.pages.filter(
393
- (p) => p.dataFetching.some((df) => !df.type.includes("Mutation"))
394
- ).length;
395
- const withMutations = repo.analysis.pages.filter(
396
- (p) => p.dataFetching.some((df) => df.type.includes("Mutation"))
397
- ).length;
398
- lines.push("| Metric | Value |");
399
- lines.push("|--------|-------|");
400
- lines.push(`| Total | **${repo.analysis.pages.length}** |`);
401
- lines.push(`| Auth Required | ${authRequired} |`);
402
- lines.push(`| With Queries | ${withQueries} |`);
403
- lines.push(`| With Mutations | ${withMutations} |`);
404
- lines.push("");
405
- const byCategory = /* @__PURE__ */ new Map();
406
- for (const page of repo.analysis.pages) {
407
- const category = page.path.split("/")[1] || "root";
408
- const existing = byCategory.get(category) || [];
409
- existing.push(page);
410
- byCategory.set(category, existing);
411
- }
412
- for (const [category, pages] of byCategory) {
413
- lines.push(`## /${category}`);
414
- lines.push("");
415
- lines.push("| Page | Auth | Layout | Data |");
416
- lines.push("|------|------|--------|------|");
417
- for (const page of pages) {
418
- const pathDisplay = page.path.replace(`/${category}`, "") || "/";
419
- const auth = page.authentication.required ? "Required" : "Public";
420
- const layout = page.layout || "-";
421
- const dataOps = [];
422
- const queries = page.dataFetching.filter((df) => !df.type.includes("Mutation"));
423
- const mutations = page.dataFetching.filter((df) => df.type.includes("Mutation"));
424
- for (const q of queries.slice(0, 2)) {
425
- const rawName = q.operationName || "";
426
- if (!rawName || rawName.trim().length < 2) continue;
427
- const isRef = rawName.startsWith("\u2192") || rawName.startsWith("->");
428
- const cleanName = rawName.replace(/^[→\->\s]+/, "");
429
- if (cleanName && cleanName.trim().length > 0) {
430
- if (isRef) {
431
- dataOps.push(
432
- `<span class="gql-ref" data-ref="${cleanName}" title="Component">${cleanName}</span>`
433
- );
434
- } else {
435
- dataOps.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
436
- }
437
- }
438
- }
439
- for (const m of mutations.slice(0, 2)) {
440
- const rawName = m.operationName || "";
441
- if (!rawName || rawName.trim().length < 2) continue;
442
- const isRef = rawName.startsWith("\u2192") || rawName.startsWith("->");
443
- const cleanName = rawName.replace(/^[→\->\s]+/, "");
444
- if (cleanName && cleanName.trim().length > 0) {
445
- if (isRef) {
446
- dataOps.push(
447
- `<span class="gql-ref mutation" data-ref="${cleanName}" title="Component">${cleanName}</span>`
448
- );
449
- } else {
450
- dataOps.push(
451
- `<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`
452
- );
453
- }
454
- }
455
- }
456
- const remaining = queries.length + mutations.length - Math.min(queries.length, 2) - Math.min(mutations.length, 2);
457
- if (remaining > 0) {
458
- dataOps.push(
459
- `<span class="gql-more" data-type="all" data-page="${page.path}">+${remaining} more</span>`
460
- );
461
- }
462
- const data = dataOps.length > 0 ? dataOps.join(" ") : "-";
463
- lines.push(`| \`${pathDisplay}\` | ${auth} | ${layout} | ${data} |`);
464
- }
465
- lines.push("");
466
- for (const page of pages) {
467
- const queries = page.dataFetching.filter((df) => !df.type.includes("Mutation"));
468
- const mutations = page.dataFetching.filter((df) => df.type.includes("Mutation"));
469
- if (queries.length > 0 || mutations.length > 0) {
470
- lines.push(`### ${page.path}`);
471
- lines.push("");
472
- lines.push(`> ${page.filePath}`);
473
- lines.push("");
474
- const validQs = queries.filter(
475
- (q) => q.operationName && q.operationName.trim().length >= 2
476
- );
477
- if (validQs.length > 0) {
478
- lines.push(`**Queries (${validQs.length})**`);
479
- lines.push("");
480
- lines.push('<div class="gql-ops-list">');
481
- for (const q of validQs) {
482
- const rawName = q.operationName || "";
483
- const isRef = rawName.startsWith("\u2192") || rawName.startsWith("->");
484
- const cleanName = rawName.replace(/^[→\->\s]+/, "");
485
- if (isRef) {
486
- lines.push(
487
- `<span class="gql-ref" data-ref="${cleanName}" title="Component">${cleanName}</span>`
488
- );
489
- } else {
490
- lines.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
491
- }
492
- }
493
- lines.push("</div>");
494
- lines.push("");
495
- }
496
- const validMuts = mutations.filter(
497
- (m) => m.operationName && m.operationName.trim().length >= 2
498
- );
499
- if (validMuts.length > 0) {
500
- lines.push(`**Mutations (${validMuts.length})**`);
501
- lines.push("");
502
- lines.push('<div class="gql-ops-list">');
503
- for (const m of validMuts) {
504
- const rawName = m.operationName || "";
505
- const isRef = rawName.startsWith("\u2192") || rawName.startsWith("->");
506
- const cleanName = rawName.replace(/^[→\->\s]+/, "");
507
- if (isRef) {
508
- lines.push(
509
- `<span class="gql-ref mutation" data-ref="${cleanName}" title="Component">${cleanName}</span>`
510
- );
511
- } else {
512
- lines.push(
513
- `<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`
514
- );
515
- }
516
- }
517
- lines.push("</div>");
518
- lines.push("");
519
- }
520
- lines.push("");
521
- }
522
- }
523
- }
524
- return lines.join("\n");
525
- }
526
- generateComponentsDoc(repo) {
527
- const lines = [`# ${repo.displayName} - Components`, ""];
528
- const byType = /* @__PURE__ */ new Map();
529
- for (const comp of repo.analysis.components) {
530
- const existing = byType.get(comp.type) || [];
531
- existing.push(comp);
532
- byType.set(comp.type, existing);
533
- }
534
- lines.push("| Type | Count |");
535
- lines.push("|------|-------|");
536
- lines.push(`| Container | ${byType.get("container")?.length || 0} |`);
537
- lines.push(`| Presentational | ${byType.get("presentational")?.length || 0} |`);
538
- lines.push(`| Layout | ${byType.get("layout")?.length || 0} |`);
539
- lines.push(`| Hook | ${byType.get("hook")?.length || 0} |`);
540
- lines.push(`| **Total** | **${repo.analysis.components.length}** |`);
541
- lines.push("");
542
- const pageComponents = /* @__PURE__ */ new Map();
543
- for (const page of repo.analysis.pages) {
544
- pageComponents.set(page.path, []);
545
- }
546
- for (const comp of repo.analysis.components) {
547
- for (const page of repo.analysis.pages) {
548
- const pageFeature = this.extractFeatureFromPage(page.filePath);
549
- const compFeature = this.extractFeatureFromComponent(comp.filePath);
550
- if (pageFeature && compFeature && pageFeature === compFeature) {
551
- pageComponents.get(page.path)?.push(comp);
552
- }
553
- }
554
- }
555
- lines.push("## By Page");
556
- lines.push("");
557
- for (const [pagePath, components] of pageComponents) {
558
- if (components.length === 0) continue;
559
- const containers = components.filter((c) => c.type === "container");
560
- const presentationals = components.filter((c) => c.type === "presentational");
561
- const hooks = components.filter((c) => c.type === "hook");
562
- lines.push(`### ${pagePath}`);
563
- lines.push("");
564
- lines.push("| Component | Type | Data |");
565
- lines.push("|-----------|------|------|");
566
- for (const comp of containers) {
567
- const dataOps = this.formatComponentDataOps(comp);
568
- lines.push(`| ${comp.name} | Container | ${dataOps || "-"} |`);
569
- }
570
- for (const comp of presentationals.slice(0, 10)) {
571
- lines.push(`| ${comp.name} | UI | - |`);
572
- }
573
- if (presentationals.length > 10) {
574
- lines.push(`| *+${presentationals.length - 10} more* | UI | - |`);
575
- }
576
- for (const comp of hooks) {
577
- lines.push(`| ${comp.name} | Hook | - |`);
578
- }
579
- lines.push("");
580
- }
581
- lines.push("## By Type");
582
- lines.push("");
583
- for (const [type, components] of byType) {
584
- lines.push(`### ${type.charAt(0).toUpperCase() + type.slice(1)} (${components.length})`);
585
- lines.push("");
586
- lines.push("| Name | File |");
587
- lines.push("|------|------|");
588
- for (const comp of components.slice(0, 25)) {
589
- const shortPath = comp.filePath.replace("src/features/", "").replace("src/", "");
590
- lines.push(`| ${comp.name} | ${shortPath} |`);
591
- }
592
- if (components.length > 25) {
593
- lines.push(`| *+${components.length - 25} more* | |`);
594
- }
595
- lines.push("");
596
- }
597
- return lines.join("\n");
598
- }
599
- extractFeatureFromPage(filePath) {
600
- const parts = filePath.split("/");
601
- if (parts.length > 1) {
602
- return parts[0];
603
- }
604
- return null;
605
- }
606
- extractFeatureFromComponent(filePath) {
607
- const match = filePath.match(/src\/features\/([^/]+)/);
608
- if (match) {
609
- return match[1];
610
- }
611
- return null;
612
- }
613
- formatComponentDataOps(comp) {
614
- const queries = [];
615
- const mutations = [];
616
- for (const hook of comp.hooks) {
617
- const queryMatch = hook.match(/(?:useQuery|Query):\s*(\w+)/);
618
- const mutationMatch = hook.match(/(?:useMutation|Mutation):\s*(\w+)/);
619
- if (queryMatch && queryMatch[1] && queryMatch[1].trim().length >= 2) {
620
- queries.push(queryMatch[1]);
621
- } else if (mutationMatch && mutationMatch[1] && mutationMatch[1].trim().length >= 2) {
622
- mutations.push(mutationMatch[1]);
623
- }
624
- }
625
- const validQueries = queries.filter((q) => q && q.trim().length >= 2);
626
- const validMutations = mutations.filter((m) => m && m.trim().length >= 2);
627
- if (validQueries.length === 0 && validMutations.length === 0) {
628
- return "";
629
- }
630
- const ops = [];
631
- const maxShowQueries = 2;
632
- const maxShowMutations = 2;
633
- const shownQueries = validQueries.slice(0, maxShowQueries);
634
- for (const name of shownQueries) {
635
- ops.push(`<span class="gql-op" data-op="${name}">${name}</span>`);
636
- }
637
- const shownMutations = validMutations.slice(0, maxShowMutations);
638
- for (const name of shownMutations) {
639
- ops.push(`<span class="gql-op mutation" data-op="${name}">${name}</span>`);
640
- }
641
- const hiddenQueries = validQueries.slice(maxShowQueries);
642
- const hiddenMutations = validMutations.slice(maxShowMutations);
643
- const remaining = hiddenQueries.length + hiddenMutations.length;
644
- if (remaining > 0) {
645
- const allQueries = JSON.stringify(validQueries).replace(/"/g, "&quot;");
646
- const allMutations = JSON.stringify(validMutations).replace(/"/g, "&quot;");
647
- ops.push(
648
- `<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>`
649
- );
650
- }
651
- return `<div class="gql-ops-inline">${ops.join(" ")}</div>`;
652
- }
653
- generateGraphQLDoc(repo) {
654
- const lines = [`# ${repo.displayName} - GraphQL`, ""];
655
- const queries = repo.analysis.graphqlOperations.filter((op) => op.type === "query");
656
- const mutations = repo.analysis.graphqlOperations.filter((op) => op.type === "mutation");
657
- const fragments = repo.analysis.graphqlOperations.filter((op) => op.type === "fragment");
658
- lines.push("| Type | Count |");
659
- lines.push("|------|-------|");
660
- lines.push(`| Query | ${queries.length} |`);
661
- lines.push(`| Mutation | ${mutations.length} |`);
662
- lines.push(`| Fragment | ${fragments.length} |`);
663
- lines.push(`| **Total** | **${repo.analysis.graphqlOperations.length}** |`);
664
- lines.push("");
665
- if (queries.length > 0) {
666
- lines.push(`## Queries`);
667
- lines.push("");
668
- for (const query of queries.slice(0, 80)) {
669
- lines.push(`### ${query.name}`);
670
- lines.push("");
671
- const returnType = query.returnType || "unknown";
672
- const varsCount = query.variables.length;
673
- const usedCount = query.usedIn.length;
674
- lines.push(
675
- `> Return: \`${returnType}\` | Variables: ${varsCount} | Used: ${usedCount} files`
676
- );
677
- lines.push("");
678
- if (query.variables.length > 0) {
679
- lines.push("| Variable | Type |");
680
- lines.push("|----------|------|");
681
- for (const v of query.variables) {
682
- lines.push(`| ${v.name} | \`${v.type}\` |`);
683
- }
684
- lines.push("");
685
- }
686
- if (query.fields && query.fields.length > 0) {
687
- lines.push("```graphql");
688
- lines.push(this.formatGraphQLFields(query.fields, 0));
689
- lines.push("```");
690
- lines.push("");
691
- }
692
- }
693
- if (queries.length > 80) lines.push(`*+${queries.length - 80} more queries*
694
- `);
695
- }
696
- if (mutations.length > 0) {
697
- lines.push(`## Mutations`);
698
- lines.push("");
699
- for (const mutation of mutations.slice(0, 80)) {
700
- lines.push(`### ${mutation.name}`);
701
- lines.push("");
702
- const varsCount = mutation.variables.length;
703
- const usedCount = mutation.usedIn.length;
704
- lines.push(`> Variables: ${varsCount} | Used: ${usedCount} files`);
705
- lines.push("");
706
- if (mutation.variables.length > 0) {
707
- lines.push("| Variable | Type |");
708
- lines.push("|----------|------|");
709
- for (const v of mutation.variables) {
710
- lines.push(`| ${v.name} | \`${v.type}\` |`);
711
- }
712
- lines.push("");
713
- }
714
- if (mutation.fields && mutation.fields.length > 0) {
715
- lines.push("```graphql");
716
- lines.push(this.formatGraphQLFields(mutation.fields, 0));
717
- lines.push("```");
718
- lines.push("");
719
- }
720
- }
721
- if (mutations.length > 80) lines.push(`*+${mutations.length - 80} more mutations*
722
- `);
723
- }
724
- if (fragments.length > 0) {
725
- lines.push(`## Fragments`);
726
- lines.push("");
727
- lines.push("| Name | Type | Fields |");
728
- lines.push("|------|------|--------|");
729
- for (const fragment of fragments.slice(0, 50)) {
730
- const fieldCount = fragment.fields?.length || 0;
731
- lines.push(`| ${fragment.name} | ${fragment.returnType || "-"} | ${fieldCount} |`);
732
- }
733
- if (fragments.length > 50) lines.push(`| *+${fragments.length - 50} more* | | |`);
734
- lines.push("");
735
- }
736
- return lines.join("\n");
737
- }
738
- formatGraphQLFields(fields, indent) {
739
- if (!fields || fields.length === 0) return "";
740
- const lines = [];
741
- for (const field of fields) {
742
- const prefix = " ".repeat(indent);
743
- if (field.fields && field.fields.length > 0) {
744
- lines.push(`${prefix}${field.name} {`);
745
- lines.push(this.formatGraphQLFields(field.fields, indent + 1));
746
- lines.push(`${prefix}}`);
747
- } else {
748
- lines.push(`${prefix}${field.name}`);
749
- }
750
- }
751
- return lines.join("\n");
752
- }
753
- generateDataFlowDoc(repo) {
754
- const lines = [`# ${repo.displayName} - Data Flow`, ""];
755
- const queryFlows = repo.analysis.dataFlows.filter(
756
- (f) => f.name.includes("\u{1F4E1}") || f.operations.some((o) => o.includes("Query"))
757
- );
758
- const mutationFlows = repo.analysis.dataFlows.filter(
759
- (f) => f.name.includes("\u270F\uFE0F") || f.operations.some((o) => o.includes("Mutation"))
760
- );
761
- const contextFlows = repo.analysis.dataFlows.filter((f) => f.source.type === "context");
762
- lines.push("## Overview");
763
- lines.push("");
764
- lines.push("| Type | Count | Direction |");
765
- lines.push("|------|-------|-----------|");
766
- lines.push(`| \`QUERY\` | ${queryFlows.length} | Server \u2192 Component |`);
767
- lines.push(`| \`MUTATION\` | ${mutationFlows.length} | Component \u2192 Server |`);
768
- lines.push(`| \`CONTEXT\` | ${contextFlows.length} | Provider \u2192 Consumer |`);
769
- lines.push(`| **Total** | **${repo.analysis.dataFlows.length}** | |`);
770
- lines.push("");
771
- lines.push("## Architecture");
772
- lines.push("");
773
- lines.push("```mermaid");
774
- lines.push("flowchart LR");
775
- lines.push(' subgraph Server["GraphQL Server"]');
776
- lines.push(" API[(API)]");
777
- lines.push(" end");
778
- lines.push(' subgraph Client["React Application"]');
779
- lines.push(" Apollo[Apollo Client]");
780
- lines.push(" Container[Container Component]");
781
- lines.push(" View[View Component]");
782
- lines.push(" end");
783
- lines.push(" API -->|Query Response| Apollo");
784
- lines.push(" Apollo -->|Cache/Data| Container");
785
- lines.push(" Container -->|Props| View");
786
- lines.push(" View -->|User Action| Container");
787
- lines.push(" Container -->|Mutation| Apollo");
788
- lines.push(" Apollo -->|GraphQL Request| API");
789
- lines.push("```");
790
- lines.push("");
791
- lines.push("## Page Data Flows");
792
- lines.push("");
793
- for (const page of repo.analysis.pages) {
794
- const pageFeature = this.extractFeatureFromPage(page.filePath);
795
- const relatedComponents = repo.analysis.components.filter((c) => {
796
- const compFeature = this.extractFeatureFromComponent(c.filePath);
797
- return compFeature === pageFeature;
798
- });
799
- const hasDataOps = page.dataFetching.length > 0 || relatedComponents.some(
800
- (c) => c.stateManagement.some((s) => s.includes("Apollo") || s.includes("Context"))
801
- );
802
- if (!hasDataOps) continue;
803
- lines.push(`### ${page.path}`);
804
- lines.push("");
805
- lines.push(`\`FILE: ${page.filePath}\``);
806
- lines.push("");
807
- const pageOps = this.getPageOperations(
808
- page,
809
- relatedComponents,
810
- repo.analysis.graphqlOperations
811
- );
812
- const validQueries = pageOps.queries.filter((q) => q && q.trim().length > 0);
813
- const validMutations = pageOps.mutations.filter((m) => m && m.trim().length > 0);
814
- if (validQueries.length > 0 || validMutations.length > 0) {
815
- lines.push("```mermaid");
816
- lines.push("flowchart LR");
817
- const pageId = page.path.replace(/[^a-zA-Z0-9]/g, "_");
818
- const safePath = page.path.replace(/"/g, "'");
819
- lines.push(` Page${pageId}["${safePath}"]`);
820
- validQueries.slice(0, 5).forEach((q, i) => {
821
- const qId = `Q${pageId}_${i}`;
822
- const safeQ = q.replace(/"/g, "'").replace(/[<>]/g, "");
823
- lines.push(` ${qId}["${safeQ}"]:::query --> Page${pageId}`);
824
- });
825
- validMutations.slice(0, 5).forEach((m, i) => {
826
- const mId = `M${pageId}_${i}`;
827
- const safeM = m.replace(/"/g, "'").replace(/[<>]/g, "");
828
- lines.push(` Page${pageId} --> ${mId}["${safeM}"]:::mutation`);
829
- });
830
- lines.push(" classDef query fill:#dbeafe,stroke:#1d4ed8,color:#1e40af");
831
- lines.push(" classDef mutation fill:#fce7f3,stroke:#be185d,color:#9d174d");
832
- lines.push("```");
833
- lines.push("");
834
- }
835
- if (pageOps.queries.length > 0 || pageOps.mutations.length > 0) {
836
- if (validQueries.length > 0) {
837
- lines.push(`**Queries (${validQueries.length})**`);
838
- lines.push("");
839
- lines.push('<div class="gql-ops-list">');
840
- for (const q of validQueries) {
841
- const isRef = q.startsWith("\u2192") || q.startsWith("->");
842
- const cleanName = q.replace(/^[→\->\s]+/, "");
843
- if (cleanName && cleanName.trim().length > 0) {
844
- if (isRef) {
845
- lines.push(
846
- `<span class="gql-ref" data-ref="${cleanName}" title="Component: ${cleanName}">${cleanName}</span>`
847
- );
848
- } else {
849
- lines.push(`<span class="gql-op" data-op="${cleanName}">${cleanName}</span>`);
850
- }
851
- }
852
- }
853
- lines.push("</div>");
854
- lines.push("");
855
- }
856
- if (validMutations.length > 0) {
857
- lines.push(`**Mutations (${validMutations.length})**`);
858
- lines.push("");
859
- lines.push('<div class="gql-ops-list">');
860
- for (const m of validMutations) {
861
- const isRef = m.startsWith("\u2192") || m.startsWith("->");
862
- const cleanName = m.replace(/^[→\->\s]+/, "");
863
- if (cleanName && cleanName.trim().length > 0) {
864
- if (isRef) {
865
- lines.push(
866
- `<span class="gql-ref mutation" data-ref="${cleanName}" title="Component: ${cleanName}">${cleanName}</span>`
867
- );
868
- } else {
869
- lines.push(
870
- `<span class="gql-op mutation" data-op="${cleanName}">${cleanName}</span>`
871
- );
872
- }
873
- }
874
- }
875
- lines.push("</div>");
876
- lines.push("");
877
- }
878
- }
879
- lines.push("---");
880
- lines.push("");
881
- }
882
- const providers = /* @__PURE__ */ new Set();
883
- for (const flow of contextFlows) {
884
- providers.add(flow.source.name);
885
- }
886
- if (providers.size > 0) {
887
- lines.push("## Context Providers");
888
- lines.push("");
889
- lines.push("| Provider | Description |");
890
- lines.push("|----------|-------------|");
891
- for (const provider of providers) {
892
- lines.push(`| \`${provider}\` | Provides shared state |`);
893
- }
894
- lines.push("");
895
- }
896
- return lines.join("\n");
897
- }
898
- getPageOperations(page, relatedComponents, _allOperations) {
899
- const queries = /* @__PURE__ */ new Set();
900
- const mutations = /* @__PURE__ */ new Set();
901
- const isValidOpName = (name) => {
902
- if (!name) return false;
903
- const trimmed = name.trim();
904
- return trimmed.length >= 2 && /[a-zA-Z]/.test(trimmed);
905
- };
906
- for (const df of page.dataFetching) {
907
- const rawName = df.operationName?.replace(/^[→\->\s]+/, "") || "";
908
- const name = rawName.replace(/Document$/g, "");
909
- if (isValidOpName(name)) {
910
- if (df.type?.includes("Mutation")) {
911
- mutations.add(name);
912
- } else {
913
- queries.add(name);
914
- }
915
- }
916
- }
917
- for (const comp of relatedComponents) {
918
- for (const hook of comp.hooks) {
919
- if (hook.includes("Query")) {
920
- const match = hook.match(/:\s*(.+)$/);
921
- if (match && isValidOpName(match[1])) {
922
- queries.add(match[1].trim());
923
- }
924
- }
925
- if (hook.includes("Mutation")) {
926
- const match = hook.match(/:\s*(.+)$/);
927
- if (match && isValidOpName(match[1])) {
928
- mutations.add(match[1].trim());
929
- }
930
- }
931
- }
932
- }
933
- return {
934
- queries: Array.from(queries).filter(isValidOpName),
935
- mutations: Array.from(mutations).filter(isValidOpName)
936
- };
937
- }
938
- generateCrossRepoDoc(report) {
939
- const lines = [
940
- "# \u30AF\u30ED\u30B9\u30EA\u30DD\u30B8\u30C8\u30EA\u5206\u6790",
941
- "",
942
- "## \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u6982\u8981",
943
- "",
944
- "```mermaid",
945
- "flowchart TB"
946
- ];
947
- for (const repo of report.repositories) {
948
- const repoId = repo.name.replace(/[^a-zA-Z0-9]/g, "_");
949
- lines.push(` subgraph ${repoId}["${repo.displayName}"]`);
950
- lines.push(` ${repoId}_core["Core"]`);
951
- lines.push(" end");
952
- }
953
- lines.push("```");
954
- lines.push("");
955
- lines.push("## API\u63A5\u7D9A");
956
- lines.push("");
957
- for (const conn of report.crossRepoAnalysis.apiConnections) {
958
- lines.push(`- **${conn.frontend}** \u2192 **${conn.backend}**: \`${conn.endpoint}\``);
959
- }
960
- lines.push("");
961
- lines.push("## \u5171\u6709\u578B");
962
- lines.push("");
963
- for (const type of report.crossRepoAnalysis.sharedTypes) {
964
- lines.push(`- \`${type}\``);
965
- }
966
- lines.push("");
967
- return lines.join("\n");
968
- }
969
- createPageDataFlowDiagram(page, relatedComponents, _dataFlows) {
970
- const lines = [];
971
- const queries = [];
972
- const mutations = [];
973
- const containers = [];
974
- for (const comp of relatedComponents) {
975
- if (comp.type === "container") {
976
- containers.push(comp.name);
977
- }
978
- for (const hook of comp.hooks) {
979
- if (hook.includes("Query") || hook.includes("\u{1F4E1}")) {
980
- const name = hook.includes(":") ? hook.split(":")[1].trim() : hook;
981
- if (!queries.includes(name)) queries.push(name);
982
- }
983
- if (hook.includes("Mutation") || hook.includes("\u270F\uFE0F")) {
984
- const name = hook.includes(":") ? hook.split(":")[1].trim() : hook;
985
- if (!mutations.includes(name)) mutations.push(name);
986
- }
987
- }
988
- }
989
- for (const df of page.dataFetching) {
990
- const name = df.operationName.replace(/^→\s*/, "");
991
- if (df.type.includes("Query") && !queries.includes(name)) {
992
- queries.push(name);
993
- } else if (df.type.includes("Mutation") && !mutations.includes(name)) {
994
- mutations.push(name);
995
- }
996
- }
997
- lines.push(`[Page: ${page.path}]`);
998
- lines.push("\u2502");
999
- if (queries.length > 0 || mutations.length > 0) {
1000
- lines.push("\u251C\u2500 \u{1F4E1} \u30C7\u30FC\u30BF\u53D6\u5F97 (Query)");
1001
- for (const q of queries.slice(0, 5)) {
1002
- lines.push(`\u2502 \u251C\u2500 ${q.substring(0, 40)}`);
1003
- lines.push(`\u2502 \u2502 \u2514\u2500 GraphQL Server \u2192 Apollo Cache \u2192 Component`);
1004
- }
1005
- if (queries.length > 5) {
1006
- lines.push(`\u2502 \u2514\u2500 ... \u4ED6 ${queries.length - 5} \u4EF6`);
1007
- }
1008
- if (mutations.length > 0) {
1009
- lines.push("\u2502");
1010
- lines.push("\u251C\u2500 \u270F\uFE0F \u30C7\u30FC\u30BF\u66F4\u65B0 (Mutation)");
1011
- for (const m of mutations.slice(0, 5)) {
1012
- lines.push(`\u2502 \u251C\u2500 ${m.substring(0, 40)}`);
1013
- lines.push(`\u2502 \u2502 \u2514\u2500 Component \u2192 GraphQL Server \u2192 Apollo Cache`);
1014
- }
1015
- if (mutations.length > 5) {
1016
- lines.push(`\u2502 \u2514\u2500 ... \u4ED6 ${mutations.length - 5} \u4EF6`);
1017
- }
1018
- }
1019
- }
1020
- if (containers.length > 0) {
1021
- lines.push("\u2502");
1022
- lines.push("\u251C\u2500 \u{1F4E6} Container Components");
1023
- for (const c of containers.slice(0, 5)) {
1024
- lines.push(`\u2502 \u2514\u2500 ${c}`);
1025
- }
1026
- if (containers.length > 5) {
1027
- lines.push(`\u2502 \u2514\u2500 ... \u4ED6 ${containers.length - 5} \u4EF6`);
1028
- }
1029
- }
1030
- lines.push("\u2502");
1031
- lines.push("\u2514\u2500 [Render]");
1032
- return lines.join("\n");
1033
- }
1034
- generateDiagramsDoc(diagrams) {
1035
- const lines = ["# Diagrams", ""];
1036
- lines.push("## Overview");
1037
- lines.push("");
1038
- lines.push("| Diagram | Type | Description |");
1039
- lines.push("|---------|------|-------------|");
1040
- for (const diagram of diagrams) {
1041
- lines.push(`| ${diagram.title} | \`${diagram.type.toUpperCase()}\` | Auto-generated |`);
1042
- }
1043
- lines.push("");
1044
- for (const diagram of diagrams) {
1045
- lines.push(`## ${diagram.title}`);
1046
- lines.push("");
1047
- lines.push(`\`TYPE: ${diagram.type.toUpperCase()}\``);
1048
- lines.push("");
1049
- lines.push("```mermaid");
1050
- lines.push(diagram.content);
1051
- lines.push("```");
1052
- lines.push("");
1053
- }
1054
- return lines.join("\n");
1055
- }
1056
- };
1057
-
1058
- export { MarkdownGenerator, MermaidGenerator };