UNPKG

ctrlshiftleft

Version:

AI-powered toolkit for embedding QA and security testing into development workflows

329 lines 13.7 kB
"use strict"; /** * ctrl.shift.left API * * This module provides programmatic access to ctrl.shift.left functionality, * allowing other tools (like Cursor AI) to leverage test generation, security analysis, * and QA checklist creation capabilities. */ 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 __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedWatcher = exports.ChecklistGenerator = exports.TestRunner = exports.TestGenerator = void 0; exports.generateTests = generateTests; exports.analyzeSecurity = analyzeSecurity; exports.generateChecklist = generateChecklist; exports.watch = watch; exports.analyzeSecurityWithAI = analyzeSecurityWithAI; exports.isAISecurityAvailable = isAISecurityAvailable; exports.setAISecurityApiKey = setAISecurityApiKey; const path_1 = __importDefault(require("path")); const promises_1 = __importDefault(require("fs/promises")); const testGenerator_1 = require("../core/testGenerator"); Object.defineProperty(exports, "TestGenerator", { enumerable: true, get: function () { return testGenerator_1.TestGenerator; } }); const testRunner_1 = require("../core/testRunner"); Object.defineProperty(exports, "TestRunner", { enumerable: true, get: function () { return testRunner_1.TestRunner; } }); const checklistGenerator_1 = require("../core/checklistGenerator"); Object.defineProperty(exports, "ChecklistGenerator", { enumerable: true, get: function () { return checklistGenerator_1.ChecklistGenerator; } }); const enhancedWatcher_1 = require("../core/enhancedWatcher"); Object.defineProperty(exports, "EnhancedWatcher", { enumerable: true, get: function () { return enhancedWatcher_1.EnhancedWatcher; } }); const fileUtils_1 = require("../utils/fileUtils"); // Re-export types for consumers __exportStar(require("../types/testTypes"), exports); __exportStar(require("../types/checklistTypes"), exports); // Dynamic import for the AI security analyzer (which is CommonJS) let aiSecurityAnalyzer = null; // Attempt to load the AI security analyzer try { // We'll use a dynamic import since it's a CommonJS module const aiSecurityAnalyzerPath = '../ai-security-analyzer.js'; import(aiSecurityAnalyzerPath).then(module => { aiSecurityAnalyzer = module.default || module; }).catch(() => { console.warn('AI security analyzer could not be loaded. AI-enhanced security analysis will not be available.'); }); } catch (error) { console.warn('AI security analyzer could not be loaded. AI-enhanced security analysis will not be available.'); } /** * Generates end-to-end tests for a component or source file * * @param sourcePath - Path to the source file or directory * @param options - Test generation options * @returns Promise resolving to the test generation result */ async function generateTests(sourcePath, options = {}) { const startTime = Date.now(); // Set default options const outputDir = options.outputDir || './tests'; const format = options.format || 'playwright'; const timeout = options.timeout || 60; // Normalize paths const absoluteSourcePath = path_1.default.isAbsolute(sourcePath) ? sourcePath : path_1.default.resolve(process.cwd(), sourcePath); const absoluteOutputDir = path_1.default.isAbsolute(outputDir) ? outputDir : path_1.default.resolve(process.cwd(), outputDir); // Ensure the output directory exists await (0, fileUtils_1.ensureDirectoryExists)(absoluteOutputDir); // Create test generator with options const generator = new testGenerator_1.TestGenerator({ format, timeout }); // Generate tests const result = await generator.generateTests(absoluteSourcePath, absoluteOutputDir); // Calculate generation time const endTime = Date.now(); const generationTime = endTime - startTime; return { ...result, generationTime }; } /** * Analyzes code for security vulnerabilities * * @param sourcePath - Path to the source file to analyze * @param options - Security analysis options * @returns Promise resolving to the security analysis result */ async function analyzeSecurity(sourcePath, options = {}) { // Normalize path const absoluteSourcePath = path_1.default.isAbsolute(sourcePath) ? sourcePath : path_1.default.resolve(process.cwd(), sourcePath); // Set default output file if not provided const defaultOutputFile = path_1.default.join(process.cwd(), 'security-reports', `${path_1.default.basename(sourcePath, path_1.default.extname(sourcePath))}-security-report.md`); const outputFile = options.outputFile || defaultOutputFile; const absoluteOutputFile = path_1.default.isAbsolute(outputFile) ? outputFile : path_1.default.resolve(process.cwd(), outputFile); // Ensure the output directory exists await (0, fileUtils_1.ensureDirectoryExists)(path_1.default.dirname(absoluteOutputFile)); // Check if AI analysis is requested and available const useAI = options.useAI && aiSecurityAnalyzer != null; if (options.useAI && !aiSecurityAnalyzer) { console.warn('AI-enhanced security analysis requested but not available. Using pattern-based analysis instead.'); } let result; if (useAI && aiSecurityAnalyzer) { // Set API key if provided if (options.apiKey) { aiSecurityAnalyzer.setApiKey(options.apiKey); } // Run AI analysis const aiResult = await aiSecurityAnalyzer.analyzeWithAI(absoluteSourcePath, { format: options.format || 'markdown', output: absoluteOutputFile, model: options.model }); // Ensure issuesBySeverity is a Record<string, number> without undefined values const issuesBySeverity = {}; if (aiResult.issuesBySeverity) { Object.entries(aiResult.issuesBySeverity).forEach(([key, value]) => { if (value !== undefined) { issuesBySeverity[key] = value; } }); } result = { score: aiResult.score || 50, issuesFound: aiResult.issuesFound || 0, issuesBySeverity, reportPath: absoluteOutputFile, aiEnhanced: true }; } else { // Use the pattern-based analysis from the VS Code extension // This is a temporary solution until we rewrite analyze-security.js as a TypeScript module const analyzeSecurity = require('../utils/securityRiskUtils').analyzeSecurity; // Run pattern-based analysis const patterns = require('../utils/securityRiskUtils').SECURITY_PATTERNS; const issues = analyzeSecurity(absoluteSourcePath, patterns); // Group by severity const issuesBySeverity = {}; issues.forEach((issue) => { const severity = issue.severity || 'MEDIUM'; issuesBySeverity[severity] = (issuesBySeverity[severity] || 0) + 1; }); // Calculate security score (simple inverse proportion to number of issues) const score = Math.max(0, 100 - issues.length * 5); // Generate report const report = require('../utils/securityRiskUtils').generateSecurityReport(absoluteSourcePath, issues); // Write report to file await promises_1.default.writeFile(absoluteOutputFile, report); result = { score, issuesFound: issues.length, issuesBySeverity, reportPath: absoluteOutputFile, aiEnhanced: false }; } return result; } /** * Generates a QA and security checklist for a source file or component * * @param sourcePath - Path to the source file or directory * @param options - Checklist generation options * @returns Promise resolving to the checklist generation result */ async function generateChecklist(sourcePath, options = {}) { // Normalize path const absoluteSourcePath = path_1.default.isAbsolute(sourcePath) ? sourcePath : path_1.default.resolve(process.cwd(), sourcePath); // Default output file const componentName = path_1.default.basename(sourcePath, path_1.default.extname(sourcePath)); const defaultOutputFile = path_1.default.join(process.cwd(), 'checklists', `${componentName}-checklist.${options.format === 'json' ? 'json' : 'md'}`); const outputFile = options.outputFile || defaultOutputFile; const absoluteOutputFile = path_1.default.isAbsolute(outputFile) ? outputFile : path_1.default.resolve(process.cwd(), outputFile); // Create output directory if it doesn't exist await (0, fileUtils_1.ensureDirectoryExists)(path_1.default.dirname(absoluteOutputFile)); // Create checklist generator const generator = new checklistGenerator_1.ChecklistGenerator({ type: options.type || 'all', format: options.format || 'json' }); // Generate checklist const result = await generator.generateChecklist(absoluteSourcePath, path_1.default.dirname(absoluteOutputFile)); // Count passing, failing, and review items let passingCount = 0; let failingCount = 0; let reviewCount = 0; result.items.forEach(item => { // Convert item status to uppercase for comparison const status = String(item.status).toUpperCase(); if (status === 'PASS' || status === 'PASSED') passingCount++; else if (status === 'FAIL' || status === 'FAILED') failingCount++; else reviewCount++; }); return { itemCount: result.itemCount, filePath: absoluteOutputFile, componentName, categories: result.categories, passingCount, failingCount, reviewCount }; } /** * Watches for file changes and triggers actions like test generation or security analysis * * @param directory - Directory to watch for changes * @param options - Watch options * @returns Function to stop watching */ function watch(directory, options = {}) { // Import the watcher module const { Watcher } = require('../core/watcher'); // Normalize directory path const absoluteDir = path_1.default.isAbsolute(directory) ? directory : path_1.default.resolve(process.cwd(), directory); // Create watcher const watcher = new Watcher({ include: options.include || ['**/*.{js,jsx,ts,tsx,vue}'], exclude: options.exclude || ['**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**'] }); // Start watching const stopWatching = watcher.watch(absoluteDir, async (filePath) => { // Call user callback if provided if (options.onChange) { await Promise.resolve(options.onChange(filePath)); } // Generate tests if requested if (options.generateTests) { try { await generateTests(filePath); } catch (error) { console.error(`Error generating tests for ${filePath}:`, error); } } // Analyze security if requested if (options.analyzeSecurity) { try { await analyzeSecurity(filePath, { useAI: !!aiSecurityAnalyzer }); } catch (error) { console.error(`Error analyzing security for ${filePath}:`, error); } } // Generate checklist if requested if (options.generateChecklists) { try { await generateChecklist(filePath); } catch (error) { console.error(`Error generating checklist for ${filePath}:`, error); } } }); return stopWatching; } /** * Analyze file security using AI-enhanced analysis if available * @param filePath Path to the file to analyze * @param options Analysis options * @returns Analysis result */ async function analyzeSecurityWithAI(filePath, options = {}) { if (!aiSecurityAnalyzer) { throw new Error('AI security analyzer is not available. Make sure the OPENAI_API_KEY environment variable is set.'); } return aiSecurityAnalyzer.analyzeWithAI(filePath, options); } /** * Check if AI security analysis is available * @returns Whether AI security analysis is available */ function isAISecurityAvailable() { return aiSecurityAnalyzer !== null; } /** * Set the OpenAI API key for AI-enhanced security analysis * @param apiKey OpenAI API key */ function setAISecurityApiKey(apiKey) { if (aiSecurityAnalyzer) { aiSecurityAnalyzer.setApiKey(apiKey); } } // Export a single object with all functions for CommonJS compatibility exports.default = { generateTests, analyzeSecurity, generateChecklist, watch, analyzeSecurityWithAI, isAISecurityAvailable, setAISecurityApiKey }; //# sourceMappingURL=index.js.map