#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); // Configuration const WATCH_DIRS = ['components', 'app', 'lib', 'styles']; const WATCH_EXTENSIONS = ['.tsx', '.ts', '.js', '.jsx', '.css', '.scss', '.json']; const COMMIT_DELAY = 30000; // 30 seconds delay before auto-commit const MAX_COMMITS_PER_HOUR = 10; class AutoCommitWatcher { constructor() { this.commitQueue = new Set(); this.commitTimeout = null; this.commitsThisHour = 0; this.lastCommitTime = 0; this.startTime = new Date(); // Reset commit counter every hour setInterval(() => { this.commitsThisHour = 0; console.log('πŸ”„ Hourly reset: Commit counter reset to 0'); }, 3600000); // Log startup info console.log('πŸš€ Timeshift Auto-Commit Watcher Started'); console.log('=========================================='); console.log(`πŸ“… Start time: ${this.startTime.toISOString()}`); console.log(`πŸ“ Watching directories: ${WATCH_DIRS.join(', ')}`); console.log(`πŸ“„ File extensions: ${WATCH_EXTENSIONS.join(', ')}`); console.log(`⏱️ Commit delay: ${COMMIT_DELAY/1000}s`); console.log(`🚦 Max commits per hour: ${MAX_COMMITS_PER_HOUR}`); console.log(`πŸ”§ Process ID: ${process.pid}`); console.log('=========================================='); } start() { WATCH_DIRS.forEach(dir => { const fullPath = path.join(process.cwd(), dir); if (fs.existsSync(fullPath)) { this.watchDirectory(fullPath); console.log(`πŸ‘€ Watching: ${fullPath}`); } else { console.log(`⚠️ Directory not found: ${fullPath}`); } }); } watchDirectory(dirPath) { try { fs.watch(dirPath, { recursive: true }, (eventType, filename) => { if (!filename) return; const filePath = path.join(dirPath, filename); const ext = path.extname(filename); // Check if file extension is in our watch list if (WATCH_EXTENSIONS.includes(ext)) { this.handleFileChange(filePath, eventType); } }); } catch (error) { console.error(`❌ Error watching directory ${dirPath}:`, error.message); } } handleFileChange(filePath, eventType) { const relativePath = path.relative(process.cwd(), filePath); console.log(`πŸ“ File changed: ${relativePath} (${eventType})`); // Add to commit queue this.commitQueue.add(relativePath); // Clear existing timeout and set new one if (this.commitTimeout) { clearTimeout(this.commitTimeout); } this.commitTimeout = setTimeout(() => { this.performAutoCommit(); }, COMMIT_DELAY); } async performAutoCommit() { // Rate limiting if (this.commitsThisHour >= MAX_COMMITS_PER_HOUR) { console.log(`⏸️ Rate limit reached (${MAX_COMMITS_PER_HOUR} commits/hour). Skipping auto-commit.`); this.commitQueue.clear(); return; } // Check if enough time has passed since last commit const now = Date.now(); if (now - this.lastCommitTime < COMMIT_DELAY) { console.log('⏸️ Too soon since last commit. Skipping.'); return; } if (this.commitQueue.size === 0) { console.log('πŸ“‹ No files in commit queue'); return; } try { // Check if there are actually changes to commit const { stdout: status } = await execAsync('git status --porcelain'); if (!status.trim()) { console.log('πŸ“ No actual changes to commit'); this.commitQueue.clear(); return; } console.log('πŸ”„ Performing auto-commit...'); // Create commit message with changed files const changedFiles = Array.from(this.commitQueue).slice(0, 5); // Limit to 5 files in message const fileList = changedFiles.join(', '); const moreFiles = this.commitQueue.size > 5 ? ` and ${this.commitQueue.size - 5} more` : ''; const message = `Auto-save: Updated ${fileList}${moreFiles}`; // Use our auto-commit script await execAsync(`./auto-commit.sh commit "${message}"`); console.log('βœ… Auto-commit successful'); this.commitsThisHour++; this.lastCommitTime = now; this.commitQueue.clear(); } catch (error) { console.error('❌ Auto-commit failed:', error.message); } } async createRestorePoint(description = 'Auto restore point') { try { console.log('πŸ”– Creating restore point...'); await execAsync(`./auto-commit.sh restore-point "${description}"`); console.log('βœ… Restore point created'); } catch (error) { console.error('❌ Failed to create restore point:', error.message); } } } // Handle process termination gracefully process.on('SIGINT', async () => { console.log('\nπŸ›‘ Stopping auto-commit watcher...'); // Perform final commit if there are pending changes try { const { stdout: status } = await execAsync('git status --porcelain'); if (status.trim()) { console.log('πŸ’Ύ Saving final changes...'); await execAsync('./auto-commit.sh commit "Final auto-save before shutdown"'); } } catch (error) { console.error('❌ Error during shutdown save:', error.message); } console.log('πŸ‘‹ Auto-commit watcher stopped'); process.exit(0); }); // Start the watcher const watcher = new AutoCommitWatcher(); watcher.start(); // Create restore point on startup setTimeout(() => { watcher.createRestorePoint('Session start'); }, 5000); // Create periodic restore points (every 30 minutes) setInterval(() => { watcher.createRestorePoint('Periodic backup'); }, 30 * 60 * 1000); console.log('πŸš€ Auto-commit system ready! Press Ctrl+C to stop.'); console.log('πŸ“‹ Commands available:'); console.log(' ./auto-commit.sh commit [message]'); console.log(' ./auto-commit.sh restore-point [description]'); console.log(' ./auto-commit.sh list'); console.log(' ./auto-commit.sh cleanup');