UNPKG

ctrlshiftleft

Version:

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

386 lines (318 loc) 16.9 kB
// Simplified VS Code Extension for ctrl.shift.left const vscode = require('vscode'); const path = require('path'); const fs = require('fs'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); /** * @param {vscode.ExtensionContext} context */ function activate(context) { console.log('ctrl.shift.left extension is now active'); // Create output channel for logs and results const outputChannel = vscode.window.createOutputChannel('ctrl.shift.left'); context.subscriptions.push(outputChannel); // Create status bar item to show current status const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); statusBarItem.text = '$(shield) CSL: Ready'; statusBarItem.tooltip = 'ctrl.shift.left: Click to analyze current file'; statusBarItem.command = 'ctrlshiftleft.analyzeCurrentFile'; statusBarItem.show(); context.subscriptions.push(statusBarItem); // Get path to CLI scripts const extensionPath = context.extensionPath; const rootPath = path.resolve(extensionPath, '..'); const toolsPath = path.join(rootPath, 'vscode-ext-test'); const cliPath = path.join(rootPath, 'bin', 'ctrlshiftleft'); // Helper function to ensure directories exist const ensureDirectoryExists = (dirPath) => { try { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); outputChannel.appendLine(`Created directory: ${dirPath}`); } } catch (error) { outputChannel.appendLine(`Error creating directory ${dirPath}: ${error.message}`); } }; // Helper function to run CLI tool directly const runToolDirectly = async (scriptPath, args) => { try { // Ensure the script exists if (!fs.existsSync(scriptPath)) { outputChannel.appendLine(`Error: Script not found: ${scriptPath}`); return false; } // Set the proper working directory to project root const workingDir = rootPath; outputChannel.appendLine(`Working directory: ${workingDir}`); // Make sure all required directories exist ensureDirectoryExists(path.join(rootPath, 'tests')); ensureDirectoryExists(path.join(rootPath, 'security-reports')); ensureDirectoryExists(path.join(rootPath, 'reports')); ensureDirectoryExists(path.join(toolsPath, 'generated-tests')); // Execute the script directly with node const cmd = `node "${scriptPath}" "${args}"`; outputChannel.appendLine(`> Running: ${cmd}`); const options = { cwd: workingDir }; outputChannel.appendLine(`Execution options: ${JSON.stringify(options)}`); const { stdout, stderr } = await execAsync(cmd, options); // Show output if (stdout) outputChannel.appendLine(stdout); if (stderr) outputChannel.appendLine(stderr); return true; } catch (error) { outputChannel.appendLine(`Error executing script: ${error.message}`); outputChannel.appendLine(error.stack); return false; } }; // Helper function to run CLI commands (tries CLI first, falls back to direct script execution) const runCliCommand = async (command, args) => { try { statusBarItem.text = '$(sync~spin) CSL: Running...'; // Create required directories - create ALL potential directories to avoid issues const testsDir = path.join(rootPath, 'tests'); const outputDir = path.join(toolsPath, 'generated-tests'); const securityReportsDir = path.join(rootPath, 'security-reports'); const reportsDir = path.join(rootPath, 'reports'); const checklistsDir = path.join(rootPath, 'checklists'); const localSecReportsDir = './security-reports'; const localTestsDir = './tests'; // Create all directories that might be needed [testsDir, outputDir, securityReportsDir, reportsDir, checklistsDir].forEach(dir => { ensureDirectoryExists(dir); }); // Create local directories relative to the active file if we have one const activeEditor = vscode.window.activeTextEditor; if (activeEditor) { const activeFileDir = path.dirname(activeEditor.document.uri.fsPath); ensureDirectoryExists(path.join(activeFileDir, 'security-reports')); ensureDirectoryExists(path.join(activeFileDir, 'tests')); ensureDirectoryExists(path.join(activeFileDir, 'checklists')); } // Set proper working directory (project root) const workingDir = rootPath; outputChannel.appendLine(`Working directory: ${workingDir}`); // First try the CLI command const cmd = `"${cliPath}" ${command} "${args}"`; outputChannel.appendLine(`> Running: ${cmd}`); try { // Execute the command with specific working directory const options = { cwd: workingDir }; outputChannel.appendLine(`CLI execution options: ${JSON.stringify(options)}`); const { stdout, stderr } = await execAsync(cmd, options); // Show output if (stdout) outputChannel.appendLine(stdout); if (stderr) outputChannel.appendLine(stderr); // Update status bar statusBarItem.text = '$(shield) CSL: Ready'; return true; } catch (cliError) { outputChannel.appendLine(`CLI execution error: ${cliError.message}`); if (cliError.stack) outputChannel.appendLine(cliError.stack); // If CLI fails, try running the script directly outputChannel.appendLine(`CLI execution failed: ${cliError.message}`); outputChannel.appendLine('Trying to run the tool script directly...'); // Map command to direct script let scriptPath; if (command === 'analyze') { scriptPath = path.join(toolsPath, 'analyze-security.js'); } else if (command === 'gen') { scriptPath = path.join(toolsPath, 'generate-tests.js'); } else if (command === 'checklist') { scriptPath = path.join(toolsPath, 'generate-checklist.js'); } else if (command === 'secure') { // For secure, we'll run each script in sequence outputChannel.appendLine('Running full scan with individual scripts...'); const analyzeScript = path.join(toolsPath, 'analyze-security.js'); const testGenScript = path.join(toolsPath, 'generate-tests.js'); const checklistScript = path.join(toolsPath, 'generate-checklist.js'); const analyzeSuccess = await runToolDirectly(analyzeScript, args); const testGenSuccess = await runToolDirectly(testGenScript, args); const checklistSuccess = await runToolDirectly(checklistScript, args); statusBarItem.text = '$(shield) CSL: Ready'; return analyzeSuccess || testGenSuccess || checklistSuccess; } if (scriptPath) { const success = await runToolDirectly(scriptPath, args); statusBarItem.text = '$(shield) CSL: Ready'; return success; } } // Update status bar statusBarItem.text = '$(shield) CSL: Ready'; return false; } catch (error) { outputChannel.appendLine(`Error: ${error.message}`); statusBarItem.text = '$(shield) CSL: Error'; return false; } }; // Register commands // 1. Analyze Current File const analyzeCommand = vscode.commands.registerCommand('ctrlshiftleft.analyzeCurrentFile', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showErrorMessage('No active file to analyze'); return; } const filePath = editor.document.uri.fsPath; outputChannel.clear(); outputChannel.show(); outputChannel.appendLine(`Analyzing file: ${filePath}`); // Run security analysis const success = await runCliCommand('analyze', filePath); if (success) { // Show a notification with results vscode.window.showInformationMessage('Security analysis complete. Check the output panel for results.'); // Try to open the security report try { const reportPath = path.join(toolsPath, 'security-report.md'); const reportUri = vscode.Uri.file(reportPath); await vscode.commands.executeCommand('markdown.showPreview', reportUri); } catch (error) { outputChannel.appendLine(`Error opening report: ${error.message}`); } } else { vscode.window.showErrorMessage('Security analysis failed. Check the output panel for details.'); } }); // 2. Generate Tests for Current File const generateTestsCommand = vscode.commands.registerCommand('ctrlshiftleft.generateTests', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showErrorMessage('No active file to generate tests for'); return; } const filePath = editor.document.uri.fsPath; outputChannel.clear(); outputChannel.show(); outputChannel.appendLine(`Generating tests for: ${filePath}`); // Run test generation const success = await runCliCommand('gen', filePath); if (success) { vscode.window.showInformationMessage('Test generation complete. Check the generated-tests directory.'); // Try to open the generated test file try { const fileName = path.basename(filePath, path.extname(filePath)); const testPath = path.join(toolsPath, 'generated-tests', `${fileName}.spec.ts`); const testUri = vscode.Uri.file(testPath); const document = await vscode.workspace.openTextDocument(testUri); await vscode.window.showTextDocument(document); } catch (error) { outputChannel.appendLine(`Error opening test file: ${error.message}`); } } else { vscode.window.showErrorMessage('Test generation failed. Check the output panel for details.'); } }); // 3. Generate QA Checklist for Current File const generateChecklistCommand = vscode.commands.registerCommand('ctrlshiftleft.generateChecklist', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showErrorMessage('No active file to generate checklist for'); return; } const filePath = editor.document.uri.fsPath; outputChannel.clear(); outputChannel.show(); outputChannel.appendLine(`Generating QA checklist for: ${filePath}`); // Run checklist generation const success = await runCliCommand('checklist', filePath); if (success) { vscode.window.showInformationMessage('QA checklist generation complete.'); // Try to open the checklist try { const checklistPath = path.join(toolsPath, 'checklist.md'); const checklistUri = vscode.Uri.file(checklistPath); await vscode.commands.executeCommand('markdown.showPreview', checklistUri); } catch (error) { outputChannel.appendLine(`Error opening checklist: ${error.message}`); } } else { vscode.window.showErrorMessage('QA checklist generation failed. Check the output panel for details.'); } }); // 4. Full Scan (analyze, generate tests, and checklist) const fullScanCommand = vscode.commands.registerCommand('ctrlshiftleft.fullScan', async () => { const editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showErrorMessage('No active file to scan'); return; } const filePath = editor.document.uri.fsPath; outputChannel.clear(); outputChannel.show(); outputChannel.appendLine(`Running full scan on: ${filePath}`); // Run the secure command which does all three const success = await runCliCommand('secure', filePath); if (success) { vscode.window.showInformationMessage('Full scan complete. Check the output panel and reports.'); // Try to open the security report try { const reportPath = path.join(toolsPath, 'security-report.md'); const reportUri = vscode.Uri.file(reportPath); await vscode.commands.executeCommand('markdown.showPreview', reportUri); } catch (error) { outputChannel.appendLine(`Error opening report: ${error.message}`); } } else { vscode.window.showErrorMessage('Full scan failed. Check the output panel for details.'); } }); // 5. Project Overview Dashboard const projectOverviewCommand = vscode.commands.registerCommand('ctrlshiftleft.projectOverview', async () => { outputChannel.clear(); outputChannel.show(); outputChannel.appendLine('Generating project overview dashboard...'); try { statusBarItem.text = '$(sync~spin) CSL: Generating...'; // Get the workspace folder const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { vscode.window.showErrorMessage('No workspace folder found'); statusBarItem.text = '$(shield) CSL: Ready'; return; } const workspacePath = workspaceFolders[0].uri.fsPath; const srcPath = path.join(workspacePath, 'src'); // Run the overview generator const overviewScript = path.join(toolsPath, 'generate-overview.js'); const cmd = `node "${overviewScript}" "${srcPath}"`; outputChannel.appendLine(`> Running: ${cmd}`); const { stdout, stderr } = await execAsync(cmd); // Show output if (stdout) outputChannel.appendLine(stdout); if (stderr) outputChannel.appendLine(stderr); // Update status bar statusBarItem.text = '$(shield) CSL: Ready'; // Try to open the overview dashboard const dashboardPath = path.join(toolsPath, 'project-overview.html'); const dashboardUri = vscode.Uri.file(dashboardPath); // Open in external browser vscode.env.openExternal(dashboardUri); vscode.window.showInformationMessage('Project overview dashboard generated and opened in browser.'); } catch (error) { outputChannel.appendLine(`Error: ${error.message}`); statusBarItem.text = '$(shield) CSL: Error'; vscode.window.showErrorMessage('Failed to generate project overview. Check the output panel for details.'); } }); // Register all commands context.subscriptions.push( analyzeCommand, generateTestsCommand, generateChecklistCommand, fullScanCommand, projectOverviewCommand ); // Show a welcome notification vscode.window.showInformationMessage('ctrl.shift.left is now active! Run security analysis on your files.'); } function deactivate() {} module.exports = { activate, deactivate };