2025-12-24 13:45:36 +00:00

1206 lines
34 KiB
JavaScript

const fs = require('fs').promises;
const os = require('os');
const path = require('path');
const AdmZip = require('adm-zip');
const { exec } = require('child_process');
const util = require('util');
const ProjectEventsService = require('./project-events');
const config = require('../config.js');
// Babel Parser for JS/TS/TSX
const babelParser = require('@babel/parser');
const babelParse = babelParser.parse;
// Local App DB Connection
const database = require('./database');
// PostCSS for CSS
const postcss = require('postcss');
const execAsync = util.promisify(exec);
module.exports = class ExecutorService {
static async readProjectTree(directoryPath) {
const paths = {
frontend: '../../../frontend',
backend: '../../../backend',
default: '../../../'
};
try {
const publicDir = path.join(__dirname, paths[directoryPath] || directoryPath || paths.default);
return await getDirectoryTree(publicDir);
} catch (error) {
console.error('Error reading directory:', error);
throw error;
}
}
static async readFileContents(filePath, showLines) {
try {
const fullPath = path.join(__dirname, filePath);
const content = await fs.readFile(fullPath, 'utf8');
if (showLines) {
const lines = content.split('\n');
const lineObject = {};
lines.forEach((line, index) => {
lineObject[index + 1] = line;
});
return lineObject;
} else {
return content;
}
} catch (error) {
console.error('Error reading file:', error);
throw error;
}
}
static async countFileLines(filePath) {
try {
const fullPath = path.join(__dirname, filePath);
// Check file exists
await fs.access(fullPath);
// Read file content
const content = await fs.readFile(fullPath, 'utf8');
// Split by newline and count
const lines = content.split('\n');
return {
success: true,
lineCount: lines.length
};
} catch (error) {
console.error('Error counting file lines:', error);
return {
success: false,
message: error.message
};
}
}
// static async readFileHeader(filePath, N = 30) {
// try {
// const fullPath = path.join(__dirname, filePath);
// const content = await fs.readFile(fullPath, 'utf8');
// const lines = content.split('\n');
//
// if (lines.length < N) {
// return { error: `File has less than ${N} lines` };
// }
//
// const headerLines = lines.slice(0, Math.min(50, lines.length));
//
// const lineObject = {};
// headerLines.forEach((line, index) => {
// lineObject[index + 1] = line;
// });
//
// return lineObject;
// } catch (error) {
// console.error('Error reading file header:', error);
// throw error;
// }
// }
static async readFileLineContext(filePath, lineNumber, windowSize, showLines) {
try {
const fullPath = path.join(__dirname, filePath);
const content = await fs.readFile(fullPath, 'utf8');
const lines = content.split('\n');
const start = Math.max(0, lineNumber - windowSize);
const end = Math.min(lines.length, lineNumber + windowSize + 1);
const contextLines = lines.slice(start, end);
if (showLines) {
const lineObject = {};
contextLines.forEach((line, index) => {
lineObject[start + index + 1] = line;
});
return lineObject;
} else {
return contextLines.join('\n');
}
} catch (error) {
console.error('Error reading file line context:', error);
throw error;
}
}
static async validateFile(filePath) {
console.log('Validating file:', filePath);
// Read file content
let content;
try {
content = await fs.readFile(filePath, 'utf8');
} catch (err) {
throw new Error(`Could not read file: ${filePath}\n${err.message}`);
}
// Determine file extension
let ext = path.extname(filePath).toLowerCase();
if (ext === '.temp') {
ext = path.extname(filePath.slice(0, -5)).toLowerCase();
}
try {
switch (ext) {
case '.js':
case '.ts':
case '.tsx': {
// Parse JS/TS/TSX with Babel
babelParse(content, {
sourceType: 'module',
// plugins array covers JS, TS, TSX, and optional JS flavors
plugins: ['jsx', 'typescript']
});
break;
}
case '.css': {
// Parse CSS with PostCSS
postcss.parse(content);
break;
}
default: {
// If the extension isn't recognized, assume it's "valid"
// or you could throw an error to force a known extension
console.warn(`No validation implemented for extension "${ext}". Skipping syntax check.`);
}
}
// If parsing succeeded, return true
return true;
} catch (parseError) {
// Rethrow parse errors with a friendlier message
throw parseError;
}
}
static async checkFrontendRuntimeLogs() {
const frontendLogPath = '../frontend/json/runtimeError.json';
try {
// Check if file exists
try {
console.log('Accessing frontend logs:', frontendLogPath);
await fs.access(frontendLogPath);
} catch (error) {
console.log('Frontend logs not found:', error);
// File doesn't exist - return empty object
return {runtime_error: {}};
}
// File exists, try to read it
try {
// Read the entire file instead of using tail
const fileContent = await fs.readFile(frontendLogPath, 'utf8');
console.log('Reading frontend logs:', fileContent);
// Handle empty file
if (!fileContent || fileContent.trim() === '') {
return {runtime_error: {}};
}
// Parse JSON content
const runtime_error = JSON.parse(fileContent);
console.log('Parsed frontend logs:', runtime_error);
return {runtime_error};
} catch (error) {
// Error reading or parsing file
console.error('Error reading frontend runtime logs:', error);
return {runtime_error: {}};
}
} catch (error) {
// Unexpected error
console.log('Error checking frontend logs:', error);
return {runtime_error: {}};
}
}
static async writeFile(filePath, fileContents, comment) {
try {
console.log(comment)
const fullPath = path.join(__dirname, filePath);
// Write to a temp file first
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, fileContents, 'utf8');
// Validate the temp file
await this.validateFile(tempPath);
// Rename temp file to original path
await fs.rename(tempPath, fullPath);
return true;
} catch (error) {
console.error('Error writing file:', error);
throw error;
}
}
static async insertFileContent(filePath, lineNumber, newContent, message) {
try {
const fullPath = path.join(__dirname, filePath);
// Check file exists
await fs.access(fullPath);
// Read and split by line
const content = await fs.readFile(fullPath, 'utf8');
const lines = content.split('\n');
// Ensure lineNumber is within [1 ... lines.length + 1]
// 1 means "insert at the very first line"
// lines.length + 1 means "append at the end"
if (lineNumber < 1) {
lineNumber = 1;
}
if (lineNumber > lines.length + 1) {
lineNumber = lines.length + 1;
}
// Convert to 0-based index
const insertIndex = lineNumber - 1;
// Prepare preview
const preview = {
insertionLine: lineNumber,
insertedLines: newContent.split('\n')
};
// Insert newContent lines at the specified index
lines.splice(insertIndex, 0, ...newContent.split('\n'));
// Write changes to a temp file first
const updatedContent = lines.join('\n');
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, updatedContent, 'utf8');
await this.validateFile(tempPath);
// Rename temp file to original path
await fs.rename(tempPath, fullPath);
return {
success: true
};
} catch (error) {
console.error('Error inserting file content:', error);
throw error;
}
}
static async replaceFileLine(filePath, lineNumber, newText, message = null) {
const fullPath = path.join(__dirname, filePath);
try {
try {
await fs.access(fullPath);
} catch (error) {
throw new Error(`File not found: ${filePath}`);
}
const content = await fs.readFile(fullPath, 'utf8');
const lines = content.split('\n');
if (lineNumber < 1 || lineNumber > lines.length) {
throw new Error(`Invalid line number: ${lineNumber}. File has ${lines.length} lines`);
}
if (typeof newText !== 'string') {
throw new Error('New text must be a string');
}
const preview = {
oldLine: lines[lineNumber - 1],
newLine: newText,
lineNumber: lineNumber
};
lines[lineNumber - 1] = newText;
const newContent = lines.join('\n');
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, newContent, 'utf8');
await this.validateFile(tempPath);
await fs.rename(tempPath, fullPath);
return {
success: true
};
} catch (error) {
console.error('Error updating file line:', error);
try {
await fs.unlink(`${fullPath}.temp`);
} catch {
}
throw {
error: error,
message: error.message,
details: error.stack
};
}
}
static async replaceFileChunk(filePath, startLine, endLine, newCode) {
try {
// Check if this is a single-line change
const newCodeLines = newCode.split('\n');
if (newCodeLines.length === 1 && endLine === startLine) {
// Redirect to replace_file_line
return await this.replaceFileLine(filePath, startLine, newCode);
}
const fullPath = path.join(__dirname, filePath);
// Check if file exists
try {
await fs.access(fullPath);
} catch (error) {
throw new Error(`File not found: ${filePath}`);
}
const content = await fs.readFile(fullPath, 'utf8');
const lines = content.split('\n');
// Adjust line numbers to array indices (subtract 1)
const startIndex = startLine - 1;
const endIndex = endLine - 1;
// Validate input parameters
if (startIndex < 0 || endIndex >= lines.length || startIndex > endIndex) {
throw new Error(`Invalid line range: ${startLine}-${endLine}. File has ${lines.length} lines`);
}
// Check type of new code
if (typeof newCode !== 'string') {
throw new Error('New code must be a string');
}
// Create changes preview
const preview = {
oldLines: lines.slice(startIndex, endIndex + 1),
newLines: newCode.split('\n'),
startLine,
endLine
};
// Apply changes to temp file first
lines.splice(startIndex, endIndex - startIndex + 1, ...newCode.split('\n'));
const newContent = lines.join(os.EOL);
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, newContent, 'utf8');
await this.validateFile(tempPath);
// Apply changes if all validations passed
await fs.rename(tempPath, fullPath);
return {
success: true
};
} catch (error) {
console.error('Error updating file slice:', error);
// Clean up temp file if exists
try {
await fs.unlink(`${fullPath}.temp`);
} catch {
}
throw {
error: error,
message: error.message,
details: error.details || error.stack
};
}
}
static async replaceCodeBlock(filePath, oldCode, newCode, message) {
try {
console.log(message);
const fullPath = path.join(__dirname, filePath);
// Check file exists
await fs.access(fullPath);
// Read file content
let content = await fs.readFile(fullPath, 'utf8');
// A small helper to unify line breaks to just `\n`
const unifyLineBreaks = (str) => str.replace(/\r\n/g, '\n');
// Normalize line breaks in file content, oldCode, and newCode
content = unifyLineBreaks(content);
oldCode = unifyLineBreaks(oldCode);
newCode = unifyLineBreaks(newCode);
// Optional: Trim trailing spaces or handle other whitespace normalization if needed
// oldCode = oldCode.trim();
// newCode = newCode.trim();
// Check if oldCode actually exists in the content
const index = content.indexOf(oldCode);
if (index === -1) {
return {
success: false,
message: 'Old code not found in file.'
};
}
// Create a preview before replacing
const preview = {
oldCodeSnippet: oldCode,
newCodeSnippet: newCode
};
// Perform replacement (single occurrence). For multiple, use replaceAll or a loop.
// If you want a global replacement, consider:
// content = content.split(oldCode).join(newCode);
content = content.replace(oldCode, newCode);
// Write to a temp file first
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, content, 'utf8');
await this.validateFile(tempPath);
// Rename temp file to original
await fs.rename(tempPath, fullPath);
return {
success: true
};
} catch (error) {
console.error('Error replacing code:', error);
return {
error: error,
message: error.message,
details: error.details || error.stack
};
}
}
//todo add validation
static async deleteFileLines(filePath, startLine, endLine, veryShortDescription) {
try {
const fullPath = path.join(__dirname, filePath);
// Check if file exists
await fs.access(fullPath);
// Read file content
const content = await fs.readFile(fullPath, 'utf8');
const lines = content.split('\n');
// Convert to zero-based indices
const startIndex = startLine - 1;
const endIndex = endLine - 1;
// Validate range
if (startIndex < 0 || endIndex >= lines.length || startIndex > endIndex) {
throw new Error(
`Invalid line range: ${startLine}-${endLine}. File has ${lines.length} lines`
);
}
// Prepare a preview of the lines being deleted
const preview = {
deletedLines: lines.slice(startIndex, endIndex + 1),
startLine,
endLine
};
// Remove lines
lines.splice(startIndex, endIndex - startIndex + 1);
// Join remaining lines and write to a temporary file
const newContent = lines.join('\n');
const tempPath = `${fullPath}.temp`;
await fs.writeFile(tempPath, newContent, 'utf8');
await this.validateFile(tempPath);
// Rename temp file to original
await fs.rename(tempPath, fullPath);
return {
success: true
};
} catch (error) {
console.error('Error deleting file lines:', error);
return {
error: error,
message: error.message,
details: error.details || error.stack
};
}
}
static async validateTypeScript(filePath, content = null) {
try {
// Basic validation of JSX syntax
const jsxErrors = [];
if (content !== null) {
// Check for matching braces
if ((content.match(/{/g) || []).length !== (content.match(/}/g) || []).length) {
jsxErrors.push("Unmatched curly braces");
}
// Check for invalid syntax in JSX attributes
if (content.includes('label={')) {
if (!content.match(/label={[^}]+}/)) {
jsxErrors.push("Invalid label attribute syntax");
}
}
if (jsxErrors.length > 0) {
return {
valid: false,
errors: jsxErrors.map(error => ({
code: 'JSX_SYNTAX_ERROR',
severity: 'error',
location: '',
message: error
}))
};
}
}
return {
valid: true,
errors: [],
errorCount: 0,
warningCount: 0
};
} catch (error) {
console.error('TypeScript validation error:', error);
return {
valid: false,
errors: [{
code: 'VALIDATION_FAILED',
severity: 'error',
location: '',
message: `TypeScript validation error: ${error.message}`
}],
errorCount: 1,
warningCount: 0
};
}
}
static async validateBackendFiles(backendPath) {
try {
// Check for syntax errors
await execAsync(`node --check ${backendPath}/src/index.js`);
// Try to run the code in a test environment
const testProcess = exec(
'NODE_ENV=test node -e "try { require(\'./src/index.js\') } catch(e) { console.error(e); process.exit(1) }"',
{cwd: backendPath}
);
return new Promise((resolve) => {
let output = '';
let error = '';
testProcess.stdout.on('data', (data) => {
output += data;
});
testProcess.stderr.on('data', (data) => {
error += data;
});
testProcess.on('close', (code) => {
if (code === 0) {
resolve({valid: true});
} else {
resolve({
valid: false,
error: error || output
});
}
});
// Timeout on validation
setTimeout(() => {
testProcess.kill();
resolve({
valid: true,
warning: 'Validation timeout, but no immediate errors found'
});
}, 5000);
});
} catch (error) {
return {
valid: false,
error: error.message
};
}
}
static async createBackup(ROOT_PATH) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupDir = path.join(ROOT_PATH, 'backups', timestamp);
try {
await fs.mkdir(path.join(ROOT_PATH, 'backups'), {recursive: true});
const dirsToBackup = ['frontend', 'backend'];
for (const dir of dirsToBackup) {
const sourceDir = path.join(ROOT_PATH, dir);
const targetDir = path.join(backupDir, dir);
await fs.mkdir(targetDir, {recursive: true});
await execAsync(
`cd "${sourceDir}" && ` +
`find . -type f -not -path "*/node_modules/*" -not -path "*/\\.*" | ` +
`while read file; do ` +
`mkdir -p "${targetDir}/$(dirname "$file")" && ` +
`cp "$file" "${targetDir}/$file"; ` +
`done`
);
}
console.log('Backup created at:', backupDir);
return backupDir;
} catch (error) {
console.error('Error creating backup:', error);
throw error;
}
}
static async restoreFromBackup(backupDir, ROOT_PATH) {
try {
console.log('Restoring from backup:', backupDir);
await execAsync(`rm -rf ${ROOT_PATH}/backend/*`);
await execAsync(`cp -r ${backupDir}/* ${ROOT_PATH}/backend/`);
return true;
} catch (error) {
console.error('Error restoring from backup:', error);
throw error;
}
}
static async updateProjectFilesFromScheme(zipFilePath) {
const MAX_FILE_SIZE = 10 * 1024 * 1024;
const ROOT_PATH = path.join(__dirname, '../../../');
try {
console.log('Checking file access...');
await fs.access(zipFilePath);
console.log('Getting file stats...');
const stats = await fs.stat(zipFilePath);
console.log('File size:', stats.size);
if (stats.size > MAX_FILE_SIZE) {
console.log('File size exceeds limit');
return {success: false, error: 'File size exceeds limit'};
}
// Copying zip file to /tmp
const tempZipPath = path.join('/tmp', path.basename(zipFilePath));
await fs.copyFile(zipFilePath, tempZipPath);
// Launching background update process
const servicesUpdate = (async () => {
try {
console.log('Stopping services...');
// await ProjectEventsService.sendEvent('SERVICE_STOP_STARTED', {
// message: 'Stopping services',
// timestamp: new Date().toISOString()
// });
await stopServices();
// await ProjectEventsService.sendEvent('SERVICE_STOP_COMPLETED', {
// message: 'Services stopped successfully',
// timestamp: new Date().toISOString()
// });
console.log('Creating zip instance...');
const zip = new AdmZip(tempZipPath);
console.log('Extracting files to:', ROOT_PATH);
zip.extractAllTo(ROOT_PATH, true);
console.log('Files extracted');
const removedFilesPath = path.join(ROOT_PATH, 'removed_files.json');
try {
await fs.access(removedFilesPath);
const removedFilesContent = await fs.readFile(removedFilesPath, 'utf8');
const filesToRemove = JSON.parse(removedFilesContent);
await removeFiles(filesToRemove, ROOT_PATH);
await fs.unlink(removedFilesPath);
} catch (error) {
console.log('No removed files to process or error accessing removed_files.json:', error);
}
// Remove temp zip file
await fs.unlink(tempZipPath);
// await ProjectEventsService.sendEvent('SERVICE_START_STARTED', {
// message: 'Starting services',
// timestamp: new Date().toISOString()
// });
// Start services after a delay
setTimeout(async () => {
try {
await startServices();
console.log('Services started successfully');
await ProjectEventsService.sendEvent('SERVICE_START_COMPLETED', {
message: 'All files have been successfully retrieved and applied.',
timestamp: new Date().toISOString()
});
} catch (e) {
console.error('Failed to start services:', e);
}
}, 3000);
} catch (error) {
console.error('Error in service update process:', error);
}
})();
servicesUpdate.catch(error => {
console.error('Background update process failed:', error);
});
console.log('Returning immediate response');
return {
success: true,
message: 'Update process initiated'
};
} catch (error) {
console.error('Critical error in updateProjectFilesFromScheme:', error);
return {
success: false,
error: error.message
};
}
}
static async getDBSchema() {
try {
return await database.getDBSchema();
} catch (error) {
console.error('Error reading schema:', error);
throw {
error: error,
message: error.message,
details: error.details || error.stack
};
}
}
static async executeSQL(query) {
try {
return await database.executeSQL(query);
} catch (error) {
console.error('Error executing query:', error);
throw {
error: error,
message: error.message,
details: error.details || error.stack
};
}
}
static async stopServices() {
return await stopServices();
}
static async startServices() {
return await startServices();
}
static async checkServicesStatus() {
return await checkStatus();
}
static async searchFiles(searchStrings) {
const results = {};
const ROOT_PATH = path.join(__dirname, '../../../');
const directories = [`${ROOT_PATH}backend/`, `${ROOT_PATH}frontend/`];
const excludeDirs = ['node_modules', 'build', 'app_shell'];
if (!Array.isArray(searchStrings)) {
searchStrings = [searchStrings];
}
for (const searchString of searchStrings) {
try {
for (const directoryPath of directories) {
const findCommand = `find '${directoryPath}' -type f ${excludeDirs.map(dir => `-not -path "*/${dir}/*"`).join(' ')} -print | xargs grep -nH -C 1 -e '${searchString}'`;
try {
const { stdout } = await execAsync(findCommand);
const lines = stdout.trim().split('\n').filter(line => line !== '');
const searchResults = {};
// searchResults['__raw_lines__'] = lines;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const parts = line.split(':');
let filePath = '';
let lineNumberStr = '';
let content = '';
let relativeFilePath = '';
let lineNum = null;
if (parts.length >= 3 && !parts[0].includes('-')) {
filePath = parts.shift();
lineNumberStr = parts.shift();
content = parts.join(':').trim();
relativeFilePath = filePath.replace(`${ROOT_PATH}`, '');
lineNum = parseInt(lineNumberStr, 10) + 1;
} else {
content = line.trim();
}
const context = [];
if (i > 0 && lines[i - 1].includes(':')) {
const prevLineParts = lines[i - 1].split(':');
if (prevLineParts.length >= 3 && !prevLineParts[0].includes('-')) {
prevLineParts.shift();
prevLineParts.shift();
context.push(prevLineParts.join(':').trim());
} else {
context.push(lines[i - 1].trim());
}
}
context.push(content);
if (i < lines.length - 1 && lines[i + 1].includes(':')) {
const nextLineParts = lines[i + 1].split(':');
if (nextLineParts.length >= 3 && !nextLineParts[0].includes('-')) {
nextLineParts.shift();
nextLineParts.shift();
context.push(nextLineParts.join(':').trim());
} else {
context.push(lines[i + 1].trim());
}
}
if (relativeFilePath && !searchResults[relativeFilePath]) {
searchResults[relativeFilePath] = [];
}
if (relativeFilePath) {
searchResults[relativeFilePath].push({
lineNumber: lineNum,
context: context.join('\n'),
// __filePathAndLine__: filePath + ':' + lineNumberStr + ':' + content,
});
}
}
if (!results[searchString]) {
results[searchString] = {};
}
Object.assign(results[searchString], searchResults);
} catch (err) {
if (!err.message.includes('No such file or directory') && !err.stderr.includes('No such file or directory')) {
console.error(`Error using find/grep for "${searchString}" in ${directoryPath}:`, err);
}
}
}
} catch (error) {
console.error(`Error searching for "${searchString}":`, error);
results[searchString] = { error: error.message };
}
}
return results;
}
}
async function getDirectoryTree(dirPath) {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
const result = {};
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory() && (
entry.name === 'node_modules' ||
entry.name === 'app-shell' ||
entry.name === '.git' ||
entry.name === '.idea'
)) {
continue;
}
const relativePath = fullPath.replace('/app', '');
if (entry.isDirectory()) {
const subTree = await getDirectoryTree(fullPath);
Object.keys(subTree).forEach(key => {
result[key.replace('/app', '')] = subTree[key];
});
} else {
const fileContent = await fs.readFile(fullPath, 'utf8');
const lineCount = fileContent.split('\n').length;
result[relativePath] = lineCount;
}
}
return result;
}
async function stopServices() {
try {
console.log('Finding service processes...');
// await ProjectEventsService.sendEvent('SERVICE_STOP_INITIATED', {
// message: 'Initiating service stop',
// timestamp: new Date().toISOString()
// });
// Frontend stopping
const { stdout: frontendProcess } = await execAsync("ps -o pid,cmd | grep '[n]ext-server' | awk '{print $1}'");
if (frontendProcess.trim()) {
console.log('Stopping frontend, pid:', frontendProcess.trim());
// await ProjectEventsService.sendEvent('FRONTEND_STOP_STARTED', {
// message: `Stopping frontend, pid: ${frontendProcess.trim()}`,
// timestamp: new Date().toISOString()
// });
// await execAsync(`kill -15 ${frontendProcess.trim()}`);
// await ProjectEventsService.sendEvent('FRONTEND_STOP_COMPLETED', {
// message: 'Frontend stopped successfully',
// timestamp: new Date().toISOString()
// });
}
// Backend stopping
const { stdout: backendProcess } = await execAsync("ps -o pid,cmd | grep '[n]ode ./src/index.js' | grep -v app-shell | awk '{print $1}'");
if (backendProcess.trim()) {
console.log('Stopping backend, pid:', backendProcess.trim());
// await ProjectEventsService.sendEvent('BACKEND_STOP_STARTED', {
// message: `Stopping backend, pid: ${backendProcess.trim()}`,
// timestamp: new Date().toISOString()
// });
// await execAsync(`kill -15 ${backendProcess.trim()}`);
// await ProjectEventsService.sendEvent('BACKEND_STOP_COMPLETED', {
// message: 'Backend stopped successfully',
// timestamp: new Date().toISOString()
// });
}
await new Promise(resolve => setTimeout(resolve, 4000));
// await ProjectEventsService.sendEvent('SERVICE_STOP_COMPLETED', {
// message: 'All services stopped successfully',
// timestamp: new Date().toISOString()
// });
return { success: true };
} catch (error) {
console.error('Error stopping services:', error);
await ProjectEventsService.sendEvent('SERVICE_STOP_FAILED', {
message: 'Error stopping services',
error: error.message,
timestamp: new Date().toISOString()
});
return { success: false, error: error.message };
}
}
async function startServices() {
try {
console.log('Starting services...');
// await ProjectEventsService.sendEvent('SERVICE_START_INITIATED', {
// message: 'Initiating service start',
// timestamp: new Date().toISOString()
// });
// await ProjectEventsService.sendEvent('FRONTEND_START_STARTED', {
// message: 'Starting frontend service',
// timestamp: new Date().toISOString()
// });
// await execAsync('yarn --cwd /app/frontend dev &');
// await ProjectEventsService.sendEvent('FRONTEND_START_COMPLETED', {
// message: 'Frontend service started successfully',
// timestamp: new Date().toISOString()
// });
// await ProjectEventsService.sendEvent('BACKEND_START_STARTED', {
// message: 'Starting backend service',
// timestamp: new Date().toISOString()
// });
// await execAsync('yarn --cwd /app/backend start &');
// await ProjectEventsService.sendEvent('BACKEND_START_COMPLETED', {
// message: 'Backend service started successfully',
// timestamp: new Date().toISOString()
// });
// await ProjectEventsService.sendEvent('SERVICE_START_COMPLETED', {
// message: 'All services started successfully',
// timestamp: new Date().toISOString()
// });
return { success: true };
} catch (error) {
console.error('Error starting services:', error);
await ProjectEventsService.sendEvent('SERVICE_START_FAILED', {
message: 'Error starting services',
error: error.message,
timestamp: new Date().toISOString()
});
return { success: false, error: error.message };
}
}
async function checkStatus() {
try {
const { stdout } = await execAsync('ps aux');
return {
success: true,
frontendRunning: stdout.includes('next-server'),
backendRunning: stdout.includes('nodemon') && stdout.includes('/app/backend'),
nginxRunning: stdout.includes('nginx: master process')
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
async function validateJSXSyntax(code) {
// Define validation rules for JSX
const rules = [
{
// JSX attribute with expression
pattern: /^[a-zA-Z][a-zA-Z0-9]*={.*}$/,
message: 'Invalid JSX attribute syntax'
},
{
// Invalid sequences
pattern: /,{2,}/,
message: 'Invalid character sequence detected',
shouldNotMatch: true
},
{
// Ternary expressions
pattern: /^[a-zA-Z][a-zA-Z0-9]*={[\w\s]+\?[^}]+:[^}]+}$/,
message: 'Invalid ternary expression in JSX'
}
];
// Validate each line
const lines = code.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// Skip empty lines
if (!trimmedLine) continue;
// Check each rule
for (const rule of rules) {
if (rule.shouldNotMatch) {
// For patterns that should not be present
if (rule.pattern.test(trimmedLine)) {
return {
valid: false,
errors: [{
code: 'JSX_SYNTAX_ERROR',
severity: 'error',
location: '',
message: rule.message
}]
};
}
} else {
// For patterns that should match
if (trimmedLine.includes('=') && !rule.pattern.test(trimmedLine)) {
return {
valid: false,
errors: [{
code: 'JSX_SYNTAX_ERROR',
severity: 'error',
location: '',
message: rule.message
}]
};
}
}
}
// Additional JSX-specific checks
if ((trimmedLine.match(/{/g) || []).length !== (trimmedLine.match(/}/g) || []).length) {
return {
valid: false,
errors: [{
code: 'JSX_SYNTAX_ERROR',
severity: 'error',
location: '',
message: 'Unmatched curly braces in JSX'
}]
};
}
}
// If all checks pass
return {
valid: true,
errors: []
};
}
async function removeFiles(files, rootPath) {
try {
for (const file of files) {
const fullPath = path.join(rootPath, file);
try {
await fs.unlink(fullPath);
console.log(`File removed: ${fullPath}`);
} catch (error) {
console.error(`Error when trying to delete a file ${fullPath}:`, error);
}
}
} catch (error) {
console.error('Error removing files:', error);
throw error;
}
}