Skip to main content
Glama
acknowledgment.ts8.76 kB
/** * JIT Safety Acknowledgment Module * * Provides first-sync safety prompts that appear when users run * `wpnav sync` for the first time. Stores acknowledgment to avoid * prompting on subsequent syncs. * * @package WP_Navigator_Pro * @since 2.4.0 */ import * as fs from 'fs'; import * as path from 'path'; import { confirmPrompt } from '../tui/prompts.js'; import { warning, info, newline, box, colorize, list } from '../tui/components.js'; // ============================================================================= // Types // ============================================================================= /** * Safety acknowledgment state persisted between sessions */ export interface SafetyState { /** Whether first sync safety has been acknowledged */ first_sync_acknowledged: boolean; /** When the acknowledgment was made (ISO timestamp) */ acknowledged_at?: string; } /** * Result of safety acknowledgment check */ export interface SafetyCheckResult { /** Whether the safety prompt was shown */ shown: boolean; /** Whether user confirmed to continue */ confirmed: boolean; /** Whether user wants to create a rollback point */ createRollback: boolean; /** Reason prompt was skipped (if shown=false) */ skipReason?: 'already_acknowledged' | 'yes_flag' | 'non_interactive' | 'dry_run'; } /** * Options for the safety check */ export interface SafetyCheckOptions { /** Project directory */ projectDir: string; /** Whether to skip confirmation (--yes flag) */ skipConfirm: boolean; /** Whether this is a Pro edition install */ isPro: boolean; /** Site name for display */ siteName?: string; /** Whether this is a dry-run (skip prompts) */ dryRun?: boolean; /** Whether running in interactive mode */ isInteractive?: boolean; } // ============================================================================= // Constants // ============================================================================= /** Path to state file within project */ const STATE_FILE_NAME = 'state.json'; const STATE_DIR_NAME = '.wpnav'; // ============================================================================= // State Management // ============================================================================= /** * Get path to state file */ function getStatePath(projectDir: string): string { return path.join(projectDir, STATE_DIR_NAME, STATE_FILE_NAME); } /** * Load safety acknowledgment state from file */ export function loadSafetyState(projectDir: string): SafetyState { const statePath = getStatePath(projectDir); try { if (fs.existsSync(statePath)) { const content = fs.readFileSync(statePath, 'utf-8'); const state = JSON.parse(content); // Handle both top-level and nested safety state const safety = state.safety || state; return { first_sync_acknowledged: safety.first_sync_acknowledged ?? false, acknowledged_at: safety.acknowledged_at, }; } } catch { // Ignore errors, return default state } return { first_sync_acknowledged: false }; } /** * Save safety acknowledgment state to file * * Merges with existing state to preserve other state fields * (like backup reminder state). */ export function saveSafetyState(projectDir: string, safetyState: SafetyState): void { const statePath = getStatePath(projectDir); const stateDir = path.dirname(statePath); try { // Read existing state let existingState: Record<string, unknown> = {}; if (fs.existsSync(statePath)) { const content = fs.readFileSync(statePath, 'utf-8'); existingState = JSON.parse(content); } // Merge safety state const newState = { ...existingState, safety: { first_sync_acknowledged: safetyState.first_sync_acknowledged, acknowledged_at: safetyState.acknowledged_at, }, }; // Ensure directory exists if (!fs.existsSync(stateDir)) { fs.mkdirSync(stateDir, { recursive: true }); } fs.writeFileSync(statePath, JSON.stringify(newState, null, 2), 'utf-8'); } catch { // Ignore errors - state is non-critical } } /** * Reset safety state (for testing) */ export function resetSafetyState(projectDir: string): void { const statePath = getStatePath(projectDir); try { if (fs.existsSync(statePath)) { const content = fs.readFileSync(statePath, 'utf-8'); const state = JSON.parse(content); // Remove safety section delete state.safety; fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf-8'); } } catch { // Ignore errors } } // ============================================================================= // Safety Prompt UI // ============================================================================= /** * Display the safety prompt and get user confirmation */ export async function showSafetyPrompt(options: { isPro: boolean; siteName?: string; isInteractive?: boolean; }): Promise<{ confirmed: boolean; createRollback: boolean }> { const { isPro, siteName, isInteractive = true } = options; newline(); box('First Sync Safety Check', { title: 'Important' }); newline(); const siteDisplay = siteName || 'your WordPress site'; warning(`You're about to sync changes to ${colorize(siteDisplay, 'bold')}`); newline(); info('Before proceeding, please confirm:'); list([ 'You have a recent backup of your WordPress site', 'You understand that this will modify content on the site', 'You have reviewed the changes with `wpnav diff` first', ]); newline(); if (!isInteractive) { // Non-interactive mode, proceed without prompts return { confirmed: true, createRollback: false }; } // Ask about backup const hasBackup = await confirmPrompt({ message: 'Have you made a recent backup?', defaultValue: false, }); if (!hasBackup) { newline(); warning('We strongly recommend making a backup before syncing.'); info( 'You can use your hosting provider, a plugin like UpdraftPlus, or export via Tools > Export.' ); newline(); const continueAnyway = await confirmPrompt({ message: 'Continue anyway?', defaultValue: false, }); if (!continueAnyway) { return { confirmed: false, createRollback: false }; } } // Offer rollback point for Pro users let createRollback = false; if (isPro) { newline(); info('Pro Feature: WP Navigator can create a rollback point before syncing.'); info('This allows you to restore your site if something goes wrong.'); newline(); createRollback = await confirmPrompt({ message: 'Create a rollback point before syncing?', defaultValue: true, }); } return { confirmed: true, createRollback }; } // ============================================================================= // Main Entry Point // ============================================================================= /** * Check and display safety acknowledgment if needed * * This should be called BEFORE sync operations. * * @param options - Safety check options * @returns Result indicating if sync should proceed and if rollback point should be created */ export async function checkSafetyAcknowledgment( options: SafetyCheckOptions ): Promise<SafetyCheckResult> { const { projectDir, skipConfirm, isPro, siteName, dryRun = false, isInteractive = true, } = options; // Skip for dry-run mode if (dryRun) { return { shown: false, confirmed: true, createRollback: false, skipReason: 'dry_run', }; } // Skip if --yes flag is set if (skipConfirm) { return { shown: false, confirmed: true, createRollback: false, skipReason: 'yes_flag', }; } // Load state const state = loadSafetyState(projectDir); // Skip if already acknowledged if (state.first_sync_acknowledged) { return { shown: false, confirmed: true, createRollback: false, skipReason: 'already_acknowledged', }; } // Skip in non-interactive mode (but log warning) if (!isInteractive) { return { shown: false, confirmed: true, createRollback: false, skipReason: 'non_interactive', }; } // Show safety prompt const { confirmed, createRollback } = await showSafetyPrompt({ isPro, siteName, isInteractive, }); // Save acknowledgment if confirmed if (confirmed) { const newState: SafetyState = { first_sync_acknowledged: true, acknowledged_at: new Date().toISOString(), }; saveSafetyState(projectDir, newState); } return { shown: true, confirmed, createRollback, }; }

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