authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-26 18:33:21-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-26 18:40:23-08:00
logb39c629dcc9801abf82df5c770dfd6555cd2719c
treeffd139ac6fa95e986a39954508c5780f32330d92
parentb95cc59e79b15528a61774d0750d86e7199b0c0b

Validate dev runner port overrides and honor env state dir

- Prefer `T3CODE_STATE_DIR` from env when `--state-dir` is unset or blank - Throw on invalid `T3CODE_PORT` overrides and always recompute `VITE_WS_URL` - Add tests for env/state-dir precedence, blank overrides, and invalid ports

2 files changed, 52 insertions(+), 6 deletions(-)

scripts/dev-runner.mjs+18-5
...@@ -151,8 +151,18 @@ export function parseDevRunnerArgs(rawArgs) {...@@ -151,8 +151,18 @@ export function parseDevRunnerArgs(rawArgs) {
151 };151 };
152}152}
153153
154function resolveStateDir(envOverrides) {154function resolveStateDir(env, envOverrides) {
155 return envOverrides.T3CODE_STATE_DIR ?? DEFAULT_DEV_STATE_DIR;155 const overrideValue = envOverrides?.T3CODE_STATE_DIR?.trim();
156 if (overrideValue) {
157 return overrideValue;
158 }
159
160 const envValue = env?.T3CODE_STATE_DIR?.trim();
161 if (envValue) {
162 return envValue;
163 }
164
165 return DEFAULT_DEV_STATE_DIR;
156}166}
157167
158export function createDevRunnerEnv({ mode, env, offset, envOverrides }) {168export function createDevRunnerEnv({ mode, env, offset, envOverrides }) {
...@@ -169,12 +179,15 @@ export function createDevRunnerEnv({ mode, env, offset, envOverrides }) {...@@ -169,12 +179,15 @@ export function createDevRunnerEnv({ mode, env, offset, envOverrides }) {
169 ...envOverrides,179 ...envOverrides,
170 };180 };
171181
172 output.T3CODE_STATE_DIR = resolveStateDir(envOverrides);182 output.T3CODE_STATE_DIR = resolveStateDir(env, envOverrides);
173183
174 const parsedServerPort = Number(output.T3CODE_PORT);184 const parsedServerPort = Number(output.T3CODE_PORT);
175 if (Number.isInteger(parsedServerPort) && parsedServerPort > 0 && parsedServerPort <= 65535) {185 if (!Number.isInteger(parsedServerPort) || parsedServerPort < 1 || parsedServerPort > 65535) {
176 output.VITE_WS_URL = `ws://localhost:${parsedServerPort}`;186 throw new Error(
187 `Invalid T3CODE_PORT override: '${String(output.T3CODE_PORT)}'. Expected an integer between 1 and 65535.`,
188 );
177 }189 }
190 output.VITE_WS_URL = `ws://localhost:${parsedServerPort}`;
178191
179 if (mode === "dev" || mode === "dev:server" || mode === "dev:web") {192 if (mode === "dev" || mode === "dev:server" || mode === "dev:web") {
180 // Running server/web in browser mode should not inherit desktop launcher state.193 // Running server/web in browser mode should not inherit desktop launcher state.
scripts/dev-runner.test.ts+34-1
...@@ -42,7 +42,7 @@ describe("createDevRunnerEnv", () => {...@@ -42,7 +42,7 @@ describe("createDevRunnerEnv", () => {
42 it("defaults state dir to ~/.t3/dev when not provided", () => {42 it("defaults state dir to ~/.t3/dev when not provided", () => {
43 const env = createDevRunnerEnv({43 const env = createDevRunnerEnv({
44 mode: "dev",44 mode: "dev",
45 env: { T3CODE_STATE_DIR: "/tmp/prod-state" },45 env: {},
46 offset: 0,46 offset: 0,
47 envOverrides: {},47 envOverrides: {},
48 });48 });
...@@ -50,6 +50,17 @@ describe("createDevRunnerEnv", () => {...@@ -50,6 +50,17 @@ describe("createDevRunnerEnv", () => {
50 expect(env.T3CODE_STATE_DIR).toBe(DEFAULT_DEV_STATE_DIR);50 expect(env.T3CODE_STATE_DIR).toBe(DEFAULT_DEV_STATE_DIR);
51 });51 });
5252
53 it("uses existing env state dir when --state-dir is not provided", () => {
54 const env = createDevRunnerEnv({
55 mode: "dev:server",
56 env: { T3CODE_STATE_DIR: "/tmp/existing-state" },
57 offset: 0,
58 envOverrides: {},
59 });
60
61 expect(env.T3CODE_STATE_DIR).toBe("/tmp/existing-state");
62 });
63
53 it("lets --state-dir override existing env state dir", () => {64 it("lets --state-dir override existing env state dir", () => {
54 const env = createDevRunnerEnv({65 const env = createDevRunnerEnv({
55 mode: "dev:server",66 mode: "dev:server",
...@@ -61,6 +72,17 @@ describe("createDevRunnerEnv", () => {...@@ -61,6 +72,17 @@ describe("createDevRunnerEnv", () => {
61 expect(env.T3CODE_STATE_DIR).toBe("/tmp/override-state");72 expect(env.T3CODE_STATE_DIR).toBe("/tmp/override-state");
62 });73 });
6374
75 it("treats whitespace-only --state-dir as missing and falls back to env/default", () => {
76 const env = createDevRunnerEnv({
77 mode: "dev:server",
78 env: { T3CODE_STATE_DIR: "/tmp/existing-state" },
79 offset: 0,
80 envOverrides: { T3CODE_STATE_DIR: " " },
81 });
82
83 expect(env.T3CODE_STATE_DIR).toBe("/tmp/existing-state");
84 });
85
64 it("recomputes websocket url when port is overridden", () => {86 it("recomputes websocket url when port is overridden", () => {
65 const env = createDevRunnerEnv({87 const env = createDevRunnerEnv({
66 mode: "dev",88 mode: "dev",
...@@ -89,4 +111,15 @@ describe("createDevRunnerEnv", () => {...@@ -89,4 +111,15 @@ describe("createDevRunnerEnv", () => {
89 expect(env.T3CODE_NO_BROWSER).toBe("1");111 expect(env.T3CODE_NO_BROWSER).toBe("1");
90 expect(env.T3CODE_AUTH_TOKEN).toBe("cli-token");112 expect(env.T3CODE_AUTH_TOKEN).toBe("cli-token");
91 });113 });
114
115 it("fails fast for invalid port overrides", () => {
116 expect(() =>
117 createDevRunnerEnv({
118 mode: "dev",
119 env: {},
120 offset: 0,
121 envOverrides: { T3CODE_PORT: "not-a-port" },
122 }),
123 ).toThrow("Invalid T3CODE_PORT override");
124 });
92});125});