UNPKG

sca-tool

Version:

Universal static code analysis tool for calculating API operation costs across multiple frameworks (NestJS, Express, Fastify)

433 lines 16.8 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigLoader = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const editor_detector_1 = require("./editor-detector"); /** * Configuration loader with comprehensive fallback system */ class ConfigLoader { static DEFAULT_CONFIG_PATHS = [ 'configs/sca.config.ts', 'configs/sca.config.js', 'configs/sca.config.json', 'sca.config.ts', 'sca.config.js', 'sca.config.json', 'sca-tool.config.json' ]; static DEFAULT_CONFIG = { operations: { 'findMany': 1, 'findUnique': 1, 'findFirst': 1, 'create': 1, 'update': 1, 'delete': 1, 'upsert': 2, 'createMany': 2, 'updateMany': 2, 'deleteMany': 2, 'count': 1, 'aggregate': 2, 'groupBy': 2, 'get': 1, 'post': 1, 'put': 1, 'patch': 1, 'fetch': 1, 'axios': 1, 'send': 1, 'sendMail': 1, 'unknown_operation': 1 }, patterns: { database: { pattern: /\.(findMany|findUnique|findFirst|create|update|delete|upsert|createMany|updateMany|deleteMany|count|aggregate|groupBy)\(/, description: 'Generic database operations' }, http: { pattern: /\.(get|post|put|patch|delete)\(/, description: 'HTTP operations' }, async_method: { pattern: /await\s+\w+\./, description: 'Async method calls' }, prisma: { pattern: /prisma\.\w+\.(findMany|findUnique|findFirst|create|update|delete|upsert|createMany|updateMany|deleteMany|count|aggregate|groupBy)/, description: 'Prisma ORM operations' }, custom_service: { pattern: /\w+Service\./, description: 'Custom service method calls' }, email_service: { pattern: /(EmailService|mailService)\.(send|sendMail)/, description: 'Email service operations' } }, parallelExecutionPatterns: [ 'Promise.all', 'Promise.allSettled', 'Promise.race', 'await Promise.all', 'await Promise.allSettled' ], ignorePatterns: [ 'console.log', 'console.error', 'console.warn', 'logger.', 'log.' ], costMultipliers: { loop: 1.5, recursion: 2.0, nested: 1.3, conditional: 1.1 }, output: { defaultFormat: 'text', defaultOutputDir: './reports', fileNaming: { pattern: '{filename}-analysis-{timestamp}', includeTimestamp: true }, html: { theme: 'light', includeCharts: true, title: 'API Cost Analysis Report', footer: 'Generated by SCA-Tool - Static Code Analyzer', openInBrowser: false } }, analysis: { costThresholds: { low: 2, medium: 5, high: 10 }, recommendations: { enabled: true, customRules: [] }, parallel: { detectParallelOperations: true, parallelThreshold: 2 }, methodFilters: { minCost: 0, maxCost: 1000, excludePatterns: [] } }, paths: { configFile: 'configs/sca.config.ts', outputDir: './reports', tempDir: './temp', cacheDir: './.sca-cache', excludePatterns: [ 'node_modules/**', 'dist/**', 'build/**', '**/*.test.ts', '**/*.spec.ts', '**/*.d.ts' ], includePatterns: [ '**/*.ts', '**/*.js', '**/*.tsx', '**/*.jsx' ], watch: { debounceMs: 500, excludePatterns: [ 'node_modules/**', 'dist/**', 'build/**' ] } }, editor: editor_detector_1.EditorDetector.detectEditor() }; /** * Load configuration with comprehensive fallback system */ static loadConfig(customConfigPath) { let config = this.deepClone(this.DEFAULT_CONFIG); // Try to load custom config const configPath = this.findConfigFile(customConfigPath); if (configPath) { try { const userConfig = this.loadConfigFile(configPath); config = this.mergeConfigs(config, userConfig); console.log(`📋 Loaded config from: ${configPath}`); } catch (error) { console.warn(`⚠️ Failed to load config from ${configPath}, using defaults:`, error instanceof Error ? error.message : String(error)); } } else if (customConfigPath) { console.warn(`⚠️ Config file not found: ${customConfigPath}, using defaults`); } return this.validateAndNormalizeConfig(config); } /** * Find the first available config file */ static findConfigFile(customPath) { const pathsToTry = customPath ? [customPath] : this.DEFAULT_CONFIG_PATHS; for (const configPath of pathsToTry) { const fullPath = path.resolve(configPath); if (fs.existsSync(fullPath)) { return fullPath; } } return null; } /** * Load and parse config file */ static loadConfigFile(configPath) { const ext = path.extname(configPath).toLowerCase(); if (ext === '.json') { const content = fs.readFileSync(configPath, 'utf8'); return JSON.parse(content); } else if (ext === '.js' || ext === '.ts') { // For TypeScript/JavaScript files, we'll try to require them // Note: This is a simplified approach - in production you might want to use ts-node delete require.cache[require.resolve(configPath)]; const module = require(configPath); return module.default || module.config || module; } throw new Error(`Unsupported config file format: ${ext}`); } /** * Deep merge two configuration objects */ static mergeConfigs(base, override) { const result = this.deepClone(base); // Merge operations if (override.operations) { result.operations = { ...result.operations, ...override.operations }; } // Merge patterns if (override.patterns) { result.patterns = { ...result.patterns, ...override.patterns }; } // Merge arrays if (override.parallelExecutionPatterns) { result.parallelExecutionPatterns = [...result.parallelExecutionPatterns, ...override.parallelExecutionPatterns]; } if (override.ignorePatterns) { result.ignorePatterns = [...result.ignorePatterns, ...override.ignorePatterns]; } // Merge nested objects if (override.costMultipliers) { result.costMultipliers = { ...result.costMultipliers, ...override.costMultipliers }; } if (override.output) { result.output = this.mergeDeep(result.output, override.output); } if (override.analysis) { result.analysis = this.mergeDeep(result.analysis, override.analysis); } if (override.paths) { result.paths = this.mergeDeep(result.paths, override.paths); } if (override.editor) { result.editor = this.mergeDeep(result.editor, override.editor); } if (override.methodCosts) { result.methodCosts = { ...result.methodCosts, ...override.methodCosts }; } return result; } /** * Deep merge utility */ static mergeDeep(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.mergeDeep(result[key] || {}, source[key]); } else if (source[key] !== undefined) { result[key] = source[key]; } } return result; } /** * Deep clone utility */ static deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; if (obj instanceof Date) return new Date(obj.getTime()); if (obj instanceof Array) return obj.map(item => this.deepClone(item)); if (obj instanceof RegExp) return new RegExp(obj); const cloned = {}; for (const key in obj) { cloned[key] = this.deepClone(obj[key]); } return cloned; } /** * Validate and normalize configuration */ static validateAndNormalizeConfig(config) { // Ensure required fields exist with fallbacks config.operations = config.operations || this.DEFAULT_CONFIG.operations; config.patterns = config.patterns || this.DEFAULT_CONFIG.patterns; config.parallelExecutionPatterns = config.parallelExecutionPatterns || this.DEFAULT_CONFIG.parallelExecutionPatterns; config.ignorePatterns = config.ignorePatterns || this.DEFAULT_CONFIG.ignorePatterns; config.costMultipliers = config.costMultipliers || this.DEFAULT_CONFIG.costMultipliers; config.output = config.output || this.DEFAULT_CONFIG.output; config.analysis = config.analysis || this.DEFAULT_CONFIG.analysis; config.paths = config.paths || this.DEFAULT_CONFIG.paths; config.editor = config.editor || this.DEFAULT_CONFIG.editor; // Normalize patterns to RegExp objects for (const [key, pattern] of Object.entries(config.patterns)) { if (typeof pattern.pattern === 'string') { try { config.patterns[key].pattern = new RegExp(pattern.pattern); } catch (error) { console.warn(`⚠️ Invalid regex pattern for ${key}: ${pattern.pattern}`); config.patterns[key].pattern = /(?:)/; // Empty regex that matches nothing } } } return config; } /** * Get output configuration with fallbacks */ static getOutputConfig(config) { const output = config.output || {}; const defaults = this.DEFAULT_CONFIG.output; return { defaultFormat: output.defaultFormat || defaults.defaultFormat, defaultOutputDir: output.defaultOutputDir || defaults.defaultOutputDir, fileNaming: { pattern: output.fileNaming?.pattern || defaults.fileNaming.pattern, includeTimestamp: output.fileNaming?.includeTimestamp ?? defaults.fileNaming.includeTimestamp }, html: { theme: output.html?.theme || defaults.html.theme, includeCharts: output.html?.includeCharts ?? defaults.html.includeCharts, customCss: output.html?.customCss || defaults.html.customCss || '', customJs: output.html?.customJs || defaults.html.customJs || '', title: output.html?.title || defaults.html.title, footer: output.html?.footer || defaults.html.footer, openInBrowser: output.html?.openInBrowser ?? defaults.html.openInBrowser } }; } /** * Get analysis configuration with fallbacks */ static getAnalysisConfig(config) { const analysis = config.analysis || {}; const defaults = this.DEFAULT_CONFIG.analysis; return { costThresholds: { low: analysis.costThresholds?.low || defaults.costThresholds.low, medium: analysis.costThresholds?.medium || defaults.costThresholds.medium, high: analysis.costThresholds?.high || defaults.costThresholds.high }, recommendations: { enabled: analysis.recommendations?.enabled ?? defaults.recommendations.enabled, customRules: analysis.recommendations?.customRules || defaults.recommendations.customRules }, parallel: { detectParallelOperations: analysis.parallel?.detectParallelOperations ?? defaults.parallel.detectParallelOperations, parallelThreshold: analysis.parallel?.parallelThreshold || defaults.parallel.parallelThreshold }, methodFilters: { minCost: analysis.methodFilters?.minCost ?? defaults.methodFilters.minCost, maxCost: analysis.methodFilters?.maxCost || defaults.methodFilters.maxCost, excludePatterns: analysis.methodFilters?.excludePatterns || defaults.methodFilters.excludePatterns } }; } /** * Get paths configuration with fallbacks */ static getPathsConfig(config) { const paths = config.paths || {}; const defaults = this.DEFAULT_CONFIG.paths; return { configFile: paths.configFile || defaults.configFile, outputDir: paths.outputDir || defaults.outputDir, tempDir: paths.tempDir || defaults.tempDir, cacheDir: paths.cacheDir || defaults.cacheDir, excludePatterns: paths.excludePatterns || defaults.excludePatterns, includePatterns: paths.includePatterns || defaults.includePatterns, watch: { debounceMs: paths.watch?.debounceMs || defaults.watch.debounceMs, excludePatterns: paths.watch?.excludePatterns || defaults.watch.excludePatterns } }; } /** * Get editor configuration with fallbacks */ static getEditorConfig(config) { const editor = config.editor || {}; const defaults = this.DEFAULT_CONFIG.editor; return { defaultEditor: editor.defaultEditor || defaults.defaultEditor, detectedEditor: editor.detectedEditor || defaults.detectedEditor || 'code', openInEditor: editor.openInEditor ?? defaults.openInEditor, editorCommands: editor.editorCommands || defaults.editorCommands, integration: { generateVSCodeTasks: editor.integration?.generateVSCodeTasks ?? defaults.integration.generateVSCodeTasks, generateEditorConfig: editor.integration?.generateEditorConfig ?? defaults.integration.generateEditorConfig } }; } } exports.ConfigLoader = ConfigLoader; //# sourceMappingURL=config-loader.js.map