UNPKG

@deriv-com/shiftai-cli

Version:

A comprehensive AI code detection and analysis CLI tool for tracking AI-generated code in projects

372 lines (317 loc) 12.1 kB
const fs = require('fs-extra'); const path = require('path'); const { execSync } = require('child_process'); const chalk = require('chalk'); const display = require('../utils/display'); const AIStorage = require('../core/ai-storage'); /** * Status Command - Shows ShiftAI installation and configuration status */ async function statusCommand(options = {}) { try { display.clearAndShowHeader('ShiftAI Status', 'Current installation and configuration status'); const cwd = process.cwd(); const checks = await runStatusChecks(cwd, options.verbose); // Display status sections console.log(display.statusList('Installation Status', checks.installation)); console.log(); console.log(display.statusList('Configuration Status', checks.configuration)); console.log(); console.log(display.statusList('Git Hooks Status', checks.hooks)); // Show detailed information if verbose if (options.verbose) { if (checks.config) { console.log(display.summary('Configuration Details', { 'Organization': checks.config.organization || 'Not set', 'Local Storage': 'Enabled', 'Strict Mode': checks.config.detection?.strictMode || false })); } if (checks.environment) { console.log(); console.log(display.statusList('Environment Variables', checks.environment)); } if (checks.analytics) { console.log(display.summary('Quick Analytics', checks.analytics)); } } // Show recommendations const recommendations = generateRecommendations(checks); if (recommendations.length > 0) { console.log(); console.log(chalk.bold.blue('💡 Recommendations:')); recommendations.forEach((rec, i) => { console.log(chalk.gray(` ${i + 1}. ${rec}`)); }); } } catch (error) { console.log(display.error('Status check failed', error.message)); process.exit(1); } } /** * Run comprehensive status checks */ async function runStatusChecks(cwd, verbose = false) { const checks = { installation: [], configuration: [], hooks: [], environment: [], config: null, analytics: null }; // Check installation status await checkInstallation(cwd, checks); // Check configuration await checkConfiguration(cwd, checks); // Check git hooks await checkGitHooks(cwd, checks); // Check environment variables if (verbose) { await checkEnvironment(cwd, checks); await checkAnalytics(cwd, checks); } return checks; } /** * Check installation status */ async function checkInstallation(cwd, checks) { // Check if in git repository try { execSync('git rev-parse --git-dir', { cwd, stdio: 'pipe' }); checks.installation.push({ label: 'Git Repository', status: 'success' }); } catch (error) { checks.installation.push({ label: 'Git Repository', status: 'error', value: 'Not found' }); } // Check package.json const packageJsonPath = path.join(cwd, 'package.json'); if (await fs.pathExists(packageJsonPath)) { checks.installation.push({ label: 'package.json', status: 'success' }); try { const packageJson = await fs.readJson(packageJsonPath); // Check for husky const hasHusky = (packageJson.devDependencies && packageJson.devDependencies.husky) || (packageJson.dependencies && packageJson.dependencies.husky); if (hasHusky) { checks.installation.push({ label: 'Husky', status: 'success', value: 'Installed' }); } else { checks.installation.push({ label: 'Husky', status: 'warning', value: 'Not installed' }); } // Check prepare script if (packageJson.scripts && packageJson.scripts.prepare) { checks.installation.push({ label: 'Prepare Script', status: 'success' }); } else { checks.installation.push({ label: 'Prepare Script', status: 'warning', value: 'Missing' }); } } catch (error) { checks.installation.push({ label: 'package.json', status: 'error', value: 'Invalid JSON' }); } } else { checks.installation.push({ label: 'package.json', status: 'warning', value: 'Not found' }); } // Check Node.js version try { const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim(); const major = parseInt(nodeVersion.slice(1).split('.')[0]); if (major >= 14) { checks.installation.push({ label: 'Node.js', status: 'success', value: nodeVersion }); } else { checks.installation.push({ label: 'Node.js', status: 'warning', value: `${nodeVersion} (requires >=14)` }); } } catch (error) { checks.installation.push({ label: 'Node.js', status: 'error', value: 'Not found' }); } } /** * Check configuration status */ async function checkConfiguration(cwd, checks) { // Check ShiftAI config file const configPath = AIStorage.getConfigPath(); if (await fs.pathExists(configPath)) { checks.configuration.push({ label: 'ShiftAI Config', status: 'success', value: 'Found' }); try { delete require.cache[require.resolve(configPath)]; const config = require(configPath); checks.config = config; // Validate config structure if (config.organization) { checks.configuration.push({ label: 'Organization', status: 'success', value: config.organization }); } else { checks.configuration.push({ label: 'Organization', status: 'warning', value: 'Not set' }); } } catch (error) { checks.configuration.push({ label: 'Config Parsing', status: 'error', value: 'Invalid format' }); } } else { checks.configuration.push({ label: 'ShiftAI Config', status: 'error', value: 'Not found' }); } // Check .env file const envPath = path.join(cwd, '.env'); if (await fs.pathExists(envPath)) { checks.configuration.push({ label: 'Environment File', status: 'success', value: 'Found' }); } else { checks.configuration.push({ label: 'Environment File', status: 'warning', value: 'Not found' }); } // Note: .env.example check removed - no longer created during installation } /** * Check git hooks status */ async function checkGitHooks(cwd, checks) { // Check .husky directory const huskyDir = path.join(cwd, '.husky'); if (await fs.pathExists(huskyDir)) { checks.hooks.push({ label: 'Husky Directory', status: 'success' }); // Check pre-commit hook const preCommitPath = path.join(huskyDir, 'pre-commit'); if (await fs.pathExists(preCommitPath)) { checks.hooks.push({ label: 'Pre-commit Hook', status: 'success', value: 'Installed' }); try { const hookContent = await fs.readFile(preCommitPath, 'utf8'); if (hookContent.includes('shiftai-cli hook pre-commit')) { checks.hooks.push({ label: 'ShiftAI Hook', status: 'success', value: 'Configured' }); } else { checks.hooks.push({ label: 'ShiftAI Hook', status: 'warning', value: 'Not configured' }); } // Check if executable const stats = await fs.stat(preCommitPath); if (stats.mode & parseInt('111', 8)) { checks.hooks.push({ label: 'Hook Permissions', status: 'success' }); } else { checks.hooks.push({ label: 'Hook Permissions', status: 'warning', value: 'Not executable' }); } } catch (error) { checks.hooks.push({ label: 'Hook Analysis', status: 'error', value: 'Cannot read hook' }); } } else { checks.hooks.push({ label: 'Pre-commit Hook', status: 'error', value: 'Not found' }); } } else { checks.hooks.push({ label: 'Husky Directory', status: 'error', value: 'Not found' }); } // Check git hooks directory const gitHooksDir = path.join(cwd, '.git', 'hooks'); if (await fs.pathExists(gitHooksDir)) { checks.hooks.push({ label: 'Git Hooks Directory', status: 'success' }); } else { checks.hooks.push({ label: 'Git Hooks Directory', status: 'error', value: 'Not found' }); } } /** * Check environment variables */ async function checkEnvironment(cwd, checks) { require('dotenv').config({ path: path.join(cwd, '.env') }); const envVars = [ 'SHIFTAI_ORG_NAME', 'SHIFTAI_REPO_NAME', 'SHIFTAI_SKIP_SAVE', 'SHIFTAI_STRICT_MODE' ]; for (const envVar of envVars) { const value = process.env[envVar]; if (value) { checks.environment.push({ label: envVar, status: 'success', value: envVar.includes('TOKEN') ? '***hidden***' : value }); } else { checks.environment.push({ label: envVar, status: 'warning', value: 'Not set' }); } } } /** * Check analytics and usage */ async function checkAnalytics(cwd, checks) { try { // Simple file scan for AI markers - scan all text-like files const AIDetector = require('../core/ai-detector'); const detector = new AIDetector(); const glob = require('glob'); // Scan common text files (excluding binary extensions) const patterns = [ '**/*', // All files '!**/*.{png,jpg,jpeg,gif,svg,ico,bmp,tiff,webp}', // Exclude images '!**/*.{mp3,mp4,avi,mov,wmv,flv,webm,ogg,wav}', // Exclude media '!**/*.{zip,rar,tar,gz,7z,pdf,doc,docx,xls,xlsx}', // Exclude archives/docs '!**/*.{exe,dll,so,dylib,bin}', // Exclude binaries '!**/*.json', // Exclude JSON files (data/config files) '!**/node_modules/**', '!**/.git/**', '!**/dist/**', '!**/build/**', '!**/.next/**', '!**/coverage/**', '!**/yarn.lock', // Exclude yarn lock files '!**/pnpm-lock.yaml' // Exclude pnpm lock files ]; const files = glob.sync(patterns, { cwd, dot: true // Include dotfiles }); let totalFiles = 0; let filesWithAI = 0; let totalBlocks = 0; for (const file of files.slice(0, 200)) { // Limit for performance const filePath = path.join(cwd, file); try { const result = await detector.scanFile(filePath); if (result.supported) { totalFiles++; if (result.hasAICode) { filesWithAI++; totalBlocks += result.blocks.length; } } } catch (error) { // Skip files with errors } } checks.analytics = { totalFiles: totalFiles, filesWithAI: filesWithAI, totalBlocks: totalBlocks, percentage: totalFiles > 0 ? Math.round((filesWithAI / totalFiles) * 100) : 0 }; } catch (error) { // Analytics is optional checks.analytics = null; } } /** * Generate recommendations based on status */ function generateRecommendations(checks) { const recommendations = []; // Check for missing configuration if (checks.configuration.some(c => c.label === 'ShiftAI Config' && c.status === 'error')) { recommendations.push('Run "shai install" to set up ShiftAI configuration'); } // Check for missing environment file if (checks.configuration.some(c => c.label === 'Environment File' && c.status === 'warning')) { recommendations.push('Create a .env file to customize organization and repository settings (optional)'); } // Check for missing hooks if (checks.hooks.some(c => c.label === 'Pre-commit Hook' && c.status === 'error')) { recommendations.push('Run "shai install" to set up git hooks'); } // Check for permission issues if (checks.hooks.some(c => c.label === 'Hook Permissions' && c.status === 'warning')) { recommendations.push('Fix hook permissions with: chmod +x .husky/pre-commit'); } // Check for missing dependencies if (checks.installation.some(c => c.label === 'Husky' && c.status === 'warning')) { recommendations.push('Install husky with: npm install --save-dev husky'); } // Check Node.js version if (checks.installation.some(c => c.label === 'Node.js' && c.status === 'warning')) { recommendations.push('Update Node.js to version 14 or higher'); } return recommendations; } module.exports = statusCommand;