ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
329 lines • 13.7 kB
JavaScript
;
/**
* 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