UNPKG

ctrlshiftleft

Version:

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

533 lines (446 loc) 15.1 kB
#!/usr/bin/env node /** * NPM Package Preparation Script * * This script prepares the ctrl.shift.left package for NPM publication by: * 1. Creating a clean directory structure * 2. Copying necessary files * 3. Ensuring proper bin files are executable * 4. Generating API documentation * * Usage: * node ./scripts/prepare-npm-package.js */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // ANSI color codes for terminal output const COLORS = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', underscore: '\x1b[4m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m' }; // Project paths const ROOT_DIR = path.resolve(__dirname, '..'); const DIST_DIR = path.join(ROOT_DIR, 'dist'); const BIN_DIR = path.join(ROOT_DIR, 'bin'); const SRC_DIR = path.join(ROOT_DIR, 'src'); const DOCS_DIR = path.join(ROOT_DIR, 'docs'); const EXAMPLES_DIR = path.join(ROOT_DIR, 'examples'); const NPM_PACKAGE_DIR = path.join(ROOT_DIR, 'npm-package'); // Files to copy directly const FILES_TO_COPY = [ { source: 'README.md', dest: 'README.md' }, { source: 'LICENSE', dest: 'LICENSE' }, { source: 'package.json', dest: 'package.json' }, { source: '.npmignore', dest: '.npmignore' }, { source: 'docs/AI_SECURITY_GUIDE.md', dest: 'docs/AI_SECURITY_GUIDE.md' }, { source: 'docs/CURSOR_INTEGRATION.md', dest: 'docs/CURSOR_INTEGRATION.md' }, { source: 'examples/vscode-tasks.json', dest: 'examples/vscode-tasks.json' } ]; // Binary files to ensure are executable const BIN_FILES = [ 'ctrlshiftleft', 'ctrlshiftleft-ai', 'ctrlshiftleft-watch-ai', 'ctrlshiftleft-cursor' ]; // Log with colors function log(message, type = 'info') { const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); switch (type) { case 'success': console.log(`${COLORS.green}${message}${COLORS.reset}`); break; case 'error': console.log(`${COLORS.red}${timestamp} ERROR: ${message}${COLORS.reset}`); break; case 'warning': console.log(`${COLORS.yellow}${timestamp} WARNING: ${message}${COLORS.reset}`); break; case 'step': console.log(`\n${COLORS.cyan}${COLORS.bright}${message}${COLORS.reset}`); break; default: console.log(`${COLORS.dim}${timestamp}${COLORS.reset} ${message}`); } } // Create directory if it doesn't exist function ensureDir(dirPath) { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); log(`Created directory: ${dirPath}`, 'success'); } } // Copy file with logging function copyFile(source, dest) { try { fs.copyFileSync(source, dest); log(`Copied ${source} to ${dest}`, 'success'); } catch (error) { log(`Failed to copy ${source}: ${error.message}`, 'error'); throw error; } } // Copy directory recursively function copyDirRecursive(source, dest) { ensureDir(dest); const entries = fs.readdirSync(source, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(source, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { copyDirRecursive(srcPath, destPath); } else { copyFile(srcPath, destPath); } } } // Run a shell command function runCommand(command, cwd = ROOT_DIR) { try { log(`Running: ${command}`); return execSync(command, { cwd, stdio: 'inherit' }); } catch (error) { log(`Command failed: ${command}`, 'error'); throw error; } } // Make a file executable function makeExecutable(filePath) { try { fs.chmodSync(filePath, '755'); log(`Made ${filePath} executable`, 'success'); } catch (error) { log(`Failed to make ${filePath} executable: ${error.message}`, 'error'); throw error; } } // Clean the distribution directory function cleanDist() { log('Cleaning distribution directory', 'step'); if (fs.existsSync(NPM_PACKAGE_DIR)) { log(`Removing existing npm-package directory: ${NPM_PACKAGE_DIR}`); fs.rmSync(NPM_PACKAGE_DIR, { recursive: true, force: true }); } ensureDir(NPM_PACKAGE_DIR); ensureDir(path.join(NPM_PACKAGE_DIR, 'bin')); ensureDir(path.join(NPM_PACKAGE_DIR, 'dist')); ensureDir(path.join(NPM_PACKAGE_DIR, 'docs')); ensureDir(path.join(NPM_PACKAGE_DIR, 'examples')); } // Create bin files function prepareBinFiles() { log('Preparing binary files', 'step'); // Copy bin files const binFiles = fs.readdirSync(BIN_DIR); for (const file of binFiles) { const source = path.join(BIN_DIR, file); const dest = path.join(NPM_PACKAGE_DIR, 'bin', file); copyFile(source, dest); makeExecutable(dest); } } // Build the TypeScript source function buildTypeScript() { log('Building TypeScript source', 'step'); // First run the TypeScript compiler runCommand('npm run build'); // Copy the dist files copyDirRecursive(DIST_DIR, path.join(NPM_PACKAGE_DIR, 'dist')); // Copy TypeScript declaration files log('Copying TypeScript declaration files'); // AI security analyzer declaration file const aiSecurityDtsSource = path.join(SRC_DIR, 'ai-security-analyzer.d.ts'); const aiSecurityDtsDest = path.join(NPM_PACKAGE_DIR, 'dist', 'ai-security-analyzer.d.ts'); if (fs.existsSync(aiSecurityDtsSource)) { copyFile(aiSecurityDtsSource, aiSecurityDtsDest); } else { log(`TypeScript declaration file not found: ${aiSecurityDtsSource}`, 'warning'); } } // Copy extra files function copyExtraFiles() { log('Copying extra files', 'step'); // Copy AI security analyzer files log('Copying AI security analyzer files'); const aiSecuritySource = path.join(SRC_DIR, 'ai-security-analyzer.js'); const aiSecurityDest = path.join(NPM_PACKAGE_DIR, 'dist', 'ai-security-analyzer.js'); if (fs.existsSync(aiSecuritySource)) { copyFile(aiSecuritySource, aiSecurityDest); } else { log(`AI security analyzer not found: ${aiSecuritySource}`, 'warning'); } // Copy cursor integration files log('Copying cursor integration files'); const cursorIntegrationSource = path.join(ROOT_DIR, 'cursor-integration'); const cursorIntegrationDest = path.join(NPM_PACKAGE_DIR, 'cursor-integration'); if (fs.existsSync(cursorIntegrationSource)) { fs.mkdirSync(cursorIntegrationDest, { recursive: true }); copyDirRecursive(cursorIntegrationSource, cursorIntegrationDest); } else { log(`Cursor integration directory not found: ${cursorIntegrationSource}`, 'warning'); } // Copy VS Code extension files log('Copying VS Code extension files'); const vscodeExtSource = path.join(ROOT_DIR, 'vscode-ext-test'); const vscodeExtDest = path.join(NPM_PACKAGE_DIR, 'vscode-ext-test'); if (fs.existsSync(vscodeExtSource)) { fs.mkdirSync(vscodeExtDest, { recursive: true }); copyDirRecursive(vscodeExtSource, vscodeExtDest); log('VS Code extension files copied successfully'); } else { log(`VS Code extension directory not found: ${vscodeExtSource}`, 'warning'); } // Copy root files for (const file of FILES_TO_COPY) { const source = path.join(ROOT_DIR, file.source); const dest = path.join(NPM_PACKAGE_DIR, file.dest); if (fs.existsSync(source)) { copyFile(source, dest); } else { log(`File not found: ${source}`, 'warning'); } } // Copy documentation log('Copying documentation'); copyDirRecursive(DOCS_DIR, path.join(NPM_PACKAGE_DIR, 'docs')); // Copy examples log('Copying examples'); copyDirRecursive(EXAMPLES_DIR, path.join(NPM_PACKAGE_DIR, 'examples')); } // Update package.json for publishing function updatePackageJson() { log('Updating package.json for publishing', 'step'); const packageJsonPath = path.join(NPM_PACKAGE_DIR, 'package.json'); if (!fs.existsSync(packageJsonPath)) { log(`package.json not found: ${packageJsonPath}`, 'error'); return; } // Read package.json const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Ensure main, types, and bin paths are correct packageJson.main = 'dist/api/index.js'; packageJson.types = 'dist/api/index.d.ts'; packageJson.bin = { 'ctrlshiftleft': 'bin/ctrlshiftleft', 'ctrlshiftleft-ai': 'bin/ctrlshiftleft-ai' }; // Clean up any private or development-specific fields delete packageJson.private; // Simplify scripts to only include what's needed after installation packageJson.scripts = { "start": "node bin/ctrlshiftleft", "ai": "node bin/ctrlshiftleft-ai" }; // Write updated package.json fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); log('Updated package.json', 'success'); } // Generate npm-shrinkwrap.json for exact dependency versions function generateShrinkwrap() { log('Generating npm-shrinkwrap.json', 'step'); // Change to the npm package directory process.chdir(NPM_PACKAGE_DIR); // Run npm install to create a package-lock.json runCommand('npm install --package-lock-only', NPM_PACKAGE_DIR); // Convert package-lock.json to npm-shrinkwrap.json runCommand('npm shrinkwrap', NPM_PACKAGE_DIR); // Change back to the root directory process.chdir(ROOT_DIR); log('Generated npm-shrinkwrap.json', 'success'); } // Create a .npmignore file if it doesn't exist function createNpmIgnore() { const npmIgnorePath = path.join(NPM_PACKAGE_DIR, '.npmignore'); if (!fs.existsSync(npmIgnorePath)) { log('Creating .npmignore', 'step'); const npmIgnoreContent = ` # Development files *.log *.tsbuildinfo .DS_Store .env .env.* .vscode .idea .git # Test files test/ tests/ __tests__/ *.test.js *.test.ts *.spec.js *.spec.ts # Build config tsconfig*.json rollup.config.js webpack.config.js babel.config.js .eslintrc* .prettierrc* # Source files (only distribute dist) src/ # CI/CD .github/ .gitlab/ .circleci/ .travis.yml # Documentation source docs/source/ `; fs.writeFileSync(npmIgnorePath, npmIgnoreContent.trim()); log('Created .npmignore', 'success'); } } // Build a dry-run package to test function testBuild() { log('Testing npm package build', 'step'); // Pack the package process.chdir(NPM_PACKAGE_DIR); runCommand('npm pack --dry-run', NPM_PACKAGE_DIR); process.chdir(ROOT_DIR); log('npm package dry-run completed successfully', 'success'); } // Test the enhanced watcher functionality function testEnhancedWatcherFunctionality() { log('Testing enhanced watcher functionality', 'step'); try { // Create a test script to verify the EnhancedWatcher works const testDir = path.join(NPM_PACKAGE_DIR, 'examples'); ensureDir(testDir); const testFile = path.join(testDir, 'ai-watcher-example.js'); const testContent = [ '#!/usr/bin/env node', '', '/**', ' * AI-Enhanced Watcher Example', ' * ', ' * This example demonstrates how to use the AI-enhanced watcher functionality', ' * from the ctrl.shift.left API to monitor your codebase in real-time.', ' */', '', 'const { EnhancedWatcher } = require(\'../dist/api\');', '', '// Create an EnhancedWatcher instance with custom options', 'const watcher = new EnhancedWatcher({', ' // Include specific file patterns', ' include: ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"],', ' ', ' // Exclude test and build files', ' exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.*"],', ' ', ' // Use AI-enhanced security analysis if OPENAI_API_KEY is available', ' useAIAnalysis: true,', ' ', ' // Generate tests automatically', ' generateTests: true,', ' ', ' // Output directories', ' testOutputDir: "./generated-tests",', ' securityOutputDir: "./security-reports",', ' checklistOutputDir: "./qa-checklists",', ' ', ' // Maximum concurrent tasks', ' maxConcurrentTasks: 2', '});', '', '// Check if AI security analysis is available', 'console.log("AI Security Analysis Available:", watcher.isAIAvailable());', '', '// Log event handler setup', 'watcher.on("ready", () => {', ' console.log("Watcher is ready and monitoring for file changes");', '});', '', 'watcher.on("file:change", (event) => {', ' console.log(`File ${event.type}: ${event.path}`);', '});', '', 'watcher.on("analysis:complete", (result) => {', ' if (result.success) {', ' console.log(`Analysis completed for ${result.filePath}`);', ' if (result.outputPath) {', ' console.log(`Output saved to: ${result.outputPath}`);', ' }', ' } else {', ' console.error(`Analysis failed for ${result.filePath}: ${result.error}`);', ' }', '});', '', '// Start watching the current directory', 'const stopWatching = watcher.watch(process.cwd());', '', 'console.log("Watcher started. Press Ctrl+C to stop.");', '', '// Handle process exit', 'process.on("SIGINT", async () => {', ' console.log("\nStopping watcher...");', ' await watcher.stop();', ' console.log("Watcher stopped.");', ' process.exit(0);', '});' ].join('\n'); fs.writeFileSync(testFile, testContent); makeExecutable(testFile); log('Created AI-enhanced watcher example file', 'success'); } catch (error) { log(`Testing enhanced watcher failed: ${error.message}`, 'warning'); console.error(error); } } // Main function async function main() { log('Starting NPM package preparation', 'step'); try { // Clean dist directory cleanDist(); // Build TypeScript source buildTypeScript(); // Prepare binary files prepareBinFiles(); // Copy extra files copyExtraFiles(); // Update package.json updatePackageJson(); // Create .npmignore createNpmIgnore(); // Generate npm-shrinkwrap.json generateShrinkwrap(); // Test enhanced watcher functionality testEnhancedWatcherFunctionality(); // Test build testBuild(); log(` ${COLORS.green}${COLORS.bright}NPM package preparation completed successfully!${COLORS.reset} ${COLORS.bright}Package location:${COLORS.reset} ${COLORS.cyan}${NPM_PACKAGE_DIR}${COLORS.reset} To publish the package: ${COLORS.yellow}cd ${NPM_PACKAGE_DIR} npm publish${COLORS.reset} To test the package locally: ${COLORS.yellow}cd ${NPM_PACKAGE_DIR} npm link cd /path/to/your/project npm link ctrlshiftleft${COLORS.reset} `); } catch (error) { log(`NPM package preparation failed: ${error.message}`, 'error'); console.error(error); process.exit(1); } } // Run the script main().catch(error => { log(`Unhandled error: ${error.message}`, 'error'); console.error(error); process.exit(1); });