ctrlshiftleft
Version:
AI-powered toolkit for embedding QA and security testing into development workflows
386 lines (318 loc) • 16.9 kB
JavaScript
// 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
};