@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,609 +1,10 @@
1
- import { RailsMapGenerator } from './chunk-E4WRODSI.js';
2
- import { detectEnvironments } from './chunk-3PWXDB7B.js';
3
- import { RestApiAnalyzer, DataFlowAnalyzer, GraphQLAnalyzer, PagesAnalyzer } from './chunk-GNBMJMET.js';
4
- import { MermaidGenerator, MarkdownGenerator } from './chunk-SSU6QFTX.js';
5
- import { PageMapGenerator } from './chunk-3YFXZAP7.js';
6
- import { analyzeRailsApp } from './chunk-OWM6WNLE.js';
7
- import { simpleGit } from 'simple-git';
8
- import * as fs from 'fs/promises';
9
- import * as path2 from 'path';
10
- import fg from 'fast-glob';
11
- import * as crypto from 'crypto';
12
- import express from 'express';
13
- import { Server } from 'socket.io';
14
- import * as http from 'http';
15
- import { marked } from 'marked';
16
- import * as net from 'net';
17
-
18
- var CACHE_VERSION = "1.1";
19
- var AnalysisCache = class {
20
- cacheDir;
21
- manifest;
22
- manifestPath;
23
- dirty = false;
24
- constructor(repoPath) {
25
- this.cacheDir = path2.join(repoPath, ".repomap-cache");
26
- this.manifestPath = path2.join(this.cacheDir, "manifest.json");
27
- this.manifest = { version: CACHE_VERSION, entries: {} };
28
- }
29
- /**
30
- * Initialize cache directory and load manifest
31
- */
32
- async init() {
33
- try {
34
- await fs.mkdir(this.cacheDir, { recursive: true });
35
- } catch (err) {
36
- console.warn(` Warning: Could not create cache directory: ${err.message}`);
37
- return;
38
- }
39
- try {
40
- const data = await fs.readFile(this.manifestPath, "utf-8");
41
- const loaded = JSON.parse(data);
42
- if (loaded.version === CACHE_VERSION) {
43
- this.manifest = loaded;
44
- } else {
45
- console.log(" Cache version mismatch, clearing cache...");
46
- await this.clear();
47
- }
48
- } catch {
49
- }
50
- }
51
- /**
52
- * Compute hash for a file
53
- */
54
- async computeFileHash(filePath) {
55
- try {
56
- const content = await fs.readFile(filePath, "utf-8");
57
- return crypto.createHash("md5").update(content).digest("hex");
58
- } catch {
59
- return "";
60
- }
61
- }
62
- /**
63
- * Compute hash for multiple files
64
- * Sort files first for consistent ordering, then use a sample of files
65
- */
66
- async computeFilesHash(filePaths) {
67
- const sortedFiles = [...filePaths].sort();
68
- const sampleSize = 50;
69
- let sampleFiles;
70
- if (sortedFiles.length <= sampleSize * 2) {
71
- sampleFiles = sortedFiles;
72
- } else {
73
- sampleFiles = [...sortedFiles.slice(0, sampleSize), ...sortedFiles.slice(-sampleSize)];
74
- }
75
- const hashes = await Promise.all(sampleFiles.map((f) => this.computeFileHash(f)));
76
- const countHash = crypto.createHash("md5").update(String(sortedFiles.length)).digest("hex");
77
- return crypto.createHash("md5").update(hashes.join("") + countHash).digest("hex");
78
- }
79
- /**
80
- * Get cached data if valid
81
- */
82
- get(key, currentHash) {
83
- const entry = this.manifest.entries[key];
84
- if (entry && entry.hash === currentHash) {
85
- return entry.data;
86
- }
87
- return null;
88
- }
89
- /**
90
- * Store data in cache
91
- */
92
- set(key, hash, data) {
93
- this.manifest.entries[key] = {
94
- hash,
95
- timestamp: Date.now(),
96
- data
97
- };
98
- this.dirty = true;
99
- }
100
- /**
101
- * Save manifest to disk
102
- */
103
- async save() {
104
- if (!this.dirty) return;
105
- try {
106
- await fs.mkdir(this.cacheDir, { recursive: true });
107
- await fs.writeFile(this.manifestPath, JSON.stringify(this.manifest, null, 2));
108
- this.dirty = false;
109
- } catch (error) {
110
- console.warn(" Warning: Failed to save cache:", error.message);
111
- }
112
- }
113
- /**
114
- * Clear all cache
115
- */
116
- async clear() {
117
- this.manifest = { version: CACHE_VERSION, entries: {} };
118
- this.dirty = true;
119
- try {
120
- await fs.rm(this.cacheDir, { recursive: true, force: true });
121
- await fs.mkdir(this.cacheDir, { recursive: true });
122
- } catch {
123
- }
124
- }
125
- /**
126
- * Get cache statistics
127
- */
128
- getStats() {
129
- const entries = Object.keys(this.manifest.entries).length;
130
- const size = JSON.stringify(this.manifest).length;
131
- return {
132
- entries,
133
- size: size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)}MB` : `${(size / 1024).toFixed(1)}KB`
134
- };
135
- }
136
- };
137
-
138
- // src/core/engine.ts
139
- var DocGeneratorEngine = class {
140
- config;
141
- mermaidGenerator;
142
- markdownGenerator;
143
- noCache;
144
- constructor(config, options) {
145
- this.config = config;
146
- this.mermaidGenerator = new MermaidGenerator();
147
- this.markdownGenerator = new MarkdownGenerator();
148
- this.noCache = options?.noCache ?? false;
149
- }
150
- /**
151
- * Run documentation generation for all configured repositories
152
- */
153
- async generate() {
154
- console.log("\u{1F680} Starting documentation generation...\n");
155
- const repositoryReports = [];
156
- for (const repoConfig of this.config.repositories) {
157
- try {
158
- console.log(`
159
- \u{1F4E6} Analyzing ${repoConfig.displayName}...`);
160
- const report2 = await this.analyzeRepository(repoConfig);
161
- repositoryReports.push(report2);
162
- console.log(`\u2705 Completed ${repoConfig.displayName}`);
163
- } catch (error) {
164
- console.error(`\u274C Failed to analyze ${repoConfig.name}:`, error.message);
165
- }
166
- }
167
- console.log("\n\u{1F517} Running cross-repository analysis...");
168
- const crossRepoAnalysis = this.analyzeCrossRepo(repositoryReports);
169
- console.log("\n\u{1F4CA} Generating diagrams...");
170
- const results = repositoryReports.map((r) => r.analysis);
171
- const crossRepoLinks = this.extractCrossRepoLinks(results);
172
- const diagrams = this.mermaidGenerator.generateAll(results, crossRepoLinks);
173
- const report = {
174
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
175
- repositories: repositoryReports,
176
- crossRepoAnalysis,
177
- diagrams
178
- };
179
- console.log("\n\u{1F4DD} Writing documentation...");
180
- await this.writeDocumentation(report);
181
- console.log("\n\u2728 Documentation generation complete!");
182
- console.log(`\u{1F4C1} Output: ${this.config.outputDir}`);
183
- return report;
184
- }
185
- /**
186
- * Analyze a single repository (with caching)
187
- */
188
- async analyzeRepository(repoConfig) {
189
- const cache = new AnalysisCache(repoConfig.path);
190
- await cache.init();
191
- const { version, commitHash } = await this.getRepoInfo(repoConfig);
192
- const sourceFiles = await fg(["**/*.{ts,tsx,graphql}"], {
193
- cwd: repoConfig.path,
194
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**", "**/build/**"],
195
- absolute: true
196
- });
197
- const contentHash = await cache.computeFilesHash(sourceFiles);
198
- const cacheKey = `analysis_v${version}_${repoConfig.name}_${commitHash}`;
199
- if (this.noCache) {
200
- console.log(` \u{1F504} Cache disabled, analyzing from scratch...`);
201
- }
202
- const cachedResult = this.noCache ? null : cache.get(cacheKey, contentHash);
203
- if (cachedResult) {
204
- console.log(` \u26A1 Using cached analysis (hash: ${contentHash.slice(0, 8)}...)`);
205
- const summary2 = {
206
- totalPages: cachedResult.pages.length,
207
- totalComponents: cachedResult.components.length,
208
- totalGraphQLOperations: cachedResult.graphqlOperations.length,
209
- totalDataFlows: cachedResult.dataFlows.length,
210
- authRequiredPages: cachedResult.pages.filter((p) => p.authentication.required).length,
211
- publicPages: cachedResult.pages.filter((p) => !p.authentication.required).length
212
- };
213
- return {
214
- name: repoConfig.name,
215
- displayName: repoConfig.displayName,
216
- version,
217
- commitHash,
218
- analysis: cachedResult,
219
- summary: summary2
220
- };
221
- }
222
- const analyzers = repoConfig.analyzers.map((analyzerType) => this.createAnalyzer(analyzerType, repoConfig)).filter((a) => a !== null);
223
- console.log(` Running ${analyzers.length} analyzers in parallel...`);
224
- const startTime = Date.now();
225
- const analysisResults = await Promise.all(analyzers.map((analyzer) => analyzer.analyze()));
226
- console.log(` Analysis completed in ${((Date.now() - startTime) / 1e3).toFixed(1)}s`);
227
- const analysis = this.mergeAnalysisResults(
228
- analysisResults,
229
- repoConfig.name,
230
- version,
231
- commitHash
232
- );
233
- cache.set(cacheKey, contentHash, analysis);
234
- await cache.save();
235
- console.log(` \u{1F4BE} Analysis cached (hash: ${contentHash.slice(0, 8)}...)`);
236
- const summary = {
237
- totalPages: analysis.pages.length,
238
- totalComponents: analysis.components.length,
239
- totalGraphQLOperations: analysis.graphqlOperations.length,
240
- totalDataFlows: analysis.dataFlows.length,
241
- authRequiredPages: analysis.pages.filter((p) => p.authentication.required).length,
242
- publicPages: analysis.pages.filter((p) => !p.authentication.required).length
243
- };
244
- return {
245
- name: repoConfig.name,
246
- displayName: repoConfig.displayName,
247
- version,
248
- commitHash,
249
- analysis,
250
- summary
251
- };
252
- }
253
- /**
254
- * Get repository version and commit info
255
- */
256
- async getRepoInfo(repoConfig) {
257
- try {
258
- const git = simpleGit(repoConfig.path);
259
- const log = await git.log({ n: 1 });
260
- const commitHash = log.latest?.hash || "unknown";
261
- let version = "unknown";
262
- try {
263
- const packageJsonPath = path2.join(repoConfig.path, "package.json");
264
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
265
- version = packageJson.version || "unknown";
266
- } catch {
267
- }
268
- return { version, commitHash };
269
- } catch {
270
- return { version: "unknown", commitHash: "unknown" };
271
- }
272
- }
273
- /**
274
- * Create analyzer instance based on type
275
- */
276
- createAnalyzer(type, config) {
277
- switch (type) {
278
- case "pages":
279
- if (config.type === "nextjs" || config.type === "rails" || config.type === "generic") {
280
- return new PagesAnalyzer(config);
281
- }
282
- break;
283
- case "graphql":
284
- return new GraphQLAnalyzer(config);
285
- case "dataflow":
286
- case "components":
287
- return new DataFlowAnalyzer(config);
288
- case "rest-api":
289
- case "api":
290
- return new RestApiAnalyzer(config);
291
- }
292
- return null;
293
- }
294
- /**
295
- * Merge multiple analysis results
296
- */
297
- mergeAnalysisResults(results, repository, version, commitHash) {
298
- const merged = {
299
- repository,
300
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
301
- version,
302
- commitHash,
303
- pages: [],
304
- graphqlOperations: [],
305
- apiCalls: [],
306
- components: [],
307
- dataFlows: [],
308
- apiEndpoints: [],
309
- models: [],
310
- crossRepoLinks: []
311
- };
312
- for (const result of results) {
313
- if (result.pages) merged.pages.push(...result.pages);
314
- if (result.graphqlOperations) merged.graphqlOperations.push(...result.graphqlOperations);
315
- if (result.apiCalls) merged.apiCalls.push(...result.apiCalls);
316
- if (result.components) merged.components.push(...result.components);
317
- if (result.dataFlows) merged.dataFlows.push(...result.dataFlows);
318
- if (result.apiEndpoints) merged.apiEndpoints.push(...result.apiEndpoints);
319
- if (result.models) merged.models.push(...result.models);
320
- if (result.crossRepoLinks) merged.crossRepoLinks.push(...result.crossRepoLinks);
321
- }
322
- return merged;
323
- }
324
- /**
325
- * Analyze cross-repository relationships
326
- */
327
- analyzeCrossRepo(reports) {
328
- const sharedTypes = [];
329
- const apiConnections = [];
330
- const navigationFlows = [];
331
- const dataFlowAcrossRepos = [];
332
- const operationsByName = /* @__PURE__ */ new Map();
333
- for (const report of reports) {
334
- for (const op of report.analysis.graphqlOperations) {
335
- const repos = operationsByName.get(op.name) || [];
336
- repos.push(report.name);
337
- operationsByName.set(op.name, repos);
338
- }
339
- }
340
- for (const [name, repos] of operationsByName) {
341
- if (repos.length > 1) {
342
- sharedTypes.push(name);
343
- }
344
- }
345
- const frontendRepos = reports.filter((r) => r.analysis.pages.length > 0);
346
- const backendRepos = reports.filter((r) => r.analysis.apiEndpoints.length > 0);
347
- for (const frontend of frontendRepos) {
348
- for (const backend of backendRepos) {
349
- for (const endpoint of backend.analysis.apiEndpoints) {
350
- apiConnections.push({
351
- frontend: frontend.name,
352
- backend: backend.name,
353
- endpoint: endpoint.path,
354
- operations: frontend.analysis.graphqlOperations.filter((op) => op.usedIn.length > 0).map((op) => op.name)
355
- });
356
- }
357
- }
358
- }
359
- return {
360
- sharedTypes,
361
- apiConnections,
362
- navigationFlows,
363
- dataFlowAcrossRepos
364
- };
365
- }
366
- /**
367
- * Extract cross-repository links
368
- */
369
- extractCrossRepoLinks(results) {
370
- const links = [];
371
- const operationsByName = /* @__PURE__ */ new Map();
372
- for (const result of results) {
373
- for (const op of result.graphqlOperations) {
374
- const existing = operationsByName.get(op.name) || [];
375
- existing.push(result);
376
- operationsByName.set(op.name, existing);
377
- }
378
- }
379
- for (const [name, repos] of operationsByName) {
380
- if (repos.length > 1) {
381
- links.push({
382
- sourceRepo: repos[0].repository,
383
- sourcePath: `graphql/${name}`,
384
- targetRepo: repos[1].repository,
385
- targetPath: `graphql/${name}`,
386
- linkType: "graphql-operation",
387
- description: `Shared GraphQL operation: ${name}`
388
- });
389
- }
390
- }
391
- return links;
392
- }
393
- /**
394
- * Write documentation to output directory
395
- */
396
- async writeDocumentation(report) {
397
- const outputDir = this.config.outputDir;
398
- await fs.mkdir(outputDir, { recursive: true });
399
- const docs = this.markdownGenerator.generateDocumentation(report);
400
- for (const [filePath, content] of docs) {
401
- const fullPath = path2.join(outputDir, filePath);
402
- const dir = path2.dirname(fullPath);
403
- await fs.mkdir(dir, { recursive: true });
404
- await fs.writeFile(fullPath, content, "utf-8");
405
- console.log(` \u{1F4C4} ${filePath}`);
406
- }
407
- const jsonPath = path2.join(outputDir, "report.json");
408
- await fs.writeFile(jsonPath, JSON.stringify(report, null, 2), "utf-8");
409
- console.log(` \u{1F4CB} report.json`);
410
- }
411
- };
412
- function isPortAvailable(port) {
413
- return new Promise((resolve) => {
414
- const server = net.createServer();
415
- server.once("error", (err) => {
416
- if (err.code === "EADDRINUSE") {
417
- resolve(false);
418
- } else {
419
- resolve(false);
420
- }
421
- });
422
- server.once("listening", () => {
423
- server.close();
424
- resolve(true);
425
- });
426
- server.listen(port);
427
- });
428
- }
429
- async function findAvailablePort(startPort, maxAttempts = 10) {
430
- for (let i = 0; i < maxAttempts; i++) {
431
- const port = startPort + i;
432
- if (await isPortAvailable(port)) {
433
- return port;
434
- }
435
- }
436
- throw new Error(
437
- `No available port found between ${startPort} and ${startPort + maxAttempts - 1}`
438
- );
439
- }
440
-
441
- // src/server/doc-server.ts
442
- var DocServer = class {
443
- config;
444
- port;
445
- app;
446
- server;
447
- io;
448
- engine;
449
- currentReport = null;
450
- envResult = null;
451
- railsAnalysis = null;
452
- constructor(config, port = 3030, options) {
453
- this.config = config;
454
- this.port = port;
455
- this.app = express();
456
- this.server = http.createServer(this.app);
457
- this.io = new Server(this.server);
458
- this.engine = new DocGeneratorEngine(config, { noCache: options?.noCache });
459
- this.setupRoutes();
460
- this.setupSocketIO();
461
- }
462
- setupRoutes() {
463
- this.app.use("/assets", express.static(path2.join(this.config.outputDir, "assets")));
464
- const cssFiles = ["common.css", "page-map.css", "docs.css", "rails-map.css"];
465
- cssFiles.forEach((file) => {
466
- this.app.get(`/${file}`, async (req, res) => {
467
- try {
468
- const cssPath = new URL(`../generators/assets/${file}`, import.meta.url);
469
- const css = await fs.readFile(cssPath, "utf-8");
470
- res.type("text/css").send(css);
471
- } catch {
472
- res.status(404).send("CSS not found");
473
- }
474
- });
475
- });
476
- this.app.get("/", (req, res) => {
477
- res.redirect("/page-map");
478
- });
479
- this.app.get("/page-map", (req, res) => {
480
- if (!this.currentReport) {
481
- res.status(503).send("Documentation not ready yet");
482
- return;
483
- }
484
- const generator = new PageMapGenerator();
485
- res.send(
486
- generator.generatePageMapHtml(this.currentReport, {
487
- envResult: this.envResult,
488
- railsAnalysis: this.railsAnalysis
489
- })
490
- );
491
- });
492
- this.app.get("/rails-map", (req, res) => {
493
- if (!this.railsAnalysis) {
494
- res.status(404).send("No Rails environment detected");
495
- return;
496
- }
497
- const generator = new RailsMapGenerator();
498
- res.send(generator.generateFromResult(this.railsAnalysis));
499
- });
500
- this.app.get("/docs", async (req, res) => {
501
- res.send(await this.renderPage("index"));
502
- });
503
- this.app.get("/docs/*", async (req, res) => {
504
- const pagePath = req.params[0] || "index";
505
- res.send(await this.renderPage(pagePath));
506
- });
507
- this.app.get("/api/report", (req, res) => {
508
- res.json(this.currentReport);
509
- });
510
- this.app.get("/api/env", (req, res) => {
511
- res.json(this.envResult);
512
- });
513
- this.app.get("/api/rails", (req, res) => {
514
- if (this.railsAnalysis) {
515
- res.json(this.railsAnalysis);
516
- } else {
517
- res.status(404).json({ error: "No Rails analysis available" });
518
- }
519
- });
520
- this.app.get("/api/diagram/:name", (req, res) => {
521
- const diagram = this.currentReport?.diagrams.find(
522
- (d) => d.title.toLowerCase().replace(/\s+/g, "-") === req.params.name
523
- );
524
- if (diagram) {
525
- res.json(diagram);
526
- } else {
527
- res.status(404).json({ error: "Diagram not found" });
528
- }
529
- });
530
- this.app.post("/api/regenerate", async (req, res) => {
531
- try {
532
- await this.regenerate();
533
- res.json({ success: true });
534
- } catch (error) {
535
- res.status(500).json({ error: error.message });
536
- }
537
- });
538
- }
539
- setupSocketIO() {
540
- this.io.on("connection", (socket) => {
541
- console.log("Client connected");
542
- socket.on("disconnect", () => {
543
- console.log("Client disconnected");
544
- });
545
- });
546
- }
547
- async renderPage(pagePath) {
548
- const cleanPath = pagePath.replace(/\.md$/, "");
549
- const mdPath = path2.join(this.config.outputDir, `${cleanPath}.md`);
550
- let content = "";
551
- try {
552
- const markdown = await fs.readFile(mdPath, "utf-8");
553
- let html = await marked.parse(markdown);
554
- html = html.replace(
555
- /<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g,
556
- '<div class="mermaid">$1</div>'
557
- );
558
- html = html.replace(/<table>/g, '<div class="table-wrapper"><table>');
559
- html = html.replace(/<\/table>/g, "</table></div>");
560
- content = html;
561
- } catch (e) {
562
- console.error(`Failed to render page: ${mdPath}`, e);
563
- content = `<h1>Page not found</h1><p>Path: ${cleanPath}</p>`;
564
- }
565
- return this.getHtmlTemplate(content);
566
- }
567
- getGraphQLData() {
568
- if (!this.currentReport) return "[]";
569
- const ops = [];
570
- for (const repo of this.currentReport.repositories) {
571
- for (const op of repo.analysis?.graphqlOperations || []) {
572
- ops.push({
573
- name: op.name,
574
- type: op.type,
575
- returnType: op.returnType,
576
- variables: op.variables,
577
- fields: op.fields,
578
- usedIn: op.usedIn
579
- });
580
- }
581
- }
582
- return JSON.stringify(ops);
583
- }
584
- getApiCallsData() {
585
- if (!this.currentReport) return "[]";
586
- const calls = [];
587
- for (const repo of this.currentReport.repositories) {
588
- for (const call of repo.analysis?.apiCalls || []) {
589
- calls.push({
590
- id: call.id,
591
- method: call.method,
592
- url: call.url,
593
- callType: call.callType,
594
- filePath: call.filePath,
595
- line: call.line,
596
- containingFunction: call.containingFunction,
597
- requiresAuth: call.requiresAuth
598
- });
599
- }
600
- }
601
- return JSON.stringify(calls);
602
- }
603
- getHtmlTemplate(content) {
604
- const graphqlData = this.getGraphQLData();
605
- const apiCallsData = this.getApiCallsData();
606
- return `<!DOCTYPE html>
1
+ import {a as a$2}from'./chunk-SL2GMDBN.js';import {a as a$3}from'./chunk-VV3A3UE3.js';import {e,d,c,b as b$2}from'./chunk-J2CM7T7U.js';import {a,b as b$1}from'./chunk-XWZH2RDG.js';import {a as a$1}from'./chunk-MOEA75XK.js';import {k}from'./chunk-UJT5KTVK.js';import {simpleGit}from'simple-git';import*as m from'fs/promises';import*as l from'path';import H from'fast-glob';import*as w from'crypto';import F from'express';import {Server}from'socket.io';import*as z from'http';import {marked}from'marked';import*as A from'net';var C="1.1",x=class{cacheDir;manifest;manifestPath;dirty=false;constructor(t){this.cacheDir=l.join(t,".repomap-cache"),this.manifestPath=l.join(this.cacheDir,"manifest.json"),this.manifest={version:C,entries:{}};}async init(){try{await m.mkdir(this.cacheDir,{recursive:!0});}catch(t){console.warn(` Warning: Could not create cache directory: ${t.message}`);return}try{let t=await m.readFile(this.manifestPath,"utf-8"),a=JSON.parse(t);a.version===C?this.manifest=a:(console.log(" Cache version mismatch, clearing cache..."),await this.clear());}catch{}}async computeFileHash(t){try{let a=await m.readFile(t,"utf-8");return w.createHash("md5").update(a).digest("hex")}catch{return ""}}async computeFilesHash(t){let a=[...t].sort(),e=50,n;a.length<=e*2?n=a:n=[...a.slice(0,e),...a.slice(-e)];let o=await Promise.all(n.map(i=>this.computeFileHash(i))),s=w.createHash("md5").update(String(a.length)).digest("hex");return w.createHash("md5").update(o.join("")+s).digest("hex")}get(t,a){let e=this.manifest.entries[t];return e&&e.hash===a?e.data:null}set(t,a,e){this.manifest.entries[t]={hash:a,timestamp:Date.now(),data:e},this.dirty=true;}async save(){if(this.dirty)try{await m.mkdir(this.cacheDir,{recursive:!0}),await m.writeFile(this.manifestPath,JSON.stringify(this.manifest,null,2)),this.dirty=!1;}catch(t){console.warn(" Warning: Failed to save cache:",t.message);}}async clear(){this.manifest={version:C,entries:{}},this.dirty=true;try{await m.rm(this.cacheDir,{recursive:!0,force:!0}),await m.mkdir(this.cacheDir,{recursive:!0});}catch{}}getStats(){let t=Object.keys(this.manifest.entries).length,a=JSON.stringify(this.manifest).length;return {entries:t,size:a>1024*1024?`${(a/1024/1024).toFixed(1)}MB`:`${(a/1024).toFixed(1)}KB`}}};var b=class{config;mermaidGenerator;markdownGenerator;noCache;constructor(t,a$1){this.config=t,this.mermaidGenerator=new a,this.markdownGenerator=new b$1,this.noCache=a$1?.noCache??false;}async generate(){console.log(`\u{1F680} Starting documentation generation...
2
+ `);let t=[];for(let i of this.config.repositories)try{console.log(`
3
+ \u{1F4E6} Analyzing ${i.displayName}...`);let r=await this.analyzeRepository(i);t.push(r),console.log(`\u2705 Completed ${i.displayName}`);}catch(r){console.error(`\u274C Failed to analyze ${i.name}:`,r.message);}console.log(`
4
+ \u{1F517} Running cross-repository analysis...`);let a=this.analyzeCrossRepo(t);console.log(`
5
+ \u{1F4CA} Generating diagrams...`);let e=t.map(i=>i.analysis),n=this.extractCrossRepoLinks(e),o=this.mermaidGenerator.generateAll(e,n),s={generatedAt:new Date().toISOString(),repositories:t,crossRepoAnalysis:a,diagrams:o};return console.log(`
6
+ \u{1F4DD} Writing documentation...`),await this.writeDocumentation(s),console.log(`
7
+ \u2728 Documentation generation complete!`),console.log(`\u{1F4C1} Output: ${this.config.outputDir}`),s}async analyzeRepository(t){let a=new x(t.path);await a.init();let{version:e,commitHash:n}=await this.getRepoInfo(t),o=await H(["**/*.{ts,tsx,graphql}"],{cwd:t.path,ignore:["**/node_modules/**","**/.next/**","**/dist/**","**/build/**"],absolute:true}),s=await a.computeFilesHash(o),i=`analysis_v${e}_${t.name}_${n}`;this.noCache&&console.log(" \u{1F504} Cache disabled, analyzing from scratch...");let r=this.noCache?null:a.get(i,s);if(r){console.log(` \u26A1 Using cached analysis (hash: ${s.slice(0,8)}...)`);let d={totalPages:r.pages.length,totalComponents:r.components.length,totalGraphQLOperations:r.graphqlOperations.length,totalDataFlows:r.dataFlows.length,authRequiredPages:r.pages.filter(q=>q.authentication.required).length,publicPages:r.pages.filter(q=>!q.authentication.required).length};return {name:t.name,displayName:t.displayName,version:e,commitHash:n,analysis:r,summary:d}}let p=t.analyzers.map(d=>this.createAnalyzer(d,t)).filter(d=>d!==null);console.log(` Running ${p.length} analyzers in parallel...`);let h=Date.now(),f=await Promise.all(p.map(d=>d.analyze()));console.log(` Analysis completed in ${((Date.now()-h)/1e3).toFixed(1)}s`);let c=this.mergeAnalysisResults(f,t.name,e,n);a.set(i,s,c),await a.save(),console.log(` \u{1F4BE} Analysis cached (hash: ${s.slice(0,8)}...)`);let B={totalPages:c.pages.length,totalComponents:c.components.length,totalGraphQLOperations:c.graphqlOperations.length,totalDataFlows:c.dataFlows.length,authRequiredPages:c.pages.filter(d=>d.authentication.required).length,publicPages:c.pages.filter(d=>!d.authentication.required).length};return {name:t.name,displayName:t.displayName,version:e,commitHash:n,analysis:c,summary:B}}async getRepoInfo(t){try{let n=(await simpleGit(t.path).log({n:1})).latest?.hash||"unknown",o="unknown";try{let s=l.join(t.path,"package.json");o=JSON.parse(await m.readFile(s,"utf-8")).version||"unknown";}catch{}return {version:o,commitHash:n}}catch{return {version:"unknown",commitHash:"unknown"}}}createAnalyzer(t,a){switch(t){case "pages":if(a.type==="nextjs"||a.type==="rails"||a.type==="generic")return new b$2(a);break;case "graphql":return new c(a);case "dataflow":case "components":return new d(a);case "rest-api":case "api":return new e(a)}return null}mergeAnalysisResults(t,a,e,n){let o={repository:a,timestamp:new Date().toISOString(),version:e,commitHash:n,pages:[],graphqlOperations:[],apiCalls:[],components:[],dataFlows:[],apiEndpoints:[],models:[],crossRepoLinks:[]};for(let s of t)s.pages&&o.pages.push(...s.pages),s.graphqlOperations&&o.graphqlOperations.push(...s.graphqlOperations),s.apiCalls&&o.apiCalls.push(...s.apiCalls),s.components&&o.components.push(...s.components),s.dataFlows&&o.dataFlows.push(...s.dataFlows),s.apiEndpoints&&o.apiEndpoints.push(...s.apiEndpoints),s.models&&o.models.push(...s.models),s.crossRepoLinks&&o.crossRepoLinks.push(...s.crossRepoLinks);return o}analyzeCrossRepo(t){let a=[],e=[],n=[],o=[],s=new Map;for(let p of t)for(let h of p.analysis.graphqlOperations){let f=s.get(h.name)||[];f.push(p.name),s.set(h.name,f);}for(let[p,h]of s)h.length>1&&a.push(p);let i=t.filter(p=>p.analysis.pages.length>0),r=t.filter(p=>p.analysis.apiEndpoints.length>0);for(let p of i)for(let h of r)for(let f of h.analysis.apiEndpoints)e.push({frontend:p.name,backend:h.name,endpoint:f.path,operations:p.analysis.graphqlOperations.filter(c=>c.usedIn.length>0).map(c=>c.name)});return {sharedTypes:a,apiConnections:e,navigationFlows:n,dataFlowAcrossRepos:o}}extractCrossRepoLinks(t){let a=[],e=new Map;for(let n of t)for(let o of n.graphqlOperations){let s=e.get(o.name)||[];s.push(n),e.set(o.name,s);}for(let[n,o]of e)o.length>1&&a.push({sourceRepo:o[0].repository,sourcePath:`graphql/${n}`,targetRepo:o[1].repository,targetPath:`graphql/${n}`,linkType:"graphql-operation",description:`Shared GraphQL operation: ${n}`});return a}async writeDocumentation(t){let a=this.config.outputDir;await m.mkdir(a,{recursive:true});let e=this.markdownGenerator.generateDocumentation(t);for(let[o,s]of e){let i=l.join(a,o),r=l.dirname(i);await m.mkdir(r,{recursive:true}),await m.writeFile(i,s,"utf-8"),console.log(` \u{1F4C4} ${o}`);}let n=l.join(a,"report.json");await m.writeFile(n,JSON.stringify(t,null,2),"utf-8"),console.log(" \u{1F4CB} report.json");}};function Q(u){return new Promise(t=>{let a=A.createServer();a.once("error",e=>{e.code,t(false);}),a.once("listening",()=>{a.close(),t(true);}),a.listen(u);})}async function O(u,t=10){for(let a=0;a<t;a++){let e=u+a;if(await Q(e))return e}throw new Error(`No available port found between ${u} and ${u+t-1}`)}var I=class{config;port;app;server;io;engine;currentReport=null;envResult=null;railsAnalysis=null;constructor(t,a=3030,e){this.config=t,this.port=a,this.app=F(),this.server=z.createServer(this.app),this.io=new Server(this.server),this.engine=new b(t,{noCache:e?.noCache}),this.setupRoutes(),this.setupSocketIO();}setupRoutes(){this.app.use("/assets",F.static(l.join(this.config.outputDir,"assets")));["common.css","page-map.css","docs.css","rails-map.css"].forEach(e=>{this.app.get(`/${e}`,async(n,o)=>{let s=[l.join(l.dirname(new URL(import.meta.url).pathname),"generators","assets",e),l.join(l.dirname(new URL(import.meta.url).pathname),"..","generators","assets",e),l.join(process.cwd(),"dist","generators","assets",e),l.join(process.cwd(),"src","generators","assets",e)];for(let i of s)try{let r=await m.readFile(i,"utf-8");o.type("text/css").send(r);return}catch{}o.status(404).send("CSS not found");});}),this.app.get("/",(e,n)=>{n.redirect("/page-map");}),this.app.get("/page-map",(e,n)=>{if(!this.currentReport){n.status(503).send("Documentation not ready yet");return}let o=new a$1;n.send(o.generatePageMapHtml(this.currentReport,{envResult:this.envResult,railsAnalysis:this.railsAnalysis}));}),this.app.get("/rails-map",(e,n)=>{if(!this.railsAnalysis){n.status(404).send("No Rails environment detected");return}let o=new a$2;n.send(o.generateFromResult(this.railsAnalysis));}),this.app.get("/docs",async(e,n)=>{n.send(await this.renderPage("index"));}),this.app.get("/docs/*path",async(e,n)=>{let o=e.params.path||"index";n.send(await this.renderPage(o));}),this.app.get("/api/report",(e,n)=>{n.json(this.currentReport);}),this.app.get("/api/env",(e,n)=>{n.json(this.envResult);}),this.app.get("/api/rails",(e,n)=>{this.railsAnalysis?n.json(this.railsAnalysis):n.status(404).json({error:"No Rails analysis available"});}),this.app.get("/api/diagram/:name",(e,n)=>{let o=this.currentReport?.diagrams.find(s=>s.title.toLowerCase().replace(/\s+/g,"-")===e.params.name);o?n.json(o):n.status(404).json({error:"Diagram not found"});}),this.app.post("/api/regenerate",async(e,n)=>{try{await this.regenerate(),n.json({success:!0});}catch(o){n.status(500).json({error:o.message});}});}setupSocketIO(){this.io.on("connection",t=>{console.log("Client connected"),t.on("disconnect",()=>{console.log("Client disconnected");});});}async renderPage(t){let a=t.replace(/\.md$/,""),e=l.join(this.config.outputDir,`${a}.md`),n="";try{let o=await m.readFile(e,"utf-8"),s=await marked.parse(o);s=s.replace(/<pre><code class="language-mermaid">([\s\S]*?)<\/code><\/pre>/g,'<div class="mermaid">$1</div>'),s=s.replace(/<table>/g,'<div class="table-wrapper"><table>'),s=s.replace(/<\/table>/g,"</table></div>"),n=s;}catch(o){console.error(`Failed to render page: ${e}`,o),n=`<h1>Page not found</h1><p>Path: ${a}</p>`;}return this.getHtmlTemplate(n)}getGraphQLData(){if(!this.currentReport)return "[]";let t=[];for(let a of this.currentReport.repositories)for(let e of a.analysis?.graphqlOperations||[])t.push({name:e.name,type:e.type,returnType:e.returnType,variables:e.variables,fields:e.fields,usedIn:e.usedIn});return JSON.stringify(t)}getApiCallsData(){if(!this.currentReport)return "[]";let t=[];for(let a of this.currentReport.repositories)for(let e of a.analysis?.apiCalls||[])t.push({id:e.id,method:e.method,url:e.url,callType:e.callType,filePath:e.filePath,line:e.line,containingFunction:e.containingFunction,requiresAuth:e.requiresAuth});return JSON.stringify(t)}getHtmlTemplate(t){let a=this.getGraphQLData(),e=this.getApiCallsData();return `<!DOCTYPE html>
607
8
  <html lang="ja">
608
9
  <head>
609
10
  <meta charset="UTF-8">
@@ -612,8 +13,8 @@ var DocServer = class {
612
13
  <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
613
14
  <script src="/socket.io/socket.io.js"></script>
614
15
  <script>
615
- window.graphqlOps = ${graphqlData};
616
- window.apiCalls = ${apiCallsData};
16
+ window.graphqlOps = ${a};
17
+ window.apiCalls = ${e};
617
18
  // Create multiple lookup maps for different naming conventions
618
19
  window.gqlMap = new Map();
619
20
  window.gqlMapNormalized = new Map();
@@ -681,10 +82,10 @@ var DocServer = class {
681
82
  <body>
682
83
  <header class="header">
683
84
  <div style="display:flex;align-items:center;gap:24px">
684
- <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${this.config.repositories[0]?.displayName || this.config.repositories[0]?.name || "Repository"}</h1>
85
+ <h1 style="cursor:pointer" onclick="location.href='/'">\u{1F4CA} ${this.config.repositories[0]?.displayName||this.config.repositories[0]?.name||"Repository"}</h1>
685
86
  <nav style="display:flex;gap:4px">
686
87
  <a href="/page-map" class="nav-link">Page Map</a>
687
- ${this.railsAnalysis ? '<a href="/rails-map" class="nav-link">Rails Map</a>' : ""}
88
+ ${this.railsAnalysis?'<a href="/rails-map" class="nav-link">Rails Map</a>':""}
688
89
  <a href="/docs" class="nav-link active">Docs</a>
689
90
  <a href="/api/report" class="nav-link" target="_blank">API</a>
690
91
  </nav>
@@ -696,14 +97,12 @@ var DocServer = class {
696
97
  <div class="nav-group">
697
98
  <span class="nav-group-title">Documentation</span>
698
99
  <div class="nav-subitems">
699
- ${this.config.repositories.map(
700
- (repo) => `
701
- <a href="/docs/repos/${repo.name}/pages">Pages</a>
702
- <a href="/docs/repos/${repo.name}/components">Components</a>
703
- <a href="/docs/repos/${repo.name}/graphql">GraphQL</a>
704
- <a href="/docs/repos/${repo.name}/dataflow">Data Flow</a>
705
- `
706
- ).join("")}
100
+ ${this.config.repositories.map(n=>`
101
+ <a href="/docs/repos/${n.name}/pages">Pages</a>
102
+ <a href="/docs/repos/${n.name}/components">Components</a>
103
+ <a href="/docs/repos/${n.name}/graphql">GraphQL</a>
104
+ <a href="/docs/repos/${n.name}/dataflow">Data Flow</a>
105
+ `).join("")}
707
106
  </div>
708
107
  </div>
709
108
  <div class="nav-group">
@@ -718,7 +117,7 @@ var DocServer = class {
718
117
  </aside>
719
118
  <div class="content-area">
720
119
  <div class="content">
721
- ${content}
120
+ ${t}
722
121
  </div>
723
122
  </div>
724
123
  </div>
@@ -1547,98 +946,12 @@ var DocServer = class {
1547
946
  }
1548
947
  </script>
1549
948
  </body>
1550
- </html>`;
1551
- }
1552
- async start(openBrowser = true) {
1553
- const rootPath = this.config.repositories[0]?.path || process.cwd();
1554
- console.log("\u{1F50D} Detecting project environments...");
1555
- this.envResult = await detectEnvironments(rootPath);
1556
- if (this.envResult.environments.length > 0) {
1557
- console.log(` Found: ${this.envResult.environments.map((e) => e.type).join(", ")}`);
1558
- for (const env of this.envResult.environments) {
1559
- if (env.features.length > 0) {
1560
- console.log(` ${env.type} features: ${env.features.join(", ")}`);
1561
- }
1562
- }
1563
- }
1564
- console.log("\n\u{1F4DA} Generating documentation...");
1565
- this.currentReport = await this.engine.generate();
1566
- if (this.envResult.hasRails) {
1567
- console.log("\n\u{1F6E4}\uFE0F Analyzing Rails application...");
1568
- try {
1569
- this.railsAnalysis = await analyzeRailsApp(rootPath);
1570
- console.log(` \u2705 Rails analysis complete`);
1571
- } catch (error) {
1572
- console.error(` \u26A0\uFE0F Rails analysis failed:`, error.message);
1573
- }
1574
- }
1575
- try {
1576
- const availablePort = await findAvailablePort(this.port);
1577
- if (availablePort !== this.port) {
1578
- console.log(`
1579
- \u26A0\uFE0F Port ${this.port} is in use, using port ${availablePort} instead`);
1580
- }
1581
- this.port = availablePort;
1582
- } catch (error) {
1583
- console.error(`
1584
- \u274C Failed to find available port: ${error.message}`);
1585
- process.exit(1);
1586
- }
1587
- this.server.listen(this.port, () => {
1588
- console.log(`
1589
- \u{1F310} Documentation server running at http://localhost:${this.port}`);
1590
- if (this.envResult?.hasRails && this.envResult?.hasNextjs) {
1591
- console.log(" \u{1F4CA} Multiple environments detected - use tabs to switch views");
1592
- }
1593
- console.log(" Press Ctrl+C to stop\n");
1594
- });
1595
- if (openBrowser) {
1596
- const open = (await import('open')).default;
1597
- await open(`http://localhost:${this.port}`);
1598
- }
1599
- if (this.config.watch.enabled) {
1600
- this.watchForChanges();
1601
- }
1602
- }
1603
- async regenerate() {
1604
- console.log("\n\u{1F504} Regenerating documentation...");
1605
- this.currentReport = await this.engine.generate();
1606
- if (this.envResult?.hasRails) {
1607
- const rootPath = this.config.repositories[0]?.path || process.cwd();
1608
- try {
1609
- this.railsAnalysis = await analyzeRailsApp(rootPath);
1610
- } catch (error) {
1611
- console.error(`\u26A0\uFE0F Rails re-analysis failed:`, error.message);
1612
- }
1613
- }
1614
- this.io.emit("reload");
1615
- console.log("\u2705 Documentation regenerated");
1616
- }
1617
- async watchForChanges() {
1618
- const watchDirs = this.config.repositories.map((r) => r.path);
1619
- let timeout = null;
1620
- for (const dir of watchDirs) {
1621
- try {
1622
- const watcher = fs.watch(dir, { recursive: true });
1623
- (async () => {
1624
- for await (const event of watcher) {
1625
- if (event.filename && (event.filename.endsWith(".ts") || event.filename.endsWith(".tsx"))) {
1626
- if (timeout) clearTimeout(timeout);
1627
- timeout = setTimeout(async () => {
1628
- await this.regenerate();
1629
- }, this.config.watch.debounce);
1630
- }
1631
- }
1632
- })();
1633
- } catch (error) {
1634
- console.warn(`Warning: Could not watch directory ${dir}:`, error.message);
1635
- }
1636
- }
1637
- }
1638
- stop() {
1639
- this.server.close();
1640
- console.log("\n\u{1F44B} Server stopped");
1641
- }
1642
- };
1643
-
1644
- export { DocGeneratorEngine, DocServer };
949
+ </html>`}async start(t=true){let a=this.config.repositories[0]?.path||process.cwd();if(console.log("\u{1F50D} Detecting project environments..."),this.envResult=await a$3(a),this.envResult.environments.length>0){console.log(` Found: ${this.envResult.environments.map(e=>e.type).join(", ")}`);for(let e of this.envResult.environments)e.features.length>0&&console.log(` ${e.type} features: ${e.features.join(", ")}`);}if(console.log(`
950
+ \u{1F4DA} Generating documentation...`),this.currentReport=await this.engine.generate(),this.envResult.hasRails){console.log(`
951
+ \u{1F6E4}\uFE0F Analyzing Rails application...`);try{this.railsAnalysis=await k(a),console.log(" \u2705 Rails analysis complete");}catch(e){console.error(" \u26A0\uFE0F Rails analysis failed:",e.message);}}try{let e=await O(this.port);e!==this.port&&console.log(`
952
+ \u26A0\uFE0F Port ${this.port} is in use, using port ${e} instead`),this.port=e;}catch(e){console.error(`
953
+ \u274C Failed to find available port: ${e.message}`),process.exit(1);}if(this.server.listen(this.port,()=>{console.log(`
954
+ \u{1F310} Documentation server running at http://localhost:${this.port}`),this.envResult?.hasRails&&this.envResult?.hasNextjs&&console.log(" \u{1F4CA} Multiple environments detected - use tabs to switch views"),console.log(` Press Ctrl+C to stop
955
+ `);}),t){let e=(await import('open')).default;await e(`http://localhost:${this.port}`);}this.config.watch.enabled&&this.watchForChanges();}async regenerate(){if(console.log(`
956
+ \u{1F504} Regenerating documentation...`),this.currentReport=await this.engine.generate(),this.envResult?.hasRails){let t=this.config.repositories[0]?.path||process.cwd();try{this.railsAnalysis=await k(t);}catch(a){console.error("\u26A0\uFE0F Rails re-analysis failed:",a.message);}}this.io.emit("reload"),console.log("\u2705 Documentation regenerated");}async watchForChanges(){let t=this.config.repositories.map(e=>e.path),a=null;for(let e of t)try{let n=m.watch(e,{recursive:!0});(async()=>{for await(let o of n)o.filename&&(o.filename.endsWith(".ts")||o.filename.endsWith(".tsx"))&&(a&&clearTimeout(a),a=setTimeout(async()=>{await this.regenerate();},this.config.watch.debounce));})();}catch(n){console.warn(`Warning: Could not watch directory ${e}:`,n.message);}}stop(){this.server.close(),console.log(`
957
+ \u{1F44B} Server stopped`);}};export{b as a,I as b};