diff --git a/src/app.ts b/src/app.ts index 1f67789b460..0e57df4715c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -44,8 +44,80 @@ export type ListenOptions = & { remoteAddress?: string; }; +function createOnListen( + basePath: string, + options: ListenOptions, +): (localAddr: Deno.NetAddr) => void { + return (params) => { + // Don't spam logs with this on live deployments + if (DENO_DEPLOYMENT_ID) return; + + const pathname = basePath + "/"; + const protocol = "key" in options && options.key && options.cert + ? "https:" + : "http:"; + + let hostname = params.hostname; + // Windows being windows... + if ( + Deno.build.os === "windows" && + (hostname === "0.0.0.0" || hostname === "::") + ) { + hostname = "localhost"; + } + // Work around https://github.com/denoland/deno/issues/23650 + hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname; + + // deno-lint-ignore no-console + console.log(); + // deno-lint-ignore no-console + console.log( + colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121), + ); + const sep = options.remoteAddress ? "" : "\n"; + const space = options.remoteAddress ? " " : ""; + + const localLabel = colors.bold("Local:"); + const address = colors.cyan( + `${protocol}//${hostname}:${params.port}${pathname}`, + ); + // deno-lint-ignore no-console + console.log(` ${localLabel} ${space}${address}${sep}`); + if (options.remoteAddress) { + const remoteLabel = colors.bold("Remote:"); + const remoteAddress = colors.cyan(options.remoteAddress); + // deno-lint-ignore no-console + console.log(` ${remoteLabel} ${remoteAddress}\n`); + } + }; +} -Deno.serve; +async function listenOnFreePort( + options: ListenOptions, + handler: ( + request: Request, + info?: Deno.ServeHandlerInfo, + ) => Promise, +) { + // No port specified, check for a free port. Instead of picking just + // any port we'll check if the next one is free for UX reasons. + // That way the user only needs to increment a number when running + // multiple apps vs having to remember completely different ports. + let firstError = null; + for (let port = 8000; port < 8020; port++) { + try { + return await Deno.serve({ ...options, port }, handler); + } catch (err) { + if (err instanceof Deno.errors.AddrInUse) { + // Throw first EADDRINUSE error if no port is free + if (!firstError) firstError = err; + continue; + } + throw err; + } + } + throw firstError; +} export let getRouter: (app: App) => Router>; // deno-lint-ignore no-explicit-any @@ -248,81 +320,16 @@ export class App { async listen(options: ListenOptions = {}): Promise { if (!options.onListen) { - options.onListen = (params) => { - const pathname = (this.config.basePath) + "/"; - const protocol = "key" in options && options.key && options.cert - ? "https:" - : "http:"; - - let hostname = params.hostname; - // Windows being windows... - if ( - Deno.build.os === "windows" && - (hostname === "0.0.0.0" || hostname === "::") - ) { - hostname = "localhost"; - } - // Work around https://github.com/denoland/deno/issues/23650 - hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname; - const address = colors.cyan( - `${protocol}//${hostname}:${params.port}${pathname}`, - ); - const localLabel = colors.bold("Local:"); - - // Don't spam logs with this on live deployments - if (!DENO_DEPLOYMENT_ID) { - // deno-lint-ignore no-console - console.log(); - // deno-lint-ignore no-console - console.log( - colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121), - ); - const sep = options.remoteAddress ? "" : "\n"; - const space = options.remoteAddress ? " " : ""; - // deno-lint-ignore no-console - console.log(` ${localLabel} ${space}${address}${sep}`); - if (options.remoteAddress) { - const remoteLabel = colors.bold("Remote:"); - const remoteAddress = colors.cyan(options.remoteAddress); - // deno-lint-ignore no-console - console.log(` ${remoteLabel} ${remoteAddress}\n`); - } - } - }; + options.onListen = createOnListen(this.config.basePath, options); } const handler = await this.handler(); if (options.port) { await Deno.serve(options, handler); - } else { - // No port specified, check for a free port. Instead of picking just - // any port we'll check if the next one is free for UX reasons. - // That way the user only needs to increment a number when running - // multiple apps vs having to remember completely different ports. - let firstError; - for (let port = 8000; port < 8020; port++) { - try { - await Deno.serve({ ...options, port }, handler); - firstError = undefined; - break; - } catch (err) { - if (err instanceof Deno.errors.AddrInUse) { - // Throw first EADDRINUSE error - // if no port is free - if (!firstError) { - firstError = err; - } - continue; - } - - throw err; - } - } - - if (firstError) { - throw firstError; - } + return; } + + await listenOnFreePort(options, handler); } }