diff --git a/scripts/dev-runner.test.ts b/scripts/dev-runner.test.ts index 1dd38a3408b..02da61275df 100644 --- a/scripts/dev-runner.test.ts +++ b/scripts/dev-runner.test.ts @@ -202,6 +202,53 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { }), ); + it.effect("pins the repo toolchain ahead of an inherited PATH", () => + Effect.gen(function* () { + // A stray /usr/bin/vp (atfs/ShapeTools) otherwise wins the bare-name + // lookup, starts no dev server, and still exits 0. + const env = yield* createDevRunnerEnv({ + mode: "dev", + baseEnv: { PATH: "/usr/local/bin:/usr/bin:/bin" }, + serverOffset: 0, + webOffset: 0, + t3Home: undefined, + browser: undefined, + autoBootstrapProjectFromCwd: undefined, + logWebSocketEvents: undefined, + host: undefined, + port: undefined, + devUrl: undefined, + tailscaleServe: undefined, + }); + + const entries = (env.PATH ?? "").split(NodePath.delimiter); + assert.ok(entries[0]?.endsWith(NodePath.join("node_modules", ".bin"))); + assert.deepEqual(entries.slice(1), ["/usr/local/bin", "/usr/bin", "/bin"]); + }), + ); + + it.effect("keeps PATH usable when none was inherited", () => + Effect.gen(function* () { + const env = yield* createDevRunnerEnv({ + mode: "dev", + baseEnv: {}, + serverOffset: 0, + webOffset: 0, + t3Home: undefined, + browser: undefined, + autoBootstrapProjectFromCwd: undefined, + logWebSocketEvents: undefined, + host: undefined, + port: undefined, + devUrl: undefined, + tailscaleServe: undefined, + }); + + assert.ok(env.PATH?.endsWith(NodePath.join("node_modules", ".bin"))); + assert.ok(!env.PATH?.includes(NodePath.delimiter)); + }), + ); + it.effect("allows browser auto-open to be explicitly enabled", () => Effect.gen(function* () { const env = yield* createDevRunnerEnv({ @@ -274,6 +321,29 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { }), ); + it.effect("prevents an ambient Command Center home from overriding the selected home", () => + Effect.gen(function* () { + const path = yield* Path.Path; + const env = yield* createDevRunnerEnv({ + mode: "dev", + baseEnv: { COMMAND_CENTER_HOME: "/opt/shared-command-center" }, + serverOffset: 0, + webOffset: 0, + t3Home: "/tmp/isolated-t3", + browser: undefined, + autoBootstrapProjectFromCwd: undefined, + logWebSocketEvents: undefined, + host: undefined, + port: undefined, + devUrl: undefined, + tailscaleServe: undefined, + }); + + assert.equal(env.T3CODE_HOME, path.resolve("/tmp/isolated-t3")); + assert.equal(env.COMMAND_CENTER_HOME, undefined); + }), + ); + it.effect("strips inherited service-launcher context", () => Effect.gen(function* () { const env = yield* createDevRunnerEnv({ diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 45d5b03fe9d..d412a503c7d 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -1,6 +1,8 @@ #!/usr/bin/env node import * as NodeOS from "node:os"; +// @effect-diagnostics-next-line nodeBuiltinImport:off - Effect's Path service has no `delimiter`. +import * as NodePath from "node:path"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; import * as NodeServices from "@effect/platform-node/NodeServices"; @@ -298,6 +300,34 @@ function resolveBaseDir(baseDir: string | undefined): Effect.Effect { + return Effect.gen(function* () { + const path = yield* Path.Path; + // import.meta.dirname is scripts/; its parent is the repo root. + const binDir = path.join(path.dirname(import.meta.dirname), "node_modules", ".bin"); + const inherited = baseEnv.PATH; + if (inherited === undefined || inherited.length === 0) { + return binDir; + } + // Effect's Path service has no `delimiter`; the separator is platform state. + const alreadyFirst = inherited.split(NodePath.delimiter).at(0) === binDir; + return alreadyFirst ? inherited : `${binDir}${NodePath.delimiter}${inherited}`; + }); +} + interface CreateDevRunnerEnvInput { readonly mode: DevMode; readonly baseEnv: NodeJS.ProcessEnv; @@ -339,6 +369,7 @@ export function createDevRunnerEnv({ const output: NodeJS.ProcessEnv = { ...baseEnv, + PATH: yield* devToolchainPath(baseEnv), PORT: String(webPort), VITE_DEV_SERVER_URL: devUrl?.toString() ?? @@ -362,6 +393,11 @@ export function createDevRunnerEnv({ if (configuredBaseDir !== undefined) { output.T3CODE_HOME = resolvedBaseDir; + // The server gives COMMAND_CENTER_HOME precedence over T3CODE_HOME. + // Once the runner has selected an explicit or worktree-local home, an + // inherited launcher home must not redirect the child back to shared + // state. + delete output.COMMAND_CENTER_HOME; } else { delete output.T3CODE_HOME; }