@@ -6,12 +6,7 @@ import { DENO_DEPLOYMENT_ID } from "./runtime/build_id.ts";
66import * as colors from "@std/fmt/colors" ;
77import { type MiddlewareFn , runMiddlewares } from "./middlewares/mod.ts" ;
88import { FreshReqContext } from "./context.ts" ;
9- import {
10- mergePaths ,
11- type Method ,
12- type Router ,
13- UrlPatternRouter ,
14- } from "./router.ts" ;
9+ import { type Method , type Router , UrlPatternRouter } from "./router.ts" ;
1510import {
1611 type FreshConfig ,
1712 normalizeConfig ,
@@ -20,6 +15,7 @@ import {
2015import { type BuildCache , ProdBuildCache } from "./build_cache.ts" ;
2116import type { ServerIslandRegistry } from "./context.ts" ;
2217import { HttpError , SetupError } from "./error.ts" ;
18+ import { mergePaths } from "./utils.ts" ;
2319
2420// TODO: Completed type clashes in older Deno versions
2521// deno-lint-ignore no-explicit-any
@@ -42,8 +38,80 @@ export type ListenOptions =
4238 & {
4339 remoteAddress ?: string ;
4440 } ;
41+ function createOnListen (
42+ basePath : string ,
43+ options : ListenOptions ,
44+ ) : ( localAddr : Deno . NetAddr ) => void {
45+ return ( params ) => {
46+ // Don't spam logs with this on live deployments
47+ if ( DENO_DEPLOYMENT_ID ) return ;
48+
49+ const pathname = basePath + "/" ;
50+ const protocol = "key" in options && options . key && options . cert
51+ ? "https:"
52+ : "http:" ;
53+
54+ let hostname = params . hostname ;
55+ // Windows being windows...
56+ if (
57+ Deno . build . os === "windows" &&
58+ ( hostname === "0.0.0.0" || hostname === "::" )
59+ ) {
60+ hostname = "localhost" ;
61+ }
62+ // Work around https://github.com/denoland/deno/issues/23650
63+ hostname = hostname . startsWith ( "::" ) ? `[${ hostname } ]` : hostname ;
64+
65+ // deno-lint-ignore no-console
66+ console . log ( ) ;
67+ // deno-lint-ignore no-console
68+ console . log (
69+ colors . bgRgb8 ( colors . rgb8 ( " 🍋 Fresh ready " , 0 ) , 121 ) ,
70+ ) ;
71+ const sep = options . remoteAddress ? "" : "\n" ;
72+ const space = options . remoteAddress ? " " : "" ;
73+
74+ const localLabel = colors . bold ( "Local:" ) ;
75+ const address = colors . cyan (
76+ `${ protocol } //${ hostname } :${ params . port } ${ pathname } ` ,
77+ ) ;
78+ // deno-lint-ignore no-console
79+ console . log ( ` ${ localLabel } ${ space } ${ address } ${ sep } ` ) ;
80+ if ( options . remoteAddress ) {
81+ const remoteLabel = colors . bold ( "Remote:" ) ;
82+ const remoteAddress = colors . cyan ( options . remoteAddress ) ;
83+ // deno-lint-ignore no-console
84+ console . log ( ` ${ remoteLabel } ${ remoteAddress } \n` ) ;
85+ }
86+ } ;
87+ }
4588
46- Deno . serve ;
89+ async function listenOnFreePort (
90+ options : ListenOptions ,
91+ handler : (
92+ request : Request ,
93+ info ?: Deno . ServeHandlerInfo ,
94+ ) => Promise < Response > ,
95+ ) {
96+ // No port specified, check for a free port. Instead of picking just
97+ // any port we'll check if the next one is free for UX reasons.
98+ // That way the user only needs to increment a number when running
99+ // multiple apps vs having to remember completely different ports.
100+ let firstError = null ;
101+ for ( let port = 8000 ; port < 8020 ; port ++ ) {
102+ try {
103+ return await Deno . serve ( { ...options , port } , handler ) ;
104+ } catch ( err ) {
105+ if ( err instanceof Deno . errors . AddrInUse ) {
106+ // Throw first EADDRINUSE error if no port is free
107+ if ( ! firstError ) firstError = err ;
108+ continue ;
109+ }
110+ throw err ;
111+ }
112+ }
113+ throw firstError ;
114+ }
47115
48116export let getRouter : < State > ( app : App < State > ) => Router < MiddlewareFn < State > > ;
49117// deno-lint-ignore no-explicit-any
@@ -249,80 +317,15 @@ export class App<State> {
249317
250318 async listen ( options : ListenOptions = { } ) : Promise < void > {
251319 if ( ! options . onListen ) {
252- options . onListen = ( params ) => {
253- const pathname = ( this . config . basePath ) + "/" ;
254- const protocol = "key" in options && options . key && options . cert
255- ? "https:"
256- : "http:" ;
257-
258- let hostname = params . hostname ;
259- // Windows being windows...
260- if (
261- Deno . build . os === "windows" &&
262- ( hostname === "0.0.0.0" || hostname === "::" )
263- ) {
264- hostname = "localhost" ;
265- }
266- // Work around https://github.com/denoland/deno/issues/23650
267- hostname = hostname . startsWith ( "::" ) ? `[${ hostname } ]` : hostname ;
268- const address = colors . cyan (
269- `${ protocol } //${ hostname } :${ params . port } ${ pathname } ` ,
270- ) ;
271- const localLabel = colors . bold ( "Local:" ) ;
272-
273- // Don't spam logs with this on live deployments
274- if ( ! DENO_DEPLOYMENT_ID ) {
275- // deno-lint-ignore no-console
276- console . log ( ) ;
277- // deno-lint-ignore no-console
278- console . log (
279- colors . bgRgb8 ( colors . rgb8 ( " 🍋 Fresh ready " , 0 ) , 121 ) ,
280- ) ;
281- const sep = options . remoteAddress ? "" : "\n" ;
282- const space = options . remoteAddress ? " " : "" ;
283- // deno-lint-ignore no-console
284- console . log ( ` ${ localLabel } ${ space } ${ address } ${ sep } ` ) ;
285- if ( options . remoteAddress ) {
286- const remoteLabel = colors . bold ( "Remote:" ) ;
287- const remoteAddress = colors . cyan ( options . remoteAddress ) ;
288- // deno-lint-ignore no-console
289- console . log ( ` ${ remoteLabel } ${ remoteAddress } \n` ) ;
290- }
291- }
292- } ;
320+ options . onListen = createOnListen ( this . config . basePath , options ) ;
293321 }
294322
295323 const handler = await this . handler ( ) ;
296324 if ( options . port ) {
297325 await Deno . serve ( options , handler ) ;
298- } else {
299- // No port specified, check for a free port. Instead of picking just
300- // any port we'll check if the next one is free for UX reasons.
301- // That way the user only needs to increment a number when running
302- // multiple apps vs having to remember completely different ports.
303- let firstError ;
304- for ( let port = 8000 ; port < 8020 ; port ++ ) {
305- try {
306- await Deno . serve ( { ...options , port } , handler ) ;
307- firstError = undefined ;
308- break ;
309- } catch ( err ) {
310- if ( err instanceof Deno . errors . AddrInUse ) {
311- // Throw first EADDRINUSE error
312- // if no port is free
313- if ( ! firstError ) {
314- firstError = err ;
315- }
316- continue ;
317- }
318-
319- throw err ;
320- }
321- }
322-
323- if ( firstError ) {
324- throw firstError ;
325- }
326+ return ;
326327 }
328+
329+ await listenOnFreePort ( options , handler ) ;
327330 }
328331}
0 commit comments