#!/usr/bin/env bun /** * Sanity check: every IP in `src/simulator-ips.ts` is referenced by * at least one non-code config file (Knot zone, unbound conf, shell * entrypoint). * * Why this exists: the e2e simulator IPs (`100.64.0.{53,54,55,56,57,100}`) * appear in code (TS) AND in non-code config files (Knot zones, shell * scripts). Code uses the canonical constants in `src/simulator-ips.ts`. * Non-code files hard-code the values because Knot zones and bash * scripts can't import TypeScript. This check fails CI when someone * renumbers a constant in code without updating the matching * hard-coded reference in a zone file or script — the cheapest way to * catch the inevitable "I changed it in one place" drift. * * Usage: * bun run packages/e2e/scripts/check-simulator-ips.ts * * Exits 0 on clean, 1 on drift. */ import { execSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { SIMULATOR_IPS } from '../src/simulator-ips'; const E2E_ROOT = join(__dirname, '..'); function listScannedFiles(): string[] { const find = execSync( `find ${E2E_ROOT}/config/dns ${E2E_ROOT}/config/resolver ${E2E_ROOT}/config/routing -type f \\( -name '*.zone' -o -name '*.conf' -o -name '*.sh' \\) 2>/dev/null`, { encoding: 'utf-8' }, ); return find.split('\n').filter(Boolean); } interface IpReference { ip: string; hits: Array<{ file: string; line: number; context: string }>; } function scan(): Map { const result = new Map(); for (const [name, ip] of Object.entries(SIMULATOR_IPS)) { result.set(name, { ip, hits: [] }); } const escapedIps = Object.values(SIMULATOR_IPS).map((ip) => ip.replace(/\./g, '\\.')); const pattern = new RegExp(`\\b(${escapedIps.join('|')})\\b`, 'g'); for (const file of listScannedFiles()) { let content: string; try { content = readFileSync(file, 'utf-8'); } catch { continue; } const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const matches = lines[i].matchAll(pattern); for (const m of matches) { const matchedIp = m[1]; for (const [, entry] of result) { if (entry.ip === matchedIp) { entry.hits.push({ file, line: i + 1, context: lines[i].trim() }); break; } } } } } return result; } function main(): void { const refs = scan(); const orphans = [...refs.entries()].filter(([, e]) => e.hits.length === 0); if (orphans.length === 0) { console.log('✓ All simulator IPs are referenced by at least one non-code config file.'); console.log(` Canonical entries (${refs.size}):`); for (const [name, e] of refs) { console.log(` ${name.padEnd(20)} ${e.ip} (${e.hits.length} reference(s))`); } process.exit(0); } console.error( `✗ ${orphans.length} canonical simulator IP(s) have no references in config files:`, ); for (const [name, e] of orphans) { console.error(` ${name.padEnd(20)} ${e.ip}`); } console.error(''); console.error('Either:'); console.error(' - The IP was renumbered in src/simulator-ips.ts but the zone/conf/script'); console.error(' files still hold the old value (search for the old IP and update).'); console.error(' - The simulator was removed but the constant lingers (delete it).'); console.error(' - The IP is genuinely unused outside code — add a `# canonical` comment'); console.error(' in any config file to satisfy the check, or remove the constant.'); process.exit(1); } main();