@junejs/server 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +4 -6
  2. package/src/app.ts +11 -0
  3. package/src/dev.ts +21 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junejs/server",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "June host adapters + dev server. Host-coupled; depends on the pure @junejs/core contract layer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,16 +29,14 @@
29
29
  "typecheck": "tsc --noEmit"
30
30
  },
31
31
  "dependencies": {
32
- "@junejs/core": "0.0.4",
33
- "marked": "^18.0.5"
32
+ "@junejs/core": "0.0.6",
33
+ "marked": "^18.0.5",
34
+ "rolldown": "^1.1.0"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "react": "^19.2.0",
37
38
  "react-dom": "^19.2.0"
38
39
  },
39
- "devDependencies": {
40
- "rolldown": "^1.1.0"
41
- },
42
40
  "repository": {
43
41
  "type": "git",
44
42
  "url": "https://github.com/junebuild/june"
package/src/app.ts CHANGED
@@ -138,6 +138,17 @@ export function createApp({ appDir: appDirInput, config = {} }: CreateAppOptions
138
138
  new Response(code, {
139
139
  headers: { "content-type": "text/javascript; charset=utf-8" },
140
140
  }),
141
+ (err: unknown) => {
142
+ // A rejected bundle must answer, not hang the request (Bun.serve
143
+ // times the handler out) — and must not be memoized, or the error
144
+ // outlives its cause.
145
+ clientBundle = undefined;
146
+ console.error("[june] client bundle failed:", err);
147
+ return new Response(`client bundle failed: ${err instanceof Error ? err.message : String(err)}\n`, {
148
+ status: 500,
149
+ headers: { "content-type": "text/plain; charset=utf-8" },
150
+ });
151
+ },
141
152
  );
142
153
  }
143
154
  return runWithTrace(newTrace(), async () => (await getPipeline()).fetch(request));
package/src/dev.ts CHANGED
@@ -4,6 +4,8 @@
4
4
  // work), load june.config.ts from the app root (the config the PoC forgot to
5
5
  // read), build the app, and serve through the detected JuneHost.
6
6
 
7
+ import { createServer as createNetServer } from "node:net";
8
+
7
9
  import { loadJuneConfig } from "./config-loader";
8
10
  import { installAsyncContext } from "./instrumentation";
9
11
  import { createApp } from "./app";
@@ -18,6 +20,21 @@ export type DevServerOptions = {
18
20
 
19
21
  export type DevServer = ServeHandle & { url: string };
20
22
 
23
+ // A taken default port must not be a dead end in dev — walk forward until a
24
+ // port binds (the Vite convention). Probed with node:net, which both hosts
25
+ // implement, so the host interface stays untouched.
26
+ async function findFreePort(start: number, tries = 20): Promise<number> {
27
+ for (let p = start; p < start + tries; p++) {
28
+ const free = await new Promise<boolean>((resolve) => {
29
+ const probe = createNetServer();
30
+ probe.once("error", () => resolve(false));
31
+ probe.listen(p, () => probe.close(() => resolve(true)));
32
+ });
33
+ if (free) return p;
34
+ }
35
+ throw new Error(`june dev: no free port between ${start} and ${start + tries - 1}`);
36
+ }
37
+
21
38
  export async function startDevServer({
22
39
  appDir,
23
40
  port = 3000,
@@ -28,6 +45,10 @@ export async function startDevServer({
28
45
  const app = createApp({ appDir, config });
29
46
  await app.warmup();
30
47
 
48
+ const freePort = await findFreePort(port);
49
+ if (freePort !== port) console.log(`[june] port ${port} is taken → using ${freePort}`);
50
+ port = freePort;
51
+
31
52
  // Live reload wraps the DEV SERVER only — the pipeline (and therefore
32
53
  // dev/built parity) never sees it. See dev-reload.ts.
33
54
  const handle = host.serve(withLiveReload((req) => app.fetch(req)), {