UNPKG

mak3r-hub

Version:

Universal Claude Code force multiplier for website development

374 lines (311 loc) • 11.3 kB
const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const cron = require('node-cron'); const TemplateManager = require('./template-manager'); /** * Automated Template Update Scheduler * Handles scheduled template updates, monitoring, and notifications */ class TemplateScheduler { constructor() { this.templateManager = new TemplateManager(); this.schedulerConfig = path.join(__dirname, '..', 'config', 'template-scheduler.json'); this.activeJobs = new Map(); this.initialized = false; } /** * Initialize scheduler with configuration */ async initialize() { if (this.initialized) return; await fs.ensureDir(path.dirname(this.schedulerConfig)); // Create default configuration if it doesn't exist if (!await fs.pathExists(this.schedulerConfig)) { const defaultConfig = { schedules: { weekly: { cron: '0 9 * * 1', // Monday at 9 AM repositories: ['ConvertStar', 'MAK3R-HUB', 'SharpCET', 'Trinity'], auto_commit: false, enabled: true, last_run: null, next_run: null }, monthly: { cron: '0 10 1 * *', // 1st of month at 10 AM repositories: ['ConvertStar', 'MAK3R-HUB', 'SharpCET', 'Trinity'], auto_commit: false, enabled: false, last_run: null, next_run: null } }, notifications: { enabled: true, slack_webhook: null, email: null }, update_settings: { preserve_manual_edits: true, backup_before_update: true, rollback_on_failure: true, max_age_days: 30 } }; await fs.writeJson(this.schedulerConfig, defaultConfig, { spaces: 2 }); } this.initialized = true; await this.loadSchedules(); } /** * Load and activate schedules from configuration */ async loadSchedules() { const config = await fs.readJson(this.schedulerConfig); for (const [name, schedule] of Object.entries(config.schedules)) { if (schedule.enabled && cron.validate(schedule.cron)) { await this.scheduleJob(name, schedule); } } console.log(chalk.green(`āœ… Template scheduler initialized with ${this.activeJobs.size} active jobs`)); } /** * Schedule a template update job */ async scheduleJob(name, schedule) { // Stop existing job if running if (this.activeJobs.has(name)) { this.activeJobs.get(name).stop(); } const job = cron.schedule(schedule.cron, async () => { console.log(chalk.blue(`šŸ•’ Running scheduled template update: ${name}`)); await this.executeScheduledUpdate(name, schedule); }, { scheduled: true, timezone: 'America/New_York' // Adjust as needed }); this.activeJobs.set(name, job); // Update next run time await this.updateScheduleMetadata(name, { next_run: this.getNextRunTime(schedule.cron) }); console.log(chalk.cyan(`šŸ“… Scheduled ${name} template updates: ${schedule.cron}`)); } /** * Execute a scheduled template update */ async executeScheduledUpdate(scheduleName, schedule) { const startTime = new Date(); try { console.log(chalk.blue(`\nšŸš€ Template Update Execution: ${scheduleName}`)); console.log('═'.repeat(60)); // Update last run time await this.updateScheduleMetadata(scheduleName, { last_run: startTime.toISOString(), status: 'running' }); // Get current template status const scanResults = await this.templateManager.scanRepositories(); // Filter by scheduled repositories const targetRepos = schedule.repositories || []; const staleTemplates = []; for (const [repoName, repoData] of Object.entries(scanResults)) { if (targetRepos.includes(repoName) || targetRepos.includes('all')) { const staleFiles = repoData.files.filter(f => f.isStale); if (staleFiles.length > 0) { staleTemplates.push({ repo: repoName, files: staleFiles }); } } } if (staleTemplates.length === 0) { console.log(chalk.green('āœ… All templates are up to date')); await this.sendNotification(`Template Update Complete: ${scheduleName}`, { status: 'success', message: 'All templates are up to date', repositories: targetRepos, execution_time: Date.now() - startTime.getTime() }); return; } // Backup current templates if enabled if (schedule.backup_before_update !== false) { await this.backupTemplates(staleTemplates); } // Execute updates (placeholder for future implementation) const updateResults = await this.performTemplateUpdates(staleTemplates, schedule); // Commit changes if auto-commit is enabled if (schedule.auto_commit && updateResults.success) { await this.commitTemplateUpdates(updateResults, scheduleName); } // Update schedule metadata await this.updateScheduleMetadata(scheduleName, { status: 'completed', last_execution_time: Date.now() - startTime.getTime(), templates_updated: updateResults.updated_count, next_run: this.getNextRunTime(schedule.cron) }); // Send success notification await this.sendNotification(`Template Update Complete: ${scheduleName}`, { status: 'success', updated_templates: updateResults.updated_count, repositories: targetRepos, execution_time: Date.now() - startTime.getTime(), auto_committed: schedule.auto_commit && updateResults.success }); console.log(chalk.green(`āœ… Scheduled update completed: ${updateResults.updated_count} templates updated`)); } catch (error) { console.error(chalk.red(`āŒ Scheduled update failed: ${error.message}`)); // Update schedule metadata with error await this.updateScheduleMetadata(scheduleName, { status: 'failed', last_error: error.message, last_execution_time: Date.now() - startTime.getTime() }); // Send error notification await this.sendNotification(`Template Update Failed: ${scheduleName}`, { status: 'error', error: error.message, execution_time: Date.now() - startTime.getTime() }); } } /** * Backup templates before updating */ async backupTemplates(staleTemplates) { const backupDir = path.join(__dirname, '..', 'backups', 'templates', new Date().toISOString().split('T')[0]); await fs.ensureDir(backupDir); for (const { repo, files } of staleTemplates) { const repoBackupDir = path.join(backupDir, repo); await fs.ensureDir(repoBackupDir); for (const file of files) { const sourceFile = file.path; const backupFile = path.join(repoBackupDir, file.file); if (await fs.pathExists(sourceFile)) { await fs.copy(sourceFile, backupFile); } } } console.log(chalk.cyan(`šŸ’¾ Templates backed up to: ${backupDir}`)); } /** * Perform template updates (placeholder) */ async performTemplateUpdates(staleTemplates, schedule) { // This is a placeholder for the actual update implementation // In a real implementation, this would: // 1. Fetch latest templates from Trinity // 2. Preserve manual edits // 3. Apply updates safely // 4. Validate changes console.log(chalk.yellow('🚧 Template update implementation coming soon...')); let updatedCount = 0; for (const { files } of staleTemplates) { updatedCount += files.length; } return { success: true, updated_count: updatedCount, failed_count: 0, details: staleTemplates }; } /** * Commit template updates to git */ async commitTemplateUpdates(updateResults, scheduleName) { const { execSync } = require('child_process'); for (const { repo } of updateResults.details) { const repoPath = this.templateManager.repositories.find(r => r.includes(repo)); if (repoPath && await fs.pathExists(repoPath)) { try { // Stage template files execSync('git add *.md', { cwd: repoPath, stdio: 'inherit' }); // Commit with scheduler info const commitMessage = `šŸ¤– Automated template updates via ${scheduleName} Updated ${updateResults.updated_count} templates Scheduler: MAK3R-HUB Template Scheduler Execution: ${new Date().toISOString()} šŸš€ Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>`; execSync(`git commit -m "${commitMessage}"`, { cwd: repoPath, stdio: 'inherit' }); console.log(chalk.green(`āœ… Changes committed to ${repo}`)); } catch (error) { console.error(chalk.yellow(`āš ļø Failed to commit ${repo}: ${error.message}`)); } } } } /** * Send notifications */ async sendNotification(title, details) { const config = await fs.readJson(this.schedulerConfig); if (!config.notifications.enabled) return; const message = { title, timestamp: new Date().toISOString(), ...details }; // Log to console (always) console.log(chalk.blue(`šŸ“¬ Notification: ${title}`)); console.log(chalk.gray(JSON.stringify(details, null, 2))); // TODO: Implement actual notification channels (Slack, email, etc.) if (config.notifications.slack_webhook) { // Send to Slack } if (config.notifications.email) { // Send email } } /** * Update schedule metadata */ async updateScheduleMetadata(scheduleName, updates) { const config = await fs.readJson(this.schedulerConfig); if (config.schedules[scheduleName]) { Object.assign(config.schedules[scheduleName], updates); await fs.writeJson(this.schedulerConfig, config, { spaces: 2 }); } } /** * Get next run time for cron expression */ getNextRunTime(cronExpression) { // This is a simplified implementation // In production, use a proper cron parser like 'cron-parser' return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Next week } /** * Get scheduler status */ async getStatus() { if (!this.initialized) { await this.initialize(); } const config = await fs.readJson(this.schedulerConfig); return { initialized: this.initialized, active_jobs: this.activeJobs.size, schedules: config.schedules, notifications: config.notifications.enabled, update_settings: config.update_settings }; } /** * Stop all scheduled jobs */ stopAll() { for (const [name, job] of this.activeJobs) { job.stop(); console.log(chalk.yellow(`ā¹ļø Stopped scheduled job: ${name}`)); } this.activeJobs.clear(); console.log(chalk.green('āœ… All template update jobs stopped')); } } module.exports = TemplateScheduler;