#!/usr/bin/env node /** * Map local test domains -> 127.0.0.1 for dev (macOS hosts + per-domain resolver). * * Usage: * sudo node scripts/setup-local-test-dns.mjs * sudo node scripts/setup-local-test-dns.mjs --remove */ import fs from 'node:fs'; import path from 'node:path'; import { execSync } from 'node:child_process'; import { DNS_MARKER, DNS_PORT, LOCAL_IP, TEST_HOSTS } from './local-test-config.mjs'; const remove = process.argv.includes('--remove'); const hostsPath = '/etc/hosts'; const resolverDir = '/etc/resolver'; const resolverBody = `nameserver 127.0.0.1\nport ${DNS_PORT}\n# ${DNS_MARKER}\n`; function flushDnsCache() { try { execSync('dscacheutil -flushcache', { stdio: 'ignore' }); execSync('killall -HUP mDNSResponder', { stdio: 'ignore' }); } catch { // best effort } } function hostLine(host) { return `${LOCAL_IP} ${host} # ${DNS_MARKER}`; } function removeHostLines(lines, host) { return lines.filter( (line) => !line.includes(DNS_MARKER) && !new RegExp(`\\s${host.replace('.', '\\.')}(\\s|$)`).test(line), ); } function hostReady(lines, host) { return lines.some( (line) => (line.includes(DNS_MARKER) && line.includes(host)) || new RegExp(`\\s${host.replace('.', '\\.')}(\\s|$)`).test(line), ); } if (remove) { if (process.getuid?.() !== 0) { console.log('需要 root 权限,请执行:'); console.log(` sudo node ${process.argv[1]} --remove`); process.exit(1); } let lines = fs.readFileSync(hostsPath, 'utf8').split('\n'); for (const host of TEST_HOSTS) { lines = removeHostLines(lines, host); try { if (fs.existsSync(path.join(resolverDir, host))) fs.unlinkSync(path.join(resolverDir, host)); } catch { // ignore } } execSync(`tee ${hostsPath}`, { input: `${lines.join('\n').replace(/\n?$/, '\n')}`, stdio: ['pipe', 'inherit', 'inherit'], }); flushDnsCache(); console.log(`已移除本地测试域名:${TEST_HOSTS.join(', ')}`); process.exit(0); } const currentLines = fs.readFileSync(hostsPath, 'utf8').split('\n'); const allReady = TEST_HOSTS.every((host) => { const resolverPath = path.join(resolverDir, host); const resolverReady = fs.existsSync(resolverPath) && fs.readFileSync(resolverPath, 'utf8').trim() === resolverBody.trim(); return hostReady(currentLines, host) && resolverReady; }); if (allReady) { console.log(`本地测试域名已配置:${TEST_HOSTS.join(', ')}`); process.exit(0); } if (process.getuid?.() !== 0) { console.log('需要 root 权限写入 /etc/hosts 和 /etc/resolver,请执行:'); console.log(` sudo pnpm setup:local-dns`); process.exit(1); } let lines = currentLines; for (const host of TEST_HOSTS) { if (!hostReady(lines, host)) { lines = lines.filter((line) => !line.includes(DNS_MARKER) || !line.includes(host)); lines.push(hostLine(host)); console.log(`已添加 hosts:${hostLine(host)}`); } else { console.log(`${host} 已在 /etc/hosts 中指向 ${LOCAL_IP}`); } } execSync(`tee ${hostsPath}`, { input: `${lines.join('\n').replace(/\n?$/, '\n')}`, stdio: ['pipe', 'inherit', 'inherit'], }); fs.mkdirSync(resolverDir, { recursive: true }); for (const host of TEST_HOSTS) { const resolverPath = path.join(resolverDir, host); fs.writeFileSync(resolverPath, resolverBody, 'utf8'); console.log(`已添加 resolver:${resolverPath} -> 127.0.0.1:${DNS_PORT}`); } flushDnsCache(); console.log(''); console.log('下一步:'); console.log(' sudo pnpm setup:local-tls # 生成本地 HTTPS 证书'); console.log(' pnpm dev # 启动全栈'); console.log(' sudo pnpm dev:local-proxy # HTTPS :443 统一入口');