Skip to content

Commit 4143d09

Browse files
authored
Merge branch 'main' into deno-jsons-clean
2 parents d185873 + 1cfb4bc commit 4143d09

25 files changed

Lines changed: 388 additions & 175 deletions

init/src/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ ${GRADIENT_CSS}`;
403403
import { define, type State } from "./utils.ts";
404404
405405
export const app = new App<State>();
406+
406407
app.use(staticFiles());
407408
408409
// this is the same as the /api/:name route defined via a file. feel free to delete this!
@@ -421,7 +422,6 @@ const exampleLoggerMiddleware = define.middleware((ctx) => {
421422
app.use(exampleLoggerMiddleware);
422423
423424
await fsRoutes(app, {
424-
dir: "./",
425425
loadIsland: (path) => import(\`./islands/\${path}\`),
426426
loadRoute: (path) => import(\`./routes/\${path}\`),
427427
});

src/app.ts

Lines changed: 84 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,7 @@ import { DENO_DEPLOYMENT_ID } from "./runtime/build_id.ts";
77
import * as colors from "@std/fmt/colors";
88
import { type MiddlewareFn, runMiddlewares } from "./middlewares/mod.ts";
99
import { 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";
1611
import {
1712
type FreshConfig,
1813
normalizeConfig,
@@ -22,6 +17,7 @@ import { type BuildCache, ProdBuildCache } from "./build_cache.ts";
2217
import type { ServerIslandRegistry } from "./context.ts";
2318
import { FinishSetup, ForgotBuild } from "./finish_setup.tsx";
2419
import { 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

50118
export 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

Comments
 (0)