UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

358 lines (357 loc) 13.9 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.ConfigManager = void 0; const path = __importStar(require("path")); const fs = __importStar(require("fs")); class ConfigManager { constructor(projectPath) { // Internal properties for dynamic detection this._projectStructure = null; this._isSourceDirDetected = false; this.projectPath = projectPath; this.fileExtensions = [".ts", ".tsx", ".js", ".jsx"]; this.rootComponentNames = ["main", "index", "app"]; this.srcDir = ""; // Will be set dynamically this.outputFileName = "analysis-results.json"; } async loadConfig() { // First detect project structure await this.detectProjectStructure(); // Then try to load user config const configPath = path.join(this.projectPath, "sicua.config.js"); try { const userConfig = require(configPath); this.mergeConfig(userConfig); } catch (error) { console.log("No custom configuration found. Using default settings."); } // Resolve paths after all config is loaded this.resolvePaths(); } /** * Detect project structure and set appropriate defaults */ async detectProjectStructure() { this._projectStructure = await this.analyzeProjectStructure(); // Set source directory based on detection this.srcDir = this._projectStructure.detectedSourceDirectory; this._isSourceDirDetected = true; // Adjust root component names based on project type this.adjustRootComponentNames(); } /** * Analyze the project structure to determine type and source directory */ async analyzeProjectStructure() { const packageJsonPath = path.join(this.projectPath, "package.json"); let detection = { projectType: "react", detectedSourceDirectory: this.projectPath, hasSourceDirectory: false, availableDirectories: [], }; // Read package.json to determine project type try { const packageContent = fs.readFileSync(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageContent); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies, }; const nextVersion = dependencies.next; if (nextVersion) { detection.projectType = "nextjs"; detection.nextjsVersion = nextVersion; // Determine router type based on version const versionMatch = nextVersion.match(/(\d+)\.(\d+)/); if (versionMatch) { const major = parseInt(versionMatch[1]); const minor = parseInt(versionMatch[2]); if (major > 13 || (major === 13 && minor >= 4)) { detection.routerType = "app"; } else { detection.routerType = "pages"; } } else { detection.routerType = "pages"; } } } catch (error) { console.warn("Could not read package.json, assuming React project"); } // Detect available directories const possibleDirectories = [ "src", "app", "pages", "components", "lib", "utils", "hooks", ]; const availableDirectories = []; for (const dir of possibleDirectories) { const dirPath = path.join(this.projectPath, dir); try { const stat = fs.statSync(dirPath); if (stat.isDirectory()) { availableDirectories.push(dir); } } catch { // Directory doesn't exist } } detection.availableDirectories = availableDirectories; // Determine source directory with priority system detection.detectedSourceDirectory = this.determineSourceDirectory(detection, availableDirectories); detection.hasSourceDirectory = availableDirectories.includes("src"); return detection; } /** * Determine the best source directory based on project structure */ determineSourceDirectory(detection, availableDirectories) { // Priority 1: src directory (universal preference) if (availableDirectories.includes("src")) { return path.join(this.projectPath, "src"); } // Priority 2: Next.js specific directories if (detection.projectType === "nextjs") { if (detection.routerType === "app" && availableDirectories.includes("app")) { return this.projectPath; // Use project root for app router without src } if (detection.routerType === "pages" && availableDirectories.includes("pages")) { return this.projectPath; // Use project root for pages router without src } } // Priority 3: Common React directories const commonReactDirs = ["components", "lib", "utils"]; for (const dir of commonReactDirs) { if (availableDirectories.includes(dir)) { return this.projectPath; // Use project root if any common directories exist } } // Fallback: project root return this.projectPath; } /** * Adjust root component names based on project type and structure */ adjustRootComponentNames() { if (!this._projectStructure) return; const { projectType, routerType, hasSourceDirectory } = this._projectStructure; // Create a comprehensive list based on project structure let adjustedNames = [...this.rootComponentNames]; if (projectType === "nextjs") { if (routerType === "app") { // App router specific entry points adjustedNames.unshift("layout", "page", "template", "loading", "error", "not-found"); if (hasSourceDirectory) { adjustedNames.push("root-layout", "global-layout"); } } else { // Pages router specific entry points adjustedNames.unshift("_app", "_document", "index"); } } else { // Regular React project adjustedNames.unshift("App", "Root", "Main", "Index"); } // Remove duplicates while preserving order this.rootComponentNames = Array.from(new Set(adjustedNames)); } /** * Update source directory (called by directory scanner) */ updateSourceDirectory(newSrcDir) { if (this._isSourceDirDetected && this.srcDir !== newSrcDir) { this.srcDir = newSrcDir; } } /** * Get detected project structure information */ getProjectStructure() { return this._projectStructure; } /** * Check if the source directory was auto-detected */ isSourceDirectoryDetected() { return this._isSourceDirDetected; } /** * Merge user configuration with detected defaults */ mergeConfig(userConfig) { if (userConfig.fileExtensions) { this.fileExtensions = userConfig.fileExtensions; } if (userConfig.rootComponentNames) { // If user provides custom root components, prepend them to detected ones const userComponents = userConfig.rootComponentNames; const detectedComponents = this.rootComponentNames; this.rootComponentNames = Array.from(new Set([...userComponents, ...detectedComponents])); } // Only override srcDir if user explicitly set it AND it exists if (userConfig.srcDir) { const userSrcDir = path.resolve(this.projectPath, userConfig.srcDir); try { const stat = fs.statSync(userSrcDir); if (stat.isDirectory()) { this.srcDir = userConfig.srcDir; this._isSourceDirDetected = false; // User override } else { console.warn(`⚠️ User-specified srcDir "${userSrcDir}" is not a directory, using detected: ${this.srcDir}`); } } catch (error) { console.warn(`⚠️ User-specified srcDir "${userSrcDir}" does not exist, using detected: ${this.srcDir}`); } } if (userConfig.outputFileName) { this.outputFileName = userConfig.outputFileName; } } /** * Resolve all paths to absolute paths */ resolvePaths() { // Resolve srcDir - handle both absolute and relative paths if (path.isAbsolute(this.srcDir)) { // Already absolute, keep as is } else if (this.srcDir === "" || this.srcDir === ".") { // Empty or current directory means project root this.srcDir = this.projectPath; } else { // Relative path, resolve against project path this.srcDir = path.resolve(this.projectPath, this.srcDir); } // Resolve output file name if (path.isAbsolute(this.outputFileName)) { // Already absolute, keep as is } else { this.outputFileName = path.resolve(this.projectPath, this.outputFileName); } // Validate that srcDir exists try { const stat = fs.statSync(this.srcDir); if (!stat.isDirectory()) { throw new Error(`Source directory is not a directory: ${this.srcDir}`); } } catch (error) { console.error(`❌ Source directory does not exist: ${this.srcDir}`); // Fallback to project root this.srcDir = this.projectPath; } } /** * Validate configuration and provide warnings for potential issues */ validateConfig() { const warnings = []; if (!this._projectStructure) { warnings.push("Project structure was not detected properly"); } // Check if file extensions make sense for detected project type if (this._projectStructure?.projectType === "nextjs") { const hasTypeScript = this.fileExtensions.some((ext) => ext.includes("ts")); if (!hasTypeScript) { warnings.push("Next.js project detected but no TypeScript extensions configured"); } } // Check if source directory contains expected files if (this.srcDir) { const hasFiles = this.checkForSourceFiles(); if (!hasFiles) { warnings.push(`Source directory "${this.srcDir}" appears to be empty or contains no matching files`); } } return warnings; } /** * Check if source directory contains files with configured extensions */ checkForSourceFiles() { try { const files = fs.readdirSync(this.srcDir, { recursive: true }); return files.some((file) => typeof file === "string" && this.fileExtensions.some((ext) => file.endsWith(ext))); } catch (error) { return false; } } /** * Get final configuration object */ getConfig() { return { fileExtensions: this.fileExtensions, rootComponentNames: this.rootComponentNames, srcDir: this.srcDir, outputFileName: this.outputFileName, }; } /** * Get configuration summary for debugging */ getConfigSummary() { const structure = this._projectStructure; return ` 📋 Configuration Summary: Project Type: ${structure?.projectType?.toUpperCase() || "Unknown"} ${structure?.projectType === "nextjs" ? `Next.js Version: ${structure.nextjsVersion}` : ""} ${structure?.routerType ? `Router Type: ${structure.routerType.toUpperCase()}` : ""} Source Directory: ${this.srcDir} ${this._isSourceDirDetected ? "(Auto-detected)" : "(User-specified)"} Available Directories: ${structure?.availableDirectories.join(", ") || "None"} File Extensions: ${this.fileExtensions.join(", ")} Root Components: ${this.rootComponentNames.slice(0, 5).join(", ")}${this.rootComponentNames.length > 5 ? "..." : ""} Output File: ${this.outputFileName} `.trim(); } } exports.ConfigManager = ConfigManager;