Skip to main content
Glama
backup-reminder.ts9.16 kB
/** * Backup Reminder Module * * Implements backup reminder logic for the sync command based on * manifest safety settings. * * @package WP_Navigator_Pro * @since 1.2.0 */ import * as fs from 'fs'; import * as path from 'path'; import { getBackupReminders, getManifestSafety, type WPNavManifest, type BackupReminderFrequency, } from './manifest.js'; import type { SiteIndexSnapshot } from './snapshots/types.js'; import { confirmPrompt } from './cli/tui/prompts.js'; import { warning, info, newline, list, colorize, box, } from './cli/tui/components.js'; // ============================================================================= // Types // ============================================================================= /** * Backup reminder state persisted between sessions */ export interface BackupReminderState { /** Whether first sync has been acknowledged */ first_sync_acknowledged: boolean; /** Last time a daily reminder was shown (ISO timestamp) */ last_daily_reminder?: string; } /** * Result of backup reminder check */ export interface BackupReminderResult { /** Whether reminder was shown */ shown: boolean; /** Whether user confirmed to continue */ confirmed: boolean; /** Reason reminder was skipped (if shown=false) */ skipReason?: 'disabled' | 'never' | 'already_acknowledged' | 'daily_shown_recently' | 'yes_flag'; /** Detected backup plugins */ detectedPlugins?: string[]; } // ============================================================================= // Known Backup Plugins // ============================================================================= /** * Known backup plugin slugs and their display names */ const BACKUP_PLUGINS: Record<string, string> = { 'updraftplus': 'UpdraftPlus', 'updraftplus-premium': 'UpdraftPlus Premium', 'all-in-one-wp-migration': 'All-in-One WP Migration', 'backupbuddy': 'BackupBuddy', 'backwpup': 'BackWPup', 'duplicator': 'Duplicator', 'duplicator-pro': 'Duplicator Pro', 'jetpack': 'Jetpack Backup', 'vaultpress': 'VaultPress', 'wp-time-capsule': 'WP Time Capsule', 'blogvault-real-time-backup': 'BlogVault', 'backup-backup': 'Backup Migration', 'xcloner-backup-and-restore': 'XCloner', 'total-upkeep': 'Total Upkeep', }; // ============================================================================= // State Management // ============================================================================= /** * Get path to state file */ function getStatePath(projectDir: string): string { return path.join(projectDir, '.wpnav', 'state.json'); } /** * Load backup reminder state from file */ export function loadBackupState(projectDir: string): BackupReminderState { const statePath = getStatePath(projectDir); try { if (fs.existsSync(statePath)) { const content = fs.readFileSync(statePath, 'utf-8'); const state = JSON.parse(content); return { first_sync_acknowledged: state.first_sync_acknowledged ?? false, last_daily_reminder: state.last_daily_reminder, }; } } catch { // Ignore errors, return default state } return { first_sync_acknowledged: false }; } /** * Save backup reminder state to file */ export function saveBackupState(projectDir: string, state: BackupReminderState): void { const statePath = getStatePath(projectDir); const stateDir = path.dirname(statePath); try { if (!fs.existsSync(stateDir)) { fs.mkdirSync(stateDir, { recursive: true }); } fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf-8'); } catch { // Ignore errors - state is non-critical } } // ============================================================================= // Plugin Detection // ============================================================================= /** * Detect backup plugins from site index snapshot */ export function detectBackupPlugins(siteIndex?: SiteIndexSnapshot): string[] { if (!siteIndex?.plugins?.active) { return []; } const detected: string[] = []; for (const plugin of siteIndex.plugins.active) { const slug = plugin.slug.toLowerCase(); if (BACKUP_PLUGINS[slug]) { detected.push(BACKUP_PLUGINS[slug]); } } return detected; } /** * Try to load site index snapshot for backup plugin detection */ export function loadSiteIndexForPluginDetection(projectDir: string): SiteIndexSnapshot | undefined { const siteIndexPath = path.join(projectDir, 'snapshots', 'site_index.json'); try { if (fs.existsSync(siteIndexPath)) { const content = fs.readFileSync(siteIndexPath, 'utf-8'); return JSON.parse(content) as SiteIndexSnapshot; } } catch { // Ignore errors } return undefined; } // ============================================================================= // Reminder Logic // ============================================================================= /** * Check if daily reminder should be shown (more than 24h since last) */ function shouldShowDailyReminder(lastReminder?: string): boolean { if (!lastReminder) { return true; } const lastTime = new Date(lastReminder).getTime(); const now = Date.now(); const twentyFourHours = 24 * 60 * 60 * 1000; return now - lastTime > twentyFourHours; } /** * Determine if backup reminder should be shown based on frequency and state */ export function shouldShowReminder( frequency: BackupReminderFrequency, state: BackupReminderState ): { show: boolean; skipReason?: string } { switch (frequency) { case 'never': return { show: false, skipReason: 'never' }; case 'first_sync_only': if (state.first_sync_acknowledged) { return { show: false, skipReason: 'already_acknowledged' }; } return { show: true }; case 'daily': if (!shouldShowDailyReminder(state.last_daily_reminder)) { return { show: false, skipReason: 'daily_shown_recently' }; } return { show: true }; case 'always': return { show: true }; default: return { show: true }; } } /** * Display the backup reminder and get user confirmation */ export async function showBackupReminder( detectedPlugins: string[], isInteractive: boolean = true ): Promise<boolean> { newline(); box('Safety Check', { title: 'Backup Reminder' }); newline(); warning('We recommend making a backup before applying changes.'); newline(); if (detectedPlugins.length > 0) { console.error(colorize('Detected backup plugins (optional):', 'dim')); list(detectedPlugins); newline(); } info("If you haven't backed up recently, consider doing that now."); newline(); if (!isInteractive) { return true; // Non-interactive mode, assume yes } const confirmed = await confirmPrompt({ message: 'Continue with sync?', defaultValue: false, }); return confirmed; } // ============================================================================= // Main Entry Point // ============================================================================= /** * Check and display backup reminder if needed * * @param manifest - Loaded manifest * @param projectDir - Project directory * @param skipConfirm - Whether to skip confirmation (--yes flag) * @returns Result indicating if sync should proceed */ export async function checkBackupReminder( manifest: WPNavManifest | undefined, projectDir: string, skipConfirm: boolean ): Promise<BackupReminderResult> { // Get backup reminder settings const reminders = getBackupReminders(manifest); // If reminders disabled, skip if (!reminders.enabled || !reminders.before_sync) { return { shown: false, confirmed: true, skipReason: 'disabled', }; } // If --yes flag, skip reminder if (skipConfirm) { return { shown: false, confirmed: true, skipReason: 'yes_flag', }; } // Load state const state = loadBackupState(projectDir); // Check if should show based on frequency const { show, skipReason } = shouldShowReminder(reminders.frequency, state); if (!show) { return { shown: false, confirmed: true, skipReason: skipReason as BackupReminderResult['skipReason'], }; } // Try to detect backup plugins const siteIndex = loadSiteIndexForPluginDetection(projectDir); const detectedPlugins = detectBackupPlugins(siteIndex); // Show reminder const confirmed = await showBackupReminder(detectedPlugins, true); // Update state if (confirmed) { const newState: BackupReminderState = { ...state, first_sync_acknowledged: true, last_daily_reminder: new Date().toISOString(), }; saveBackupState(projectDir, newState); } return { shown: true, confirmed, detectedPlugins: detectedPlugins.length > 0 ? detectedPlugins : undefined, }; } /** * Reset backup reminder state (for testing) */ export function resetBackupState(projectDir: string): void { const statePath = getStatePath(projectDir); try { if (fs.existsSync(statePath)) { fs.unlinkSync(statePath); } } catch { // Ignore errors } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server