#!/usr/bin/env node /** * 启动文档站 dev server,启动前挑一个真正空闲的端口。 * * 为什么不用 Nuxt 自带的端口回退:Nuxt 靠“试绑 host”判断端口是否空闲,而 Windows 上 Node * 默认开启 SO_REUSEADDR,**允许多个进程绑定同一个 addr:port**,于是“能不能绑上”根本判断不出 * 冲突 —— 3000 已被别的服务占用时,探测仍然成功,Nuxt 就继续用 3000。 * * 因此这里改用“实际建立 TCP 连接”来判断:只要 127.0.0.1 或 ::1 任一能连上,就说明该端口 * 正在被监听(可覆盖占用方绑在 0.0.0.0 / 127.0.0.1 / ::1 的所有常见情况),然后依次向上找。 * * 起始端口:DOCS_PORT > PORT > 3000。 */ import { spawn } from 'node:child_process' import { connect } from 'node:net' const start = Number(process.env.DOCS_PORT || process.env.PORT || 3000) const MAX_TRIES = 100 /** 能否连接到 host:port(能连上即为“正在被监听”) */ function reachable(port, host) { return new Promise((resolve) => { const socket = connect({ port, host }) const done = (busy) => { socket.destroy() resolve(busy) } socket.setTimeout(400) socket.once('connect', () => done(true)) socket.once('timeout', () => done(false)) socket.once('error', () => done(false)) }) } async function isBusy(port) { const [v4, v6] = await Promise.all([reachable(port, '127.0.0.1'), reachable(port, '::1')]) return v4 || v6 } let port = start while (port < start + MAX_TRIES && (await isBusy(port))) port++ if (port !== start) console.log(`端口 ${start} 已被占用,改用 ${port}`) const child = spawn('nuxt', ['dev', '--port', String(port)], { stdio: 'inherit', shell: true, }) child.on('exit', (code) => process.exit(code ?? 0))