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