UNPKG

@choi2021/next-route-visualizer

Version:

πŸ—ΊοΈ Visualize Next.js routes as an interactive diagram with advanced function analysis. Supports dynamic route detection, function-based navigation patterns, and comprehensive Next.js route mapping. Generate JSON data and beautiful HTML reports to explore

130 lines (129 loc) β€’ 7.13 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.visualizeRoutes = visualizeRoutes; const path_1 = __importDefault(require("path")); const promises_1 = __importDefault(require("fs/promises")); const routeParser_js_1 = require("../parser/routeParser.js"); const child_process_1 = require("child_process"); function visualizeRoutes(targetRoot) { return __awaiter(this, void 0, void 0, function* () { console.log(`\n🎯 Next.js 라우트 μ‹œκ°ν™”λ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€...`); console.log(`πŸ“ ν”„λ‘œμ νŠΈ: ${targetRoot}`); console.log(`⏰ 뢄석 쀑... μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”.\n`); try { // 1. 라우트 νŒŒμ‹± (API μ œμ™Έ, νŽ˜μ΄μ§€+μ»΄ν¬λ„ŒνŠΈ 포함) const parser = new routeParser_js_1.RouteParser(targetRoot, { analyzeNavigation: true, includeMetadata: true, }); const result = yield parser.parse(); // 2. API 라우트 제거 및 index 파일 μˆ˜μ • (ν›„μ²˜λ¦¬) const filteredNodes = result.nodes.filter((node) => { if (node.type === "api" || node.path.startsWith("/api/")) { console.log(`❌ API 라우트 μ œμ™Έ: ${node.path}`); return false; } return true; }); // index νŒŒμΌλ“€μ„ 루트 경둜둜 λ³€ν™˜ const fixedNodes = filteredNodes.map((node) => { if (node.path === "/index" || node.id === "/index") { console.log(`🏠 index νŒŒμΌμ„ 루트둜 λ³€ν™˜: ${node.filePath} -> /`); return Object.assign(Object.assign({}, node), { id: "/", path: "/", label: "/", metadata: Object.assign(Object.assign({}, node.metadata), { depth: 0, segments: [""] }) }); } return node; }); // 쀑볡 λ…Έλ“œ 제거 (같은 pathλ₯Ό κ°€μ§„ λ…Έλ“œλ“€ 쀑 κ°€μž₯ 큰 파일 선택) const nodeMap = new Map(); fixedNodes.forEach((node) => { const existing = nodeMap.get(node.path); if (!existing || node.fileSize > existing.fileSize) { nodeMap.set(node.path, node); } }); const uniqueNodes = Array.from(nodeMap.values()); // API κ΄€λ ¨ μ—£μ§€ 제거 및 /index -> / λ³€ν™˜ const filteredEdges = result.edges .filter((edge) => { if (edge.source.startsWith("/api/") || edge.target.startsWith("/api/")) { return false; } return true; }) .map((edge) => { const newEdge = Object.assign({}, edge); if (edge.source === "/index") newEdge.source = "/"; if (edge.target === "/index") newEdge.target = "/"; return newEdge; }); // 3. 톡계 μž¬κ³„μ‚° const pageNodes = uniqueNodes.filter((n) => n.type === "page"); const dynamicNodes = uniqueNodes.filter((n) => n.type === "dynamic"); const finalResult = { nodes: uniqueNodes.sort((a, b) => a.path.localeCompare(b.path)), edges: filteredEdges, summary: { totalPages: pageNodes.length, totalDynamicRoutes: dynamicNodes.length, maxDepth: Math.max(...uniqueNodes.map((n) => n.metadata.depth)), totalNavigationLinks: filteredEdges.length, }, }; console.log("\nπŸŽ‰ 뢄석 μ™„λ£Œ!"); console.log("================"); console.log(`πŸ“„ νŽ˜μ΄μ§€: ${finalResult.summary.totalPages}개`); console.log(`πŸ”„ 닀이내믹 라우트: ${finalResult.summary.totalDynamicRoutes}개`); console.log(`πŸ”— λ„€λΉ„κ²Œμ΄μ…˜ 링크: ${finalResult.summary.totalNavigationLinks}개`); // 4. route-graph.json μ €μž₯ const graphPath = path_1.default.resolve(targetRoot, "route-graph.json"); yield promises_1.default.writeFile(graphPath, JSON.stringify(finalResult, null, 2)); console.log(`\nπŸ’Ύ 라우트 데이터: ${path_1.default.relative(process.cwd(), graphPath)}`); // 5. μ‹œκ°ν™” HTML 생성 const templatePath = path_1.default.resolve(__dirname, "../../visualize.html"); const outPath = path_1.default.resolve(targetRoot, "route-visualizer.html"); try { let html = yield promises_1.default.readFile(templatePath, "utf8"); // 데이터 μ‚½μž…μ„ μœ„ν•œ μ•ˆμ „ν•œ JSON λ³€ν™˜ const safeJson = JSON.stringify(finalResult, null, 2) .replace(/</g, "\\u003c") .replace(/>/g, "\\u003e"); // 예제 데이터 λ‘œλ“œ ν•¨μˆ˜λ₯Ό μ‹€μ œ λ°μ΄ν„°λ‘œ ꡐ체 html = html.replace(/fetch\("\.\/example-next\/route-graph\.json"\)/g, `Promise.resolve(${safeJson})`); yield promises_1.default.writeFile(outPath, html, "utf8"); console.log(`🎨 μ‹œκ°ν™” 파일: ${path_1.default.relative(process.cwd(), outPath)}`); // 6. λΈŒλΌμš°μ € μžλ™ μ˜€ν”ˆ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"; (0, child_process_1.exec)(`${openCmd} "${outPath}"`); console.log(`\n🌐 λΈŒλΌμš°μ €μ—μ„œ μ‹œκ°ν™”λ₯Ό ν™•μΈν•˜μ„Έμš”!`); console.log(`πŸ’‘ Tip: νŽ˜μ΄μ§€κ°„ λ„€λΉ„κ²Œμ΄μ…˜ 관계λ₯Ό μΈν„°λž™ν‹°λΈŒν•˜κ²Œ 탐색할 수 μžˆμŠ΅λ‹ˆλ‹€.`); } catch (_a) { console.warn(`⚠️ μ‹œκ°ν™” ν…œν”Œλ¦Ώμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: ${templatePath}`); console.log(`πŸ“Š λŒ€μ‹  route-graph.json을 ν™•μΈν•˜μ„Έμš”: ${path_1.default.relative(process.cwd(), graphPath)}`); } } catch (error) { console.error("❌ 였λ₯˜:", error.message); throw error; } }); }