import axios from "axios"; import dns from "dns"; import { promisify } from "util"; import url from "url"; const resolve4 = promisify(dns.resolve4); // This script verifies that external logging and analytics APIs are reachable // from the deployment environment (which could be behind corporate firewalls or VPNs). const umamiEndpoint = process.env.UMAMI_API_ENDPOINT || "https://analytics.infra.mintel.me"; const sentryDsn = process.env.SENTRY_DSN || ""; async function checkUmami() { console.log(`\nšŸ” Checking Umami Analytics API Availability...`); console.log(` Endpoint: ${umamiEndpoint}`); try { // Umami usually exposes a /api/heartbeat or /api/health if we know the route. // Trying root or /api/auth/verify (which will give 401 but proves routing works). // A simple GET to the configured endpoint should return a 200 or 401, not a 5xx/timeout. const response = await axios.get( `${umamiEndpoint.replace(/\/$/, "")}/api/health`, { timeout: 5000, validateStatus: () => true, // Accept any status, we just want to know it's reachable and not 5xx }, ); // As long as it's not a 502/503/504 Bad Gateway/Timeout, the service is "up" from our perspective if (response.status >= 500) { throw new Error( `Umami API responded with server error HTTP ${response.status}`, ); } console.log(` āœ… Umami Analytics is reachable (HTTP ${response.status})`); return true; } catch (error) { const err = error as Error; // If /api/health fails completely, maybe try a DNS check as a fallback try { console.warn( ` āš ļø HTTP check failed, falling back to DNS resolution...`, ); const umamiHost = new url.URL(umamiEndpoint).hostname; await resolve4(umamiHost); console.log( ` āœ… Umami Analytics DNS resolved successfully (${umamiHost})`, ); return true; } catch (error) { const dnsErr = error as Error; console.error( ` āŒ CRITICAL: Umami Analytics is completely unreachable! ${err.message} | DNS: ${dnsErr.message}`, ); return false; } } } async function checkSentry() { console.log(`\nšŸ” Checking Glitchtip/Sentry Error Tracking Availability...`); if (!sentryDsn) { console.log(` ā„¹ļø No SENTRY_DSN provided in environment. Skipping.`); return true; } try { const parsedDsn = new url.URL(sentryDsn); const host = parsedDsn.hostname; console.log(` Host: ${host}`); // We do a DNS lookup to ensure the runner can actually resolve the tracking server const addresses = await resolve4(host); if (addresses && addresses.length > 0) { console.log(` āœ… Glitchtip/Sentry domain resolved: ${addresses[0]}`); // Optional: Quick TCP/HTTP check to the host root (Glitchtip usually runs on 80/443 root) try { const proto = parsedDsn.protocol || "https:"; await axios.get(`${proto}//${host}/api/0/`, { timeout: 5000, validateStatus: () => true, }); console.log(` āœ… Glitchtip/Sentry API root responds to HTTP.`); } catch { console.log( ` āš ļø Glitchtip/Sentry HTTP ping failed or timed out, but DNS is valid. Proceeding.`, ); } return true; } throw new Error("No IP addresses found for DSN host"); } catch (error) { const err = error as Error; console.error( ` āŒ CRITICAL: Glitchtip/Sentry DSN is invalid or hostname is unresolvable! ${err.message}`, ); return false; } } async function main() { console.log("šŸš€ Starting External API Connectivity Smoke Test..."); let hasErrors = false; const umamiOk = await checkUmami(); if (!umamiOk) hasErrors = true; const sentryOk = await checkSentry(); if (!sentryOk) hasErrors = true; if (hasErrors) { console.error( `\n🚨 POST-DEPLOY CHECK FAILED: One or more critical external APIs are unreachable.`, ); console.error( ` This might mean the deployment environment lacks outbound internet access, `, ); console.error( ` DNS is misconfigured, or the upstream services are down.`, ); process.exit(1); } console.log(`\nšŸŽ‰ SUCCESS: All required external APIs are reachable!`); process.exit(0); } main();