358 lines
10 KiB
JavaScript
358 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Auto-Sync Script para RDO Project
|
|
* Monitora mudanças em tempo real e sincroniza com GitHub a cada 15 segundos
|
|
* Autor: Sistema RDO
|
|
* Versão: 3.0
|
|
*/
|
|
|
|
import chokidar from 'chokidar';
|
|
import { exec } from 'child_process';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import util from 'util';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname } from 'path';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const execAsync = util.promisify(exec);
|
|
|
|
// Configurações
|
|
const CONFIG = {
|
|
REMOTE_REPO: 'https://github.com/Reifonas/TS_RDO.git',
|
|
BRANCH: 'main',
|
|
SYNC_INTERVAL: 15000, // 15 segundos
|
|
PROJECT_PATH: process.cwd(),
|
|
LOG_PATH: path.join(process.cwd(), 'logs'),
|
|
WATCH_PATTERNS: [
|
|
'**/*',
|
|
'!node_modules/**',
|
|
'!.git/**',
|
|
'!dist/**',
|
|
'!build/**',
|
|
'!logs/**',
|
|
|
|
'!*.log',
|
|
'!.env.local'
|
|
]
|
|
};
|
|
|
|
// Estado do aplicativo
|
|
let hasChanges = false;
|
|
let isProcessing = false;
|
|
let lastSyncTime = 0;
|
|
let changeQueue = new Set();
|
|
|
|
// Criar diretório de logs se não existir
|
|
if (!fs.existsSync(CONFIG.LOG_PATH)) {
|
|
fs.mkdirSync(CONFIG.LOG_PATH, { recursive: true });
|
|
}
|
|
|
|
/**
|
|
* Função de logging com timestamp
|
|
*/
|
|
function log(message, level = 'INFO') {
|
|
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
const logMessage = `[${timestamp}] [${level}] ${message}`;
|
|
|
|
// Console com cores
|
|
const colors = {
|
|
ERROR: '\x1b[31m', // Vermelho
|
|
WARN: '\x1b[33m', // Amarelo
|
|
SUCCESS: '\x1b[32m', // Verde
|
|
INFO: '\x1b[36m', // Ciano
|
|
RESET: '\x1b[0m' // Reset
|
|
};
|
|
|
|
console.log(`${colors[level] || colors.INFO}${logMessage}${colors.RESET}`);
|
|
|
|
// Salvar em arquivo
|
|
const logFile = path.join(CONFIG.LOG_PATH, `auto-sync-${new Date().toISOString().split('T')[0]}.log`);
|
|
fs.appendFileSync(logFile, logMessage + '\n');
|
|
}
|
|
|
|
/**
|
|
* Verificar se é um repositório Git
|
|
*/
|
|
async function isGitRepository() {
|
|
try {
|
|
await execAsync('git rev-parse --git-dir');
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inicializar repositório Git
|
|
*/
|
|
async function initializeGitRepository() {
|
|
try {
|
|
log('Inicializando repositório Git...', 'INFO');
|
|
|
|
await execAsync('git init');
|
|
await execAsync(`git remote add origin ${CONFIG.REMOTE_REPO}`);
|
|
await execAsync(`git branch -M ${CONFIG.BRANCH}`);
|
|
|
|
log('Repositório Git inicializado com sucesso', 'SUCCESS');
|
|
return true;
|
|
} catch (error) {
|
|
log(`Erro ao inicializar repositório: ${error.message}`, 'ERROR');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verificar se há mudanças no repositório
|
|
*/
|
|
async function checkForChanges() {
|
|
try {
|
|
const { stdout } = await execAsync('git status --porcelain');
|
|
return stdout.trim().length > 0;
|
|
} catch (error) {
|
|
log(`Erro ao verificar mudanças: ${error.message}`, 'ERROR');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gerar mensagem de commit automática
|
|
*/
|
|
async function generateCommitMessage() {
|
|
try {
|
|
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
|
|
// Obter estatísticas das mudanças
|
|
const { stdout: modifiedFiles } = await execAsync('git diff --name-only HEAD').catch(() => ({ stdout: '' }));
|
|
const { stdout: newFiles } = await execAsync('git ls-files --others --exclude-standard').catch(() => ({ stdout: '' }));
|
|
const { stdout: deletedFiles } = await execAsync('git diff --name-only --diff-filter=D HEAD').catch(() => ({ stdout: '' }));
|
|
|
|
const changes = [];
|
|
if (modifiedFiles.trim()) changes.push(`Modified: ${modifiedFiles.trim().split('\n').length} files`);
|
|
if (newFiles.trim()) changes.push(`Added: ${newFiles.trim().split('\n').length} files`);
|
|
if (deletedFiles.trim()) changes.push(`Deleted: ${deletedFiles.trim().split('\n').length} files`);
|
|
|
|
let message = `Auto-sync: ${timestamp}`;
|
|
if (changes.length > 0) {
|
|
message += ` - ${changes.join(', ')}`;
|
|
}
|
|
|
|
return message;
|
|
} catch (error) {
|
|
return `Auto-sync: ${new Date().toISOString().replace('T', ' ').substring(0, 19)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sincronizar com GitHub
|
|
*/
|
|
async function syncWithGitHub() {
|
|
if (isProcessing) {
|
|
log('Sincronização já em andamento, aguardando...', 'WARN');
|
|
return false;
|
|
}
|
|
|
|
isProcessing = true;
|
|
|
|
try {
|
|
log('Iniciando sincronização com GitHub...', 'INFO');
|
|
|
|
// Verificar se é repositório Git
|
|
if (!(await isGitRepository())) {
|
|
log('Não é um repositório Git. Inicializando...', 'WARN');
|
|
if (!(await initializeGitRepository())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Verificar mudanças
|
|
if (!(await checkForChanges())) {
|
|
log('Nenhuma mudança detectada', 'INFO');
|
|
return true;
|
|
}
|
|
|
|
// Pull das mudanças remotas (se houver)
|
|
try {
|
|
await execAsync(`git pull origin ${CONFIG.BRANCH} --rebase`);
|
|
log('Pull realizado com sucesso', 'INFO');
|
|
} catch (error) {
|
|
log('Primeira sincronização ou sem mudanças remotas', 'INFO');
|
|
}
|
|
|
|
// Adicionar arquivos
|
|
await execAsync('git add .');
|
|
log('Arquivos adicionados ao stage', 'INFO');
|
|
|
|
// Commit
|
|
const commitMessage = await generateCommitMessage();
|
|
await execAsync(`git commit -m "${commitMessage}"`);
|
|
log(`Commit realizado: ${commitMessage}`, 'INFO');
|
|
|
|
// Push
|
|
await execAsync(`git push origin ${CONFIG.BRANCH}`);
|
|
log('Push realizado com sucesso!', 'SUCCESS');
|
|
|
|
// Resetar estado
|
|
hasChanges = false;
|
|
changeQueue.clear();
|
|
lastSyncTime = Date.now();
|
|
|
|
return true;
|
|
|
|
} catch (error) {
|
|
log(`Erro durante sincronização: ${error.message}`, 'ERROR');
|
|
return false;
|
|
} finally {
|
|
isProcessing = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inicializar monitoramento de arquivos
|
|
*/
|
|
function initializeWatcher() {
|
|
log('Inicializando monitoramento de arquivos...', 'INFO');
|
|
log(`Padrões monitorados: ${CONFIG.WATCH_PATTERNS.join(', ')}`, 'INFO');
|
|
|
|
const watcher = chokidar.watch(CONFIG.WATCH_PATTERNS, {
|
|
ignored: /(^|[\/\\])\../, // Ignorar arquivos ocultos
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
awaitWriteFinish: {
|
|
stabilityThreshold: 1000,
|
|
pollInterval: 100
|
|
}
|
|
});
|
|
|
|
// Eventos de mudança
|
|
watcher
|
|
.on('add', path => {
|
|
log(`Arquivo adicionado: ${path}`, 'INFO');
|
|
hasChanges = true;
|
|
changeQueue.add(path);
|
|
})
|
|
.on('change', path => {
|
|
log(`Arquivo modificado: ${path}`, 'INFO');
|
|
hasChanges = true;
|
|
changeQueue.add(path);
|
|
})
|
|
.on('unlink', path => {
|
|
log(`Arquivo removido: ${path}`, 'INFO');
|
|
hasChanges = true;
|
|
changeQueue.add(path);
|
|
})
|
|
.on('error', error => {
|
|
log(`Erro no monitoramento: ${error}`, 'ERROR');
|
|
})
|
|
.on('ready', () => {
|
|
log('Monitoramento de arquivos ativo!', 'SUCCESS');
|
|
});
|
|
|
|
return watcher;
|
|
}
|
|
|
|
/**
|
|
* Loop principal de sincronização
|
|
*/
|
|
function startSyncLoop() {
|
|
setInterval(async () => {
|
|
if (hasChanges && !isProcessing) {
|
|
const timeSinceLastSync = Date.now() - lastSyncTime;
|
|
|
|
if (timeSinceLastSync >= CONFIG.SYNC_INTERVAL) {
|
|
log(`Mudanças detectadas (${changeQueue.size} arquivos). Iniciando sincronização...`, 'INFO');
|
|
await syncWithGitHub();
|
|
} else {
|
|
const remainingTime = Math.ceil((CONFIG.SYNC_INTERVAL - timeSinceLastSync) / 1000);
|
|
log(`Aguardando ${remainingTime}s para próxima sincronização...`, 'INFO');
|
|
}
|
|
}
|
|
}, 5000); // Verificar a cada 5 segundos
|
|
}
|
|
|
|
/**
|
|
* Limpeza de logs antigos
|
|
*/
|
|
function cleanOldLogs() {
|
|
try {
|
|
const files = fs.readdirSync(CONFIG.LOG_PATH);
|
|
const cutoffDate = new Date();
|
|
cutoffDate.setDate(cutoffDate.getDate() - 7); // Manter logs por 7 dias
|
|
|
|
files.forEach(file => {
|
|
if (file.endsWith('.log')) {
|
|
const filePath = path.join(CONFIG.LOG_PATH, file);
|
|
const stats = fs.statSync(filePath);
|
|
|
|
if (stats.mtime < cutoffDate) {
|
|
fs.unlinkSync(filePath);
|
|
log(`Log antigo removido: ${file}`, 'INFO');
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
log(`Erro ao limpar logs antigos: ${error.message}`, 'ERROR');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tratamento de sinais do sistema
|
|
*/
|
|
function setupSignalHandlers() {
|
|
process.on('SIGINT', () => {
|
|
log('Recebido SIGINT. Finalizando...', 'WARN');
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', () => {
|
|
log('Recebido SIGTERM. Finalizando...', 'WARN');
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('uncaughtException', (error) => {
|
|
log(`Erro não capturado: ${error.message}`, 'ERROR');
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Função principal
|
|
*/
|
|
async function main() {
|
|
log('=== Auto-Sync RDO v3.0 ===', 'SUCCESS');
|
|
log(`Repositório: ${CONFIG.REMOTE_REPO}`, 'INFO');
|
|
log(`Branch: ${CONFIG.BRANCH}`, 'INFO');
|
|
log(`Intervalo de sincronização: ${CONFIG.SYNC_INTERVAL / 1000}s`, 'INFO');
|
|
log(`Diretório: ${CONFIG.PROJECT_PATH}`, 'INFO');
|
|
|
|
// Configurar tratamento de sinais
|
|
setupSignalHandlers();
|
|
|
|
// Limpar logs antigos
|
|
cleanOldLogs();
|
|
|
|
// Mudar para o diretório do projeto
|
|
process.chdir(CONFIG.PROJECT_PATH);
|
|
|
|
// Inicializar monitoramento
|
|
const watcher = initializeWatcher();
|
|
|
|
// Iniciar loop de sincronização
|
|
startSyncLoop();
|
|
|
|
log('Sistema de auto-sync ativo! Pressione Ctrl+C para parar.', 'SUCCESS');
|
|
|
|
// Manter o processo ativo
|
|
process.stdin.resume();
|
|
}
|
|
|
|
// Executar automaticamente
|
|
main().catch(error => {
|
|
console.error(`Erro fatal: ${error.message}`);
|
|
process.exit(1);
|
|
});
|
|
|
|
export { main, syncWithGitHub, log }; |