[cli] Poll the Dev Command server on both IPv4 and IPv6 in vc dev (#8542)

This change will allow the downstream Dev Command server be able to listen on either of `127.0.0.1` for IPv4 or `[::1]` for IPv6.

- Fixes #6639
- Fixes #8511
- Fixes #8121
- Closes #8512
- Closes #8384
This commit is contained in:
Nathan Rajlich
2022-09-09 11:57:50 -07:00
committed by GitHub
parent c9f7ca23a8
commit 5b36eaacff
3 changed files with 31 additions and 23 deletions

View File

@@ -153,7 +153,7 @@ export default class DevServer {
private filter: (path: string) => boolean; private filter: (path: string) => boolean;
private podId: string; private podId: string;
private devProcess?: ChildProcess; private devProcess?: ChildProcess;
private devProcessPort?: number; private devProcessOrigin?: string;
private devServerPids: Set<number>; private devServerPids: Set<number>;
private originalProjectSettings?: ProjectSettings; private originalProjectSettings?: ProjectSettings;
private projectSettings?: ProjectSettings; private projectSettings?: ProjectSettings;
@@ -1014,14 +1014,14 @@ export default class DevServer {
// Configure the server to forward WebSocket "upgrade" events to the proxy. // Configure the server to forward WebSocket "upgrade" events to the proxy.
this.server.on('upgrade', async (req, socket, head) => { this.server.on('upgrade', async (req, socket, head) => {
await this.startPromise; await this.startPromise;
if (!this.devProcessPort) { if (!this.devProcessOrigin) {
this.output.debug( this.output.debug(
`Detected "upgrade" event, but closing socket because no frontend dev server is running` `Detected "upgrade" event, but closing socket because no frontend dev server is running`
); );
socket.destroy(); socket.destroy();
return; return;
} }
const target = `http://127.0.0.1:${this.devProcessPort}`; const target = this.devProcessOrigin;
this.output.debug(`Detected "upgrade" event, proxying to ${target}`); this.output.debug(`Detected "upgrade" event, proxying to ${target}`);
this.proxy.ws(req, socket, head, { target }); this.proxy.ws(req, socket, head, { target });
}); });
@@ -1820,8 +1820,8 @@ export default class DevServer {
if (!match) { if (!match) {
// If the dev command is started, then proxy to it // If the dev command is started, then proxy to it
if (this.devProcessPort) { if (this.devProcessOrigin) {
const upstream = `http://127.0.0.1:${this.devProcessPort}`; const upstream = this.devProcessOrigin;
debug(`Proxying to frontend dev server: ${upstream}`); debug(`Proxying to frontend dev server: ${upstream}`);
// Add the Vercel platform proxy request headers // Add the Vercel platform proxy request headers
@@ -2000,7 +2000,7 @@ export default class DevServer {
// - when there is no asset // - when there is no asset
// - when the asset is not a Lambda (the dev server must take care of all static files) // - when the asset is not a Lambda (the dev server must take care of all static files)
if ( if (
this.devProcessPort && this.devProcessOrigin &&
(!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda')) (!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda'))
) { ) {
debug('Proxying to frontend dev server'); debug('Proxying to frontend dev server');
@@ -2012,14 +2012,7 @@ export default class DevServer {
} }
this.setResponseHeaders(res, requestId); this.setResponseHeaders(res, requestId);
return proxyPass( return proxyPass(req, res, this.devProcessOrigin, this, requestId, false);
req,
res,
`http://127.0.0.1:${this.devProcessPort}`,
this,
requestId,
false
);
} }
if (!foundAsset) { if (!foundAsset) {
@@ -2348,12 +2341,11 @@ export default class DevServer {
p.on('exit', (code, signal) => { p.on('exit', (code, signal) => {
this.output.debug(`Dev command exited with "${signal || code}"`); this.output.debug(`Dev command exited with "${signal || code}"`);
this.devProcessPort = undefined; this.devProcessOrigin = undefined;
}); });
await checkForPort(port, 1000 * 60 * 5); const devProcessHost = await checkForPort(port, 1000 * 60 * 5);
this.devProcessOrigin = `http://${devProcessHost}:${port}`;
this.devProcessPort = port;
} }
} }
@@ -2641,15 +2633,29 @@ function needsBlockingBuild(buildMatch: BuildMatch): boolean {
return typeof builder.shouldServe !== 'function'; return typeof builder.shouldServe !== 'function';
} }
async function checkForPort(port: number, timeout: number): Promise<void> { async function checkForPort(port: number, timeout: number): Promise<string> {
const opts = { host: '127.0.0.1' }; let host;
const start = Date.now(); const start = Date.now();
while (!(await isPortReachable(port, opts))) { while (!(host = await getReachableHostOnPort(port))) {
if (Date.now() - start > timeout) { if (Date.now() - start > timeout) {
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`); break;
} }
await sleep(100); await sleep(100);
} }
if (!host) {
throw new Error(`Detecting port ${port} timed out after ${timeout}ms`);
}
return host;
}
async function getReachableHostOnPort(port: number): Promise<string | false> {
const optsIpv4 = { host: '127.0.0.1' };
const optsIpv6 = { host: '::1' };
const results = await Promise.all([
isPortReachable(port, optsIpv6).then(r => r && `[${optsIpv6.host}]`),
isPortReachable(port, optsIpv4).then(r => r && optsIpv4.host),
]);
return results.find(Boolean) || false;
} }
function filterFrontendBuilds(build: Builder) { function filterFrontendBuilds(build: Builder) {

View File

@@ -1,7 +1,6 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --port $PORT",
"build": "vite build", "build": "vite build",
"serve": "vite preview" "serve": "vite preview"
}, },

View File

@@ -0,0 +1,3 @@
{
"devCommand": "vite --port $PORT --host ::1"
}