UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

305 lines (304 loc) 11.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RouteScanner = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs")); const SpecialFileCoverageScanner_1 = require("./SpecialFileCoverageScanner"); const utils_1 = require("../utils"); /** * Scans Next.js app directory for routes and integrates special file coverage */ class RouteScanner { constructor(appDirectory) { this.appDirectory = appDirectory; this.specialFileScanner = new SpecialFileCoverageScanner_1.SpecialFileCoverageScanner(appDirectory); this.validExtensions = [".js", ".jsx", ".ts", ".tsx"]; } /** * Scans the entire app directory and returns all route structures */ scanAllRoutes() { const routes = []; try { const pageFiles = this.findAllPageFiles(this.appDirectory); for (const pageFile of pageFiles) { const route = this.buildRouteStructure(pageFile); if (route) { routes.push(route); } } } catch (error) { console.error("Error scanning routes:", error); } return routes; } /** * Scans a specific route path */ scanRoute(routePath) { const pageFilePath = this.findPageFile(routePath); if (!pageFilePath) { return null; } return this.buildRouteStructure(pageFilePath); } /** * Gets all route paths in the app directory */ getAllRoutePaths() { const pageFiles = this.findAllPageFiles(this.appDirectory); return pageFiles.map((pageFile) => this.extractRoutePathFromFile(pageFile)); } /** * Checks if a route exists */ routeExists(routePath) { return this.findPageFile(routePath) !== null; } /** * Gets route metadata for a specific path */ getRouteMetadata(routePath) { const segments = (0, utils_1.parseRoutePath)(routePath); return this.buildRouteMetadata(segments, routePath); } /** * Finds all page.js/page.tsx files in the app directory */ findAllPageFiles(directory, basePath = "") { const pageFiles = []; try { const entries = fs.readdirSync(directory, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(directory, entry.name); const relativePath = path.join(basePath, entry.name); if (entry.isDirectory()) { // Skip certain directories if (this.shouldSkipDirectory(entry.name)) { continue; } // Recursively scan subdirectories pageFiles.push(...this.findAllPageFiles(fullPath, relativePath)); } else if (entry.isFile()) { // Check if it's a page file if ((0, utils_1.isPageFile)(entry.name)) { pageFiles.push(fullPath); } } } } catch (error) { console.warn(`Error reading directory ${directory}:`, error); } return pageFiles; } /** * Finds page file for a specific route path */ findPageFile(routePath) { const segments = (0, utils_1.parseRoutePath)(routePath); let currentPath = this.appDirectory; // Navigate through route segments for (const segment of segments) { currentPath = path.join(currentPath, segment); if (!fs.existsSync(currentPath)) { // Try to find dynamic route that matches const parentDir = path.dirname(currentPath); const dynamicMatch = this.findDynamicRouteMatch(parentDir, segment); if (dynamicMatch) { currentPath = path.join(parentDir, dynamicMatch); } else { return null; } } } // Look for page file in the final directory return this.findPageFileInDirectory(currentPath); } /** * Builds complete route structure from a page file */ buildRouteStructure(pageFilePath) { const routePath = this.extractRoutePathFromFile(pageFilePath); const segments = (0, utils_1.parseRoutePath)(routePath); const metadata = this.buildRouteMetadata(segments, routePath); if (!metadata) { return null; } const routeSegments = this.buildRouteSegments(segments); return { routePath, pageFilePath, segments: routeSegments, metadata, }; } /** * Builds route segments with special file coverage */ buildRouteSegments(segments) { const routeSegments = []; // Add root segment const rootSpecialFiles = this.specialFileScanner.getSegmentSpecialFiles(""); routeSegments.push({ name: "", isDynamic: false, isCatchAll: false, isRouteGroup: false, specialFiles: rootSpecialFiles, depth: 0, }); // Add each route segment for (let i = 0; i < segments.length; i++) { const segment = segments[i]; const segmentPath = segments.slice(0, i + 1).join("/"); const specialFiles = this.specialFileScanner.getSegmentSpecialFiles(segmentPath); routeSegments.push({ name: segment, isDynamic: (0, utils_1.isDynamicSegment)(segment), isCatchAll: (0, utils_1.isCatchAllSegment)(segment), isRouteGroup: (0, utils_1.isRouteGroup)(segment), specialFiles, depth: i + 1, }); } return routeSegments; } /** * Builds route metadata from segments */ buildRouteMetadata(segments, routePath) { const isDynamic = segments.some((segment) => (0, utils_1.isDynamicSegment)(segment)); const isCatchAll = segments.some((segment) => (0, utils_1.isCatchAllSegment)(segment)); const routeGroups = segments.filter((segment) => (0, utils_1.isRouteGroup)(segment)); return { isDynamic, isCatchAll, routeGroup: routeGroups.length > 0 ? routeGroups[0] : undefined, depth: segments.length, segments: segments.filter((segment) => !(0, utils_1.isRouteGroup)(segment)), // Exclude route groups from segments }; } /** * Extracts route path from page file path */ extractRoutePathFromFile(pageFilePath) { const relativePath = path.relative(this.appDirectory, path.dirname(pageFilePath)); if (!relativePath || relativePath === ".") { return "/"; } // Convert file path to route path const segments = relativePath.split(path.sep); const routeSegments = segments.filter((segment) => !(0, utils_1.isRouteGroup)(segment)); return "/" + routeSegments.join("/"); } /** * Finds page file in a specific directory */ findPageFileInDirectory(directory) { try { for (const ext of this.validExtensions) { const pageFile = path.join(directory, `page${ext}`); if (fs.existsSync(pageFile)) { return pageFile; } } } catch (error) { console.warn(`Error searching for page file in ${directory}:`, error); } return null; } /** * Finds dynamic route match in a directory */ findDynamicRouteMatch(directory, segment) { try { const entries = fs.readdirSync(directory, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && (0, utils_1.isDynamicSegment)(entry.name)) { // Check if this dynamic route has a page file const pageFile = this.findPageFileInDirectory(path.join(directory, entry.name)); if (pageFile) { return entry.name; } } } } catch (error) { console.warn(`Error finding dynamic route match in ${directory}:`, error); } return null; } /** * Checks if a directory should be skipped during scanning */ shouldSkipDirectory(dirName) { // Skip common non-route directories const skipDirs = [ "node_modules", ".next", ".git", "public", "styles", "components", "lib", "utils", ]; return skipDirs.includes(dirName) || dirName.startsWith("."); } /** * Gets special file coverage for a specific route */ getRouteSpecialFileCoverage(routePath) { return this.specialFileScanner.scanRouteSpecialFiles(routePath); } /** * Gets coverage summary for all routes */ getAllRoutesCoverageSummary() { const routes = this.scanAllRoutes(); const routesWithMissingFiles = []; let totalCoverage = 0; for (const route of routes) { const coverage = this.specialFileScanner.scanRouteSpecialFiles(route.routePath); const summary = this.specialFileScanner.getCoverageSummary(coverage); totalCoverage += summary.coveragePercentage; if (summary.missingFiles.length > 0) { routesWithMissingFiles.push(route.routePath); } } return { totalRoutes: routes.length, routesWithMissingFiles, averageCoverage: routes.length > 0 ? totalCoverage / routes.length : 0, }; } } exports.RouteScanner = RouteScanner;