← back to Dear Bubbe Nextjs

archived/tests/purge-cache-puppeteer.js

254 lines

#!/usr/bin/env node

/**
 * Cloudflare Cache Purge with Puppeteer
 * Automates login and cache clearing using browser automation
 */

require('dotenv').config({ path: '.env.local' });
const puppeteer = require('puppeteer');

const CONFIG = {
  email: process.env.CLOUDFLARE_EMAIL,
  password: process.env.CLOUDFLARE_PASSWORD,
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
  domain: process.env.CLOUDFLARE_DOMAIN,
  headless: true, // Set to false for debugging
  timeout: 60000,
};

// Validate environment variables
function validateConfig() {
  const missing = [];
  if (!CONFIG.email) missing.push('CLOUDFLARE_EMAIL');
  if (!CONFIG.password) missing.push('CLOUDFLARE_PASSWORD');
  if (!CONFIG.accountId) missing.push('CLOUDFLARE_ACCOUNT_ID');
  if (!CONFIG.domain) missing.push('CLOUDFLARE_DOMAIN');

  if (missing.length > 0) {
    console.error('❌ Missing required environment variables in .env.local:');
    missing.forEach(v => console.error(`   - ${v}`));
    process.exit(1);
  }
}

async function purgeCloudflareCache() {
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('  Cloudflare Cache Purge (Puppeteer)');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('');

  validateConfig();

  const browser = await puppeteer.launch({
    headless: CONFIG.headless,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
      '--disable-gpu',
    ],
  });

  let page;

  try {
    page = await browser.newPage();

    // Set viewport
    await page.setViewport({ width: 1920, height: 1080 });

    // Set longer timeout
    page.setDefaultTimeout(CONFIG.timeout);

    console.log('→ Navigating to Cloudflare login...');
    await page.goto('https://dash.cloudflare.com/login', {
      waitUntil: 'networkidle2',
    });

    console.log('→ Entering credentials...');

    // Wait for email input and enter email
    await page.waitForSelector('input[type="email"]');
    await page.type('input[type="email"]', CONFIG.email, { delay: 50 });

    // Wait for password input and enter password
    await page.waitForSelector('input[type="password"]');
    await page.type('input[type="password"]', CONFIG.password, { delay: 50 });

    console.log('→ Submitting login form...');

    // Click login button
    await page.click('button[type="submit"]');

    // Wait for navigation after login
    console.log('→ Waiting for login to complete...');
    await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 30000 });

    // Check if we need to handle 2FA or security challenge
    const currentUrl = page.url();
    if (currentUrl.includes('challenge') || currentUrl.includes('verify')) {
      console.log('⚠️  Security challenge detected!');
      console.log('   You may need to manually verify the login.');
      console.log('   Opening browser in non-headless mode...');

      // If headless, we need to abort and tell user to run with headless: false
      if (CONFIG.headless) {
        throw new Error('Security challenge detected. Please run with headless: false to complete verification manually.');
      }

      // Wait for user to complete challenge
      console.log('   Please complete the security challenge in the browser...');
      await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 120000 });
    }

    console.log('✓ Login successful');

    // Navigate to the domain's caching page
    const cachingUrl = `https://dash.cloudflare.com/${CONFIG.accountId}/${CONFIG.domain}/caching/configuration`;
    console.log(`→ Navigating to caching configuration...`);
    await page.goto(cachingUrl, { waitUntil: 'networkidle2' });

    // Wait a moment for the page to fully load
    await page.waitForTimeout(2000);

    console.log('→ Looking for "Purge Everything" button...');

    // Try multiple selectors for the purge button
    const purgeButtonSelectors = [
      'button[data-testid="purge-everything-btn"]',
      'button:has-text("Purge Everything")',
      'button >> text=Purge Everything',
      'button >> text=Purge',
    ];

    let purgeButton = null;
    for (const selector of purgeButtonSelectors) {
      try {
        purgeButton = await page.$(selector);
        if (purgeButton) {
          console.log(`✓ Found button with selector: ${selector}`);
          break;
        }
      } catch (e) {
        // Continue to next selector
      }
    }

    if (!purgeButton) {
      // Try finding by text content
      const buttons = await page.$$('button');
      for (const button of buttons) {
        const text = await page.evaluate(el => el.textContent, button);
        if (text && text.includes('Purge Everything')) {
          purgeButton = button;
          console.log('✓ Found button by text content');
          break;
        }
      }
    }

    if (!purgeButton) {
      console.log('⚠️  Could not find "Purge Everything" button');
      console.log('   Taking screenshot for debugging...');
      await page.screenshot({ path: '/tmp/cloudflare-purge-error.png', fullPage: true });
      throw new Error('Purge Everything button not found. Screenshot saved to /tmp/cloudflare-purge-error.png');
    }

    console.log('→ Clicking "Purge Everything" button...');
    await purgeButton.click();

    // Wait for confirmation dialog
    await page.waitForTimeout(1000);

    console.log('→ Looking for confirmation button...');

    // Try to find and click the confirmation button
    const confirmSelectors = [
      'button[data-testid="confirmation-modal-confirm"]',
      'button >> text=Purge',
      'button >> text=Confirm',
      'button:has-text("Purge")',
    ];

    let confirmButton = null;
    for (const selector of confirmSelectors) {
      try {
        confirmButton = await page.$(selector);
        if (confirmButton) {
          console.log(`✓ Found confirmation with selector: ${selector}`);
          break;
        }
      } catch (e) {
        // Continue
      }
    }

    if (confirmButton) {
      console.log('→ Confirming purge...');
      await confirmButton.click();
      await page.waitForTimeout(2000);
      console.log('✓ Cache purge initiated');
    } else {
      console.log('⚠️  Could not find confirmation button, purge may require manual confirmation');
      await page.screenshot({ path: '/tmp/cloudflare-confirm-error.png', fullPage: true });
    }

    // Check for success message
    await page.waitForTimeout(2000);
    const pageContent = await page.content();

    if (pageContent.includes('success') || pageContent.includes('purged') || pageContent.includes('cleared')) {
      console.log('✓ Cache purged successfully');
    } else {
      console.log('⚠️  Purge status unclear, please verify manually');
    }

    // Take final screenshot
    console.log('→ Taking final screenshot...');
    await page.screenshot({ path: '/tmp/cloudflare-purge-final.png', fullPage: true });
    console.log('✓ Screenshot saved to /tmp/cloudflare-purge-final.png');

  } catch (error) {
    console.error('');
    console.error('❌ Error during cache purge:');
    console.error('   ', error.message);

    if (page) {
      try {
        await page.screenshot({ path: '/tmp/cloudflare-error.png', fullPage: true });
        console.error('   Screenshot saved to /tmp/cloudflare-error.png');
      } catch (e) {
        // Ignore screenshot errors
      }
    }

    throw error;
  } finally {
    if (browser) {
      await browser.close();
    }
  }

  console.log('');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('  ✓ Puppeteer automation completed');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('');
}

// Run if called directly
if (require.main === module) {
  purgeCloudflareCache()
    .then(() => {
      console.log('✓ Success');
      process.exit(0);
    })
    .catch(error => {
      console.error('✗ Failed:', error.message);
      process.exit(1);
    });
}

module.exports = { purgeCloudflareCache };