diff --git a/.changeset/early-pandas-teach.md b/.changeset/early-pandas-teach.md new file mode 100644 index 0000000000..abc23ded3c --- /dev/null +++ b/.changeset/early-pandas-teach.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix `astro preview` port retry logic diff --git a/packages/astro/src/core/preview/index.ts b/packages/astro/src/core/preview/index.ts index 221046d39a..b39bd009ba 100644 --- a/packages/astro/src/core/preview/index.ts +++ b/packages/astro/src/core/preview/index.ts @@ -38,38 +38,55 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO }).pipe(res); }); - // Start listening on `hostname:port`. let port = config.devOptions.port; const { hostname } = config.devOptions; - await new Promise((resolve, reject) => { - const onError = (err: NodeJS.ErrnoException) => { - if (err.code && err.code === 'EADDRINUSE') { - info(logging, 'astro', msg.portInUse({ port })); - port++; - } else { - error(logging, 'preview', err.stack); - server.removeListener('error', onError); - reject(err); - } - }; + let httpServer: http.Server; - server - .listen(port, hostname, () => { - info(logging, 'preview', msg.devStart({ startupTime: performance.now() - startServerTime })); - info(logging, 'preview', msg.devHost({ host: `http://${hostname}:${port}${base}` })); - resolve(server); - }) - .on('error', (err: NodeJS.ErrnoException) => { - process.exit(1); - }); - }); + /** Expose dev server to `port` */ + function startServer(timerStart: number): Promise { + let showedPortTakenMsg = false; + let showedListenMsg = false; + return new Promise((resolve, reject) => { + const listen = () => { + httpServer = server.listen(port, hostname, () => { + if (!showedListenMsg) { + info(logging, 'astro', msg.devStart({ startupTime: performance.now() - timerStart })); + info(logging, 'astro', msg.devHost({ host: `http://${hostname}:${port}${base}` })); + } + showedListenMsg = true; + resolve(); + }); + httpServer?.on('error', onError); + }; + + const onError = (err: NodeJS.ErrnoException) => { + if (err.code && err.code === 'EADDRINUSE') { + if (!showedPortTakenMsg) { + info(logging, 'astro', msg.portInUse({ port })); + showedPortTakenMsg = true; // only print this once + } + port++; + return listen(); // retry + } else { + error(logging, 'astro', err.stack); + httpServer?.removeListener('error', onError); + reject(err); // reject + } + }; + + listen(); + }); + } + + // Start listening on `hostname:port`. + await startServer(startServerTime); return { hostname, port, - server, + server: httpServer!, stop: async () => { - server.close(); + httpServer.close(); }, }; }