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; } }