`toWebRequest` always called `init.body = Readable.toWeb(req)` for any
non-GET/HEAD method. For requests where Node has no body to deliver
(e.g. a POST with `Content-Length: 0`), this hands undici an
already-consumed stream and `new Request(...)` throws
TypeError: Response body object should not be disturbed or locked
That bubbled up as occasional 500s during e2e runs.
Gate the body assignment on Content-Length > 0 (or Transfer-Encoding:
chunked), matching what the HTTP semantics actually allow.
`for await (chunk of response.body)` works for simple responses but can
leave the web ReadableStream in a "disturbed/locked" state when the
TanStack Start handler peeks at the body internally, surfacing on the
next request as
TypeError: Response body object should not be disturbed or locked
Switch to `Readable.fromWeb(response.body).pipe(res)` so the stream is
consumed via Node's stream machinery with backpressure and proper
cleanup. Resolves the Promise on `res.close`/`finish` and propagates
errors from either end.
Previous setup ran the studio under `vite preview` and signalled e2e
mode via `NODE_ENV=test`. That broke twice on this branch:
1. `NODE_ENV=test` in the shell overrides Vite's default
`NODE_ENV=production` for `vite build`, which makes
`@vitejs/plugin-react` emit the dev JSX runtime (`jsxDEV`). The
production SSR bundle doesn't have `jsxDEV` available, so every SSR
request crashed with `TypeError: jsxDEV is not a function`.
2. `vite preview` is a static SPA preview; it can't reuse the
`fetch`-handler shape that `apps/studio/api/server.js` exposes to
Vercel. Self-hosted prod needs its own HTTP listener for the same
handler.
Changes:
- Swap `NODE_ENV=test` for `MODE=test` end-to-end. `vite build --mode
test` keeps `NODE_ENV=production` automatically (per Vite docs), so
the React plugin stays on the prod JSX runtime and TanStack Start
bakes the right `NODE_ENV` into the SSR bundle. `.env.test` overrides
load through the standard `loadEnv(mode, ...)` order, so the
developer's `.env.local` no longer leaks `NEXT_PUBLIC_IS_PLATFORM=true`
into e2e builds.
- Explicitly set `NEXT_PUBLIC_IS_PLATFORM=false` in
`generateLocalEnv.js` so `.env.test` (which loads after `.env.local`)
wins regardless of what a developer has set locally.
- Add `apps/studio/scripts/serve.js`: a small zero-dep Node HTTP
adapter that loads env files, serves `dist/client/*` statics with
proper cache headers, and forwards everything else to the
`fetch`-handler exported by `dist/server/server.js` — the same
handler `api/server.js` hands to Vercel. `pnpm start` runs this;
`pnpm preview` keeps the old `vite preview` flow as a debug
escape-hatch.
- Add `dist/**` to studio's turbo outputs so cached builds restore
the Vite output (not just the legacy `.next/**`).
- Declare `MODE`, `PORT` in turbo env so cache invalidates correctly
when switching between test and production builds.