Skip to content

Commit 03128d8

Browse files
committed
fix(dev): keep host resolution consistent across the dev stack
1 parent 0de5742 commit 03128d8

5 files changed

Lines changed: 64 additions & 7 deletions

File tree

apps/desktop/scripts/dev-electron.mjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import waitOn from "wait-on";
55

66
import { desktopDir, resolveElectronPath } from "./electron-launcher.mjs";
77

8-
const port = Number(process.env.ELECTRON_RENDERER_PORT ?? 5733);
9-
const devServerUrl = `http://localhost:${port}`;
8+
const rendererPort = Number(process.env.ELECTRON_RENDERER_PORT ?? 5733);
9+
const devServerUrl = process.env.VITE_DEV_SERVER_URL?.trim() || `http://localhost:${rendererPort}`;
10+
const parsedDevServerUrl = new URL(devServerUrl);
11+
const devServerPort = Number(
12+
parsedDevServerUrl.port || (parsedDevServerUrl.protocol === "https:" ? 443 : 80),
13+
);
1014
const requiredFiles = [
1115
"dist-electron/main.js",
1216
"dist-electron/preload.js",
@@ -21,7 +25,7 @@ const restartDebounceMs = 120;
2125
const childTreeGracePeriodMs = 1_200;
2226

2327
await waitOn({
24-
resources: [`tcp:${port}`, ...requiredFiles.map((filePath) => `file:${filePath}`)],
28+
resources: [`tcp:${devServerPort}`, ...requiredFiles.map((filePath) => `file:${filePath}`)],
2529
});
2630

2731
const childEnv = { ...process.env };

apps/web/vite.config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ import { defineConfig } from "vite";
66
import pkg from "./package.json" with { type: "json" };
77

88
const port = Number(process.env.PORT ?? 5733);
9+
const DEFAULT_DEV_HOST = "127.0.0.1";
10+
const isWildcardHost = (host: string): boolean =>
11+
host === "0.0.0.0" || host === "::" || host === "[::]";
12+
const configuredHost = process.env.T3CODE_HOST?.trim();
13+
const resolvedHost = configuredHost === "[::]" ? "::" : configuredHost;
14+
const bindHost = resolvedHost && resolvedHost !== "localhost" ? resolvedHost : DEFAULT_DEV_HOST;
15+
const hmrHost =
16+
!resolvedHost || resolvedHost === "localhost" || isWildcardHost(resolvedHost)
17+
? DEFAULT_DEV_HOST
18+
: resolvedHost;
919
const sourcemapEnv = process.env.T3CODE_WEB_SOURCEMAP?.trim().toLowerCase();
1020

1121
const buildSourcemap =
@@ -41,14 +51,15 @@ export default defineConfig({
4151
tsconfigPaths: true,
4252
},
4353
server: {
54+
host: bindHost,
4455
port,
4556
strictPort: true,
4657
hmr: {
4758
// Explicit config so Vite's HMR WebSocket connects reliably
4859
// inside Electron's BrowserWindow. Vite 8 uses console.debug for
4960
// connection logs — enable "Verbose" in DevTools to see them.
5061
protocol: "ws",
51-
host: "localhost",
62+
host: hmrHost,
5263
},
5364
},
5465
build: {

scripts/dev-runner.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ it.layer(NodeServices.layer)("dev-runner", (it) => {
6767
]);
6868

6969
assert.equal(env.T3CODE_STATE_DIR, defaultStateDir);
70+
assert.equal(env.VITE_WS_URL, "ws://127.0.0.1:3773");
71+
assert.equal(env.VITE_DEV_SERVER_URL, "http://127.0.0.1:5733");
7072
}),
7173
);
7274

@@ -89,7 +91,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => {
8991

9092
assert.equal(env.T3CODE_STATE_DIR, resolve("/tmp/override-state"));
9193
assert.equal(env.T3CODE_PORT, "4222");
92-
assert.equal(env.VITE_WS_URL, "ws://localhost:4222");
94+
assert.equal(env.VITE_WS_URL, "ws://127.0.0.1:4222");
9395
assert.equal(env.T3CODE_NO_BROWSER, "1");
9496
assert.equal(env.T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD, "0");
9597
assert.equal(env.T3CODE_LOG_WS_EVENTS, "1");
@@ -98,6 +100,28 @@ it.layer(NodeServices.layer)("dev-runner", (it) => {
98100
}),
99101
);
100102

