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 | import { Effect } from "effect"; | 5 | import { Effect } from "effect"; |
| 6 | 6 | ||
| 7 | import { | 7 | import { |
| 8 | checkPortAvailabilityOnHosts, | ||
| 8 | createDevRunnerEnv, | 9 | createDevRunnerEnv, |
| 9 | findFirstAvailableOffset, | 10 | findFirstAvailableOffset, |
| 10 | resolveModePortOffsets, | 11 | resolveModePortOffsets, |
| @@ -164,11 +165,11 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 153 unmodified lines | |||
| 164 | const env = yield* createDevRunnerEnv({ | 165 | const env = yield* createDevRunnerEnv({ |
| 165 | mode: "dev:desktop", | 166 | mode: "dev:desktop", |
| 166 | baseEnv: { | 167 | baseEnv: { |
| 167 | T3CODE_PORT: "3773", | 168 | T3CODE_PORT: "13773", |
| 168 | T3CODE_MODE: "web", | 169 | T3CODE_MODE: "web", |
| 169 | T3CODE_NO_BROWSER: "0", | 170 | T3CODE_NO_BROWSER: "0", |
| 170 | T3CODE_HOST: "0.0.0.0", | 171 | T3CODE_HOST: "0.0.0.0", |
| 171 | VITE_WS_URL: "ws://localhost:3773", | 172 | VITE_WS_URL: "ws://localhost:13773", |
| 172 | }, | 173 | }, |
| 173 | serverOffset: 0, | 174 | serverOffset: 0, |
| 174 | webOffset: 0, | 175 | webOffset: 0, |
| @@ -193,6 +194,28 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 18 unmodified lines | |||
| 193 | assert.equal(env.VITE_WS_URL, "ws://127.0.0.1:4222"); | 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 | describe("findFirstAvailableOffset", () => { | 221 | describe("findFirstAvailableOffset", () => { |
| @@ -211,7 +234,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 12 unmodified lines | |||
| 211 | 234 | ||
| 212 | it.effect("advances until all required ports are available", () => | 235 | it.effect("advances until all required ports are available", () => |
| 213 | Effect.gen(function* () { | 236 | Effect.gen(function* () { |
| 214 | const taken = new Set([3773, 5733, 3774, 5734]); | 237 | const taken = new Set([13773, 5733, 13774, 5734]); |
| 215 | const offset = yield* findFirstAvailableOffset({ | 238 | const offset = yield* findFirstAvailableOffset({ |
| 216 | startOffset: 0, | 239 | startOffset: 0, |
| 217 | requireServerPort: true, | 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 | Effect.gen(function* () { | 250 | Effect.gen(function* () { |
| 228 | const offset = yield* findFirstAvailableOffset({ | 251 | const offset = yield* findFirstAvailableOffset({ |
| 229 | startOffset: 59_803, | 252 | startOffset: 59_802, |
| 230 | requireServerPort: true, | 253 | requireServerPort: false, |
| 231 | requireWebPort: false, | 254 | requireWebPort: true, |
| 232 | checkPortAvailability: () => Effect.succeed(true), | 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 | describe("resolveModePortOffsets", () => { | 293 | describe("resolveModePortOffsets", () => { |
| 241 | it.effect("uses a shared fallback offset for dev mode", () => | 294 | it.effect("uses a shared fallback offset for dev mode", () => |
| 242 | Effect.gen(function* () { | 295 | Effect.gen(function* () { |
| 243 | const taken = new Set([3773, 5733]); | 296 | const taken = new Set([13773, 5733]); |
| 244 | const offsets = yield* resolveModePortOffsets({ | 297 | const offsets = yield* resolveModePortOffsets({ |
| 245 | mode: "dev", | 298 | mode: "dev", |
| 246 | startOffset: 0, | 299 | startOffset: 0, |
| @@ -270,7 +323,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { · 23 unmodified lines | |||
| 270 | 323 | ||
| 271 | it.effect("shifts only server offset for dev:server", () => | 324 | it.effect("shifts only server offset for dev:server", () => |
| 272 | Effect.gen(function* () { | 325 | Effect.gen(function* () { |
| 273 | const taken = new Set([3773]); | 326 | const taken = new Set([13773]); |
| 274 | const offsets = yield* resolveModePortOffsets({ | 327 | const offsets = yield* resolveModePortOffsets({ |
| 275 | mode: "dev:server", | 328 | mode: "dev:server", |
| 276 | startOffset: 0, | 329 | startOffset: 0, |
| @@ -9,11 +9,12 @@ import { Config, Data, Effect, Hash, Layer, Logger, Option, Path, Schema } from · 8 unmodified lines | |||
| 9 | import { Argument, Command, Flag } from "effect/unstable/cli"; | 9 | import { Argument, Command, Flag } from "effect/unstable/cli"; |
| 10 | import { ChildProcess } from "effect/unstable/process"; | 10 | import { ChildProcess } from "effect/unstable/process"; |
| 11 | 11 | ||
| 12 | const BASE_SERVER_PORT = 3773; | 12 | const BASE_SERVER_PORT = 13773; |
| 13 | const BASE_WEB_PORT = 5733; | 13 | const BASE_WEB_PORT = 5733; |
| 14 | const MAX_HASH_OFFSET = 3000; | 14 | const MAX_HASH_OFFSET = 3000; |
| 15 | const MAX_PORT = 65535; | 15 | const MAX_PORT = 65535; |
| 16 | const DESKTOP_DEV_LOOPBACK_HOST = "127.0.0.1"; | 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 | export const DEFAULT_T3_HOME = Effect.map(Effect.service(Path.Path), (path) => | 19 | export const DEFAULT_T3_HOME = Effect.map(Effect.service(Path.Path), (path) => |
| 19 | path.join(homedir(), ".t3"), | 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 | const defaultCheckPortAvailability: PortAvailabilityCheck<NetService> = (port) => | 241 | const defaultCheckPortAvailability: PortAvailabilityCheck<NetService> = (port) => |
| 225 | Effect.gen(function* () { | 242 | Effect.gen(function* () { |
| 226 | const net = yield* NetService; | 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 | interface FindFirstAvailableOffsetInput<R = NetService> { | 249 | interface FindFirstAvailableOffsetInput<R = NetService> { |