accs-cli
Version:
ACCS CLI — Full-featured developer tool for scaffolding, running, building, and managing multi-language projects
364 lines (309 loc) • 9.85 kB
JavaScript
/**
* Doctor command - System health check
*/
import chalk from 'chalk';
import boxen from 'boxen';
import { logger } from '../utils/logger.js';
import { SystemCheck } from '../utils/system-check.js';
import { configManager } from '../config/config-manager.js';
import { FileUtils } from '../utils/file-utils.js';
import path from 'path';
import semver from 'semver';
import os from 'os';
export function doctorCommand(program) {
program
.command('doctor')
.option('-f, --fix', 'Attempt to fix common issues')
.option('-v, --verbose', 'Show detailed information')
.description('Check system requirements and configuration')
.action(async (options) => {
try {
await runDoctor(options);
} catch (error) {
logger.error('Doctor check failed:', error.message);
process.exit(1);
}
});
}
async function runDoctor(options) {
console.log(boxen(chalk.blue.bold('🏥 ACCS System Doctor'), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue'
}));
const issues = [];
// System requirements check
const systemChecks = await SystemCheck.runFullCheck();
const systemIssues = analyzeSystemChecks(systemChecks, options);
issues.push(...systemIssues);
// Configuration check
const configIssues = await checkConfiguration(options);
issues.push(...configIssues);
// Project structure check
const projectIssues = await checkProjectStructure(options);
issues.push(...projectIssues);
// Environment check
const envIssues = await checkEnvironment(options);
issues.push(...envIssues);
// Display results
logger.separator();
displaySummary(issues, options);
// Attempt fixes if requested
if (options.fix && issues.length > 0) {
await attemptFixes(issues, options);
}
// Exit with appropriate code
const criticalIssues = issues.filter(i => i.severity === 'error');
if (criticalIssues.length > 0) {
process.exit(1);
}
}
function analyzeSystemChecks(checks) {
SystemCheck.displayResults(checks);
const issues = [];
Object.entries(checks).forEach(([tool, result]) => {
if (!result.installed) {
issues.push({
type: 'system',
severity: tool === 'node' ? 'error' : 'warning',
title: `${tool} is not installed`,
description: `${tool} is required for certain features to work`,
fix: `Install ${tool} from the official website`,
tool
});
} else if (!result.valid) {
issues.push({
type: 'system',
severity: 'warning',
title: `${tool} version may be incompatible`,
description: `Version ${result.version} may not work correctly`,
fix: `Consider updating ${tool}`,
tool
});
}
});
return issues;
}
async function checkConfiguration(options) {
logger.section('Configuration Check');
const issues = [];
try {
configManager.validate();
logger.success('Configuration is valid');
if (options.verbose) {
logger.info(`Config file: ${chalk.cyan(configManager.getConfigPath())}`);
const config = configManager.getAll();
Object.entries(config).forEach(([key, value]) => {
logger.info(` ${key}: ${chalk.yellow(JSON.stringify(value))}`);
});
}
} catch (error) {
issues.push({
type: 'config',
severity: 'error',
title: 'Invalid configuration',
description: error.message,
fix: 'Run: accs config --reset'
});
logger.error('Configuration is invalid:', error.message);
}
return issues;
}
async function checkProjectStructure(options) {
logger.section('Project Structure Check');
const issues = [];
const projectRoot = FileUtils.getProjectRoot();
// Check for package.json
const packageJsonPath = path.join(projectRoot, 'package.json');
if (FileUtils.exists(packageJsonPath)) {
try {
const packageJson = await FileUtils.readJson(packageJsonPath);
logger.success('package.json found and valid');
if (options.verbose) {
logger.info(`Name: ${packageJson.name || 'unnamed'}`);
logger.info(`Version: ${packageJson.version || 'unversioned'}`);
}
// Check for accs scripts
if (packageJson.scripts) {
const accsScripts = Object.entries(packageJson.scripts)
.filter(([, script]) => script.includes('accs'));
if (accsScripts.length > 0) {
logger.success(`Found ${accsScripts.length} ACCS script(s)`);
}
}
} catch (error) {
issues.push({
type: 'project',
severity: 'warning',
title: 'Invalid package.json',
description: error.message,
fix: 'Fix JSON syntax errors'
});
}
} else {
issues.push({
type: 'project',
severity: 'info',
title: 'No package.json found',
description: 'This might not be a Node.js project',
fix: 'Run: npm init or accs init'
});
}
// Check for common directories
const commonDirs = ['src', 'dist', 'public', 'assets'];
const existingDirs = commonDirs.filter(dir =>
FileUtils.exists(path.join(projectRoot, dir))
);
if (existingDirs.length > 0) {
logger.success(`Found directories: ${existingDirs.join(', ')}`);
} else {
logger.info('No common project directories found');
}
return issues;
}
async function checkEnvironment() {
logger.section('Environment Check');
const issues = [];
// Check Node.js version
const nodeVersion = process.version;
const requiredNode = '18.0.0';
if (semver.gte(nodeVersion, requiredNode)) {
logger.success(`Node.js ${nodeVersion} (✓ Compatible)`);
} else {
issues.push({
type: 'environment',
severity: 'error',
title: 'Node.js version too old',
description: `Current: ${nodeVersion}, Required: ${requiredNode}+`,
fix: 'Update Node.js to the latest LTS version'
});
}
// Check memory
const memoryUsage = process.memoryUsage();
const totalMemoryMB = Math.round(memoryUsage.rss / 1024 / 1024);
logger.info(`Memory usage: ${totalMemoryMB} MB`);
if (totalMemoryMB > 500) {
issues.push({
type: 'environment',
severity: 'warning',
title: 'High memory usage',
description: `Current usage: ${totalMemoryMB} MB`,
fix: 'Consider restarting or checking for memory leaks'
});
}
// Check permissions
const tempPath = path.join(os.tmpdir(), 'accs-test-' + Date.now());
try {
await FileUtils.createDir(tempPath);
await FileUtils.remove(tempPath);
logger.success('File system permissions OK');
} catch (error) {
issues.push({
type: 'environment',
severity: 'error',
title: 'File system permission issues',
description: error.message,
fix: 'Check file/directory permissions'
});
}
return issues;
}
function displaySummary(issues, options) {
logger.section('Summary');
if (issues.length === 0) {
const message = chalk.green('✨ All checks passed! Your system is ready to use ACCS.');
console.log(boxen(message, {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green'
}));
return;
}
// Group issues by severity
const errors = issues.filter(i => i.severity === 'error');
const warnings = issues.filter(i => i.severity === 'warning');
const info = issues.filter(i => i.severity === 'info');
if (errors.length > 0) {
logger.error(`${errors.length} critical issue(s) found:`);
errors.forEach(issue => displayIssue(issue, options));
}
if (warnings.length > 0) {
logger.warn(`${warnings.length} warning(s) found:`);
warnings.forEach(issue => displayIssue(issue, options));
}
if (info.length > 0 && options.verbose) {
logger.info(`${info.length} informational item(s):`);
info.forEach(issue => displayIssue(issue, options));
}
// Show suggestions
logger.separator();
if (errors.length > 0) {
logger.error('Please fix critical issues before using ACCS');
} else if (warnings.length > 0) {
logger.warn('Consider fixing warnings for better experience');
}
logger.info(`Run ${chalk.cyan('accs doctor --fix')} to attempt automatic fixes`);
}
function displayIssue(issue, options) {
const icon = {
error: '✖',
warning: '⚠',
info: 'ℹ'
}[issue.severity];
const color = {
error: 'red',
warning: 'yellow',
info: 'blue'
}[issue.severity];
console.log(` ${chalk[color](icon)} ${issue.title}`);
if (options.verbose) {
console.log(` ${chalk.gray(issue.description)}`);
if (issue.fix) {
console.log(` ${chalk.cyan('Fix:')} ${issue.fix}`);
}
}
}
async function attemptFixes(issues, options) {
logger.section('Attempting Fixes');
let fixedCount = 0;
for (const issue of issues) {
try {
const fixed = await attemptFix(issue, options);
if (fixed) {
logger.success(`Fixed: ${issue.title}`);
fixedCount++;
}
} catch (error) {
logger.error(`Failed to fix "${issue.title}": ${error.message}`);
}
}
logger.separator();
if (fixedCount > 0) {
logger.success(`Fixed ${fixedCount} issue(s)`);
logger.info('Run doctor again to verify fixes');
} else {
logger.warn('No issues could be automatically fixed');
logger.info('Please address the issues manually');
}
}
async function attemptFix(issue) {
switch (issue.type) {
case 'config':
if (issue.title.includes('Invalid configuration')) {
configManager.reset();
return true;
}
break;
case 'project':
if (issue.title.includes('No package.json')) {
// Could create a basic package.json, but this might not be desired
return false;
}
break;
default:
return false;
}
return false;
}