103+
it.effect("formats IPv6 hosts correctly in dev URLs", () =>
104+
Effect.gen(function* () {
105+
const env = yield* createDevRunnerEnv({
106+
mode: "dev",
107+
baseEnv: {},
108+
serverOffset: 0,
109+
webOffset: 0,
110+
stateDir: undefined,
111+
authToken: undefined,
112+
noBrowser: undefined,
113+
autoBootstrapProjectFromCwd: undefined,
114+
logWebSocketEvents: undefined,
115+
host: "::1",
116+
port: 4222,
117+
devUrl: undefined,
118+
});
119+
120+
assert.equal(env.VITE_WS_URL, "ws://[::1]:4222");
121+
assert.equal(env.VITE_DEV_SERVER_URL, "http://[::1]:5733");
122+
}),
123+
);
124+
101125
it.effect("does not force websocket logging on in dev mode when unset", () =>
102126
Effect.gen(function* () {
103127
const env = yield* createDevRunnerEnv({

scripts/dev-runner.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const BASE_SERVER_PORT = 3773;
1313
const BASE_WEB_PORT = 5733;
1414
const MAX_HASH_OFFSET = 3000;
1515
const MAX_PORT = 65535;
16+
const DEFAULT_DEV_HOST = "127.0.0.1";
1617

1718
export const DEFAULT_DEV_STATE_DIR = Effect.map(Effect.service(Path.Path), (path) =>
1819
path.join(homedir(), ".t3", "dev"),
@@ -130,6 +131,21 @@ interface CreateDevRunnerEnvInput {
130131
readonly devUrl: URL | undefined;
131132
}
132133

134+
const isWildcardHost = (host: string): boolean =>
135+
host === "0.0.0.0" || host === "::" || host === "[::]";
136+
137+
const formatHostForUrl = (host: string): string =>
138+
host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
139+
140+
const resolveDevHost = (host: string | undefined): string => {
141+
const trimmedHost = host?.trim();
142+
if (!trimmedHost || trimmedHost === "localhost" || isWildcardHost(trimmedHost)) {
143+
return DEFAULT_DEV_HOST;
144+
}
145+
146+
return trimmedHost;
147+
};
148+
133149
export function createDevRunnerEnv({
134150
mode,
135151
baseEnv,
@@ -148,14 +164,15 @@ export function createDevRunnerEnv({
148164
const serverPort = port ?? BASE_SERVER_PORT + serverOffset;
149165
const webPort = BASE_WEB_PORT + webOffset;
150166
const resolvedStateDir = yield* resolveStateDir(stateDir);
167+
const webHost = resolveDevHost(host);
151168

152169
const output: NodeJS.ProcessEnv = {
153170
...baseEnv,
154171
T3CODE_PORT: String(serverPort),
155172
PORT: String(webPort),
156173
ELECTRON_RENDERER_PORT: String(webPort),
157-
VITE_WS_URL: `ws://localhost:${serverPort}`,
158-
VITE_DEV_SERVER_URL: devUrl?.toString() ?? `http://localhost:${webPort}`,
174+
VITE_WS_URL: `ws://${formatHostForUrl(webHost)}:${serverPort}`,
175+
VITE_DEV_SERVER_URL: devUrl?.toString() ?? `http://${formatHostForUrl(webHost)}:${webPort}`,
159176
T3CODE_STATE_DIR: resolvedStateDir,
160177
};
161178

turbo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"T3CODE_PORT",
1111
"T3CODE_NO_BROWSER",
1212
"T3CODE_STATE_DIR",
13+
"T3CODE_HOST",
1314
"T3CODE_AUTH_TOKEN",
1415
"T3CODE_DESKTOP_WS_URL"
1516
],

0 commit comments

Comments
 (0)