figma-restoration-mcp-vue-tools
Version:
Professional Figma Component Restoration Kit - MCP tools with snapDOM-powered high-quality screenshots, intelligent shadow detection, and advanced diff analysis for Vue component restoration. Features enhanced figma_compare with color-coded region analysi
529 lines (453 loc) • 13.6 kB
JavaScript
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { execSync } from 'child_process';
import chalk from 'chalk';
/**
* Status manager for auto-start functionality
* Handles startup status tracking, process management, and duplicate detection
*/
class StatusManager {
constructor(projectPath = process.cwd()) {
this.projectPath = projectPath;
this.statusFile = join(projectPath, '.vscode', 'auto-start-status.json');
this.lockFile = join(projectPath, '.vscode', 'auto-start.lock');
this._status = null;
}
/**
* Get default status structure
*/
getDefaultStatus() {
return {
isRunning: false,
pid: null,
port: null,
startTime: null,
lastError: null,
retryCount: 0,
command: null,
version: '1.0.0'
};
}
/**
* Load status from file
*/
loadStatus() {
try {
if (existsSync(this.statusFile)) {
const content = readFileSync(this.statusFile, 'utf8');
const status = JSON.parse(content);
// Validate and migrate old status format if needed
return this.validateAndMigrateStatus(status);
}
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to load status file: ${error.message}`));
}
return this.getDefaultStatus();
}
/**
* Validate and migrate status format
*/
validateAndMigrateStatus(status) {
const defaultStatus = this.getDefaultStatus();
const migrated = { ...defaultStatus, ...status };
// Ensure required fields exist
if (!migrated.version) {
migrated.version = '1.0.0';
}
// Validate data types
if (typeof migrated.isRunning !== 'boolean') {
migrated.isRunning = false;
}
if (migrated.pid && typeof migrated.pid !== 'number') {
migrated.pid = parseInt(migrated.pid) || null;
}
if (migrated.port && typeof migrated.port !== 'number') {
migrated.port = parseInt(migrated.port) || null;
}
if (migrated.retryCount && typeof migrated.retryCount !== 'number') {
migrated.retryCount = parseInt(migrated.retryCount) || 0;
}
return migrated;
}
/**
* Save status to file
*/
saveStatus(status = null) {
const statusToSave = status || this._status || this.getDefaultStatus();
try {
// Ensure .vscode directory exists
const vscodeDir = dirname(this.statusFile);
if (!existsSync(vscodeDir)) {
execSync(`mkdir -p "${vscodeDir}"`);
}
const content = JSON.stringify(statusToSave, null, 2);
writeFileSync(this.statusFile, content, 'utf8');
this._status = statusToSave;
return true;
} catch (error) {
console.error(chalk.red(`Failed to save status: ${error.message}`));
return false;
}
}
/**
* Get current status
*/
getStatus() {
if (!this._status) {
this._status = this.loadStatus();
}
return { ...this._status };
}
/**
* Update status
*/
updateStatus(updates) {
const current = this.getStatus();
const updated = { ...current, ...updates };
// Always update timestamp when status changes
if (updates.isRunning !== undefined) {
updated.startTime = updates.isRunning ? new Date().toISOString() : current.startTime;
}
this._status = updated;
return this.saveStatus(updated);
}
/**
* Check if process is actually running
*/
isProcessRunning(pid) {
if (!pid) return false;
try {
// Send signal 0 to check if process exists
process.kill(pid, 0);
return true;
} catch (error) {
// Process doesn't exist or we don't have permission
return false;
}
}
/**
* Get process information
*/
getProcessInfo(pid) {
if (!pid) return null;
try {
const cmd = `ps -p ${pid} -o pid,ppid,command,etime,pcpu,pmem`;
const output = execSync(cmd, { encoding: 'utf8' });
const lines = output.trim().split('\n');
if (lines.length > 1) {
const processLine = lines[1].trim();
const parts = processLine.split(/\s+/);
return {
pid: parseInt(parts[0]),
ppid: parseInt(parts[1]),
command: parts.slice(2, -3).join(' '),
etime: parts[parts.length - 3],
cpu: parts[parts.length - 2],
memory: parts[parts.length - 1]
};
}
} catch (error) {
// Process not found or command failed
}
return null;
}
/**
* Validate current status against actual system state
*/
validateStatus() {
const status = this.getStatus();
const issues = [];
const fixes = {};
// Check if marked as running but process doesn't exist
if (status.isRunning && status.pid) {
if (!this.isProcessRunning(status.pid)) {
issues.push(`Process ${status.pid} is not running`);
fixes.isRunning = false;
fixes.pid = null;
fixes.lastError = 'Process terminated unexpectedly';
}
}
// Check if start time is too old (more than 1 hour ago)
if (status.isRunning && status.startTime) {
const startTime = new Date(status.startTime);
const now = new Date();
const hoursSinceStart = (now - startTime) / (1000 * 60 * 60);
if (hoursSinceStart > 1) {
issues.push(`Status shows running for ${hoursSinceStart.toFixed(1)} hours`);
// Don't auto-fix this, just warn
}
}
// Apply fixes if any issues found
if (Object.keys(fixes).length > 0) {
this.updateStatus(fixes);
}
return {
valid: issues.length === 0,
issues,
fixes: Object.keys(fixes)
};
}
/**
* Start tracking a new process
*/
startTracking(pid, port, command) {
const updates = {
isRunning: true,
pid: pid,
port: port,
command: command,
startTime: new Date().toISOString(),
lastError: null,
retryCount: 0
};
return this.updateStatus(updates);
}
/**
* Stop tracking current process
*/
stopTracking(error = null) {
const updates = {
isRunning: false,
pid: null,
lastError: error
};
return this.updateStatus(updates);
}
/**
* Increment retry count
*/
incrementRetry() {
const status = this.getStatus();
return this.updateStatus({
retryCount: (status.retryCount || 0) + 1
});
}
/**
* Reset retry count
*/
resetRetry() {
return this.updateStatus({ retryCount: 0 });
}
/**
* Check if another instance is already starting (lock mechanism)
*/
acquireLock() {
try {
if (existsSync(this.lockFile)) {
const lockContent = readFileSync(this.lockFile, 'utf8');
const lockData = JSON.parse(lockContent);
// Check if lock is stale (older than 2 minutes)
const lockTime = new Date(lockData.timestamp);
const now = new Date();
const minutesSinceLock = (now - lockTime) / (1000 * 60);
if (minutesSinceLock < 2) {
return {
acquired: false,
reason: 'Another instance is starting',
lockData
};
} else {
// Stale lock, remove it
this.releaseLock();
}
}
// Create new lock
const lockData = {
pid: process.pid,
timestamp: new Date().toISOString(),
command: process.argv.join(' ')
};
writeFileSync(this.lockFile, JSON.stringify(lockData, null, 2), 'utf8');
return {
acquired: true,
lockData
};
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to acquire lock: ${error.message}`));
return {
acquired: true, // Assume we can proceed if lock mechanism fails
error: error.message
};
}
}
/**
* Release the startup lock
*/
releaseLock() {
try {
if (existsSync(this.lockFile)) {
execSync(`rm -f "${this.lockFile}"`);
}
return true;
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to release lock: ${error.message}`));
return false;
}
}
/**
* Check if we should skip startup based on current status
*/
shouldSkipStartup() {
const status = this.getStatus();
const validation = this.validateStatus();
// Skip if already running and process is valid
if (status.isRunning && status.pid && this.isProcessRunning(status.pid)) {
return {
skip: true,
reason: 'Development server is already running',
pid: status.pid,
port: status.port
};
}
// Skip if started very recently (within 30 seconds)
if (status.startTime) {
const startTime = new Date(status.startTime);
const now = new Date();
const secondsSinceStart = (now - startTime) / 1000;
if (secondsSinceStart < 30) {
return {
skip: true,
reason: 'Development server was started recently',
secondsAgo: Math.round(secondsSinceStart)
};
}
}
// Skip if too many recent retries
if (status.retryCount && status.retryCount >= 3) {
return {
skip: true,
reason: 'Too many recent startup attempts',
retryCount: status.retryCount
};
}
return {
skip: false,
validation
};
}
/**
* Print current status
*/
printStatus() {
const status = this.getStatus();
const validation = this.validateStatus();
console.log(chalk.bold('\nAuto-Start Status:'));
console.log('━'.repeat(40));
// Running status
if (status.isRunning) {
console.log(chalk.green('✓') + ' Status: Running');
if (status.pid) {
const processInfo = this.getProcessInfo(status.pid);
if (processInfo) {
console.log(` PID: ${status.pid}`);
console.log(` Command: ${processInfo.command}`);
console.log(` Runtime: ${processInfo.etime}`);
console.log(` CPU: ${processInfo.cpu}%`);
console.log(` Memory: ${processInfo.memory}%`);
} else {
console.log(chalk.red(` PID: ${status.pid} (process not found)`));
}
}
if (status.port) {
console.log(` Port: ${status.port}`);
}
if (status.startTime) {
const startTime = new Date(status.startTime);
console.log(` Started: ${startTime.toLocaleString()}`);
}
} else {
console.log(chalk.gray('○') + ' Status: Not running');
if (status.lastError) {
console.log(chalk.red(` Last Error: ${status.lastError}`));
}
}
// Retry information
if (status.retryCount > 0) {
console.log(chalk.yellow(` Retry Count: ${status.retryCount}`));
}
// Validation issues
if (!validation.valid) {
console.log(chalk.yellow('\nValidation Issues:'));
validation.issues.forEach(issue => {
console.log(chalk.yellow(` • ${issue}`));
});
if (validation.fixes.length > 0) {
console.log(chalk.blue(` Applied fixes: ${validation.fixes.join(', ')}`));
}
}
console.log('');
}
/**
* Clean up status and lock files
*/
cleanup() {
try {
this.releaseLock();
// Reset status to default
this.saveStatus(this.getDefaultStatus());
return true;
} catch (error) {
console.error(chalk.red(`Failed to cleanup: ${error.message}`));
return false;
}
}
}
// Export for use in other modules
export default StatusManager;
// CLI usage
if (import.meta.url === `file://${process.argv[1]}`) {
const manager = new StatusManager();
const command = process.argv[2];
switch (command) {
case 'show':
case 'status':
manager.printStatus();
break;
case 'validate':
const validation = manager.validateStatus();
if (validation.valid) {
console.log(chalk.green('✓ Status is valid'));
} else {
console.log(chalk.yellow('⚠ Status validation issues:'));
validation.issues.forEach(issue => {
console.log(chalk.yellow(` • ${issue}`));
});
}
break;
case 'cleanup':
if (manager.cleanup()) {
console.log(chalk.green('✓ Cleanup completed'));
} else {
console.log(chalk.red('✗ Cleanup failed'));
process.exit(1);
}
break;
case 'lock':
const lockResult = manager.acquireLock();
if (lockResult.acquired) {
console.log(chalk.green('✓ Lock acquired'));
} else {
console.log(chalk.yellow(`⚠ Lock not acquired: ${lockResult.reason}`));
process.exit(1);
}
break;
case 'unlock':
if (manager.releaseLock()) {
console.log(chalk.green('✓ Lock released'));
} else {
console.log(chalk.red('✗ Failed to release lock'));
process.exit(1);
}
break;
default:
console.log('Usage: status-manager.js <command>');
console.log('Commands:');
console.log(' show|status - Show current status');
console.log(' validate - Validate current status');
console.log(' cleanup - Clean up status and lock files');
console.log(' lock - Acquire startup lock');
console.log(' unlock - Release startup lock');
break;
}
}