#!/usr/bin/env node /** * Compare endpoints from JSON export with NPMplus configuration * Identifies missing domains, mismatches, and discrepancies */ import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const PROJECT_ROOT = join(__dirname, '..'); // Load endpoints JSON const endpointsData = JSON.parse( readFileSync(join(PROJECT_ROOT, 'reports', 'endpoints-export.json'), 'utf8') ); // NPMplus configuration from configure-npmplus-domains.js const NPMPLUS_DOMAINS = [ // sankofa.nexus zone (placeholder - routes to Blockscout) { domain: 'sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, { domain: 'phoenix.sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, { domain: 'the-order.sankofa.nexus', target: 'http://192.168.11.140:80', websocket: false }, // d-bis.org zone - RPC endpoints (UPDATED IPs) { domain: 'explorer.d-bis.org', target: 'http://192.168.11.140:4000', websocket: false }, { domain: 'rpc-http-pub.d-bis.org', target: 'http://192.168.11.221:8545', websocket: true }, { domain: 'rpc-ws-pub.d-bis.org', target: 'http://192.168.11.221:8546', websocket: true }, { domain: 'rpc-http-prv.d-bis.org', target: 'http://192.168.11.211:8545', websocket: true }, { domain: 'rpc-ws-prv.d-bis.org', target: 'http://192.168.11.211:8546', websocket: true }, { domain: 'dbis-admin.d-bis.org', target: 'http://192.168.11.130:80', websocket: false }, { domain: 'dbis-api.d-bis.org', target: 'http://192.168.11.155:3000', websocket: false }, { domain: 'dbis-api-2.d-bis.org', target: 'http://192.168.11.156:3000', websocket: false }, { domain: 'secure.d-bis.org', target: 'http://192.168.11.130:80', websocket: false }, // mim4u.org zone { domain: 'mim4u.org', target: 'http://192.168.11.36:80', websocket: false }, { domain: 'secure.mim4u.org', target: 'http://192.168.11.36:80', websocket: false }, { domain: 'training.mim4u.org', target: 'http://192.168.11.36:80', websocket: false }, // defi-oracle.io zone - ThirdWeb RPC { domain: 'rpc.public-0138.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, { domain: 'rpc.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, { domain: 'wss.defi-oracle.io', target: 'https://192.168.11.240:443', websocket: true }, ]; // Extract domains from endpoints JSON const endpointDomains = new Map(); for (const endpoint of endpointsData) { if (endpoint.domain && endpoint.domain.trim() !== '') { const domains = endpoint.domain.split(',').map(d => d.trim()); for (const domain of domains) { if (!endpointDomains.has(domain)) { endpointDomains.set(domain, []); } endpointDomains.get(domain).push({ vmid: endpoint.vmid, ip: endpoint.ip, port: endpoint.port, protocol: endpoint.protocol, service: endpoint.service, status: endpoint.status, endpoint: endpoint.endpoint }); } } } // Parse NPMplus targets const npmplusMap = new Map(); for (const config of NPMPLUS_DOMAINS) { const url = new URL(config.target); npmplusMap.set(config.domain, { target: config.target, ip: url.hostname, port: url.port || (url.protocol === 'https:' ? '443' : '80'), protocol: url.protocol.replace(':', ''), websocket: config.websocket }); } // Compare const comparison = { matches: [], mismatches: [], missingInNPMplus: [], missingInEndpoints: [], notes: [] }; // Check domains from endpoints for (const [domain, endpoints] of endpointDomains) { const npmplusConfig = npmplusMap.get(domain); if (!npmplusConfig) { comparison.missingInNPMplus.push({ domain, endpoints }); continue; } // Find matching endpoint const matchingEndpoint = endpoints.find(ep => ep.ip === npmplusConfig.ip && ep.port === npmplusConfig.port ); if (matchingEndpoint) { comparison.matches.push({ domain, npmplus: npmplusConfig, endpoint: matchingEndpoint }); } else { comparison.mismatches.push({ domain, npmplus: npmplusConfig, endpoints: endpoints, issue: `IP/Port mismatch: NPMplus targets ${npmplusConfig.ip}:${npmplusConfig.port}, but endpoints show ${endpoints.map(e => `${e.ip}:${e.port}`).join(', ')}` }); } } // Check domains in NPMplus but not in endpoints for (const [domain, config] of npmplusMap) { if (!endpointDomains.has(domain)) { comparison.missingInEndpoints.push({ domain, npmplus: config }); } } // Generate report console.log(''); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('📊 Endpoints vs NPMplus Comparison Report'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log(`📋 Summary:`); console.log(` ✅ Matches: ${comparison.matches.length}`); console.log(` ⚠️ Mismatches: ${comparison.mismatches.length}`); console.log(` ❌ Missing in NPMplus: ${comparison.missingInNPMplus.length}`); console.log(` ❌ Missing in Endpoints: ${comparison.missingInEndpoints.length}`); console.log(''); // Matches if (comparison.matches.length > 0) { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(`✅ MATCHES (${comparison.matches.length})`); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); for (const match of comparison.matches) { console.log(` ${match.domain}`); console.log(` NPMplus: ${match.npmplus.target} ${match.npmplus.websocket ? '(WebSocket ✓)' : ''}`); console.log(` Endpoint: ${match.endpoint.endpoint} (VMID ${match.endpoint.vmid}, ${match.endpoint.service})`); console.log(''); } } // Mismatches if (comparison.mismatches.length > 0) { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(`⚠️ MISMATCHES (${comparison.mismatches.length})`); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); for (const mismatch of comparison.mismatches) { console.log(` ${mismatch.domain}`); console.log(` NPMplus: ${mismatch.npmplus.target}`); console.log(` Issue: ${mismatch.issue}`); console.log(` Available endpoints:`); for (const ep of mismatch.endpoints) { console.log(` - ${ep.endpoint} (VMID ${ep.vmid}, ${ep.service}, ${ep.status})`); } console.log(''); } } // Missing in NPMplus if (comparison.missingInNPMplus.length > 0) { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(`❌ MISSING IN NPMPLUS (${comparison.missingInNPMplus.length})`); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log(' These domains exist in endpoints but are not configured in NPMplus:'); console.log(''); for (const missing of comparison.missingInNPMplus) { console.log(` ${missing.domain}`); for (const ep of missing.endpoints) { console.log(` - ${ep.endpoint} (VMID ${ep.vmid}, ${ep.service}, ${ep.status})`); } console.log(''); } } // Missing in Endpoints if (comparison.missingInEndpoints.length > 0) { console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(`❌ MISSING IN ENDPOINTS (${comparison.missingInEndpoints.length})`); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(''); console.log(' These domains are configured in NPMplus but not found in endpoints:'); console.log(''); for (const missing of comparison.missingInEndpoints) { console.log(` ${missing.domain}`); console.log(` NPMplus target: ${missing.npmplus.target}`); console.log(''); } } // Export JSON const fs = await import('fs'); const comparisonFile = join(PROJECT_ROOT, 'reports', 'endpoints-npmplus-comparison.json'); fs.writeFileSync( comparisonFile, JSON.stringify(comparison, null, 2) ); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(`📄 Detailed comparison saved to: reports/endpoints-npmplus-comparison.json`); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('');