Showing the changes to scripts (2 of 11 files). Show all
Unified · Split
| @@ -5,6 +5,7 @@ import { assert, describe, it } from "@effect/vitest"; · 4 unmodified lines | ||
| 5 | 5 | import { Effect } from "effect"; |
| 6 | 6 | |
| 7 | 7 | import { |
| 8 | + checkPortAvailabilityOnHosts, | |
| 8 | 9 | createDevRunnerEnv, |
| 9 | 10 | findFirstAvailableOffset, |
| 10 | 11 | resolveModePortOffsets, |
| @@ -164,11 +165,11 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 153 unmodified lines | ||
| 164 | 165 | const env = yield* createDevRunnerEnv({ |
| 165 | 166 | mode: "dev:desktop", |
| 166 | 167 | baseEnv: { |
| 167 | − T3CODE_PORT: "3773", | |
| 168 | + T3CODE_PORT: "13773", | |
| 168 | 169 | T3CODE_MODE: "web", |
| 169 | 170 | T3CODE_NO_BROWSER: "0", |
| 170 | 171 | T3CODE_HOST: "0.0.0.0", |
| 171 | − VITE_WS_URL: "ws://localhost:3773", | |
| 172 | + VITE_WS_URL: "ws://localhost:13773", | |
| 172 | 173 | }, |
| 173 | 174 | serverOffset: 0, |
| 174 | 175 | webOffset: 0, |
| @@ -193,6 +194,28 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 18 unmodified lines | ||
| 193 | 194 | assert.equal(env.VITE_WS_URL, "ws://127.0.0.1:4222"); |
| 194 | 195 | }), |
| 195 | 196 | ); |
| 197 | + | |
| 198 | + it.effect("defaults dev server mode to the higher backend port range", () => | |
| 199 | + Effect.gen(function* () { | |
| 200 | + const env = yield* createDevRunnerEnv({ | |
| 201 | + mode: "dev", | |
| 202 | + baseEnv: {}, | |
| 203 | + serverOffset: 0, | |
| 204 | + webOffset: 0, | |
| 205 | + t3Home: undefined, | |
| 206 | + noBrowser: undefined, | |
| 207 | + autoBootstrapProjectFromCwd: undefined, | |
| 208 | + logWebSocketEvents: undefined, | |
| 209 | + host: undefined, | |
| 210 | + port: undefined, | |
| 211 | + devUrl: undefined, | |
| 212 | + }); | |
| 213 | + | |
| 214 | + assert.equal(env.T3CODE_PORT, "13773"); | |
| 215 | + assert.equal(env.VITE_HTTP_URL, "http://localhost:13773"); | |
| 216 | + assert.equal(env.VITE_WS_URL, "ws://localhost:13773"); | |
| 217 | + }), | |
| 218 | + ); | |
| 196 | 219 | }); |
| 197 | 220 | |
| 198 | 221 | describe("findFirstAvailableOffset", () => { |
| @@ -211,7 +234,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 12 unmodified lines | ||
| 211 | 234 | |
| 212 | 235 | it.effect("advances until all required ports are available", () => |
| 213 | 236 | Effect.gen(function* () { |
| 214 | − const taken = new Set([3773, 5733, 3774, 5734]); | |
| 237 | + const taken = new Set([13773, 5733, 13774, 5734]); | |
| 215 | 238 | const offset = yield* findFirstAvailableOffset({ |
| 216 | 239 | startOffset: 0, |
| 217 | 240 | requireServerPort: true, |
| @@ -223,16 +246,46 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 5 unmodified lines | ||
| 223 | 246 | }), |
| 224 | 247 | ); |
| 225 | 248 | |
| 226 | − it.effect("allows offsets where only non-required ports exceed max", () => | |
| 249 | + it.effect("allows offsets where the non-required server port exceeds max", () => | |
| 227 | 250 | Effect.gen(function* () { |
| 228 | 251 | const offset = yield* findFirstAvailableOffset({ |
| 229 | − startOffset: 59_803, | |
| 230 | − requireServerPort: true, | |
| 231 | − requireWebPort: false, | |
| 252 | + startOffset: 59_802, | |
| 253 | + requireServerPort: false, | |
| 254 | + requireWebPort: true, | |
| 232 | 255 | checkPortAvailability: () => Effect.succeed(true), |
| 233 | 256 | }); |
| 234 | 257 | |
| 235 | − assert.equal(offset, 59_803); | |
| 258 | + assert.equal(offset, 59_802); | |
| 259 | + }), | |
| 260 | + ); | |
| 261 | + }); | |
| 262 | + | |
| 263 | + describe("checkPortAvailabilityOnHosts", () => { | |
| 264 | + it.effect("checks overlapping hosts sequentially to avoid self-interference", () => | |
| 265 | + Effect.gen(function* () { | |
| 266 | + let inFlightCount = 0; | |
| 267 | + const calls: Array<[number, string]> = []; | |
| 268 | + | |
| 269 | + const available = yield* checkPortAvailabilityOnHosts( | |
| 270 | + 13_773, | |
| 271 | + ["127.0.0.1", "0.0.0.0", "::"], | |
| 272 | + (port, host) => | |
| 273 | + Effect.promise(async () => { | |
| 274 | + calls.push([port, host]); | |
| 275 | + inFlightCount += 1; | |
| 276 | + const overlapped = inFlightCount > 1; | |
| 277 | + await Promise.resolve(); | |
| 278 | + inFlightCount -= 1; | |
| 279 | + return !overlapped; | |
| 280 | + }), | |
| 281 | + ); | |
| 282 | + | |
| 283 | + assert.equal(available, true); | |
| 284 | + assert.deepStrictEqual(calls, [ | |
| 285 | + [13_773, "127.0.0.1"], | |
| 286 | + [13_773, "0.0.0.0"], | |
| 287 | + [13_773, "::"], | |
| 288 | + ]); | |
| 236 | 289 | }), |
| 237 | 290 | ); |
| 238 | 291 | }); |
| @@ -240,7 +293,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 1 unmodified line | ||
| 240 | 293 | describe("resolveModePortOffsets", () => { |
| 241 | 294 | it.effect("uses a shared fallback offset for dev mode", () => |
| 242 | 295 | Effect.gen(function* () { |
| 243 | − const taken = new Set([3773, 5733]); | |
| 296 | + const taken = new Set([13773, 5733]); | |
| 244 | 297 | const offsets = yield* resolveModePortOffsets({ |
| 245 | 298 | mode: "dev", |
| 246 | 299 | startOffset: 0, |
| @@ -270,7 +323,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 23 unmodified lines | ||
| 270 | 323 | |
| 271 | 324 | it.effect("shifts only server offset for dev:server", () => |
| 272 | 325 | Effect.gen(function* () { |
| 273 | − const taken = new Set([3773]); | |
| 326 | + const taken = new Set([13773]); | |
| 274 | 327 | const offsets = yield* resolveModePortOffsets({ |
| 275 | 328 | mode: "dev:server", |
| 276 | 329 | startOffset: 0, |
| @@ -9,11 +9,12 @@ import { Config, Data, Effect, Hash, Layer, Logger, Option, Path, Schema } from · 8 unmodified lines | ||
| 9 | 9 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 10 | 10 | import { ChildProcess } from "effect/unstable/process"; |
| 11 | 11 | |
| 12 | −const BASE_SERVER_PORT = 3773; | |
| 12 | +const BASE_SERVER_PORT = 13773; | |
| 13 | 13 | const BASE_WEB_PORT = 5733; |
| 14 | 14 | const MAX_HASH_OFFSET = 3000; |
| 15 | 15 | const MAX_PORT = 65535; |
| 16 | 16 | const DESKTOP_DEV_LOOPBACK_HOST = "127.0.0.1"; |
| 17 | +const DEV_PORT_PROBE_HOSTS = ["127.0.0.1", "0.0.0.0", "::1", "::"] as const; | |
| 17 | 18 | |
| 18 | 19 | export const DEFAULT_T3_HOME = Effect.map(Effect.service(Path.Path), (path) => |
| 19 | 20 | path.join(homedir(), ".t3"), |
| @@ -221,10 +222,28 @@ function portPairForOffset(offset: number): { · 201 unmodified lines | ||
| 221 | 222 | }; |
| 222 | 223 | } |
| 223 | 224 | |
| 225 | +export function checkPortAvailabilityOnHosts<R>( | |
| 226 | + port: number, | |
| 227 | + hosts: ReadonlyArray<string>, | |
| 228 | + canListenOnHost: (port: number, host: string) => Effect.Effect<boolean, never, R>, | |
| 229 | +): Effect.Effect<boolean, never, R> { | |
| 230 | + return Effect.gen(function* () { | |
| 231 | + for (const host of hosts) { | |
| 232 | + if (!(yield* canListenOnHost(port, host))) { | |
| 233 | + return false; | |
| 234 | + } | |
| 235 | + } | |
| 236 | + | |
| 237 | + return true; | |
| 238 | + }); | |
| 239 | +} | |
| 240 | + | |
| 224 | 241 | const defaultCheckPortAvailability: PortAvailabilityCheck<NetService> = (port) => |
| 225 | 242 | Effect.gen(function* () { |
| 226 | 243 | const net = yield* NetService; |
| 227 | − return yield* net.isPortAvailableOnLoopback(port); | |
| 244 | + return yield* checkPortAvailabilityOnHosts(port, DEV_PORT_PROBE_HOSTS, (candidatePort, host) => | |
| 245 | + net.canListenOnHost(candidatePort, host), | |
| 246 | + ); | |
| 228 | 247 | }); |
| 229 | 248 | |
| 230 | 249 | interface FindFirstAvailableOffsetInput<R = NetService> { |